zoukankan      html  css  js  c++  java
  • 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

  • 相关阅读:
    Tensorflow学习笔记1
    强化学习——从最简单的开始入手
    MATLAB R2017a 安装与破解
    C# 理解lock
    Bayer Pattern
    OpenCV参考手册之Mat类详解
    opencv学习之颜色空间转换cvtColor()
    UNICODE下CString转string
    解决VS2013报错fopen、sprintf等函数安全的问题
    Convert between cv::Mat and QImage 两种图片类转换
  • 原文地址:https://www.cnblogs.com/wxdlut/p/13992726.html
Copyright © 2011-2022 走看看