JVM中的类加载是延迟加载的策略,在运行时,当需要用到某个类时才会去真正装载该类。但是在工作中遇到一个违反这一常识的例子,在本文中记录一下

问题现象

class Parent

  1. public class Parent {}

class Child

  1. public class Child extends Parent {
  2. }

class ChildPassChild

  1. public class ChildPassChild {
  2. public ChildPassChild(Child child) {
  3. System.out.println(child.getClass().getSimpleName());
  4. }
  5. }

class ParentPassChild

  1. public class ParentPassChild {
  2. public ParentPassChild(Parent parent) {
  3. System.out.println(parent.getClass().getSimpleName());
  4. }
  5. }

case1

Test1

  1. public class Test {
  2. public static void foo() {
  3. System.out.println("foo");
  4. }
  5. public static void parentPassChild() {
  6. ParentPassChild parentPassChild = new ParentPassChild(new Child());
  7. System.out.println(parentPassChild);
  8. }
  9. public static void main(String[] args) {
  10. Test.foo();
  11. }
  12. }

理论上Test#main函数只执行了foo方法,按照lazy加载的策略,并不会触发ParentPassChild等类的加载,实际上
image.png
加载了以下三个类,而没有加载 ParentPassChild

  1. javatest.classloading.Test
  2. javatest.classloading.Parent
  3. javatest.classloading.Child

case2

  1. public class Test2 {
  2. public static void foo() {
  3. System.out.println("foo");
  4. }
  5. public static void childPassChild() {
  6. ChildPassChild childPassChild = new ChildPassChild(new Child());
  7. System.out.println(childPassChild);
  8. }
  9. public static void main(String[] args) {
  10. Test2.foo();
  11. }
  12. }

和第一个测试不一样的地方在于测试类中的另一个静态方法使用的是ChildPassChild,也就是没有类型cast,这个情况下,确实如预期只加载了 javatest.classloading.Test2
image.png

结论

相信这样一对比就能有所发现,在一个类中存在这种涉及类型cast,即使是隐式的子类cast成父类的行为,就可能导致父类和子类被提前加载。

经过咨询JVM的同学 @凌峻,查看相关JVM虚拟机规范,得到如下信息,大致意思就是class编译和执行是分离的,jvm无法依赖编译得到的class文件就是符合规范的,因此会有一个环节去verfify class文件符合一定的规范。
image.png

image.png
如规范中所说,因为loading class是一个比较重的操作,所以大部分情况都会采用延迟加载的策略,但是如果存在类型cast,那么必须加载相应的class A B, 来判断这是不是一个安全的行为。