类的生命周期
1)加载:根据指定地址加载.class字节码文件到jvm方法区。
2)验证:校验字节码内部格式是否符合语义。
3)准备:给类分配内存,给类的static变量设置初始值。
4)解析:解析类、接口、字段、方法等。
5)初始化:给静态变量赋值,执行静态代码块。
6)使用:创建实例。
7)卸载:从jvm方法区中卸载。
jvm内存结构如下
image.png
类加载器(ClassLoader)######
加载字节码的重任就是在classloader的肩上,类加载器主要分2大类,核心类库加载器和其它类加载器,其它类加载器细分可以有3种,扩展类加载器、应用类加载器和自定义类加载器。
1)核心类加载器(Bootstrap ClassLoader):又叫启动类加载器,顶层类加载器,由c++实现,是jvm的一部分,负责加载%JAVA_HOME%/lib目录中或-Xbootclasspath中参数指定的路径中的,并且是虚拟机识别的(按名称)类库。
2)扩展类加载器(Extention ClassLoader):由核心类加载器加载,实现为sun.misc.Launcher$ExtClassLoader,负责加载目录%JRE_HOME%/lib/ext目录中或-Djava.ext.dirs中参数指定的路径中的jar包和class文件。
3)应用类加载器(Application ClassLoader ):也称为系统类加载器(System ClassLoader,可由java.lang.ClassLoader.getSystemClassLoader()获取),实现为sun.misc.Launcher$AppClassLoader,由启动类加载器加载,负责加载当前应用classpath下的所有类。
image.png
ExtClassLoader和AppClassLoader都继承自URLClassLoader,而最终的父类则为ClassLoader。
双亲委派模型
类加载的流程要从jvm的入口Lancher开始,
Launcher初始化了ExtClassLoader和AppClassLoader,并将AppClassLoader设置为线程上下文类加载器,
同时,初始化AppClassLoader时传入了ExtClassLoader实例,使AppClassLoader的parent属性设置为ExtClassLoader。
这里要理顺一下类加载器和父类加载器:
1)类加载器
- ClassLodarDemo为我们自己创建的类,其类加载器为AppClassLoader;
- DNSNameService为%JRE_HOME%/lib/ext目录下的类,其类加载器为ExtClassLoader;
-
String存在于rt.jar中,但其类加载器为null,这里是应为rt.jar由Bootstrap ClassLoader加载,而Bootstrap ClassLoader是由C++编写,属于JVM的一部分
2)父类加载器(parent)
每个类加载器,都有一个父类加载器(parent),AppClassLoader的父类加载器为ExtClassLoader,ExtClassLoader的父类加载器为null,null并不代表ExtClassLoader没有父类加载器,而是Bootstrap ClassLoader。双亲委派模型就是从这个父类加载器的关系来的。
image.png
执行流程:
类加载器在加载类或者其他资源时,使用的是如上图所示的双亲委派模型,这种模型要求除了顶层的BootStrap ClassLoader外,其余的类加载器都应当有自己的父类加载器(父类加载器不是父类继承),如果一个类加载器收到了类加载请求,首先会把这个请求委派给父类加载器加载,只有父类加载器无法完成类加载请求时,子类加载器才会尝试自己去加载。
这个加载机制有什么用呢?还得了解下面一个概念。
类的唯一性
对于任意一个类,都需要由加载它的类加载器和这个类本身一同确立其在Java虚拟机中的唯一性,每一个类加载器,都拥有一个独立的类名称空间。
因此,同样一个类如果想保证唯一性就应该只能有一个类加载器加载,至于是父类还是子类就是这个双亲委派模型解决的问题。
下面有段测试代码可以帮助我们理解类的唯一性和双亲委派模型:
public class ClassLoaderDemo {
public static void main(String[] args) throws Exception {
URL path = new File("/data/classloader").toURI().toURL();
DiskClassLoader diskClassLoaderA = new DiskClassLoader(path);
Class<?> clazzA = diskClassLoaderA.loadClass("BeLoadedClass");
Method sayA = clazzA.getMethod("say");
Object instanceA = clazzA.newInstance();
sayA.invoke(instanceA);
System.out.println(diskClassLoaderA);
System.out.println("clazzA@" + clazzA.hashCode());
System.out.println("====");
DiskClassLoader diskClassLoaderB = new DiskClassLoader(path, diskClassLoaderA);
Class<?> clazzB = diskClassLoaderB.loadClass("BeLoadedClass");
Method sayB = clazzB.getMethod("say");
Object instanceB = clazzA.newInstance();
sayB.invoke(instanceB);
System.out.println(diskClassLoaderB);
System.out.println("clazzB@" + clazzB.hashCode());
System.out.println("====");
DiskClassLoader diskClassLoaderC = new DiskClassLoader(path);
Class<?> clazzC = diskClassLoaderC.loadClass("BeLoadedClass");
Method sayC = clazzC.getMethod("say");
Object instanceC = clazzC.newInstance();
sayC.invoke(instanceC);
System.out.println(diskClassLoaderC);
System.out.println("clazzC@" + clazzC.hashCode());
System.out.println("====");
System.out.println("clazzA == clazzB " + (clazzA == clazzB));
System.out.println("clazzC == clazzB " + (clazzC == clazzB));
}
}
结果:
I'm Loaded by com.manerfan.jvm.oom.DiskClassLoader@4b67cf4d
com.manerfan.jvm.DiskClassLoader@4b67cf4d
clazzA@312714112
====
I'm Loaded by com.manerfan.jvm.oom.DiskClassLoader@4b67cf4d
com.manerfan.jvm.DiskClassLoader@29453f44
clazzB@312714112
====
I'm Loaded by com.manerfan.jvm.oom.DiskClassLoader@5cad8086
com.manerfan.jvm.DiskClassLoader@5cad8086
clazzC@1639705018
====
clazzA == clazzB true
clazzC == clazzB false
破坏双亲委派模型
有时候我们需要利用双亲委派模型,破坏双亲委派模型,来达到我们的某些特殊需求。
例如一些框架的热加载原理:既然一个class不能被重复加载,那我就动态生成新的类加载器,这样就会加载新的class类。
类的卸载
类的卸载有2个条件:
1)该Class的所有实例都被GC。
2)该Class的ClassLoader实例被GC。









网友评论