我们知道,我们写的java代码称为源码,想要能够被jvm执行首先需要编译成.class文件,那么编译完到使用又都经理的哪些阶段呢?主要分为以下三个阶段:

  • 加载:查找并加载类的二进制数据(.class文件硬盘到内存的一个过程)。
  • 连接
     - 验证:确保被加载的类的正确性。
     - 准备:为类的 静态变量分配内存,并将其初始化为默认值。
     - 解析:把类中的符号引用转换为直接引用。
  • 初始化:为类的静态变量赋予正确的初始值。

实例(一)

  1. class Singleton1 {
  2. private static Singleton1 singleton = new Singleton1();
  3. public static int counter1;
  4. public static int counter2 = 0;
  5.  
  6. public Singleton1() {
  7. counter1++;
  8. counter2++;
  9. }
  10.  
  11. public static Singleton1 getInstance() {
  12. return singleton;
  13. }
  14. }
  15.  
  16. class Singleton2 {
  17. public static int counter1;
  18. public static int counter2 = 0;
  19. private static Singleton2 singleton = new Singleton2();
  20.  
  21. public Singleton2() {
  22. counter1++;
  23. counter2++;
  24. }
  25.  
  26. public static Singleton2 getInstance() {
  27. return singleton;
  28. }
  29. }
  30.  
  31. public class Test0010 {
  32. public static void main(String[] args) {
  33. Singleton1 singleton1 = Singleton1.getInstance();
  34. System.out.println(singleton1.counter1);
  35. System.out.println(singleton1.counter2);
  36. Singleton2 singleton2 = Singleton2.getInstance();
  37. System.out.println(singleton2.counter1);
  38. System.out.println(singleton2.counter2);
  39. }
  40. }

结果:

如果和你想的不一样的话,不要急,继续往下看:

Java程序对类的使用方式可分为两种

  • 主动使用
  • 被动使用

主动使用

  • 创建类的实例
  • 访问某个类或接口的静态变量,或者对该静态变量赋值
  • 调用类的静态方法
  • 反射(如Class.forName(“com.xxx.Test”))
  • 初始化一个类的子类
  • Java虚拟机启动时被标明为启动类的类(Java ClassA)

除了主动使用外的其他方式都是被动使用。所有的Java虚拟机实现必须在每个类或接口被Java程序“首次主动使用”时才初始化他们。会过头来解析上面的例子:
  我们在使用一个类之前首先要经历:加载->链接->初始化的过程,而在链接阶段又有验证准备解析几个过程,重点是准备阶段:为类的 静态变量分配内存,并将其初始化为默认值。这里很重要,也就是我们在首次主动使用类之前,类中的静态变量已经被初始化为了默认值,而在真正的主动使用时才会将静态变量初始化为应有的初始值。来看类Singleton1,在链接阶段过后,类中静态成员的情况是这样子的:

  1. class Singleton1 {
  2. private static Singleton1 singleton = null;
  3. public static int counter1 = 0;
  4. public static int counter2 = 0;
  5. }

在Test0010的main方法中,我们调用了Singleton1的如下方法和成员,这里是对Singleton1类的首次主动使用,会引发类的初始化。

  1. Singleton1 singleton1 = Singleton1.getInstance();
  2. System.out.println(singleton1.counter1);
  3. System.out.println(singleton1.counter2);

初始化执行过程中主要执行类的构造方法,并且按照顺序执行里面的静态语句和静态代码块

执行private static Singleton1 singleton = new Singleton1(); 时,counter1和counter2均自增,此时counter1和counter2的值均为1,继续往下执行时counter2 又被赋值为0,所以最终
counter1和counter2的值分别为1和0。同样的道理不难看出Singleton2中初始化操作顺序执行静态语句之后,counter1和counter2的值分别为1和1。

实例(二)

  1. class FinalTest1 {
  2. /**
  3. * 编译时常量,访问它不会初始化这个类
  4. */
  5. public static final int x = 100 / 2;
  6.  
  7. static {
  8. System.out.println("final block ~~~");
  9. }
  10. }
  11.  
  12. class FinalTest2 {
  13. /**
  14. * 运行时常量,访问它会初始化这个类
  15. */
  16. public static final int x = new Random().nextInt(100);
  17.  
  18. static {
  19. System.out.println("final block ~~~");
  20. }
  21. }
  22.  
  23. public class Test0020 {
  24. public static void main(String[] args) {
  25. System.out.println(FinalTest1.x);
  26. System.out.println(FinalTest2.x);
  27. }
  28. }

我们看到,FinalTest1 中的静态代码块没有执行,而FinalTest2中的静态代码块执行了。
结论:首次主动访问编译时常量,不会初始化这个类;而首次主动访问运行时常量,会初始化这个类。

同时要注意:静态代码块在类初始化时候才会执行。

实例(三)

  1. public class Test0030 {
  2. static {
  3. System.out.println("main static block");
  4. }
  5.  
  6. public static void main(String[] args) {
  7. System.out.println(Child.b);
  8. }
  9. }
  10.  
  11. class Parent {
  12. static {
  13. System.out.println("parent static block");
  14. }
  15. }
  16.  
  17. class Child extends Parent {
  18. static int b = 4;
  19.  
  20. static {
  21. System.out.println("child static block");
  22. }
  23. }

结论: 当初始化一个类时,会先初始化这个类的父类。

实例(四)

  1. public class Test0050 {
  2. public static void main(String[] args) {
  3. System.out.println(Child.a);
  4. }
  5. }
  6.  
  7. class Parent {
  8. static int a = 3;
  9.  
  10. static {
  11. System.out.println("parent static block");
  12. }
  13. }
  14.  
  15. class Child extends Parent {
  16. static {
  17. System.out.println("child static block");
  18. }
  19. }

