论类型转换导致 JVM 类加载提前报错的问题 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
HikariLan
V2EX    Java

论类型转换导致 JVM 类加载提前报错的问题

  •  
  •   HikariLan
    shaokeyibb 2024-09-09 01:26:16 +08:00 3296 次点击
    这是一个创建于 397 天前的主题,其中的信息可能已经有所发展或是发生改变。

    今天下午,一朋友在某群 at 我,神秘兮兮的说道要考我一个问题。题目是这样的:

    在 Java 中有 Father 和 Son 类,其中 Son 继承了 Father 类,两类均有 method 方法,现在 Main 类的 main 方法有如下调用:

    Father f = new Son(); f.method(); 

    问题是,编译此代码,完成后删除 Son.class,请问代码会报错吗?

    我嗤之以鼻,这还用问吗?我甚至可以告诉你这个代码会报的错一定是 NoClassDefFoundError,这也太简单了你拿这个来考我 balabala...

    然而朋友鬼魅一笑(?),你别急啊,题还没出完呢:

    在上述代码的基础上,加入一个 flag justFalse,并环绕到上述代码中:

    // in Main.java static boolean justFalse = false; // in Main#main method if (justFalse) { Father f = new Son(); f.method(); } 

    同样编译后删除 Son.class,请问代码还还会报错吗?

    我大笑(?)道,这还用问?justFalse 永远是 false,也就是说内部代码永远不可能执行到,那么 Son 类也就永远不可能进入初始化阶段,所以这个代码肯定就不会报错了,这也太简单了你拿这个来考我 balabala...

    然后朋友发来的一张图让我沉默了:

    image-20240908232717213

    竟然真的会报错,难道 JVM 虚拟机会提前解析并未执行的代码行中包含的类引用吗?不对啊,这和我以前的实践完全不一样,怎么会这样...... 就在我陷入自我怀疑的时候,下一题来了:

    在上述代码的基础上,如果把 Father f = new Son(); 修改为 Son f = new Son();,在同样的操作下,请问代码还还会报错吗?

    我小心翼翼地问道:不会这样它就不会报错了吧...

    朋友淡淡说道:正是。

    我的脑海中此时一万匹草泥马奔驰而过,各种名词在我的大脑中穿过:类加载、静态分派、运行时多态、分支预测... 但没有一个能解释这个诡异的现象。

    我的天塌了。

    深入了解 JVM 类加载机制

    当说到 JVM 的类加载机制,很多人可能会脱口而出:加载、验证、准备、解析、初始化。如果你接着问他,他可能还会告诉你,解析这个阶段在某些情况下可以在初始化阶段之后开始,这被 JVM 虚拟机称为“惰性解析("lazy" or "late" resolution )”。那么,出现上述情况的原因可能是因为惰性解析被提前了吗?

    然而答案是否定的,在任何情况下,对于一个类,无论其静态分派的类型是什么,其解析都会延迟进行。(即使在 JVM specs 中这种行为是未定义的,虚拟机实现可以选择立刻解析或是延迟解析)

    那么问题出在哪里了呢?经过一番查证,我发现这个报错其实是在 JVM 类加载的验证阶段产生的。

    注意,这里说的不是验证 Son.class 或是 Father.class,而是 Main.class。如果你仔细观察上面给出的堆栈轨迹(在 Oracle JDK 1.8, Hotspot VM 下),其中有一段就是 sun.launcher.LauncherHelper.checkAndLoadMain(LauncherHelper.java:632)

    在类加载阶段,JVM 虚拟机会试图校验一个类的某些部分是否是未被破坏以及符合预期的。在对 Main.class 类的加载过程中,对于 Father f = new Son();f.method();,产生了一个包含向上类型转换的多态函数调用,对于这种调用,JVM 虚拟机会试图进行校验,这就需要加载 Son.class 的类结构,而 Son.class 已经被我们删除了,所以产生了报错。

    这种检查需要同时包含 typecast 以及多态函数调用,在上述代码中,无论将变量类型修改为变量的实际类型 Son,亦或者删去对 method 方法的调用,那么也不会产生报错。

    最后,如何验证上述推断是正确的呢?很简单,使用 -noverify 参数关闭 JVM 的类加载校验,你就可以发现上述代码正常运行了。

    (上述代码在 Java 1.8 和 Java 21 的 Hotspot 虚拟机上均能复现。为方便行文,对部分内容有所改编。)

    6 条回复    2024-09-11 16:29:06 +08:00
    RightHand
        1
    RightHand  
       2024-09-09 08:44:14 +08:00 via Android
    别写 import 直接写含包名的类(别放同包)
    BuffDog
        2
    BuffDog  
       2024-09-09 11:18:47 +08:00
    搬砖的天天研究这些东西,又没有写 JVM 的能力
    Subilan
        3
    Subilan  
       2024-09-09 11:28:05 +08:00 via iPhone
    有趣极了。
    siweipancc
        4
    siweipancc  
       2024-09-09 12:55:22 +08:00 via iPhone
    ……你实例化用全路径,我的预想是跟前端动态 import 一样,可以运行。
    YepTen
        5
    YepTen  
       2024-09-11 11:22:06 +08:00
    因吹斯听
    635925926
        6
    635925926  
       2024-09-11 16:29:06 +08:00
    一看就是 import 的问题
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     909 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 23ms UTC 21:57 PVG 05:57 LAX 14:57 JFK 17:57
    Do have faith in what you're doing.
    ubao snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86