jvm类加载机制
java类生命周期
这张图表现了一个类的生命周期,从javac编译到卸载类,而"类加载"只包括了加载,连接,初始化这三个过程
区分"类加载"和"加载",加载只是类加载的第一个环节.
加载
加载阶段是类加载过程的第一个阶段.在这个阶段,JVM 的主要目的是将字节码从各个位置(网络、磁盘等)转化为二进制字节流加载到内存中,接着会为这个类在 JVM 的方法区创建一个对应的 Class 对象,这个 Class 对象就是这个类各种数据的访问入口.
验证
在加载阶段时,jvm已经对class文件进行过文件格式验证,但是,这还不够,虽然此时方法区中已经包含了这个类对象,但是如果要使用这个类,在连接阶段还要进行一次检验.
其中包括以下两个类型:
- **JVM规范校验.**JVM 会对字节流进行文件格式校验,判断其是否符合 JVM 规范,是否能被当前版本的虚拟机处理.例如:文件是否是以
0x cafe babe
开头,主次版本号是否在当前虚拟机处理范围之内等. - **代码逻辑校验.**JVM 会对代码组成的数据流和控制流进行校验,确保 JVM 运行该字节码文件后不会出现致命错误.例如一个方法要求传入 int 类型的参数,但是使用它的时候却传入了一个 String 类型的参数.一个方法要求返回 String 类型的结果,但是最后却没有返回结果.代码中引用了一个名为 Apple 的类,但是你实际上却没有定义 Apple 类.
准备
Java 中的变量有「类变量」(被static所修饰)和「成员变量」(没有被static修饰)两种类型
在准备阶段只会为类变量分配内存,并将其初始化为默认值.(此时为默认值,在初始化的时候才会给变量赋值)即在方法区中分配这些变量所使用的内存空间.比如
public static int value = 123;
此时在准备阶段过后的初始值为0而不是123;将value赋值为123的putstatic指令是程序被编译后,存放于类构造器方法之中.特例:
public static final int number = 3;
final 关键字在 Java 中代表不可改变的意思,意思就是说 number 的值一旦赋值就不会在改变了.既然一旦赋值就不会再改变,那么就必须一开始就给其赋予用户想要的值,因此被 final 修饰的类变量在准备阶段就会被赋予想要的值.
解析
当通过准备阶段之后,JVM 针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符 7 类引用进行解析.这个阶段的主要任务是将其在常量池中的符号引用替换成直接其在内存中的直接引用.
初始化
初始化阶段是执行类构造器方法的过程.方法是由编译器自动收集类中的类变量的赋值操作和静态语句块中的语句合并而成的.虚拟机会保证方法执行之前,父类的方法已经执行完毕.如果一个类中没有对静态变量赋值也没有静态语句块,那么编译器可以不为这个类生成()方法.java中,对于初始化阶段,有且只有以下五种情况才会对要求类立刻“初始化”(加载,验证,准备,自然需要在此之前开始):
- 使用new关键字实例化对象,访问或者设置一个类的静态字段(被final修饰,已在编译器把结果放入常量池的静态除外)的时候,以及调用一个类的静态方法的时候.
- 使用java.lang.reflect包的方法对类进行反射调用的时候,如果类没有进行过初始化,则需要先触发其初始化.
- 当初始化一个类时,如果其父类还没有进行初始化,需要先对其父类进行初始化
- 当虚拟机启动时,需要制定一个执行主线程的类(含有main方法的类),虚拟机会先初始化这个类
- 当使用 JDK1.7 动态语言支持时,如果一个 java.lang.invoke.MethodHandle实例最后的解析结果 REF_getstatic,REF_putstatic,REF_invokeStatic 的方法句柄,并且这个方法句柄所对应的类没有进行初始化,则需要先出触发其初始化.
此时初始化只会初始化类变量,成员变量要在调用时才会被初始化.
使用
当 JVM 完成初始化阶段之后.JVM 便开始从入口方法开始执行用户的程序代码.
卸载
当用户程序代码执行完毕后.JVM 便开始销毁创建的 Class 对象.最后负责运行的 JVM 也退出内存.
类执行过程
- 确定类变量的初始值 在准备阶段,分开类变量和成员变量,JVM 会为类变量初始化零值,这时候类变量会有一个初始的零值.如果是被 final 修饰的类变量,则直接会被初始成用户想要的值.
- 初始化入口方法 进入初始化阶段,jvm会寻找整个main方法的入口,从而初始化main方法所在的整个类.当需要对一个类进行初始化时,先会初始化类构造器,之后实例化时初始化对象构造器.
- 初始化类构造器 jvm收集类变量的赋值语句,静态代码块,最终组成类构造器交给jvm处理
- 初始化对象构造器 jvm收集成员变量的赋值语句,普通代码块,最后是构造方法,最终组成对象构造器交给jvm处理
如果在初始化 main 方法所在类的时候遇到了其他类的初始化.那么就先加载对应的类.加载完成之后返回.如此反复循环.最终返回 main 方法所在类.
例子1
class Grandpa
{
//静态代码块
static
{
System.out.println("爷爷在静态代码块");
}
}
class Father extends Grandpa
{
//静态代码块
static
{
System.out.println("爸爸在静态代码块");
}
//类变量
public static int factor = 25;
//构造方法
public Father()
{
System.out.println("我是爸爸~");
}
}
class Son extends Father
{
//静态代码块
static
{
System.out.println("儿子在静态代码块");
}
//构造方法
public Son()
{
System.out.println("我是儿子~");
}
}
public class InitializationDemo
{
public static void main(String[] args)
{
System.out.println("爸爸的岁数:" + Son.factor); //入口
}
}
- 上述代码排序模拟jvm已区分好类变量和成员变量
- jvm会寻找整个main方法的入口,从而初始化main方法所在的整个类InitializationDemo打印Son.factor
- 但是 Son 类中并没有定义这个类成员变量,于是往父类去找,我们在 Father 类中找到了对应的类成员变量,于是触发了 Father 的初始化,初始化 main 方法所在类的时候遇到了其他类的初始化.那么就先加载对应的类
- 当初始化一个类时,如果其父类还没有进行初始化,需要先对其父类进行初始化,初始化Grandpa类,收集静态代码块// System.out.println(“爷爷在静态代码块”);
- Father初始化,收集类变量的赋值语句,静态代码块,在这里没有调用Father的构造方法,所以不用管.//public static int factor = 25; //System.out.println(“爸爸在静态代码块”);
- 最终返回 main 方法所在类.// System.out.println(“爸爸的岁数:” +25);
输出结果:
爷爷在静态代码块
爸爸在静态代码块
爸爸的岁数:25
例子2
class Grandpa
{
//静态代码块
static
{
System.out.println("爷爷在静态代码块");
}
//构造方法
public Grandpa() {
System.out.println("我是爷爷~");
}
}
class Father extends Grandpa
{
//静态代码块
static
{
System.out.println("爸爸在静态代码块");
}
//构造方法
public Father()
{
System.out.println("我是爸爸~");
}
}
class Son extends Father
{
//静态代码块
static
{
System.out.println("儿子在静态代码块");
}
//构造方法
public Son()
{
System.out.println("我是儿子~");
}
}
public class InitializationDemo
{
public static void main(String[] args)
{
new Son(); //入口
}
}
- 上述代码排序模拟jvm已区分好类变量和成员变量
- jvm会寻找整个main方法的入口,从而初始化main方法所在的整个类InitializationDemo
- 初始化 main 方法所在类的时候遇到了Son类的初始化(使用new关键字实例化对象).那么就先加载对应的类
- 当初始化一个类时,先会初始化类构造器,如果其父类还没有进行初始化,需要先对其父类进行初始化
- 最后,先初始化Grandpa类,收集静态代码块// System.out.println(“爷爷在静态代码块”);
- 初始化Father类,收集静态代码块 //System.out.println(“爸爸在静态代码块”);
- 初始化Son类,收集静态代码块//System.out.println(“儿子在静态代码块”);
- 之后初始化对象构造器.先初始化父类的对象构造器,// System.out.println(“我是爷爷");//System.out.println("我是爸爸”);// System.out.println(“我是儿子~”);
- 最终返回 main 方法所在类.
输出结果:
爷爷在静态代码块
爸爸在静态代码块
儿子在静态代码块
我是爷爷~
我是爸爸~
我是儿子~
例子3
public class Book {
//类变量
static int amount = 112;
static Book book = new Book();
//静态代码块
static
{
System.out.println("书的静态代码块");
}
//静态方法,调用时才会初始化
public static void staticFunction(){
System.out.println("书的静态方法");
}
//成员变量
int price = 110;
//普通代码块
{
System.out.println("书的普通代码块");
}
//构造方法
Book()
{
System.out.println("书的构造方法");
System.out.println("price=" + price +",amount=" + amount);
}
//入口main方法
public static void main(String[] args)
{
staticFunction();
}
}
- 上述代码排序模拟jvm已区分好类变量和成员变量,此时static int amount = 0,static Book book=null;
- jvm会寻找整个main方法的入口,从而初始化main方法所在的整个类Book
- 先会初始化类构造器,收集类变量的赋值语句//static int amount =112
- static Book book = new Book();对象实例化,则初始化对象构造器.//int price = 110;// System.out.println(“书的普通代码块”);//System.out.println(“书的构造方法”);//System.out.println(“price=” + price +“,amount=” + amount);
- 收集静态代码块 //System.out.println(“书的静态代码块”);
- 调用staticFunction();//System.out.println(“书的静态方法”);
输出结果:
书的普通代码块
书的构造方法
price=110 ,amount=112
书的静态代码块
书的静态方法
例子4
public class Book {
//类变量
static int amount = 112;
//此处删除了 book 变量
//静态代码块
static
{
System.out.println("书的静态代码块");
}
//静态方法,调用时才会初始化
public static void staticFunction(){
System.out.println("书的静态方法");
}
//成员变量
int price = 110;
//普通代码块
{
System.out.println("书的普通代码块");
}
//构造方法
Book()
{
System.out.println("书的构造方法");
System.out.println("price=" + price +",amount=" + amount);
}
//入口main方法
public static void main(String[] args)
{
staticFunction();
Book book = new Book();
}
}
- 上述代码排序模拟jvm已区分好类变量和成员变量,此时static int amount = 0
- jvm会寻找整个main方法的入口,从而初始化main方法所在的整个类Book
- 先会初始化类构造器,收集类变量的赋值语句//static int amount =112
- 收集静态代码块 //System.out.println(“书的静态代码块”);
- 调用静态方法//System.out.println(“书的静态方法”);
- 返回main函数
- 实例化对象,new Book();初始化对象构造器//int price = 110;// System.out.println(“书的普通代码块”);//System.out.println(“书的构造方法”);// System.out.println(“price=” + price +“,amount=” + amount);
输出结果:
书的静态代码块
书的静态方法
书的普通代码块
书的构造方法
price=110,amount=112
tln(“书的静态代码块”);
- 调用静态方法//System.out.println(“书的静态方法”);
- 返回main函数
- 实例化对象,new Book();初始化对象构造器//int price = 110;// System.out.println(“书的普通代码块”);//System.out.println(“书的构造方法”);// System.out.println(“price=” + price +“,amount=” + amount);
输出结果:
书的静态代码块
书的静态方法
书的普通代码块
书的构造方法
price=110,amount=112