1.类加载器:
在上一篇博文中《jvm类加载器机制一》中的7个阶段,加载->验证->准备->解析->初始化->使用->卸载,只有加载是我们可以掌控的,其他的阶段均是有jvm来完成的。
jvm设计团队考虑到类加载的各种的需求,所以这个地方给了我们很大的空间来让我们来自由的选择如何来进行类的加载,于是这一章我们就可以好好的研究一下类加载器的机制,以及实现一个简单的热加载的应用案例
2.类加载的层次结构
image.png-
2.1 各个加载器所加载的内容
-
Bootstrap ClassLoader :负责加载 "/lib" 下面的核心的类库,开发者无法直接获取到启动类加载器,类加载器也是一个类,所以启动类加载器还肩负这加载类加载器的重任
-
Extension ClassLoader:负责加载 "/ext" 目录下的类库文件,开发者可以使用标准的扩展类加载器
3.Application ClassLoader:(见名知意也被称之System ClassLoader),主要负责的是加载当前应用程序的类加载器
执行的先后顺序:Bootstrap ClassLoader > Extension ClassLoader > Application Loader;
image.png
事实上我们阅读:sum.misc.Launcher 的源码我们可以发现,在Bootstrap ClassLoader的代码中先后加载了Extension 和Application 类加载器
3.双亲委托机制:
jvm为了自身的安全,所以设计了一个双亲为委托的机制,即一个类加载前,它为先委托给父加载器来加载,如果父加载器无法找到则一步步的往子加载器进行传递,如果我们自定义类加载器的话如果想继续依照这个原则,则直接复写findClass方法即可,如果想打破这个规则则复写loadClass即可
一般把类加载器用的十分精到的是各个的应用服务器tomcat、jetty等,因为他们在部署多个应用、多个版本的类库时,都需要借助于类的加载器来实行,有时间我也会写这个方面的内容
4.学以致用,我们学习这个可以干什么呢:
- 热加载/热部署
- 远程加载字节码文件
- 实现类的加密解密
5.热加载的原理以及简单的代码的实现
5.1 自定义类加载器
class MyClassLoader extends ClassLoader {
private String classpath;
public MyClassLoader(String classpath) {
this.classpath = classpath;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
try {
byte[] classDate = getDate(name);
if (classDate == null) {
} else {
return defineClass(name, classDate, 0, classDate.length);
}
} catch (IOException e) {
e.printStackTrace();
}
return super.findClass(name);
}
private byte[] getDate(String className) throws IOException {
InputStream in = null;
ByteArrayOutputStream out = null;
String path = classpath + File.separatorChar +
className.replace('.', File.separatorChar) + ".class";
try {
in = new FileInputStream(path);
out = new ByteArrayOutputStream();
byte[] buffer = new byte[2048];
int len = 0;
while ((len = in.read(buffer)) != -1) {
out.write(buffer, 0, len);
}
return out.toByteArray();
} catch (FileNotFoundException e) {
e.printStackTrace();
} finally {
in.close();
out.close();
}
return null;
}
}
5.2 要热加载的类
public class TestClassLoader {
public void test(Integer i){
if (i.intValue() == 1){
System.err.println("自定义类加载器:===:=================");
System.err.println("直接类加载器:"+TestClassLoader.class.getClassLoader());
System.err.println("父类加载器:"+TestClassLoader.class.getClassLoader().getParent());
System.out.println("===============");
}else{
System.out.println("系统类加载器:===:=================");
System.out.println("直接类加载器:"+TestClassLoader.class.getClassLoader());
System.out.println("父类加载器:"+TestClassLoader.class.getClassLoader().getParent());
}
}
}
5.3 要热加载的类
public class Main {
/**
* 1.jvm加载的类的个数一直在增长,但是内存不增长
* 2.一旦修改TestClassLoader后,jvm内存中关于TestClassLoader的内容也会发生改变
* 3.class文件和加载它的类加载器构成在jvm的唯一的标识
* 4.尽管不同的类加载器加载的类实例对象不是同一个,但是他们其中的一个内容发生改变,另一个也发生改变
* 5.热部署/热加载 的自定义类加载的原理:我们无法卸载类在装载类,但是我们可以替换类和类加载器来达到更新jvm中的类的信息
*
*/
@Test
public void test1() throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException, InterruptedException {
ClassLoadingMXBean classLoadingMXBean = ManagementFactory.getClassLoadingMXBean();
MemoryMXBean memoryMXBean = ManagementFactory.getMemoryMXBean();
URL resource = TestClassLoader.class.getClassLoader().getResource("");
String path = resource.getPath();
/* System.out.println("现在jvm中拥有的类的个数:== " + classLoadingMXBean.getLoadedClassCount());
System.out.println("现在jvm堆区使用的内存:== " + memoryMXBean.getHeapMemoryUsage());
System.out.println("现在jvm非堆区使用的内存:== " + memoryMXBean.getNonHeapMemoryUsage());*/
while (true) {
TestClassLoader c = new TestClassLoader();
c.test(1);
MyClassLoader loader = new MyClassLoader(path);
Class clazz = loader.findClass("TestClassLoader");
if (clazz != null) {
Object o = clazz.newInstance();
Method test = clazz.getMethod("test", Integer.class);
test.invoke(o, 2);
System.out.println(clazz.getClassLoader().toString());
}
Thread.sleep(2000);
/* System.out.println("现在jvm堆区使用的内存:== " + memoryMXBean.getHeapMemoryUsage());
System.out.println("现在jvm非堆区使用的内存:== " + memoryMXBean.getNonHeapMemoryUsage());
System.out.println("现在jvm中拥有的类的个数:== " + classLoadingMXBean.getLoadedClassCount());*/
}
}
@Test
public void test2() throws Exception {
URL resource = TestClassLoader.class.getClassLoader().getResource("");
String path = resource.getPath();
MyClassLoader loader = new MyClassLoader(path);
Class clazz1 = loader.findClass("TestClassLoader");
Object o = clazz1.newInstance();
System.out.println(o instanceof TestClassLoader);
}
}
程序写的比较简陋,只是一个验证性的,请大家见谅
总结:
- 1.在两个测试中可以发现 class和加载它的类加载器构成jvm中的唯一的标识;
- 2.类的卸载是我们无法控制的阶段,所以我们只能替换类的加载器来到达更新jvm中class文件的目的
网友评论