什么时候加载?
- 遇到new、getstatic、putstatic或invokestatic这4条字节码指令时,如果类没有进行过初始化,则需要先触发其初始化。生成这4条指令的最常见的Java代码场景是:
- 使用new关键字实例化对象的时候
- 读取或设置一个类的静态字段(被final修饰、已在编译期把结果放入常量池的静态字段除外)的时候
- 调用一个类的静态方法的时候
- 使用java.lang.reflect包的方法对类进行反射调用的时候,如果类没有进行过初始化,则需要先触发其初始化。
- 当初始化一个类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化。
- 当虚拟机启动时,用户需要指定一个要执行的主类(包含main()方法的那个类) ,虚拟机会先初始化这个主类。
- 当使用JDK 1.7的动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且这个方法句柄所对应的类没有进行过初始化,则需要先触发其初始化。
主类,还有正常或反射情况下的new创建、引用static的方法和变量(final static是常量,除外)时,如果类还未初始化,则加载之。
注意:
子类.父类静态变量,并不会初始化子类。
类[] = new 类[10],数组的情况不会初始化。
类.final static xxx,常量在编译阶段会存入调用类的常量池中,不会初始化。
加载的过程?
加载
用类名获取二进制字节流(文件or其它),再将字节流代表的静态存储结构转化为方法区的运行时数据结构(1.8在本地内存的元空间里),然后在内存中生产一个代表该类的Class对象,作为方法区这个类各种数据的访问入口。
验证
验证class字节码各种格式是否正确
准备
为类变量(static)在方法区内分配内存,并设置这些变量的初始值。
注意,初始值一般是0或null,static xx = 123 也是0,在后面类构造器
解析
将常量池内的符号引用替换为直接引用。Class类文件的结构
初始化【重点】
类初始化是执行类构造器
使用 、 卸载
暂略
对象实例化过程?
先判断类是否初始化过,没的话调用;有过的话跳过。
- 在堆内存中开辟一块空间(保存对象头、实例变量、填充数据)
- 开辟空间分配一个地址(指针碰撞或者空闲列表两种分配方式)
- 把对象的所有非静态成员加载到所开辟的空间下(从方法区的非静态区域中加载,类加载的时候.class文件的非静态内容就是加载到这里的)
- 所有的非静态成员加载完成之后,对所有非静态成员变量进行默认初始化
- 所有非静态成员变量默认初始化完成之后,调用构造函数
- 在构造函数入栈执行时,分为两部分:先执行构造函数中的隐式三步,再执行构造函数中书写的代码
- 隐式三步:
- 执行super语句
- 对开辟空间下的所有非静态成员变量进行显式初始化
- 执行构造代码块(注:代码块与非静态成员变量显示初始化无先后顺序,与代码顺序相关)
- 在隐式三步执行完之后,执行构造函数中书写的代码
- 隐式三步:
- 在整个构造函数执行完并弹栈后,把空间分配的地址赋值给一个引用对象(对象的访问定位有句柄和直接指针两种方式)
多层关系总的来说就是:
- 父类静态代码块/静态变量初始化 ->
- 子类静态代码块/静态变量初始化 ->
- 父类代码块/变量初始化 ->
- 父类构造函数 ->
- 子类代码块/变量初始化 ->
- 子类构造函数
注意:
同一个class类只初始化一次。
无构造函数代码,也会有个默认的空构造函数。
构造函数开头有个默认的super()。
static final 的是常量,会在更早的类准备时期(当前是类初始化时期)就设置好。
参考文章
https://read.douban.com/reader/ebook/15233695/
https://zhuanlan.zhihu.com/p/33509426