一,类加载机制
虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验,转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制
二,类加载的时机----什么时候类进行加载?
- 遇到new,getstatic,putstatic和invokestatic这四条字节码指令时,如果没有进行过初始化,则需要先触发其初始化
- new:使用new关键字实例化对象的时候
- getstatic:设置一个类的静态字段
- putstatic:读取一个类的静态字段
若静态字段被final修饰,已经在编译期把结果放入常量池的静态字段除外 - 调用一个类的静态方法的时候
- 使用java.lang.reflect包的方法对类进行反射调用的时候,如果类没有进行过初始化,则需要先触发其初始化
- 当初始化一个类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父类初始化
- 当虚拟机启动时,用户需要指定一个要执行的主类(包含main()方法的那个类),虚拟机会先初始化这个主类
- 当使用JDK1.7的动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果REF_getstatic, REF_putstatic, REF_invokestatic的方法句柄,并且这个方法句柄所对应的类没有进行过初始化,则需要先触发其初始化
需要注意的是:
1,对于静态字段,只有直接定义这个字段的类才会被初始化,因此通过子类来引用父类中定义的静态字段,
只会触发父类的初始化而不会触发子类的初始化
2,常量(final)在编译阶段会存入调用类的常量池中,本质上并没有直接引用到定义常量的类,因此不会
触发定义常量的类的初始化
3,当接口初始化时,并不要求其父接口全部都完成了初始化,只有在真正使用到父接口的时候(如引用接口
中定义的常量)才会初始化.
三,类加载的过程
java虚拟机中类加载的全过程----加载,链接(验证,准备,解析),初始化
1,加载
在加载阶段,虚拟机需要完成以下三件事情
- 通过一个类的全限定名来获取定义此类的二进制字节流
- 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
- 在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口
2,验证
- 文件格式验证
主要是验证字节流是否符合Class文件格式的规范 - 元数据验证
对字节码描述的信息进行语义分析,保证其描述的信息符合Java语言规范的要求
-字节码验证
通过数据流和控制流分析,确定程序语义是合法的,符合逻辑的 - 符号引用验证
虚拟机将符号引用转化为直接引用的时候,这个转化动作将在解析阶段发生
3,准备
准备阶段是正式为类变量分配内存并设置初始化值的阶段.这些变量所使用的内存都将在方法区中进行分配
4,解析
- 类或接口的解析
- 字段解析
- 类方法解析
- 接口方法解析
5,初始化
初始化阶段是执行类构造器<clinit>()方法的过程
- <clinit>()方法怎么生成的呢?
<clinit>()方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块块(static{})中的语句合并产生的,编译器收集的顺序是由语句在源文件中出现的顺序所决定的,静态语句块只能访问到定义在静态语句块之前的变量;定义在他之后的变量,在前面的静态语句块可以赋值,但是不能访问. - <clinit>()方法的一些特点
- <clinit>()方法与类的构造方法(或者说实例构造器<init>())不同,他不需要显式地调用父类构造器,虚拟机会保证子类的<clinit>()方法执行之前,父类的<clinit>()已经执行完毕. 因此在虚拟机中第一个被执行<clinit>()的类是java.lang.Object
- 由于父类的<clinit>()方法先执行,也就意味着父类中定义的静态语句块要优先于子类的变量赋值操作
- <clinit>()对于类或接口来说不是必须的,如果一个类中没有静态语句块,也没有对静态变量的赋值操作**,那么编译器可以不为这个类生成<clinit>()
- 接口中不能使用静态语句块,但仍然有变量初始化的赋值操作,因此接口与类一样都会生成<clinit>()方法.但是接口与类不同,执行接口的<clinit>()方法不需要先执行父接口的<clinit>()方法.只有当父接口中定义的变量使用时,父接口才会初始化.另外,接口的实现类在初始化时也一样不会执行接口的<clinit>()方法.
- 虚拟机会保证一个类的<clinit>()方法在多线程环境中被正确的执行
四,练习题
1,打印结果是什么?
class SingleTon {
private static SingleTon singleTon = new SingleTon();
public static int count1;
public static int count2 = 0;
private SingleTon() {
count1++;
count2++;
}
public static SingleTon getInstance() {
return singleTon;
}
}
public class Test {
public static void main(String[] args) {
SingleTon singleTon = SingleTon.getInstance();
System.out.println("count1=" + singleTon.count1);
System.out.println("count2=" + singleTon.count2);
}
}
2,打印结果是什么?
public class Test {
public static int k = 0;
public static Test t1 = new Test("t1");
public static Test t2 = new Test("t2");
public static int i = print("i");
public static int n = 99;
private int a = 0;
public int j = print("j");
{
print("构造块");
}
static {
print("静态块");
}
public Test(String str) {
System.out.println((++k) + ":" + str + " i=" + i + " n=" + n);
++i;
++n;
}
public static int print(String str) {
System.out.println((++k) + ":" + str + " i=" + i + " n=" + n);
++n;
return ++i;
}
public static void main(String args[]) {
Test t = new Test("init");
}
}
参考:
1,<<深入理解Java虚拟机 JVM高级特性与最佳实践 第二版 周志明>>