1、首先类的加载共分为7个步骤
加载 -> 验证 -> 准备 -> 解析 -> 初始化 -> 使用 -> 卸载
加载
class B{
public static void main(String[] args) {
A a = new A();
}
}
当我们的代码中使用到某个类时,JVM会将用到的类加载到内存中.
首先你的代码中包含“main()”方法的主类一定会在JVM进程启动之后被加载到内存,开始执行你的“main()”方法中的代码,接着就会将A.class加载到内存中
验证
根据Java虚拟机的规范来检测class文件是否正确,
准备
class B{
public static void main(String[] args) {
A a = new A();
}
}
class A {
static int a1;
int a2 = 100;
}
当class文件验证没有问题后,JVM开始进行准备工作,为类A分配一定的内存空间,然后初始化static变量分配默认值
解析
解析阶段是虚拟机将常量池内的符号引用替换成直接引用的过程。直接引用是直接指向目标的指针,相对偏移量或是一个能间接定位到目标的句柄。直接引用和虚拟机实现的内存有关,同一个符号引用在不同虚拟机实例上翻译出来的直接引用不尽相同
初始化
初始化阶段是类加载过程的最后一步,到了该阶段才真正开始执行类定义的Java程序代码,根据程序员通过代码定制的主观计划去初始化类变量和其他资源,是执行类构造器初始化方法的过程
执行new A();初始化这个类的实例,那么会加载这个类,然后初始化这个类,如果初始化之前父类没有没加载,那么需要先加载父类然后初始化。
使用
卸载
被GC回收
2、 类加载器的种类
2.1、Bootstrap ClassLoader
用于加载JAVA_HOME/jre/lib文件夹中的jar文件,支撑你的java程序运行的核心类库
2.2、Extension ClassLoader
用于加载JAVA_HOME/jre/lib/ext文件夹中的jar文件,支撑你的java程序运行的扩展库
2.3、Application ClassLoader
用于加载classpath下的jar文件,大致就理解为去加载你写好的Java代码吧,这个类加载器就负责加载你写好的那些类到内存里。
2.4、自定义加载器
根据自己的需求自定义实现加载器
3、类加载的双亲委派机制
JVM的类加载器是有亲子层级结构的,就是说启动类加载器是最上层的,扩展类加载器在第二层,第三层是应用程序类加载器,最后一层是自定义类加载器。双亲委派机制正式基于这种结构实现的
假设你的应用程序类加载器需要加载一个类,他首先会委派给自己的父类加载器去加载,最终委派传导到顶层的类加载器去加载
Application ClassLoader --委派不加载-->>> Extension ClassLoader --委派不加载-->>> Bootstrap ClassLoader
然后从顶层依次向下查找要加载的类
Application ClassLoader --查找加载-->>> Extension ClassLoader --查找加载-->>> Bootstrap ClassLoader
如果找到就直接返回,找不到则抛出ClassNotFoundException异常。
3.1、双亲委派模式的好处
因为这样可以避免重复加载,当父亲已经加载了该类的时候,就没有必要 ClassLoader再加载一次。考虑到安全因素,我们试想一下,如果不使用这种委托模式,那我们就可以随时使用自定义的String来动态替代java核心api中定义的类型,这样会存在非常大的安全隐患,而双亲委托的方式,就可以避免这种情况,因为String已经在启动时就被引导类加载器(Bootstrcp ClassLoader)加载,所以用户自定义的ClassLoader永远也无法加载一个自己写的String,除非你改变JDK中ClassLoader搜索类的默认算法。
类加载器虽然只用于实现类的加载动作,但它在Java程序中起到的作用却远远不限于类加载阶段。对于任意一个类,都需要由加载它的类加载器和这个类本身一同确立其在Java虚拟机中的唯一性,每一个类加载器,都拥有一个独立的类名称空间。这句话可以表达更通俗一些:比较两个类是否”相等”,只有再这两个类是有同一个类加载器加载的前提下才有意义,否则,即使这两个类来源于同一个Class 文件,被同一个虚拟机加载,只要加载它们的类加载器不同,那这两个类就必定不相等。