先上结论:

以下情况会触发类的初始化:

  1. 遇到new,getstatic,putstatic,invokestatic这4条指令;
  2. 使用java.lang.reflect包的方法对类进行反射调用;
  3. 初始化一个类的时候,如果发现其父类没有进行过初始化,则先初始化其父类(注意!如果其父类是接口的话,则不要求初始化父类);
  4. 当虚拟机启动时,用户需要指定一个要执行的主类(包含main方法的那个类),虚拟机会先初始化这个主类;
  5. 当使用jdk1.7的动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果REF_getstatic,REF_putstatic,REF_invokeStatic的方法句柄,并且这个方法句柄所对应的类没有进行过初始化,则先触发其类初始化;

以下情况不会触发类的初始化:

  1. 同类子类引用父类的静态字段,不会导致子类初始化。至于是否会触发子类的加载和验证,取决于虚拟机的具体实现;
public class ClassTest1 {public static void main(String[] args) {System.out.println(SubClass.num);
//        SubClass subClass=new SubClass();
//        System.out.println(SubClass.num);}
}
class SubClass extends SuperClass{static {System.out.println(111111111);}
}
class SuperClass{static{System.out.println(2222222);}static int num=100;
}

2.通过数组定义来引用类,也不会触发类的初始化;例如:People[] ps = new People[100];

public class ClassTest2 {public static void main(String[] args) {ArrayClass[] why=new ArrayClass[10];}
}class ArrayClass{static {System.out.println(1111);}
}

3.引用一个类的常量也不会触发类的初始化

public class ClassTest3 {public static void main(String[] args) {System.out.println(ConstVariable.TEL);}
}
class ConstVariable{static {System.out.println(1111);}public static final String TEL="10086";
}

Java是如何加载并初始化的

Java虚拟机如何把编译好的.class文件加载到虚拟机里面?加载之后如何初始化类?静态类变量和实例类变量的初始化过程是否相同,分别是如何初始化的呢?这篇文章就

是解决上面3个问题的。

若有不正之处,请多多谅解并欢迎各位能够给予批评指正,提前谢谢各位了。

1. Java虚拟机加载.class过程

虚拟机把Class文件加载到内存,然后进行校验解析初始化,最终形成java类型,这就是虚拟机的类加载机制。加载,验证,准备,初始化这5个阶段的顺序是确定的,

类的加载过程,必须按照这种顺序开始。这些阶段通常是相互交叉和混合进行的。解析阶段在某些情况下,可以在初始化阶段之后再开始---为了支持java语言的运行时绑定。

Java虚拟机规范中,没有强制约束什么时候要开始加载,但是,却严格规定了几种情况必须进行初始化(加载,验证,准备则需要在初始化之前开始):

1)遇到 new、getstatic、putstatic、或者invokestatic 这4条字节码指令,如果没有类没有进行过初始化,则触发初始化

2)使用java.lang.reflect包的方法,对垒进行反射调用的时候,如果没有初始化,则先触发初始化

3)初始化一个类时候,如果发现父类没有初始化,则先触发父类的初始化

2. 加载,验证,解析

加载就是通过指定的类全限定名,获取此类的二进制字节流,然后将此二进制字节流转化为方法区的数据结构,在内存中生成一个代表这个类的Class对象。验证是为了确

保Class文件中的字节流符合虚拟机的要求,并且不会危害虚拟机的安全。加载和验证阶段比较容易理解,这里就不再过多的解释。解析阶段比较特殊,解析阶段是虚拟机

将常量池中的符号引用转换为直接引用的过程。如果想明白解析的过程,得先了解一点class文件的一些信息。class文件采用一种类似C语言的结构体的伪结构来存储我们编

码的java类的各种信息。其中,class文件中常量池(constant_pool)是一个类似表格的仓库,里面存储了我们编写的java类的类和接口的全限定名,字段的名称和描述符,

方法的名称和描述符。在java虚拟机将class文件加载到虚拟机内存之后,class类文件中的常量池信息以及其他的数据会被保存到java虚拟机内存的方法区。我们知道class文件

的常量池存放的是java类的全名,接口的全名和字段名称描述符,方法的名称和描述符等信息,这些数据加载到jvm内存的方法区之后,被称做是符号引用。而把这些类的

