读Java 虚拟机类加载引发的血案

最近在看 Java 虚拟机类加载的知识点,结果让我发现了自己一个曾经一直纠结,又没彻底弄懂的类加载黑洞,从而引发下面一系列的测试血案

相信面试过的你们也会见过类似下面测试的这几道题

不过,答案你真的理解了么?

话不多说,直接 GKD

相关学习巨佬博

站在巨佬的肩膀上

https://www.jianshu.com/p/b6547abd0706

https://www.jianshu.com/p/8c8d6cba1f8e

https://www.cnblogs.com/wcd144140/p/7273974.html

看完,大佬们应该都全部理解透了...可惜我不是大佬,所以...哈哈哈 GKD 吧

下面就是测试过程种发现的一些疑惑点,赶紧记录一波.....哎,难顶

测试开始

先思考下下面代码输出什么


class Singleton {

    public Singleton() {
        System.out.println("Singleton new instance");
    }

    static {
        System.out.println("Singleton static block");
    }

    {
        System.out.println("Singleton  block !!!");
    }

}

public class NewTest {
    public static void main(String args[]){
        Singleton singleton = new Singleton();
    }
}

输出结果

Singleton static block
Singleton  block !!!
Singleton new instance


当然,大佬们应该都能知道答案...毕竟,新手入门级的野怪,谁都打得过

这个对我这小菜鸡也算还比较容易理解:

    加载连接过程,没有需要处理的 static 

    new Singleton() 直接开始类的初始化了,所以输出直接按照类的初始化顺序来就好了

类的初始化的执行顺序

没有父类的情况:

1)类的静态属性
2)类的静态代码块
3)类的非静态属性
4)类的非静态代码块
5)构造方法


有父类的情况:

1)父类的静态属性
2)父类的静态代码块
3)子类的静态属性
4)子类的静态代码块
5)父类的非静态属性
6)父类的非静态代码块
7)父类构造方法
8)子类非静态属性
9)子类非静态代码块
10)子类构造方法


这里有个小误区,是我自己的误区~~

比如下面这个例子

class ParentSingleton{
    
public static int value = 100;
    
public ParentSingleton(){
    System.out.println("ParentSingleton new instance");
}
    
static {
    System.out.println("ParentSingleton static block");
}

{
    System.out.println("ParentSingleton  block !!! ");
}
    
}

当要初始化上面这个类的时候,会输出什么

如果这时候,我们只看上面的初始化顺序,会觉得这样输出,根据顺序来嘛

ParentSingleton static block
ParentSingleton  block !!! 
ParentSingleton new instance

???

OMG,错了,这里的顺序不是说,只要初始化,就要全部按照顺序一一执行。。不是这样的

实际上只会输出

ParentSingleton static block

如果有 创建这个类的实例,比如 new ParentSingleton()

才会

ParentSingleton  block !!! 
ParentSingleton new instance

是的,这里的误区,我曾经一度搞错了。。。尴尬

那再看这个测试

class Singleton {
private static Singleton singleton = new Singleton();

private Singleton() {
    System.out.println("Singleton new instance");
}

public static void forTest() {
    
}

static {
    System.out.println("Singleton static block");
}

{
    System.out.println("Singleton  block !!! ");
}

}

public class TestSingleton {
public static void main(String args[]){
    Singleton.forTest();
}
}

看完资料的我,逐渐膨胀,毕竟100多斤的胖子,我想的输出应该是

Singleton static block
Singleton  block !!!
Singleton new instance

然后运行一看,懵逼了,结果是

Singleton  block !!! 
Singleton new instance
Singleton static block

咋回事啊,小老弟,结果乱套了...为什么不是先执行 static 代码块先了

认真想了一波,也不知道对不对,只能疯狂测试这样子...

经过一番测试,查看资料...最终...我觉得是这样子的



整个的流程详解:

执行的第一步:Singleton.forTest();

这时候,对Singleton类进行加载和连接,所以首先需要对它进行加载和连接操作。

在连接-准备阶段,要讲给静态变量赋予默认初始值,这里还没到执行 forTest

初始值是   singleton = null

加载和连接完毕之后,再进行初始化工作

private static Singleton singleton = new Singleton();

所以执行去到了 new Singleton();  这里因为 new 会引起 Singleton 的初始化

需要执行 Singleton构造函数里面的内容

但是又因为非static初始化块,这里面的代码在创建java对象实例时执行,而且在构造器之前!!!!就是这东西。。


所以输出应该是

Singleton  block !!! 
Singleton new instance

