JVM-JVM如何加载类
一、Java 语言的类型可以分为两大类:
- 基本类型(primitive types)
- 引用类型(reference types):类、接口、数组类和泛型参数(泛型参数会在编译中被擦除),因此Java虚拟机里的引用类型实际上只有前三种
- 数组类:是由 Java 虚拟机直接生成的(Java中数组的特性)
- 类、接口:有对应的字节流,都会被加载到Java虚拟机中,成为类或接口
- 最常见:Java编译器生成的class文件
- 其他:也可以在程序内部直接生成,或者从网络中获取(例如网页中内嵌的小程序 Java applet)字节流
二、Java虚拟机是如何加载类的
1、加载:
- 定义:加载,是指查找字节流,并且据此创建类的过程。前面提到,对于数组类来说,它并没有对应的字节流,而是由 Java 虚拟机直接生成的。对于其他的类来说,Java 虚拟机则需要借助类加载器来完成查找字节流的过程
- 双亲委派模型:村里的建筑师有一个潜规则,就是接到单子自己不能着手干,得先给师傅过过目。师傅不接手的情况下,才能自己来。在 Java 虚拟机中,这个潜规则有个特别的名字,叫双亲委派模型。每当一个类加载器接收到加载请求时,它会先将请求转发给父类加载器。在父类加载器没有找到所请求的类的情况下,该类加载器才会尝试去加载。
- 爷:共同的祖师爷(启动类加载器 BootstrapClassLoader):加载最基础、最重要的类,启动类加载器是由 C++ 实现的,没有对应的 Java 类,因此在 Java 中只能用 null 来指代。
- 父:扩展类加载器(sun.misc.Launcher$ExtClassLoader):它负责加载相对次要、但又通用的类,比如存放在 JRE 的 lib/ext 目录下 jar 包中的类(以及由系统变量 java.ext.dirs 指定的类)
- Java 9 扩展类加载器被改名为平台类加载器(platform class loader)
- 子:应用类加载器(sun.misc.Launcher$AppClassLoader):它负责加载应用程序路径下的类。(这里的应用程序路径,便是指虚拟机参数 -cp/-classpath、系统变量 java.class.path 或环境变量 CLASSPATH 所指定的路径。)默认情况下,应用程序中包含的类便是由应用类加载器加载的。
- 自定义:还可以自定义类加载器,来实现特殊的加载方式。举例来说,我们可以对 class 文件进行加密,加载时再利用自定义的类加载器对其解密。
- 类加载器还有命名空间的作用,在 Java 虚拟机中,类的唯一性是由类加载器实例以及类的全名一同确定的
例子:
package javap.loader; import sun.misc.Launcher; public class LoaderTest { public static void main(String[] args) { /** * sun.misc.Launcher类是java的入口,在启动java应用的时候会首先创建Launcher类, * 创建Launcher类的时候会准备应用程序运行中需要的类加载器。 * Laucher是由JVM创建的,它类加载器应该是BootStrapClassLoader, 这是一个C++编写的类加载器,所以打印null */ System.out.println("Launcher.classLoader=" + Launcher.class.getClassLoader()); // null // 获取AppClassLoader System.out.println("SystemClassLoader=" + ClassLoader.getSystemClassLoader()); // sun.misc.Launcher$AppClassLoader@135fbaa4 // 获取父加载器 ClassLoader classLoader = LoaderTest.class.getClassLoader(); System.out.println("this.classLoader=" + classLoader); // sun.misc.Launcher$AppClassLoader@135fbaa4 System.out.println("this.classLoader.father=" + classLoader.getParent()); // sun.misc.Launcher$ExtClassLoader@2503dbd3 System.out.println("this.classLoader.father.father=" + classLoader.getParent().getParent()); // null System.out.println("this.classLoader.father.father.father=" + classLoader.getParent().getParent().getParent()); // NullPointerException exception } }
结果:
Launcher.classLoader=null SystemClassLoader=sun.misc.Launcher$AppClassLoader@135fbaa4 this.classLoader=sun.misc.Launcher$AppClassLoader@135fbaa4 this.classLoader.father=sun.misc.Launcher$ExtClassLoader@2503dbd3 this.classLoader.father.father=null Exception in thread "main" java.lang.NullPointerException at javap.loader.LoaderTest.main(LoaderTest.java:21) Process finished with exit code 1
双亲委派模型说明:(查看)
双亲委派模型最大的好处就是让Java类同其类加载器一起具备了一种带优先级的层次关系。举个例子来说明下:比如我们要加载顶层的Java类——java.lang.Object类,无论我们用哪个类加载器去加载Object类,这个加载请求最终都会委托给启动类加载器(Bootstrap ClassLoader),这样就保证了所有加载器加载的Object类都是同一个类。如果没有双亲委派模型,就会出现 Wupx::Object 和 Huyx::Object 这样两个不同的Object类。
“ClassLoader+类的全路径” 可唯一确定一个类对象,如果相同的字节码文件被不同的ClassLoader加载,将在内存中生成两个不同的对象,这样在引用、赋值、类型转换等情况下都会存在问题。jvm使用双亲委派模型来尽最大程度保证相同的class被相同的加载器所加载。
例:
1 public class test { 2 3 public static void main(String[] args) throws Throwable{ 4 Class myClazz = loadClass(new String[] { "file:/Users/**/Documents/dc-x/test/target/classes/" }, null, "com.MyClassLoader"); 5 System.out.println(myClazz.getClassLoader()); 6 7 Class appClazz1 = Class.forName("com.MyClassLoader"); 8 9 System.out.println(myClazz.equals(appClazz1)); 10 11 Class appClazz2 = Class.forName("com.MyClassLoader"); 12 13 System.out.println(appClazz1.equals(appClazz2)); 14 MyClassLoader myClassLoader = (MyClassLoader)myClazz.newInstance(); // 这说明,相同路径下的.class文件,被不同类加载器加载到内存中会生成两个不同的对象;进行类型转换时发生ClassCastException 15 } 16 17 public static Class<?> loadClass(String[] pathArray, ClassLoader parentClassLoader, String className) throws Throwable { 18 List<URL> list = new ArrayList<>(); 19 for (String path : pathArray) { 20 URL url = new URL(path); 21 list.add(url); 22 } 23 24 URL[] urls = list.toArray(new URL[list.size()]); 25 URLClassLoader classLoader = new URLClassLoader(urls, parentClassLoader); 26 27 Class<?> clazz = classLoader.loadClass(className); 28 29 return clazz; 30 } 31 }
结果:
java.net.URLClassLoader@1d44bcfa false true Exception in thread "main" java.lang.ClassCastException: com.MyClassLoader cannot be cast to com.MyClassLoader at com.test.main(test.java:21)
2、链接
链接,是指将创建成的类合并至 Java 虚拟机中,使之能够执行的过程。它可分为验证、准备以及解析三个阶段。
- 验证:确保被加载类能够满足 Java 虚拟机的约束条件
- 准备:准备阶段的目的,
- 则是为被加载类的静态字段分配内存并初始化为默认值 比如一些基本数据类型,int默认值为0,long默认是0L等(但是没有真正初始化)。Java 代码中对静态字段的具体初始化,则会在稍后的初始化阶段中进行。
- 还会构造与该类相关联的方法表。
- 解析(非必须,只有有符号引号需要转换的时候才有):解析阶段的目的,正是将这些符号引用解析成为实际引用。如果符号引用指向一个未被加载的类,或者未被加载类的字段或方法,那么解析将触发这个类的加载(但未必触发这个类的链接以及初始化。)
- 举例来说,对于一个方法调用,编译器会生成一个包含目标方法所在类的名字、目标方法的名字、接收参数类型以及返回值类型的符号引用,来指代所要调用的方法。
3、初始化(房子装修之后才可以入驻)
类加载的最后一步是初始化,初始化便是为标记为静态常量值(static final)的字段赋值,以及执行 < clinit > 方法的过程。Java 虚拟机会通过加锁来确保类的 < clinit > 方法仅被执行一次。
- 如果直接赋值的静态字段被 final 所修饰,并且它的类型是基本类型或字符串时,那么该字段便会被 Java 编译器标记成常量值(ConstantValue),其初始化直接由 Java 虚拟机完成。
- 除此之外的直接赋值操作,以及所有静态代码块中的代码,则会被 Java 编译器置于同一方法中,并把它命名为 < clinit >。
那么,类的初始化何时会被触发呢?JVM 规范枚举了下述多种触发情况:
- 当虚拟机启动时,初始化用户指定的主类;
- 当遇到用以新建目标类实例的 new 指令时,初始化 new 指令的目标类;
- 当遇到调用静态方法的指令时,初始化该静态方法所在的类;
- 当遇到访问静态字段的指令时,初始化该静态字段所在的类;
- 子类的初始化会触发父类的初始化;
- 如果一个接口定义了 default 方法,那么直接实现或者间接实现该接口的类的初始化,会触发该接口的初始化;
- 使用反射 API 对某个类进行反射调用时,初始化这个类;
- 当初次调用 MethodHandle 实例时,初始化该 MethodHandle 指向的方法所在的类。
例子: // 著名的单例延迟初始化例子,只有当调用 Singleton.getInstance 时,程序才会访问 LazyHolder.INSTANCE,才会触发对 LazyHolder 的初始化(对应第 4 种情况),继而新建一个 Singleton 的实例。
// 由于类初始化是线程安全的,并且仅被执行一次,因此程序可以确保多线程环境下有且仅有一个 Singleton 实例。 (单例模式)
1 public class Singleton { 2 private Singleton() {} 3 private static class LazyHolder { 4 static final Singleton INSTANCE = new Singleton(); 5 } 6 public static Singleton getInstance() { 7 return LazyHolder.INSTANCE; 8 } 9 }
三、自定义类加载器
常见用途:将一个class用特定规则加密,然后在自定义的ClassLoader进行解密后在程序中加载使用。只有在我们自定义的加载器里能解密,提高了程序安全性。
自定义classLoader建议--覆盖findClass()方法,而不要直接改写loadClass()方法。
1、如果不想打破双亲委派模型,那么只需要重写findClass方法即可(一般选择这个)
2、如果想打破双亲委派模型,那么就重写整个loadClass方法
步骤: 1. 编写一个类继承自ClassLoader抽象类。 2. 复写它的findClass()方法。 3. 在findClass()方法中调用defineClass()。 // // defineClass方法将字节码转化为类
先看一下ClassLoader里面的核心方法:
1 protected Class<?> loadClass(String name, boolean resolve) 2 throws ClassNotFoundException 3 { 4 synchronized (getClassLoadingLock(name)) { 5 // First, check if the class has already been loaded 6 Class<?> c = findLoadedClass(name); 7 if (c == null) { 8 long t0 = System.nanoTime(); 9 try {
// 否则,会继续向上委派,若委派到启动类加载器仍然未能加载,则使用当前类加载器(findClass)进行加载。 10 if (parent != null) { 11 c = parent.loadClass(name, false); 12 } else { 13 c = findBootstrapClassOrNull(name); 14 } 15 } catch (ClassNotFoundException e) { 16 // ClassNotFoundException thrown if class not found 17 // from the non-null parent class loader 18 } 19 20 if (c == null) { 21 // If still not found, then invoke findClass in order 22 // to find the class. 23 long t1 = System.nanoTime(); 24 c = findClass(name); 25 26 // this is the defining class loader; record the stats 27 sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0); 28 sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1); 29 sun.misc.PerfCounter.getFindClasses().increment(); 30 } 31 } 32 if (resolve) { 33 resolveClass(c); 34 } 35 return c; 36 } 37 }
例:自定义类加载器
1 package javap.loader; 2 3 import java.io.File; 4 import java.io.FileInputStream; 5 6 public class MyClassLoader extends ClassLoader{ 7 8 /** 9 * class文件所在路径 10 */ 11 private String classPath; 12 13 14 public MyClassLoader(String classPath) { 15 this.classPath = classPath; 16 } 17 18 public MyClassLoader(ClassLoader parent, String classPath) { 19 super(parent); 20 this.classPath = classPath; 21 } 22 23 /** 24 * 覆盖findClass()方法,而不要直接改写loadClass()方法 25 * @param name 26 * @return 27 * @throws ClassNotFoundException 28 */ 29 @Override 30 protected Class<?> findClass(String name) throws ClassNotFoundException { 31 32 // System.out.println("in MyClassLoader$findClass"); 33 //return null; 34 35 // (1)加载class文件转为byte[] 36 byte[] classBytes = readClass(name); 37 if (classBytes != null) { 38 // (2)byte[]转换为class,即调用defineClass 39 return defineClass(name, classBytes, 0, classBytes.length); 40 } 41 42 return super.findClass(name); 43 } 44 45 46 /** 47 * class文件读入为byte[]数组 48 * @param className 49 * @return 50 */ 51 private byte[] readClass(String className) { 52 try { 53 54 String path = classPath + File.separatorChar + className.replace('.', File.separatorChar) + ".class"; 55 56 FileInputStream fis = new FileInputStream(path); 57 int len = fis.available(); 58 byte[] data = new byte[len]; 59 fis.read(data); 60 fis.close(); 61 return data; 62 63 } catch (Exception e) { 64 return null; 65 } 66 } 67 }
要被加载的类:
1 package javap.loader; 2 3 public class Test { 4 public void say(String classLoadName){ 5 System.out.println("In Test: Hello " + classLoadName); 6 } 7 }
测试入口类:
1 package javap.loader; 2 3 import java.lang.reflect.Method; 4 5 public class TestMyClassLoader { 6 7 public static void main(String[] args) { 8 9 try { 10 /** 11 * 个人的经验来看,最容易出问题的点是第27(33)行的打印出来的是"sun.misc.Launcher$AppClassLoader"。 12 * 造成这个问题的关键在于idea是自动编译的,Test.java这个类在ctrl+S保存之后或者在Test.java文件不编辑若干秒后, 13 * MyEclipse会帮我们用户自动编译Test.java,并生成到CLASSPATH也就是target目录下。在CLASSPATH下有Test.class, 14 * 那么自然是由Application ClassLoader来加载这个.class文件了。 15 */ 16 // 自定义类加载器(参数为class文件路径) 17 MyClassLoader myClassLoader = new MyClassLoader(ClassLoader.getSystemClassLoader().getParent(), "/Users/**/work/code/testDemo/src/main/java/"); 18 19 // (1)用loadClass加载, 指定完整的包名+类名 20 Class c1 = myClassLoader.loadClass("javap.loader.Test"); 21 if (c1 == null) { 22 return; 23 } 24 // c1 != null 25 Object obj1 = c1.newInstance(); 26 Method method1 = c1.getMethod("say", new Class[]{String.class}); 27 method1.invoke(obj1, "[c1]" + c1.getClassLoader().toString()); 28 29 // (2)Class.forName也可以指定类加载器 30 Class c2 = Class.forName("javap.loader.Test", true, myClassLoader); 31 Object obj2 = c2.newInstance(); 32 Method method2 = c2.getMethod("say", new Class[]{String.class}); 33 method2.invoke(obj2, "[c2]" + c2.getClassLoader().toString()); 34 35 36 // 用应用程序类加载器 sun.misc.Launcher$AppClassLoader 37 Class c3 = Class.forName("javap.loader.Test"); 38 Object obj3 = c3.newInstance(); 39 Method method3 = c3.getMethod("say", new Class[]{String.class}); 40 method3.invoke(obj3, "[c3]" + c3.getClassLoader().toString()); 41 42 } catch (Exception e) { 43 e.printStackTrace(); 44 } 45 } 46 }
结果:
In Test: Hello [c1]javap.loader.MyClassLoader@4b67cf4d
In Test: Hello [c2]javap.loader.MyClassLoader@4b67cf4d
In Test: Hello [c3]sun.misc.Launcher$AppClassLoader@135fbaa4
https://zhuanlan.zhihu.com/p/72066969
四、类加载过程初始化顺序
例1:
1 package javap.loader; 2 3 /** 4 * 控制台打印 5 */ 6 class Log{ 7 public static String baseFieldInit(){System.out.println("Base:Normal Field");return "";} 8 9 public static String baseStaticFieldInit(){System.out.println("Base:Static Field");return "";} 10 11 public static String fieldInit(){System.out.println("Derived:Normal Field");return "";} 12 13 public static String staticFieldInit(){System.out.println("Derived:Static Field");return "";} 14 } 15 /** 16 * 基类 17 */ 18 class Base { 19 /*1*/ static {System.out.println("Base:Static Block 1");} 20 21 /*1*/ private static String staticValue=Log.baseStaticFieldInit(); 22 23 /*1*/ static {System.out.println("Base:Static Block 2");} 24 25 /*3*/ {System.out.println("Base:Normal Block 1");} 26 27 /*3*/ private String value=Log.baseFieldInit(); 28 29 /*3*/ {System.out.println("Base:Normal Block 2");} 30 31 /*4*/ Base(){System.out.println("Base:Constructor");} 32 } 33 /** 34 * 派生类 35 */ 36 public class Derived extends Base{ 37 38 /*2*/ static {System.out.println("Derived:Static Block 1");} 39 40 /*2*/ private static String staticValue=Log.staticFieldInit(); 41 42 /*2*/ static {System.out.println("Derived:Static Block 2");} 43 44 /*5*/ {System.out.println("Derived:Normal Block 1");} 45 46 /*5*/ private String value=Log.fieldInit(); 47 48 /*5*/ {System.out.println("Derived:Normal Block 2");} 49 50 /*6*/ Derived(){System.out.println("Derived:Derived Constructor");} 51 52 53 54 /** 55 * MAIN 主线程 56 */ 57 public static void main(String[] args){ 58 Derived d=new Derived(); 59 } 60 }
结果:
Base:Static Block 1 Base:Static Field Base:Static Block 2 Derived:Static Block 1 Derived:Static Field Derived:Static Block 2 Base:Normal Block 1 Base:Normal Field Base:Normal Block 2 Base:Constructor Derived:Normal Block 1 Derived:Normal Field Derived:Normal Block 2 Derived:Derived Constructor
解释:
init是对象构造器方法,也就是说在程序执行 new 一个对象调用该对象类的 constructor 方法时才会执行init方法,而clinit是类构造器方法,也就是在jvm进行类加载—–验证—-解析—–初始化,中的初始化阶段jvm会调用clinit方法。(查看)
- 子类构造方法 Method "<init>":"()V" 内部执行逻辑,new对象时候执行
- 执行父类的构造方法 invokespecial Method Base."<init>":"()V";
- 按顺序初始化 非静态属性(非静态代码库)
- 静态块(属性)初始化方法:static Method "<clinit>":"()V" 内部执行逻辑,类加载的初始化阶段执行
- 按顺序初始化 静态属性(静态代码库)
1 MacBook-Pro-2:loader$ java -jar ../asmtools.jar jdis Derived.class 2 package javap/loader; 3 4 super public class Derived 5 extends Base 6 version 52:0 7 { 8 9 private static Field staticValue:"Ljava/lang/String;"; 10 private Field value:"Ljava/lang/String;"; 11 12 Method "<init>":"()V" 13 stack 2 locals 1 14 { 15 aload_0; 16 invokespecial Method Base."<init>":"()V"; // (1)调用父类构造方法 17 getstatic Field java/lang/System.out:"Ljava/io/PrintStream;"; 18 ldc String "Derived:Normal Block 1"; // (2)初始化非静态块 19 invokevirtual Method java/io/PrintStream.println:"(Ljava/lang/String;)V"; 20 aload_0; 21 invokestatic Method Log.fieldInit:"()Ljava/lang/String;"; 22 putfield Field value:"Ljava/lang/String;"; // (2)初始化非静态属性 23 getstatic Field java/lang/System.out:"Ljava/io/PrintStream;"; 24 ldc String "Derived:Normal Block 2"; 25 invokevirtual Method java/io/PrintStream.println:"(Ljava/lang/String;)V"; 26 getstatic Field java/lang/System.out:"Ljava/io/PrintStream;"; 27 ldc String "Derived:Derived Constructor"; 28 invokevirtual Method java/io/PrintStream.println:"(Ljava/lang/String;)V"; 29 return; 30 31 } 32 33 public static Method main:"([Ljava/lang/String;)V" 34 stack 2 locals 2 35 { 36 new class Derived; 37 dup; 38 invokespecial Method "<init>":"()V"; 39 astore_1; 40 return; 41 42 } 43 44 static Method "<clinit>":"()V" 45 stack 2 locals 0 46 { 47 getstatic Field java/lang/System.out:"Ljava/io/PrintStream;"; 48 ldc String "Derived:Static Block 1"; 49 invokevirtual Method java/io/PrintStream.println:"(Ljava/lang/String;)V"; 50 invokestatic Method Log.staticFieldInit:"()Ljava/lang/String;"; 51 putstatic Field staticValue:"Ljava/lang/String;"; 52 getstatic Field java/lang/System.out:"Ljava/io/PrintStream;"; 53 ldc String "Derived:Static Block 2"; 54 invokevirtual Method java/io/PrintStream.println:"(Ljava/lang/String;)V"; 55 return; 56 } 57 58 } // end Class Derived 59 MacBook-Pro-2:loader$ java -jar ../asmtools.jar jdis Base.class 60 package javap/loader; 61 62 super class Base 63 version 52:0 64 { 65 66 private static Field staticValue:"Ljava/lang/String;"; 67 private Field value:"Ljava/lang/String;"; 68 69 Method "<init>":"()V" 70 stack 2 locals 1 71 { 72 aload_0; 73 invokespecial Method java/lang/Object."<init>":"()V"; 74 getstatic Field java/lang/System.out:"Ljava/io/PrintStream;"; 75 ldc String "Base:Normal Block 1"; 76 invokevirtual Method java/io/PrintStream.println:"(Ljava/lang/String;)V"; 77 aload_0; 78 invokestatic Method Log.baseFieldInit:"()Ljava/lang/String;"; 79 putfield Field value:"Ljava/lang/String;"; 80 getstatic Field java/lang/System.out:"Ljava/io/PrintStream;"; 81 ldc String "Base:Normal Block 2"; 82 invokevirtual Method java/io/PrintStream.println:"(Ljava/lang/String;)V"; 83 getstatic Field java/lang/System.out:"Ljava/io/PrintStream;"; 84 ldc String "Base:Constructor"; 85 invokevirtual Method java/io/PrintStream.println:"(Ljava/lang/String;)V"; 86 return; 87 88 } 89 90 static Method "<clinit>":"()V" 91 stack 2 locals 0 92 { 93 getstatic Field java/lang/System.out:"Ljava/io/PrintStream;"; 94 ldc String "Base:Static Block 1"; 95 invokevirtual Method java/io/PrintStream.println:"(Ljava/lang/String;)V"; 96 invokestatic Method Log.baseStaticFieldInit:"()Ljava/lang/String;"; 97 putstatic Field staticValue:"Ljava/lang/String;"; 98 getstatic Field java/lang/System.out:"Ljava/io/PrintStream;"; 99 ldc String "Base:Static Block 2"; 100 invokevirtual Method java/io/PrintStream.println:"(Ljava/lang/String;)V"; 101 return; 102 } 103 104 } // end Class Base
结果证明:
对象在class文件加载完毕,以及为各成员在方法区开辟好内存空间之后,就开始所谓“初始化”的步骤:
1. 基类静态代码块,基类静态成员字段 (并列优先级,按代码中出现先后顺序执行)(只有第一次加载类时执行)
2. 派生类静态代码块,派生类静态成员字段 (并列优先级,按代码中出现先后顺序执行)(只有第一次加载类时执行)
3. 基类普通代码块,基类普通成员字段 (并列优先级,按代码中出现先后顺序执行)
4. 基类构造函数
5. 派生类普通代码块,派生类普通成员字段 (并列优先级,按代码中出现先后顺序执行)
6. 派生类构造函数
例2:
1 package javap.loader; 2 3 4 /** 5 * 解释:根据类加载的顺序,以下两步在做的事情 6 * 2、链接阶段:为被加载的静态字段分配内存(使用默认值0,并未真正初始化) 7 * 3、初始化阶段:从静态代码块,基类静态成员字段进行初始化 (并列优先级,按代码中出现先后顺序执行)(只有第一次加载类时执行) 8 * 9 * 因此,main函数执行时候,为被加载的静态字段分配内存 10 */ 11 public class StaticTest { 12 13 public static int k = 203; 14 // public static int i = print("i_go"); 15 16 public static StaticTest t1 = new StaticTest("t1"); 17 public static StaticTest t2 = new StaticTest("t2"); 18 public static int i = print("i_go"); 19 public static int n = 99; 20 public int j = print("j_yes"); 21 22 { 23 print("构造块"); 24 } 25 26 static{ 27 print("静态块"); 28 } 29 30 public StaticTest(String str) { 31 System.out.println((++k) + ":" + str + "-StaticTest-i=" + i + " n=" + n + ", t1=" + t1 + ", t2=" + t2); 32 ++n; 33 ++i; 34 } 35 36 public static int print(String str) { 37 System.out.println((++k) + ":" + str + "-print-i=" + i + " n=" + n + ", t1=" + t1 + ", t2=" + t2); 38 ++i; 39 return ++n; 40 } 41 42 public static void main(String[] args) { 43 StaticTest t = new StaticTest("init"); 44 } 45 46 }
执行结果:
204:j_yes-print-i=0 n=0, t1=null, t2=null 205:构造块-print-i=1 n=1, t1=null, t2=null 206:t1-StaticTest-i=2 n=2, t1=null, t2=null 207:j_yes-print-i=3 n=3, t1=javap.loader.StaticTest@2503dbd3, t2=null 208:构造块-print-i=4 n=4, t1=javap.loader.StaticTest@2503dbd3, t2=null 209:t2-StaticTest-i=5 n=5, t1=javap.loader.StaticTest@2503dbd3, t2=null 210:i_go-print-i=6 n=6, t1=javap.loader.StaticTest@2503dbd3, t2=javap.loader.StaticTest@4b67cf4d 211:静态块-print-i=7 n=99, t1=javap.loader.StaticTest@2503dbd3, t2=javap.loader.StaticTest@4b67cf4d 212:j_yes-print-i=8 n=100, t1=javap.loader.StaticTest@2503dbd3, t2=javap.loader.StaticTest@4b67cf4d 213:构造块-print-i=9 n=101, t1=javap.loader.StaticTest@2503dbd3, t2=javap.loader.StaticTest@4b67cf4d 214:init-StaticTest-i=10 n=102, t1=javap.loader.StaticTest@2503dbd3, t2=javap.loader.StaticTest@4b67cf4d
执行结果分析:
五、类加载过程深入分析(加载、链接、初始化)
new LazyHolder()与new LazyHolder[2]对比
例1:
1 package javap.loader; 2 3 public class SingletonTest { 4 private SingletonTest() { 5 } 6 7 private static class LazyHolder { 8 static final SingletonTest INSTANCE = new SingletonTest(); 9 10 static { 11 System.out.println("LazyHolder.<clinit>"); 12 } 13 } 14 15 public static Object getInstance(boolean flag) { 16 if (flag) return new LazyHolder(); // 下一个例子把此处替换为 LazyHolder[2]; 17 return LazyHolder.INSTANCE; 18 } 19 20 public static void main(String[] args) { 21 getInstance(true); 22 System.out.println("----"); 23 getInstance(false); 24 } 25 }
java -verbose:class javap.loader.SingletonTest (如下结果省略了前部分)
1 [Loaded java.security.UnresolvedPermission from /Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home/jre/lib/rt.jar] 2 [Loaded java.security.BasicPermissionCollection from /Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home/jre/lib/rt.jar] 3 [Loaded javap.loader.SingletonTest from file:/Users/wuxiong.wx/work/code/testDemo/src/main/java/] 4 [Loaded sun.launcher.LauncherHelper$FXHelper from /Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home/jre/lib/rt.jar] 5 [Loaded java.lang.Class$MethodArray from /Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home/jre/lib/rt.jar] 6 [Loaded java.lang.Void from /Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home/jre/lib/rt.jar] 7 [Loaded javap.loader.SingletonTest$LazyHolder from file:/Users/wuxiong.wx/work/code/testDemo/src/main/java/] // (1)加载 8 LazyHolder.<clinit> //(2)初始化(静态块)—— 已经完成链接 9 ---- 10 [Loaded java.lang.Shutdown from /Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home/jre/lib/rt.jar] 11 [Loaded java.lang.Shutdown$Lock from /Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home/jre/lib/rt.jar]
1 $ java -jar ../asmtools.jar jdis SingletonTest.class 2 package javap/loader; 3 4 super public class SingletonTest 5 version 52:0 6 { 7 8 9 private Method "<init>":"()V" 10 stack 1 locals 1 11 { 12 aload_0; 13 invokespecial Method java/lang/Object."<init>":"()V"; 14 return; 15 } 16 17 public static Method getInstance:"(Z)Ljava/lang/Object;" 18 stack 3 locals 1 19 { 20 iload_0; 21 ifeq L13; 22 new class SingletonTest$LazyHolder; 23 dup; 24 aconst_null; 25 invokespecial Method SingletonTest$LazyHolder."<init>":"(Ljavap/loader/SingletonTest$1;)V"; 26 areturn; 27 L13: stack_frame_type same; 28 getstatic Field SingletonTest$LazyHolder.INSTANCE:"Ljavap/loader/SingletonTest;"; 29 areturn; 30 } 31 32 public static Method main:"([Ljava/lang/String;)V" 33 stack 2 locals 1 34 { 35 iconst_1; 36 invokestatic Method getInstance:"(Z)Ljava/lang/Object;"; 37 pop; 38 getstatic Field java/lang/System.out:"Ljava/io/PrintStream;"; 39 ldc String "----"; 40 invokevirtual Method java/io/PrintStream.println:"(Ljava/lang/String;)V"; 41 iconst_0; 42 invokestatic Method getInstance:"(Z)Ljava/lang/Object;"; 43 pop; 44 return; 45 } 46 47 synthetic Method "<init>":"(Ljavap/loader/SingletonTest$1;)V" 48 stack 1 locals 2 49 { 50 aload_0; 51 invokespecial Method "<init>":"()V"; 52 return; 53 } 54 55 static synthetic InnerClass class SingletonTest$1; 56 private static InnerClass LazyHolder=class SingletonTest$LazyHolder of class SingletonTest; 57 58 } // end Class SingletonTest
1 $ java -jar ../asmtools.jar jdis SingletonTest\$LazyHolder.class 2 package javap/loader; 3 4 super class SingletonTest$LazyHolder 5 version 52:0 6 { 7 8 static final Field INSTANCE:"Ljavap/loader/SingletonTest;"; 9 10 private Method "<init>":"()V" 11 stack 1 locals 1 12 { 13 aload_0; 14 invokespecial Method java/lang/Object."<init>":"()V"; 15 return; 16 } 17 18 synthetic Method "<init>":"(Ljavap/loader/SingletonTest$1;)V" 19 stack 1 locals 2 20 { 21 aload_0; 22 invokespecial Method "<init>":"()V"; 23 return; 24 } 25 26 static Method "<clinit>":"()V" 27 stack 3 locals 0 28 { 29 new class SingletonTest; 30 dup; 31 aconst_null; 32 invokespecial Method SingletonTest."<init>":"(Ljavap/loader/SingletonTest$1;)V"; 33 putstatic Field INSTANCE:"Ljavap/loader/SingletonTest;"; 34 getstatic Field java/lang/System.out:"Ljava/io/PrintStream;"; 35 ldc String "LazyHolder.<clinit>"; 36 invokevirtual Method java/io/PrintStream.println:"(Ljava/lang/String;)V"; 37 return; 38 } 39 40 static synthetic InnerClass class SingletonTest$1; 41 private static InnerClass LazyHolder=class SingletonTest$LazyHolder of class SingletonTest; 42 43 } // end Class SingletonTest$LazyHolder
例2:把16行的LazyHolder()替换为 LazyHolder[2],则 java -verbose:class javap.loader.SingletonTest (如下结果省略了前部分)结果如下:
1 [Loaded java.security.BasicPermissionCollection from /Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home/jre/lib/rt.jar] 2 [Loaded javap.loader.SingletonTest from file:/Users/wuxiong.wx/work/code/testDemo/src/main/java/] 3 [Loaded sun.launcher.LauncherHelper$FXHelper from /Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home/jre/lib/rt.jar] 4 [Loaded java.lang.Class$MethodArray from /Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home/jre/lib/rt.jar] 5 [Loaded java.lang.Void from /Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home/jre/lib/rt.jar] 6 [Loaded javap.loader.SingletonTest$LazyHolder from file:/Users/wuxiong.wx/work/code/testDemo/src/main/java/] // (1)加载 7 ---- 8 LazyHolder.<clinit> // (2)初始化(静态块)- 已经完成链接 9 [Loaded java.lang.Shutdown from /Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home/jre/lib/rt.jar] 10 [Loaded java.lang.Shutdown$Lock from /Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home/jre/lib/rt.jar]
1 $ java -jar ../asmtools.jar jdis SingletonTest.class 2 package javap/loader; 3 4 super public class SingletonTest 5 version 52:0 6 { 7 8 9 private Method "<init>":"()V" 10 stack 1 locals 1 11 { 12 aload_0; 13 invokespecial Method java/lang/Object."<init>":"()V"; 14 return; 15 } 16 17 public static Method getInstance:"(Z)Ljava/lang/Object;" 18 stack 1 locals 1 19 { 20 iload_0; 21 ifeq L9; 22 iconst_2; 23 anewarray class SingletonTest$LazyHolder; 24 areturn; 25 L9: stack_frame_type same; 26 getstatic Field SingletonTest$LazyHolder.INSTANCE:"Ljavap/loader/SingletonTest;"; 27 areturn; 28 } 29 30 public static Method main:"([Ljava/lang/String;)V" 31 stack 2 locals 1 32 { 33 iconst_1; 34 invokestatic Method getInstance:"(Z)Ljava/lang/Object;"; 35 pop; 36 getstatic Field java/lang/System.out:"Ljava/io/PrintStream;"; 37 ldc String "----"; 38 invokevirtual Method java/io/PrintStream.println:"(Ljava/lang/String;)V"; 39 iconst_0; 40 invokestatic Method getInstance:"(Z)Ljava/lang/Object;"; 41 pop; 42 return; 43 } 44 45 synthetic Method "<init>":"(Ljavap/loader/SingletonTest$1;)V" 46 stack 1 locals 2 47 { 48 aload_0; 49 invokespecial Method "<init>":"()V"; 50 return; 51 } 52 53 static synthetic InnerClass class SingletonTest$1; 54 private static InnerClass LazyHolder=class SingletonTest$LazyHolder of class SingletonTest; 55 56 } // end Class SingletonTest
1 $ java -jar ../asmtools.jar jdis SingletonTest\$LazyHolder.class 2 package javap/loader; 3 4 super class SingletonTest$LazyHolder 5 version 52:0 6 { 7 8 static final Field INSTANCE:"Ljavap/loader/SingletonTest;"; 9 10 private Method "<init>":"()V" 11 stack 1 locals 1 // stack 1 修改为 stack 0,然后去看下一个例子 12 { 13 aload_0; 14 invokespecial Method java/lang/Object."<init>":"()V"; 15 return; 16 } 17 18 static Method "<clinit>":"()V" 19 stack 3 locals 0 20 { 21 new class SingletonTest; 22 dup; 23 aconst_null; 24 invokespecial Method SingletonTest."<init>":"(Ljavap/loader/SingletonTest$1;)V"; 25 putstatic Field INSTANCE:"Ljavap/loader/SingletonTest;"; 26 getstatic Field java/lang/System.out:"Ljava/io/PrintStream;"; 27 ldc String "LazyHolder.<clinit>"; 28 invokevirtual Method java/io/PrintStream.println:"(Ljava/lang/String;)V"; 29 return; 30 } 31 32 private static InnerClass LazyHolder=class SingletonTest$LazyHolder of class SingletonTest; 33 static synthetic InnerClass class SingletonTest$1; 34 35 } // end Class SingletonTest$LazyHolder
例3:修改上述 SingletonTest$LazyHolder.class 字节码里面构造方法的 操作数栈大小为0(查看)
操作步骤:
- java -jar ../asmtools.jar jdis SingletonTest$LazyHolder.class > SingletonTest$LazyHolder.jasm.1
- cp SingletonTest$LazyHolder.jasm.1 SingletonTest$LazyHolder.jasm
- vi 打开 SingletonTest$LazyHolder.jasm修改
- java -jar ../asmtools.jar jasm SingletonTest$LazyHolder.jasm 即重新更新字节码SingletonTest$LazyHolder.class
过程疑问及解答:
疑问:LazyHolder.INSTANCE应该是触发内部类LazyHolder的加载(其中的初始化步骤会执行<clinit>);因为没有new LazyHolder()应该不会执行它的构造方法<init>啊,怎么会抛出异常呢???
解答:应该是在“链接”的“验证”阶段,JVM发现<init>的操作数栈过小,直接验证不通过;
结论:
- new LazyHolder[2]:虚拟机必须知道(加载)有这个类,才能创建这个类的数组(容器),但是这个类并没有被使用到(没有达到初始化的条件),所以不会初始化。
- 新建数组的时候并不是要使用这个类(只是定义了放这个类的容器),所以不会被链接,调用getInstance(false)的时候约等于告诉虚拟机(即return LazyHolder.INSTANCE;)
,我要使用这个类了,你把这个类造好(链接),然后把static修饰的字符赋予变量(初始化)。
结果:
1 [Loaded java.security.UnresolvedPermission from /Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home/jre/lib/rt.jar] 2 [Loaded java.security.BasicPermissionCollection from /Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home/jre/lib/rt.jar] 3 [Loaded javap.loader.SingletonTest from file:/Users/wuxiong.wx/work/code/testDemo/src/main/java/] 4 [Loaded sun.launcher.LauncherHelper$FXHelper from /Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home/jre/lib/rt.jar] 5 [Loaded java.lang.Class$MethodArray from /Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home/jre/lib/rt.jar] 6 [Loaded java.lang.Void from /Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home/jre/lib/rt.jar] 7 [Loaded javap.loader.SingletonTest$LazyHolder from file:/Users/wuxiong.wx/work/code/testDemo/src/main/java/] 8 ---- 9 [Loaded java.lang.VerifyError from /Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home/jre/lib/rt.jar] 10 Exception in thread "main" [Loaded java.lang.Throwable$PrintStreamOrWriter from /Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home/jre/lib/rt.jar] 11 [Loaded java.lang.Throwable$WrappedPrintStream from /Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home/jre/lib/rt.jar] 12 [Loaded java.util.IdentityHashMap from /Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home/jre/lib/rt.jar] 13 [Loaded java.util.IdentityHashMap$KeySet from /Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home/jre/lib/rt.jar] 14 java.lang.VerifyError: Operand stack overflow 15 Exception Details: 16 Location: 17 javap/loader/SingletonTest$LazyHolder.<init>()V @0: aload_0 18 Reason: 19 Exceeded max stack size. 20 Current Frame: 21 bci: @0 22 flags: { flagThisUninit } 23 locals: { uninitializedThis } 24 stack: { } 25 Bytecode: 26 0x0000000: 2ab7 0006 b1 27 28 at javap.loader.SingletonTest.getInstance(SingletonTest.java:17) 29 at javap.loader.SingletonTest.main(SingletonTest.java:23) 30 [Loaded java.lang.Shutdown from /Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home/jre/lib/rt.jar] 31 [Loaded java.lang.Shutdown$Lock from /Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home/jre/lib/rt.jar]
读郑雨迪《深入拆解Java虚拟机》 -- 第三讲 Java虚拟机是如何加载Java类的 https://blog.csdn.net/Ti_an_Di/article/details/81415685
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· 葡萄城 AI 搜索升级:DeepSeek 加持,客户体验更智能
· 什么是nginx的强缓存和协商缓存
· 一文读懂知识蒸馏
2016-11-17 Quartz中时间表达式的设置-----corn表达式 (转)