全限定名,方法描述符等转化为jvm可以直接获取的jvm内存地址,指针等的过程,就是解析。虚拟机实现可以对第一次的解析结果进行缓存,避免解析动作的重复执行。

在解析类的全限定名的时候,假设当前所处的类为D,如果要把一个从未解析过的符号引用N解析为一个类或者接口C的直接引用,具体的执行办法就是虚拟机会把代表N的

全限定名传递给D的类加载器去加载这个类C。这块可能不太好理解,但是我们可以直接理解为调用D类的ClassLoader来加载N,然后就完成了N--->C的解析,就可以了。

3. 准备阶段

之所以把在解析阶段前面的准备阶段,拿到解析阶段之后讲,是因为,准备阶段已经涉及到了类数据的初始化赋值。和我们本文讲的初始化有关系,所以,就拿到这里来讲

述。在java虚拟机加载class文件并且验证完毕之后,就会正式给类变量分配内存并设置类变量的初始值。这些变量所使用的内存都将在方法区分配。注意这里说的是类变量,

也就是static修饰符修饰的变量,在此时已经开始做内存分配,同时也设置了初始值。比如在 Public static int value = 123 这句话中,在执行准备阶段的时候,会给value

分配内存并设置初始值0, 而不是我们想象中的123. 那么什么时候 才会将我们写的123 赋值给 value呢?就是我们下面要讲的初始化阶段。

4. 初始化阶段

类初始化阶段是类加载过程的最后阶段。在这个阶段,java虚拟机才真正开始执行类定义中的java程序代码。Java虚拟机是怎么完成初始化的呢?这要从编译开始讲起。在编

译的时候,编译器会自动收集类中的所有静态变量(类变量)和静态语句块(static{}块)中的语句合并产生的,编译器收集的顺序是根据语句在java代码中的顺序决定的。

收集完成之后,会编译成java类的 static{} 方法,java虚拟机则会保证一个类的static{} 方法在多线程或者单线程环境中正确的执行,并且只执行一次。在执行的过程中,便完

成了类变量的初始化。值得说明的是,如果我们的java类中,没有显式声明static{}块,如果类中有静态变量,编译器会默认给我们生成一个static{}方法。 我们可以通过

javap -c 的命令,来看一下java字节码中编译器为我们生成或者合并的static{} 方法:

public class StaticValInitTest {public static int value = 123;}

上面我们讲述的是单类的情况,如果出现继承呢?如果有继承的话,父类中的类变量该如何初始化?这点由虚拟机来解决:虚拟机会保证在子类的static{}方法执行之

前,父类的static{}方法已经执行完毕。由于父类的static{}方法先执行,也就意味着父类的静态变量要优先于子类的静态变量赋值操作。

上面讲的都是静态变量,实例变量怎么解决呢?实例变量的初始化,其实是和静态变量的过程是类似的,但是时间和地点都不同哦。我们以下面的Dog类为例来讲一讲。

public class Dog {public String type = "tai di";public int age = 3;
}

1)当用new Dog() 创建对象的时候,首先在堆上为Dog对象分配足够的空间。

2)这块存储空间会被清零,这就是自动将Dog对象中的所有基本类型的数据都设置成了默认值,而引用类型则被设置成了null(类似静态类的准备阶段的过程)

3)Java收集我们的实例变量赋值语句,合并后在构造函数中执行赋值语句。没有构造函数的,系统会默认给我们生成构造函数。

至此,java类初始化的理论基础已经完成了,其中的大部分的理论和思想都出自《深入理解java虚拟机》这本书。有了以上的理论基础,

再复杂的类初始化的情况,我们都可以应对了,下面就拿一个例子做一个具体的分析吧

public class Insect {private int i = 9;protected int j;protected static int x1 = printInit("static Insect.x1 initialized");Insect() {System.out.println("基类构造函数阶段: i = " + i + ", j = " + j);j = 39;}static int printInit(String s) {System.out.println(s);return 47;}
}public class Beetle extends Insect {protected int k = printInit("Beetle.k initialized");protected static int x2 = printInit("static Beetle.x2 initialized");    public static void main(String[] args) {        Beetle b = new Beetle();}
}

