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(查看

操作步骤:

  1.  java -jar ../asmtools.jar jdis SingletonTest\$LazyHolder.class > SingletonTest\$LazyHolder.jasm.1
  2.  cp  SingletonTest\$LazyHolder.jasm.1  SingletonTest\$LazyHolder.jasm
  3. vi 打开 SingletonTest\$LazyHolder.jasm修改
  4. 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

posted on 2020-11-17 10:00  gogoy  阅读(202)  评论(0编辑  收藏  举报

导航