结论:有当程序访问的静态变量或静态方法确实在当前类或接口中定义时,才可以认为是对类的主动使用。

实例(五)

  1. public class Test0060 {
  2.  
  3. public static void main(String[] args) throws ClassNotFoundException {
  4. ClassLoader classLoader = ClassLoader.getSystemClassLoader();
  5. Class clazz = classLoader.loadClass("com.chengli.jvm.classloader.v0060.CL");
  6. System.out.println("------------------------------");
  7. clazz = Class.forName("com.chengli.jvm.classloader.v0060.CL");
  8. }
  9. }
  10.  
  11. class CL {
  12. static {
  13. System.out.println("class CL static block");
  14. }
  15. }

结论:调用ClassLoader的loadClass方法加载一个类,并不是对类的主动使用

jvm - 类的初始化过程的更多相关文章

  1. 【深入理解Java虚拟机】类的初始化过程

    类的初始化过程 类的加载过程.png 加载 将 Class 文件以二进制的形式加载到内存中 验证 校验 Class 文件是否安全,是否被正确的修改等 准备 为类变量申请内存,设置默认值,(初始化变量的 ...

  2. Red5源代码分析 - 关键类及其初始化过程

    原文地址:http://semi-sleep.javaeye.com/blog/348768 Red5如何响应rmpt的请求,中间涉及哪些关键类? 响应请求的流程如下: 1.Red5在启动时会调用RT ...

  3. JVM -- 类的初始化

    <深入理解Java虚拟机> 第二版中介绍到了类的加载过程. 一个类从加载入内存到卸载出内存为止,整个生命周期包括: Loading(加载)-----Verification(验证)---- ...

  4. Java类的初始化过程及清理

    一.类的数据成员初始化 Java中类的数据成员初试化可能有两种形式. 在定义类成员变量的地方直接提供初始化值(这是C++中不允许的) 在构造器中初试化.(Java中不存在类似C++中的初始化列表) 两 ...

  5. java类的初始化过程

    1 先初始化父类的静态成员和静态块,然后初始化子类的静态成员和静态块,然后再初始化父类,然后再初始化子类. 2 先初始化父类 3 单个类初始化的顺序 先初始化成员变量和代码块,后调用构造函数 4 如果 ...

  6. 类的初始化过程(难点)--------java基础总结

    前言:看到这么好的东西,忍不住又写到了博客上面 Student s = new Student();在内存中究竟做了哪些事情呢? ①加载student.class文件进内存. ②为栈内存s开辟空间. ...

  7. 深入理解Java对象的创建过程:类的初始化与实例化

    摘要: 在Java中,一个对象在可以被使用之前必须要被正确地初始化,这一点是Java规范规定的.在实例化一个对象时,JVM首先会检查相关类型是否已经加载并初始化,如果没有,则JVM立即进行加载并调用类 ...

  8. JVM类生命周期概述:加载时机与加载过程

    一个.java文件在编译后会形成相应的一个或多个Class文件,这些Class文件中描述了类的各种信息,并且它们最终都需要被加载到虚拟机中才能被运行和使用.事实上,虚拟机把描述类的数据从Class文件 ...

  9. 深入学习Java对象创建的过程:类的初始化与实例化

    在Java中,一个对象在可以被使用之前必须要被正确地初始化,这一点是Java规范规定的.在实例化一个对象时,JVM首先会检查相关类型是否已经加载并初始化,如果没有,则JVM立即进行加载并调用类构造器完 ...

随机推荐

  1. 【Docker】文件拷贝

    从容器复制到主机sudo docker cp containerID:container_path host_path docker cp 5c6ce895b979:/root/LearnPaddle ...

  2. 服务器虚拟化ESXi 5.5安装过程

    研究服务器虚拟化实践小结: 实验服务器硬件: 主板 华硕P8B-C/2L CPU Intel Xeon E3-1230 V2 3.3GHz RAM 8G ECC 1600MHz 硬盘 2T 2块 软件 ...

  3. C#实现如何判断一个数组中是否有重复的元素

    如何判断一个数组中是否有重复的元素 实现判断数组中是否包含有重复的元素方法 这里用C#代码给出实例 方法一:可以新建一个hashtable利用hashtable的Contains方法进行查找 /// ...

  4. IOS多线程处理

    http://www.jianshu.com/p/0b0d9b1f1f19 首页专题下载手机应用 显示模式登录     注册登录 添加关注 作者 伯恩的遗产2015.07.29 00:37* 写了35 ...

  5. Slapper帮助Dapper实现一对多

    Dapper的Query的方法提供了多个泛型重载可以帮助我们实现导航属性的查询 1对1 public class Employees4List { public int Id { get; set; ...

  6. linux 下安装 redis

    https://redis.io/ 1.下载 $ cd /usr/local/ $ wget http://download.redis.io/releases/redis-4.0.7.tar.gz ...

  7. 牛客多校10 D Rikka with Prefix Sum 不是数据结构

    https://www.nowcoder.com/acm/contest/148/D 题意: 1e5个数,1e5个操作,操作分为: 1.区间加. 2.整个数列替换为前缀和. 3.区间查询. 查询数小于 ...

  8. LU decomposition can be viewed as the matrix form of Gaussian elimination.

    https://en.wikipedia.org/wiki/LU_decomposition One way to find the LU decomposition of this simple m ...

  9. hyperledger-fabric/qemu/kvm/virtual-manager -------vagrant-virtual-box

    天我也遇到了这个问题,原因是你的 vagrant 版本跟你的 virtualbox 版本不匹配,解决的方法是,更换 virtualbox 的版本.我的 vagrant 版本是 1.8.4 ,virtu ...

  10. mysql缓冲