类的加载指的是将类的.class文件中的二进制数据读入到内存中,然后在堆区创建一个这个类的java.lang.Class对象。
类的加载的最终产品是位于堆区中的Class对象。Class对象封装了类在方法区内的数据结构,并且向Java程序员提供了访问方法区内的数据结构的接口。
一个java文件从被加载到被卸载这个生命过程,总共要经历7个阶段,JVM将类的生命周期过程分为:
加载阶段是类加载过程的第一个阶段。在这个阶段,JVM 的主要目的是将字节码从各个位置(网络、磁盘等)转化为二进制字节流加载到内存中。
加载类的方式有以下几种:
验证是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。验证阶段大致会完成4个阶段的检验动作:
验证阶段是非常重要的,但不是必须的,它对程序运行期没有影响,如果所引用的类经过反复验证,那么可以考虑采用-Xverifynone参数来关闭大部分的类验证措施,以缩短虚拟机类加载的时间。
准备阶段是为类变量在方法区分配内存并设置默认值的阶段。对于该阶段有以下几点需要注意:
先看下面一个例子:
package com.morris.jvm.load;
public class PrepareTest {
public static int a = 10;
public static final int b = 10;
}
在准备阶段a=0,而b=10。因为final修饰的常量(可直接计算出结果)不会导致类的初始化,是一种被动引用。更严谨的说法是常量在编译阶段会将其value生成一个ConstandValue,直接赋予10。
解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程,解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符7类符号引用进行。
符号引用就是一组符号来描述目标,可以是任何字面量。
直接引用就是直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄。
初始化,为类的静态变量赋予正确的初始值,主要对类变量进行初始化。在Java中对类变量进行初始值设定有两种方式:
初始化阶段最重要的一件事就是执行
注意:静态代码块中只能对后面的变量进行赋值,不能进行访问。
static {
x = 20;
System.out.println(x); // 此行编译不通过
}
public static int x = 10;
再来看一个例子,深刻理解类的初始化:
package com.morris.jvm.load;
public class BookTest {
static BookTest book = new BookTest();
static int amount = 112;
static {
System.out.println("书的静态代码块");
}
int price = 110;
{
System.out.println("书的普通代码块");
}
BookTest() {
System.out.println("书的构造方法");
System.out.println("price=" + price + ",amount=" + amount);
}
public static void main(String[] args) {
staticFunction();
}
public static void staticFunction() {
System.out.println("书的静态方法");
}
}
运行结果如下:
书的普通代码块
书的构造方法
price=110,amount=0
书的静态代码块
书的静态方法
对于BookTest类,其类构造器可以简单表示如下:
static BookTest book = new BookTest();
static {
System.out.println("书的静态代码块");
}
static int amount = 112;
对于BookTest类,其对象构造器可以简单表示如下:
{
System.out.println("书的普通代码块");
}
int price = 110;
BookTest() {
System.out.println("书的构造方法");
System.out.println("price=" + price +",amount=" + amount);
}
}
类的初始化顺序如下:
每个类只有在首次主动使用才会被初始化。
以下是类的6种主动使用场景。
关于类的主动使用与被动使用,下面是几个容易混淆的例子:
package com.morris.jvm.load;
public class ArrayLoadTest {
public static void main(String[] args) {
A[] arrays = new A[10]; // // 只不过是在堆内存开辟了4byte*10的空间
}
public static class A {
static {
System.out.println("class A init.");
}
}
}
package com.morris.jvm.load;
import java.util.Random;
public class ConstantTest {
public static void main(String[] args) {
System.out.println(A.MAX);
System.out.println(A.RANDOM);
}
public static class A {
public static final int MAX = 10; // 引用类的静态常量不会导致类的初始化
public static final int RANDOM = new Random().nextInt(); // 只有在初始化后才能得到具体值,会导致了类的初始化
static {
System.out.println("class A init.");
}
}
}
运行结果如下:
10
class A init.
-859057135
从下面的字节码可以看出常量A.MAX在编译阶段会直接赋予10,而常量A.RANDOM需要初始化ConstantTest类后才能得出结果。
// access flags 0x19
public final static I MAX = 10
// access flags 0x19
public final static I RANDOM
// access flags 0x8
static ()V
L0
LINENUMBER 15 L0
NEW java/util/Random
DUP
INVOKESPECIAL java/util/Random. ()V
INVOKEVIRTUAL java/util/Random.nextInt ()I
PUTSTATIC com/morris/jvm/classloader/ConstantTest$A.RANDOM : I
L1
LINENUMBER 18 L1
GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
LDC "class A init."
INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
L2
LINENUMBER 19 L2
RETURN
MAXSTACK = 2
MAXLOCALS = 0
}
当JVM完成初始化阶段之后,JVM便开始从入口方法开始执行用户的程序代码。
JVM规定了一个Class只有在满足一下三个条件的时候才会被GC回收,也就是类的卸载。
最后以一个面试题来结束本文。
package com.morris.jvm.load;
public class StaticTest {
public static int k = 0;
public static StaticTest t1 = new StaticTest("t1");
public static StaticTest t2 = new StaticTest("t2");
public static int i = print("i");
public static int n = 99;
public int j = print("j");
{
print("构造块");
}
static{
print("静态块");
}
public StaticTest(String str) {
System.out.println((++k) + ":" + str + " i=" + i + " n=" + n);
++n;
++i;
}
public static int print(String str) {
System.out.println((++k) + ":" + str + " i=" + i + " n=" + n);
++i;
return ++n;
}
public static void main(String[] args) {
new StaticTest("init");
}
}
运行结果如下:
1:j i=0 n=0
2:构造块 i=1 n=1
3:t1 i=2 n=2
4:j i=3 n=3
5:构造块 i=4 n=4
6:t2 i=5 n=5
7:i i=6 n=6
8:静态块 i=7 n=99
9:j i=8 n=100
10:构造块 i=9 n=101
11:init i=10 n=102
更多精彩内容关注本人公众号:架构师升级之路