java类从加载、连接到初始化过程详解

Java代码在编译后会转化成Java字节码,字节码被类加载器加载到JVM里,JVM执行字节码,最终需要转化成汇编指令在CPU上执行,Java中所使用的并发机制依赖于JVM的实现和CPU的指令。
类加载期的类加载过程:
在Java代码中,类型的加载、连接与初始化过程都是在程序运行期间完成的。

在如下几种情况下,Java虚拟机将结束生命周期:
1. 执行了System.exit()方法
2. 程序正常执行结束
3. 程序在执行过程中遇到了异常或错误而异常终止
4. 由于操作系统出现了错误而导致Java虚拟机进程终止

类的字节码从磁盘上进入到内存里的五个大的阶段:
1. 加载:查找并加载类的二进制数据
2. 连接:
- 验证: 确保被加载的类的正确性
- 准备:为类的 静态变量分配内存,并将初始化默认值(类的实例变量还没有)
- 解析:把类中的符号引用转换为直接引用
3. 初始化:为类的静态变量赋予正确的初始值
4. 使用(如创建类的对象,调用类的方法)
5. 卸载(将驻留在内存里的类的数据结构销毁掉,卸载后不能再使用该类创建对象了)

类的加载:

  • 类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在内存中创建一个java.lang.Class对象(HotSpot虚拟机将Class对象放在了方法区中)用来封装类在方法区的数据结构。
  • 加载.class文件的方式
- 从本地系统中直接加载
- 通过网络下载.class文件
- 从zip,jar等归档文件中加载.class文件
- 从专用数据库中提取.class文件
- 将Java源文件动态编译为.class文件(如web开发中的jsp会在运行的时候转换为servlet的.class文件)

初始化时机:
  • Java程序对类的使用方式可分为两种:
    - 主动使用
    - 被动使用
  • 所有的Java虚拟机实现必须在每个类或接口被Java程序“首次主动使用”时才初始化他们。
  • 主动使用(七种)
- 创建类的实例
- 访问某个类或接口的静态变量(助记符:getstatic)(但static final的常量除外,它在常量池中),或者对该静态变量赋值(助记符:putstatic)
- 调用类的静态方法(助记符:invokestatic)
- 反射(如Class.forName("com.test.Test"))
- 初始化一个类的子类
- Java虚拟机启动时被标明为启动类的类(Java Test,即含有main()方法的类)
- JDK1.7开始提供的动态语言支持:
java.lang.invoke.MethodHandle实例的解析结果REF_getStatic,REF_putStatic,REF_invokeStatic句柄对应的类没有初始化,则初始化

  • 除了以上七种情况,其他使用Java类的方式都被看作是对类的被动使用,都不会导致类的初始化。不被初始化的类不影响它可不可以加载和连接等步骤的执行,可以通过-XX:+TraceClassLoading虚拟机参数用于追踪类的加载信息并打印出来。
  • 助记符:java字节码中用一些编码来表示指令的一些动作含义,使用助记符更好的帮助我们去记忆和理解这些字节码指令。编码、助记符和指令之间的对应关系可查阅:虚拟机字节码指令表
/**
 * 主动使用-初始化
 *
 ** 对于静态字段来说,只有直接定义了该字段的类才会被初始化
 ** 当一个类在初始化时,要求其父类全部都已经初始化完毕了
 */
public class MyTest1 {
    public static void main(String[] args) {
        System.out.println(MyChild1.parentStr); // 执行1
        // System.out.println(MyChild1.childStr); // 执行2
    }
}

class MyParent1 {
    public static String parentStr = "parentStr";

    static {
        System.out.println("MyParent1 static");
    }
}

class MyChild1 extends MyParent1 {
    public static String childStr = "childStr";

    static {
        System.out.println("MyChild1 static");
    }
}
执行一:


执行二:


加入-XX:+TraceClassLoading虚拟机参数执行一:

说明虽然虚拟机没有对MyChild1进行初始化,但JVM也完成了对MyChild1的加载。

虚拟机参数三种格式:
-XX:+<option> 表示开启option选项
-XX:-<option> 表示关闭option选项
-XX:<option>=<value> 表示将option选项的值设置为value

/**
 * 主动使用-初始化
 * 常量在编译阶段就会被存入到调用这个常量的方法所在的类的常量池中,
 * 本质上,调用类并没有直接引用到定义常量的类,因此并不会触发定义常量的类的初始化
 * 注意:这里指的是将常量存放到了MyTest2的常量池中,之后MyTest2与MyParent2就没有任何关系了
 * 甚至,我们可以将MyParent2的class文件删除
 *
 * 助记符:ldc表示将int,float或是String类型的常量值从常量池中推送至栈顶。
 *       bipush表示将单字节(-128~127)的常量值推送至栈顶
 *       sipush表示将一个短整型常量值(-32768-32767)推送至栈顶
 *       iconst_1表示将int类型的1推送至栈顶(iconst_1 ~  iconst_5)
 */
public class MyTest2 {
    public static void main(String[] args) {
        System.out.println(MyParent2.parentStr);
        System.out.println(MyParent2.s);
        System.out.println(MyParent2.i);
        System.out.println(MyParent2.m);
    }
}

class MyParent2 {
    // public static String parentStr = "parentStr"; // 执行1
    public static final String parentStr = "parentStr final"; // 执行2

    public static final  short s = 127;

    public static final int i =128;

    public static final int m = 2;

    static {
        System.out.println("myParent2 static");
    }
}
执行一:


执行二:


反编译MyTest2.class:


总结:

图片来自于网络,若侵权删










  • 4
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值