而根据类的初始化顺序,要执行 static 代码块,应该输出
Singleton static block

完成初始化后

接下来就到真正调用 forTest 方法了,方法什么都不做,没输出


所以,总的答案就是

Singleton  block !!! 
Singleton new instance
Singleton static block



这里最大的原因就是,连接加载的时候,要给属性初始化,而这里的初始化又刚好是 创建java 实例,需要执行构造,执行构造的前面又必须先执行 {} 大括号非 static 块

而不是和第一个测试例子那样,static 属性不需要初始化,所以....

IG 永不加班,但我需要哇 继续测试吧...

继续测试验证

class Singleton {
private static Singleton singleton = new Singleton();

private Singleton() {
    System.out.println("Singleton new instance");
}

public static Singleton getSingleton() {
    return new Singleton();
}

static {
    System.out.println("Singleton static block");
}

{
    System.out.println("Singleton  block !!! ");
}

}

public class TestSingleton {
public static void main(String args[]){
    Singleton singleton = Singleton.getSingleton();
}
}

输出结果

emm, 再次根据上面自己的理解,走一遍

应该是

Singleton  block !!! 
Singleton new instance
Singleton static block
Singleton  block !!! 
Singleton new instance


这里后面第二次 new 为啥不引起第二次 类的初始化?? 因为一个类只能初始化一次啊

new 只是创建实例,不再初始化了

所以在调用 getSingleton 的时候,只创建实例就好了,而创建实例就是

Singleton  block !!! 
Singleton new instance


在同一个类加载器下面只能初始化类一次,如果已经初始化了就不必要初始化了.

为什么只初始化一次呢?类加载的最终结果就是在堆中存有唯一一个Class对象,我们通过Class对象找到的那个唯一的

噢? 运行看一手,丢 对了。。。。

还有 存在 final 的时候,和存在父类的时候,下面慢慢再测试验证....

继续测试

class Singleton extends ParentSingleton {

public Singleton() {
    System.out.println("Singleton new instance");
}

static {
    System.out.println("Singleton static block");
}

{
    System.out.println("Singleton  block !!! ");
}

}

class ParentSingleton{
    
public ParentSingleton(){
    System.out.println("ParentSingleton new instance");
}
    
static {
    System.out.println("ParentSingleton static block");
}

{
    System.out.println("ParentSingleton  block !!! ");
}
    
}

public class TestSingleton {
public static void main(String args[]){
    Singleton singleton = new Singleton();
}
}

输出结果

这个,很明了,还是按照上面的类的初始化,有父类的情况按顺序调用,输出


ParentSingleton static block
Singleton static block
ParentSingleton  block !!! 
ParentSingleton new instance
Singleton  block !!! 
Singleton new instance

继续测试

那个人 又来了。。。改成和上面没有父类一样的情况

class Singleton extends ParentSingleton {
private static Singleton singleton = new Singleton();

private Singleton() {
    System.out.println("Singleton new instance");
}

public static Singleton getSingleton() {
    return singleton;
}

static {
    System.out.println("Singleton static block");
}

{
    System.out.println("Singleton  block !!! ");
}

}

class ParentSingleton{
    
public ParentSingleton(){
    System.out.println("ParentSingleton new instance");
}
    
static {
    System.out.println("ParentSingleton static block");
}

{
    System.out.println("ParentSingleton  block !!! ");
}
    
}

public class TestSingleton {
public static void main(String args[]){
    Singleton singleton = Singleton.getSingleton();
}
}

输出结果

这里,就开始懵了。。。有点


先看结果


ParentSingleton static block
ParentSingleton  block !!! 
ParentSingleton new instance
Singleton  block !!! 
Singleton new instance
Singleton static block


其实,很容易看清了,现在,再走一遍流程吧

执行到 Singleton.getSingleton() 时

先加载 Singleton  ,这时因为 Singleton 有父类,需要需要加载父类先

加载父类 ParentSingleton,根据加载流程,在连接-准备阶段,要讲给静态变量赋予默认初始值,但父类没有 static 属性需要赋值初始化什么的,但是根据顺序,需要初始化static 代码块

ParentSingleton static block


这时候回到 子类的 加载流程

根据连接-准备阶段,子类有需要处理的属性 private static Singleton singleton = new Singleton()

赋值默认值先,singleton = null

然后初始化 singleton = new Singleton()

根据上面的经验,这里是创建实例 ,并引起初始化,正常应该是

Singleton  block !!! 
Singleton new instance
Singleton static block