上面例子来自《java编程思想》,以上代码的执行结果是什么呢?如果对上面我们讲的理论理解的话,很容易就知道结果是:

static Insect.x1 initialized

static Beetle.x2 initialized

基类构造函数阶段: i = 9, j = 0

Beetle.k initialized

具体的执行结果过程是:

在执行Beetle 类的 main方法的时候,因为该main方法是static方法,我们在上面已经知道,在执行类的static方法的时候,如果该类没有初始化,则要进行初始化,

因此,我们在执行main方法的时候,会执行加载--验证--准备--解析---初始化这个过程。在进行最后的初始化的时候,又有一个约束:虚拟机会保证在子类的static{}

方法执行之前,父类的static{}方法已经执行完毕。所以,在执行完解析之后,会先执行父类的初始化,在执行父类初始化的时候,

输出: static Insect.x1 initialized

然后接着初始化子类,输出:static Beetle.x2 initialized

以上两行输出,是静态变量的初始化,是在第一次调用静态方法,即,在执行new、getstatic、putstatic、或者invokestatic 这4条字节码指令时候触发的。所以,

你如果把上例中的static main 方法中的 Beetle b = new Beetle();

注释掉,上面两行仍然会输出出来。然后就是执行Beetle b = new Beetle();这句代码了。我们知道,在实例化子类对象的时候,会自动调用父类的构造函数。

所以,接着就输出:基类构造函数阶段: i = 9, j = 0

紧接着是执行自己的构造函数,在堆上创建类实例对象,实例对象空间清零,然后执行赋值语句k = printInit("Beetle.k initialized");

输出: Beetle.k initialized

至此,整个类加载并初始化完毕,是不是理解起来就很简单了,趁胜追击,我们还是再来看一个例子吧:

public class Base {Base() {preProcess();}void preProcess() {}
}public class Derived extends Base {public String whenAmISet = "set when declared"; @Override void preProcess() {whenAmISet = "set in preProcess";}    public static void main(String[] args) {Derived d = new Derived();System.out.println(d.whenAmISet);}
}

一个地方比较绕:父类在执行构造函数的时候,调用了子类(导出类)重载过的方法,在子类的重载方法中,给实例变量做了一次赋值,正是这次赋值,干扰了我们对类初始化的理解。

我们不管类里面是怎么做的,还按照我们上个例子中那样进行分析:

1. 执行Derived 类 static main 方法的时候,执行类变量初始化,但是此例中父类和子类都没有类变量,所以此步骤什么都不做,进行实例变量初始化

2. 执行new Derived()的时候,先调用了父类的构造函数,因为子类的重载,调用了子类的preProcess方法,为实例变量whenAmISet 赋值为"set in preProcess"

3. 然后执行子类Derived 的构造函数,在构造函数中,有编译器为我们收集生成的实例变量赋值语句,最终,又将实例变量whenAmISet 赋值为"set when declared"

4. 所以最终的输出是: set when declared

如果对这个还不太理解的话,可以再Derived 类里面添加注释,改成下面的样子,输出看看,是不是对这个执行过程更清晰了呢?

public class Derived extends Base {// 准备阶段赋值 whenAmISet=nullpublic String whenAmISet = "set when declared";public Derived() {System.out.println("do son constructor");}@Override void preProcess() {System.out.println("do son process");System.out.println("whenAmISet:" + whenAmISet);whenAmISet = "set in preProcess";System.out.println("whenAmISet:" + whenAmISet);System.out.println("set in preProcess end");}public static void main(String[] args) {Derived d = new Derived();System.out.println(d.whenAmISet);}
}    

参考文献:

http://www.cnblogs.com/jimxz/p/3974939.html

https://www.cnblogs.com/hujinshui/p/10422521.html

