加载->连接->初始化->使用->卸载
其中连接包括:验证->准备->解析
Class文件需要加载到虚拟机之后才能运行。
系统加载Class文件的步骤:加载->连接->初始化。
1.通过全类名加载该类对应的二进制字节流。
2.将该二进制字节流代表的静态存储结构存到方法区的运行时数据结构中。
3.在内存中生成一个代表该类的Class对象,作为方法区这些数据的访问入口。
一个非数组类的加载阶段可控性最强,可以在这个阶段自定义类加载器去控制字节流的获取方法(重写类加载器的classLoad()
方法)。数组类型不通过类加载器创建,他直接由JVM创建。
加载和连接阶段的部分阶段是可以交叉执行的,加载阶段尚未结束,连接过程可能就开始了。
文件格式验证。
元数据验证。
字节码验证。
符号引用验证。
准备阶段为类变量分配内存并赋初始值的过程。这些内存将在方法区分配。
1.内存分配过程中只分配类变量(被static修饰的变量,类实例变量在类实例化的时候一起被分配在堆中)。
2.从概念上讲类变量被分配在方法区,在JDK7之前没有问题。在JDK7之后,HotSpot虚拟机将永久代中的常量池、静态变量等移到了堆中,相应的类变量随着Class对象一起移动到了堆中。
3.赋初始值指的是赋0值,比如public static int n = 10
,这时候n的值为0,如果被final修饰则为对应的值。
解析是将常量池中的符号引用转变成直接引用。
符号引用就是用一组符号来描述目标,符号可以是任意字面量。直接引用就是直接指向目标的指针、相对偏移量或者间接定位目标的句柄。在程序实际运行过程中,只有符号引用是不够的,例如:在执行方法的时候,需要明确知道方法所在的位置。Java虚拟机为每个类都准备了一张方法表来存放类中所有方法,当需要调用一个方法的时候只需要知道该方法在方法表中的偏移量即可。通过解析符号引用就可以直接转变为方法在方法表中的位置,从而实现对方法的调用。
初始化阶段是执行<cinit>()
的过程,是类加载的最后一步,在这一步JVM才开始执行字节码。
<cinit>()
是虚拟机自己添加的方法,能够保证多线程安全,因为他是加锁的方法,同时也可能会造成线程阻塞。
对于初始化阶段,虚拟接严格规定了只有6种情况才会初始化类(主动使用类的时候):
1.当遇到new,getstatic,putstatic,invokestatic这四条指令的时候。
2.使用反射对类进行调用,如果类还有没有初始化就先初始化类。
3.初始化一个类如果其父类还没有初始化就先初始化其父类。
4.当虚拟机启动时,用户需要定义一个启动类(main方法),虚拟机会先初始化含有main方法的类。
5.MethodHandle
和 VarHandle
可以看作是轻量级的反射调用机制,而要想使用这 2 个调用, 就必须先使用 findStaticVarHandle
来初始化要调用的类。
6.当一个类实现了JDK8新加入的默认方法(被default修饰的方法)时会初始化该接口。
卸载即该对象被GC。
满足以下三个条件即可被GC:
1.该类的所有实例都被GC,堆中找不到该类的任何实例。
2.该类没有在任何地方被引用。
3.加载该类的类加载器被GC。
参考:JavaGuide