Java-类加载(类的生命周期)
类从被加载到虚拟机内存开始,到卸载出内存为止。

解析阶段在某些情况下可以在初始化后再开始,这是为了支持 Java 语言的运行时绑定。
一、类加载时机
JVM 规范没有强制约束类加载过程的第一阶段(加载)什么时候开始,但对于“初始化”阶段,有着严格的规定。
1.1.有且仅有 5 种情况必须立即对类进行“初始化”:
1.在遇到 new、putstatic、getstatic、invokestatic 字节码指令时,如果类尚未初始化,则需要先触发初始化。
2.对类进行反射调用时,如果类还没有初始化,则需要先触发初始化。
3.初始化一个类时,如果其父类还没有初始化,则需要先初始化父类。
4.虚拟机启动时,用于需要指定一个包含 main() 方法的主类,虚拟机会先初始化这个主类。
5.当使用 JDK 1.7 的动态语言支持时,如果一个 java.lang.invoke.MethodHandle 实例最后的解析结果为 REF_getStatic、REF_putStatic、REF_invokeStatic 的方法句柄,并且这个方法句柄所对应的类还没初始化,则需要先触发初始化。
这 5 种场景中的行为称为对一个类进行主动引用,除此之外,其它所有引用类的方式都不会触发初始化,称为被动引用。
1.2.几种被动引用:
1.通过子类引用父类的静态字段,不会导致子类初始化。对于静态字段,只有直接定义这个字段的类才会被初始化。
class SuperClass {
static {
System.out.println("SuperClass init!");
}
public static int value = 123;
}
class SubClass extends SuperClass {
static {
System.out.println("SubClass init!");
}
}
public class NotInitialization {
public static void main(String[] args) {
System.out.println(SubClass.value);
// SuperClass init!
}
}
2.通过数组定义来引用类,不会触发此类的初始化。
class SuperClass2 {
static {
System.out.println("SuperClass init!");
}
public static int value = 123;
}
public class NotInitialization2 {
public static void main(String[] args) {
SuperClass2[] superClasses = new SuperClass2[10];
}
}
3.常量在编译阶段会存入调用类的常量池中,本质上并没有直接引用到定义常量的类,因此不会触发定义常量的类的初始化。
class ConstClass {
static {
System.out.println("ConstClass init!");
}
public static final String HELLO_BINGO = "Hello Bingo";
}
public class NotInitialization3 {
public static void main(String[] args) {
System.out.println(ConstClass.HELLO_BINGO);
}
}
编译通过之后,常量存储到 NotInitialization 类的常量池中,NotInitialization 的 Class 文件中并没有 ConstClass 类的符号引用入口,这两个类在编译成 Class 之后就没有任何联系了。
1.3.关于接口加载
当一个类在初始化时,要求其父类全部都已经初始化过了,但是一个接口在初始化时,并不要求其父接口全部都完成了初始化,当真正用到父接口的时候才会初始化。
二、类的加载过程
2.1.加载
JVM 需要完成 3 件事:
1.通过类的全限定名获取该类的二进制字节流。
2.将二进制字节流所代表的静态结构转化为方法区的运行时数据结构。
3.在内存中创建一个代表该类的 java.lang.Class 对象,作为方法区这个类的各种数据的访问入口。
怎样获取类的二进制字节流,JVM 没有限制。除了从编译好的 .class 文件中读取,还有以下几种方式:
从 zip 包中读取,如 jar、war 等
从网络中获取
通过动态代理生成代理类的二进制字节流
从数据库中读取
。。。
数组类本身不通过类加载器创建,由 JVM 直接创建,再由类加载器创建数组中的元素类。
加载阶段与连接阶段的部分内容交叉进行,但这两个阶段的开始仍然保持先后顺序。
2.2.验证
确保 Class 文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。
2.3.准备
为类变量(静态成员变量)分配内存并设置初始值的阶段。这些变量(不包括实例变量)所使用的内存都在方法区中进行分配。
基本类型初始值(JDK8)https://docs.oracle.com/javase/specs/jls/se8/html/jls-4.html#jls-4.12.5
对于 byte 类型,默认值为零,即(byte)0。
对于 short 类型,默认值为零,即(short)0。
对于 int 类型,默认值为零,即 0。
对于 long 类型,默认值为零,即 0L。
对于 float 类型,默认值为正零,即 0.0f。
对于 double 类型,默认值为正零,即 0.0d。
对于 char 类型,默认值为空字符,即 '\u0000'。
对于 boolean 类型,默认值为 false。
对于所有引用类型,默认值为 null。
存在特殊情况 https://www.jianshu.com/p/520295a63967
/**
* 准备阶段过后的初始值为 0 而不是 123,这时候尚未开始执行任何 Java 方法
*/
public static int value = 123; /**
* 同时使用 final 、static 来修饰的变量(常量),并且这个变量的数据类型是基本类型或者 String 类型,就生成 ConstantValue 属性来进行初始化。
* 没有 final 修饰或者并非基本类型及 String 类型,则选择在 <clinit> 方法中进行初始化。
* 准备阶段虚拟机会根据 ConstantValue 的设置将 value 赋值为 123
*/
public static final int value = 123;
2.4.解析
虚拟机将常量池内的符号引用替换为直接引用。会把该类所引用的其他类全部加载进来( 引用方式:继承、实现接口、域变量、方法定义、方法中定义的本地变量)
https://www.cnblogs.com/shinubi/articles/6116993.html
符号引用:一个 java 文件会编译成一个class文件。在编译时,java 类并不知道所引用的类的实际地址,因此只能使用符号引用来代替。
直接引用:直接指向目标的指针(指向方法区,Class 对象)、指向相对偏移量(指向堆区,Class 实例对象)或指向能间接定位到目标的句柄。
2.5.初始化
类加载过程的最后一步,是执行类构造器 <clinit>() 方法的过程。
<init>() 与 <clinit>() 介绍: https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-2.html#jvms-2.9
https://blog.csdn.net/u013309870/article/details/72975536
<init>():为 Class 类实例构造器,对非静态变量解析初始化,一个类构造器对应个。
<clinit>():为 Class 类构造器对静态变量,静态代码块进行初始化,通常一个类对应一个,不带参数,且是 void 返回。当一个类没有静态语句块,也没有对类变量的赋值操作,那么编译器可以不为这个类生成 <clinit>() 方法
加载顺序:
<clinit>() 方法是由编译器自动收集类中的所有类变量的赋值动作语句和静态块(static {})中的语句合并产生的,编译器收集的顺序由语句在源文件中出现的顺序所决定。
静态语句块中只能访问定义在静态语句块之前的变量,定义在它之后的变量,在前面的静态语句块中可以赋值,但不能访问。
static {
i = 0; // 给后面的变量赋值,可以正常编译通过
System.out.println(i); // 使用后面的变量,编译器会提示“非法向前引用”
}
static int i = 1;
虚拟机会保证在子类的 <clinit>() 方法执行之前,父类的 <clinit>() 方法已经执行完毕。
由于父类的 <clinit>() 方法先执行,意味着父类中定义的静态语句块要优先于子类的变量赋值操作。
static class Parent {
static {
A = 2;
}
public static int A = 1;
}
static class Sub extends Parent {
public static int B = A;
}
public static void main(String[] args) {
System.out.println(Sub.B); // 输出 1
}
来看一个类属性加载顺序的问题
public class JvmTest {
public static JvmTest jt = new JvmTest();
public static int a;
public static int b = 0;
static {
a++;
b++;
}
public JvmTest() {
a++;
b++;
}
public static void main(String[] args) {
/**
* 准备阶段:为 jt、a、b 分配内存并赋初始值 jt=null、a=0、b=0
* 解析阶段:将 jt 指向内存中的地址
* 初始化:jt 代码位置在最前面,这时候 a=1、b=1
* a 没有默认值,不执行,a还是1,b 有默认值,b赋值为0
* 静态块过后,a=2、b=1
*/
System.out.println(a); // 输出 2
System.out.println(b); // 输出 1
}
}
关于接口初始化:
接口中不能使用静态代码块,但接口也需要通过 <clinit>() 方法为接口中定义的静态成员变量显式初始化。
接口与类不同,接口的 <clinit>() 方法不需要先执行父类的 <clinit>() 方法,只有当父接口中定义的变量被使用时,父接口才会初始化。
虚拟机会保证一个类的 <clinit>() 方法在多线程环境中被正确加锁、同步。如果多个线程同时去初始化一个类,那么只会有一个线程去执行这个类的 <clinit>() 方法。
https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-5.html
https://github.com/doocs/jvm/blob/master/docs/08-load-class-time.md
https://github.com/doocs/jvm/blob/master/docs/09-load-class-process.md
Java-类加载(类的生命周期)的更多相关文章
- Java - JVM - 类的生命周期
概述 简述 JVM 里 类的生命周期 上次写了 30%, 居然丢了 难受, 又要重新写 类的生命周期 加载 使用 卸载 1. 加载 概述 类型的加载 大体流程 装载 连接 验证 准备 解析(可选的) ...
- JVM类加载器及Java类的生命周期
预定义类加载器(三种): 启动(Bootstrap)类加载器: 是用本地代码实现的类装入器,它负责将<Java_Runtime_Home>/lib下面的类库加载到内存中(比如rt.jar) ...
- 乐字节Java反射之三:方法、数组、类加载器和类的生命周期
本文承接上一篇:乐字节Java发射之二:实例化对象.接口与父类.修饰符和属性 继续讲述Java反射之三:方法.数组.类加载器 一.方法 获取所有方法(包括父类或接口),使用Method即可. publ ...
- Java类的生命周期详解
引言 最近有位细心的朋友在阅读笔者的文章时,对java类的生命周期问题有一些疑惑,笔者打开百度搜了一下相关的问题,看到网上的资料很少有把这个问题讲明白的,主要是因为目前国内java方面的教材大多只是告 ...
- 【转】Java 类的生命周期详解
一. 引 言 最近有位细心的朋友在阅读笔者的文章时,对java类的生命周期问题有一些疑惑,笔者打开百度搜了一下相关的问题,看到网上的资料很少有把这个问题讲明白的,主要是因为目前国内java方面的教材大 ...
- 【转载】详解java类的生命周期
原文地址:http://blog.csdn.net/zhengzhb/article/details/7517213 引言 最近有位细心的朋友在阅读笔者的文章时,对java类的生命周期问题有一些疑惑, ...
- JVM 类的生命周期、类加载器
类的加载.连接与初始化 • 1. 加载:查找并加载类的二进制数据 • 2. 连接 – 2.1 验证:确保被加载的类的正确性 ...
- [Java]类的生命周期(下)类的初始化[转]
上接深入java虚拟机——深入java虚拟机(二)——类加载器详解(上),在上一篇文章中,我们讲解了类的生命周期的加载和连接,这一篇我们接着上面往下看. 类的初始化:在类的生命周期执行完加载和连接之后 ...
- [Java]类的生命周期(上)类的加载和连接[转]
本文来自:曹胜欢博客专栏.转载请注明出处:http://blog.csdn.net/csh624366188 类加载器,顾名思义,类加载器(class loader)用来加载 Java 类到 Java ...
- JVM-类加载过程(Java类的生命周期)
什么是类加载 类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个java.lang.Class对象,用来封装类在方法区内的数据结构.类的 ...
随机推荐
- js 前端请求头里传 token
参考:https://blog.csdn.net/qq_34309704/article/details/80572077 1.Token:token是客户端频繁向服务器端请求数据,服务器频繁的去数据 ...
- 百度编辑神器ueditor在ajax或form提交内容时候异常
百度编辑神器ueditor在ajax或form提交内容时候异常,一:⑴web.config中<system.web> <httpRuntime requestValidationMo ...
- KeyError: 'pass_ticket'
使用wxpy模块对接微信登陆,在扫描程序弹出二维码后,使用手机微信扫描登陆之后报KeyError: 'pass_ticket'. 原因就是微信禁止该账号登陆微信网页版,如下图: 所以,去找到能让账号登 ...
- QTP(15)
Test15001_两位数加法器 Option Explicit Dim num1,num2,result,ex 'result 实际结果 = 被测系统结果输入框中的值 'ex 预期结果 = num1 ...
- linux下进程间通信的机制
今天突然想起了nginx解决惊群的方法,就是在多个进程间利用锁来保证同一时刻只能有一个worker进程在自己的epoll中加入监听的句柄,那么进程间是怎么共享变量的呢,下面就介绍一下共享内存 共享内存 ...
- Python:pip 安装第三方库,速度很慢的解决办法
场景 想安装 Django 库 在 cmd 敲入命令 pip install Django 但是发现下载安装文件非常慢 原因:实质访问的下载网站是 https://pypi.Python.org/si ...
- Java一致性Hash算法的实现
哈希hashhash的意思是散列,目的将一组输入的数据均匀的分开.打散,往往用来配合路由算法做负载均衡,多用在分布式系统中.比如memcached它只提供了K V的存储.读取,如果使用了多台memca ...
- 前端每周清单第 49 期:Webpack 4 Beta 尝鲜,React Windowing 与 setState 分析,Web Worker 实战
前端每周清单专注前端领域内容,以对外文资料的搜集为主,帮助开发者了解一周前端热点:分为新闻热点.开发教程.工程实践.深度阅读.开源项目.巅峰人生等栏目.欢迎关注[前端之巅]微信公众号(ID: fron ...
- python中sys.argv[]用法
sys.argv[]的作用: 在运行python文件的时候往文件里面传递参数. 从函数外部获取到变量值 import sys arg = sys.argv[0] args = sys.argv[:] ...
- 天刀默认src截图保存文件夹位置在哪里?
C:\Users\Public\Documents\WuXia 注意有的电脑显示的是public documents,实际进去就是documents