Java什么时候会触发类初始化及原理(详解)相关推荐

  1. java源码系列:HashMap底层存储原理详解——4、技术本质-原理过程-算法-取模具体解决什么问题

    目录 简介 取模具体解决什么问题? 通过数组特性,推导ascii码计算出来的下标值,创建数组非常占用空间 取模,可保证下标,在HashMap默认创建下标之内 简介 上一篇文章,我们讲到 哈希算法.哈希 ...

  2. java file用法_Java File 类的使用方法详解(转)

    转自:http://www.codeceo.com/article/java-file-class.html Java File类的功能非常强大,利用Java基本上可以对文件进行所有的操作.本文将对J ...

  3. Java设计模式之观察者模式(UML类图分析+代码详解)

    大家好,我是一名在算法之路上不断前进的小小程序猿!体会算法之美,领悟算法的智慧~ 希望各位博友走过路过可以给我点个免费的赞,你们的支持是我不断前进的动力!! 加油吧!未来可期!! 本文将介绍java设 ...

  4. Java 并发编程CAS、volatile、synchronized原理详解

    CAS(CompareAndSwap) 什么是CAS? 在Java中调用的是Unsafe的如下方法来CAS修改对象int属性的值(借助C来调用CPU底层指令实现的): /*** * @param o ...

  5. java源码系列:HashMap底层存储原理详解——5、技术本质-原理过程-算法-取模会带来一个什么问题?什么是哈希冲突?为什么要用链表?

    目录 取模会带来一个什么问题? 演示什么是哈希冲突(哈希碰撞)? 为什么要用链表? 其他--布隆过滤器 取模会带来一个什么问题? 好,那同学们这样他能达到一个目的,但是呢,它也会带来的一个问题,那它会 ...

  6. 【Android架构师java原理详解】二;反射原理及动态代理模式

    前言: 本篇为Android架构师java原理专题二:反射原理及动态代理模式 大公司面试都要求我们有扎实的Java语言基础.而很多Android开发朋友这一块并不是很熟练,甚至半路初级底子很薄,这给我 ...

  7. Java HashSet的实现原理详解

    HashSet是Java Map类型的集合类中最常使用的,本文基于Java1.8,对于HashSet的实现原理做一下详细讲解. (Java1.8源码:http://docs.oracle.com/ja ...

  8. Java HashMap的实现原理详解

    HashMap是Java Map类型的集合类中最常使用的,本文基于Java1.8,对于HashMap的实现原理做一下详细讲解. (Java1.8源码:http://docs.oracle.com/ja ...

  9. Cesium 核心类Viewer-查看器详解

    Cesium 核心类Viewer-查看器详解 1 简介 A base widget for building applications. It composites all of the standa ...

最新文章

  1. windows10下mysql-8.0.21的安装和使用
  2. 计算机应用基础是高中吗,职业高中学业水平测试计算机应用基础试卷(A)及答案...
  3. Android开发--用户定位服务--UserLocation
  4. Android app内语言环境切换
  5. 带你看明白class二进制文件!
  6. Spring中ApplicationContext加载机制和配置初始化
  7. 线性代数之行列式矩阵术语中英对照
  8. mongodb安装_Windows系统安装运行Mongodb服务
  9. c语言插入特定的字符串,C语言实现:将一个字符串插入到另一个字符串的指定位置...
  10. android studio初始化设置,Android studio 初始设置
  11. 计算机一级应用于段落还是文字,计算机一级复习资料
  12. 【干货】普华永道:新形势下,企业如何进行数字化转型.pdf(附下载链接)
  13. C++ std::move/std::forward/完美转发
  14. Google帝国研究——Google的产业构成
  15. 三菱plc控制步进电机实例_FX3U PLC通过手摇轮,如何手动控制步进电机
  16. ubuntu alise设置
  17. 通过yolov5训练自己的模型中遇到的一些问题及解决办法
  18. 蓝迪游戏正式开放源代码
  19. 如何在官网选择其他JDK版本进行下载
  20. so动态库生成、链接方法

热门文章

  1. 360 android 应用市场,360手机助手在安卓应用市场占大份额
  2. AdGuard4.0新版本广告拦截程序 新增功能介绍
  3. Vue2基础篇-21-非单文件组件
  4. 在linux终端中重命名文件,Linux系统中重命名文件的方法有哪些
  5. 小学-知识与能力【10】
  6. js中Object.freeze()函数的作用
  7. CAD完美转ArcGIS的操作方法技巧
  8. python用于cad_【笔记】利用Python自动化操作AutoCAD
  9. 网易云web安全工程师第一天
  10. 微信小程序复用公众号资质快速认证