但是,重点来了 !!

类实例创建过程:按照父子继承关系进行初始化,首先执行父类的初始化块部分
然后是父类的构造方法;再执行本类继承的子类的初始化块,最后是子类的构造方法

也就是

ParentSingleton  block !!! 
ParentSingleton new instance


同时子类的初始化,因为初始化子类它有父类,所以需要先初始化父类(但是这里因为父类已经初始化了,就不再初始化了)


所以结果是:

ParentSingleton static block
ParentSingleton  block !!! 
ParentSingleton new instance
Singleton  block !!! 
Singleton new instance
Singleton static block

最终测试

class Singleton extends ParentSingleton {
private static Singleton singleton = new Singleton();

private Singleton() {
    System.out.println("Singleton new instance");
}

public static Singleton getSingleton() {
    return singleton;
}

static {
    System.out.println("Singleton static block");
}

{
    System.out.println("Singleton  block !!! ");
}

}

class ParentSingleton{
    
private static ParentSingleton parentSingleton = new ParentSingleton();   
    
public ParentSingleton(){
    
    System.out.println("ParentSingleton new instance");
}
    
static {
    System.out.println("ParentSingleton static block");
}

{
    System.out.println("ParentSingleton  block !!! ");
}
    
}

public class TestSingleton {
public static void main(String args[]){
    Singleton singleton = Singleton.getSingleton();
}
}

测试结果

ParentSingleton  block !!! 
ParentSingleton new instance
ParentSingleton static block
ParentSingleton  block !!! 
ParentSingleton new instance
Singleton  block !!! 
Singleton new instance
Singleton static block


加载一个类时,先加载父类


按照先加载,创建实例,初始化,这个顺序就发现 很通顺的写出答案了


哈哈哈哈哈,终于清楚了 所以一切的一切,都是创建实例这个东西。。搞得我头晕

部分特殊不引起类初始化记录

先记录下吧

1. 通过子类引用父类的静态字段,不会导致子类初始化,对于静态字段,只有直接定义这个字段的类才会被初始化
2. 通过数组定义来引用类,不会触发此类的初始化
3. 常量在编译阶段会存入调用类的常量池中,本质上并没有直接引用到定义常量的类,因此不会触发定义常量的类的初始化
4. public static final int x =6/3;  能够在编译时期确定的,叫做编译常量,不会引起类的初始化!!!
5. public static final int x =new Random().nextInt(100); 运行时才能确定下来的,叫做运行时常量,运行常量会引起类的初始化!!!

引起类初始化记录

在虚拟机规范中使用了一个很强烈的限定语:“有且仅有”,这5种场景中的行为称为对类进行主动引用。除此之外,所有引用类的方式都不会触发初始化,称为被动引用

    5种必须初始化的场景如下

1.  遇到new、getstatic、putstatic或invokestatic这4条字节码指令时,如果类没有初始化,则需要先触发其初始化

    这4条指令对应的的常见场景分别是:使用new关键字实例化对象、读取或设置一个类的静态字段(被final修饰、已在编译期把结果放入常量池的静态字段除外)的时候,以及调用一个类的静态方法的时候
    
注:静态内容是跟类关联的而不是类的对象

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

注:反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法
    
    对于任意一个对象,都能够调用它的任意一个方法和属性
    
    这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制,这相对好理解为什么需要初始化类
    
3.  当初始化一个类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化

注:子类执行构造函数前需先执行父类构造函数

4.  当虚拟机启动时,用户需要指定一个要执行的主类(包含main()方法的那个类),虚拟机会先初始化这个主类

注:main方法是程序的执行入口

5.  当使用JDK1.7的动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且这个方法句柄所对应的类没有进行过初始化。则需要先触发其初始化

注:JDK1.7的一种新增的反射机制,都是对类的一种动态操作

这回,以后看代码的时候,就不会再被这些执行加载顺序弄混了,对优化代码可能还是有帮助的吧

再不说,也能再让我看到这些测试题,或者问我加载的过程,怎么也能处理回答个7788了吧

可能其中个人理解有部分纰漏,还请大佬们指出~~蟹蟹鸭

后面还要去验证测试下面这些情况

1.  子类引用指向父类
2.  ...等等

任重而道远,我是桥豆麻袋,下回再见~

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 196,165评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,503评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,295评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,589评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,439评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,342评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,749评论 3 387
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,397评论 0 255
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,700评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,740评论 2 313
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,523评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,364评论 3 314
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,755评论 3 300
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,024评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,297评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,721评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,918评论 2 336