参考博客
关于类加载器参考 这篇博客贴两张图:
类的加载就是将class文件中的二进制数据读取到内存中,然后将该字节流所代表的静态数据结构转化为方法区中运行的数据结构,并且在堆内存中生成一个java.lang.Class对象作为访问方法区数据结构的入口
加载:加载指的是将类的class文件读入到内存,并为之创建一个java.lang.Class对象,也就是说,当程序中使用任何类时,系统都会为之建立一个java.lang.Class对象。
类的加载由类加载器完成,类加载器通常由JVM提供,这些类加载器也是前面所有程序运行的基础,JVM提供的这些类加载器通常被称为系统类加载器。除此之外,开发者可以通过继承ClassLoader基类来创建自己的类加载器。
通过使用不同的类加载器,可以从不同来源加载类的二进制数据,通常有如下几种来源。
1.从本地文件系统加载class文件,这是前面绝大部分示例程序的类加载方式。
2.从JAR包加载class文件,这种方式也是很常见的,前面介绍JDBC编程时用到的数据库驱动类就放在JAR文件中,JVM可以从JAR文件中直接加载该class文件。
3.通过网络加载class文件。
4.把一个Java源文件动态编译,并执行加载。
类加载器通常无须等到“首次使用”该类时才加载该类,Java虚拟机规范允许系统预先加载某些类。
验证:确保当前class文件的字节流所包含的内容符合当前JVM的规范要求,并且不会出现危害JVM自身安全的代码,当前字节流不符合规范会抛出VerifyError的异常,或者子异常,验证的信息有:(1)文件格式:验证二进制文件是什么类型,验证是否符合当前JVM规范,(2)元数据验证:检查类是否有父类、接口,验证其父类、接口的合法性, 验证被final修饰的类, 验证是否是抽象类,是否实现了父类的抽象方法或者接口中的方法, 验证方法的重载。(3)字节码验证,主要验证程序的控制流程比如循环、分支等,(4)符号验证,主要验证符号引用转换为直接引用时的合法性
准备:当一个Class文件的字节流通过验证,就开始为该对象的类变量,也就是静态变量,分配内存和初始值,各种数据类型的初始值:
初始值问题:
private static int a=10; //在准备阶段a的值为0
private static final int b=10; //final修饰的静态变量不会导致类的初始化,所以b=10;
解析:原本class文件中的类、接口、方法、字段都是一些符号,在解析阶段在常量池中找到这些的符号引用,替换为直接引用,也就是建立连接。说明一下:符号引用:符号引用是以一组符号来描述所引用的目标,符号可以是任何的字面形式的字面量,只要不会出现冲突能够定位到就行。布局和内存无关。直接引用:是指向目标的指针,偏移量或者能够直接定位的句柄。该引用是和内存中的布局有关的,并且一定加载进来的。
初始化:初始化一般是在new对象或者调用静态方法(也就是第一次使用该类的时候)进行初始化。
执行()方法(clinit是class initialize的简写),()方法再编译过程中生成,此方法中包含了所有类变量的赋值以及静态代码语句块的执行代码,编译器收集的顺序是由执行语句在源文件中的出现顺序来决定的,静态语句块只能对后面的静态变量进行赋值,而不能对其进行访问,
public class test2 {
static {
System.out.println(X);(1)
X =100;
}
private static int X = 10;
}
在(1)位置,会编译错误,illegal forward reference,另外,父类的静态语句块会被优先执行java编译器会生成()方法,但是该方法不是每一个类都会生成,如果某一个类无静态变量或者静态语句块,就没有生成()方法的必要,()方法在触发了类的初始化是被调用,如果有多个线程同时访问这个方法,不会引起线程安全问题.
例子:
package ClassTest;
public class Singleton {
//(1)
private static int x = 0;
private static int y ;
private static Singleton singleton = new Singleton();//(2)
private Singleton(){
x++;
y++;
}
public static Singleton getSingleton(){
return singleton;
}
public static void main(String[] args) {
Singleton singleton = Singleton.getSingleton();
System.out.println("这是X变量"+singleton.x);
System.out.println("这是Y变量"+singleton.y);
}
}
当(2)语句放到(1)位置时,输出的结果会有变化,目前这个代码输出结果会是:X=1,Y=1。当语句变动后,输出结果为:X=0,Y=1。解释一下:
private static int x = 0;
private static int y ;
private static Singleton singleton = new Singleton();
在连接阶段的准备过程中,每个变量都会被赋一个初始值 x=0 y=0 singleton = null,跳过解析过程,在初始化阶段每一个变量赋正确的值
x=0,y=0,singleton=new Singleton(),在new Singleton的时候执行类的构造函数,对x和y进行了自增,因此结果为x=1,y=1.
private static Singleton singleton = new Singleton();
private static int x = 0;
private static int y ;
在连接阶段的准备过程中,每个变量都会被赋一个初始值 singleton = null,x=0,y=0 跳过解析过程,在初始化阶段每一个变量赋正确的值,首先会进入Singleton的构造函数,执行完构造函数后 x=1,y=1,然后开始初始化x,y, x被赋予0的值,y没有被赋值,因此在构造函数中的值即为y的值,y=1,所以结果为x=0,y=1