一、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

JVM-JVM如何加载类的更多相关文章

  1. JVM总结-虚拟机加载类

    从 class 文件到内存中的类,按先后顺序需要经过加载.链接以及初始化三大步骤.其中,链接过程中同样需要验证:而内存中的类没有经过初始化,同样不能使用.那么,是否所有的 Java 类都需要经过这几步 ...

  2. 虚拟机(JVM)如何加载类

    首先JVM加载类的一般流程分三步: 加载 链接 初始化 那么是否全部Java类都是这样三步走的方式加载呢?我们可以从Java的数据类型去出发.Java分基本类型和引用类型.其中按照面向对象的特性,一切 ...

  3. JVM加载类的过程,双亲委派机制中的方法

    JVM加载类的过程: 1)JVM中类的整个生命周期: 加载=>验证=>准备=>解析=>初始化=>使用=>卸载  1.1.加载 类的加载阶段,主要是获取定义此类的二进 ...

  4. jvm中加载类的全过程

    ClassLoader的作用:概括来说就是将编译后的class装载.加载到机器内存中,为了以后的程序的执行提供前提条件. jvm的整个生命周期,如下图所示 加载=>验证=>准备=>解 ...

  5. jvm加载类(更新中)

    作为jvm的用户,从使用者角度来看,我们给jvm输入一个class文件,得到了一个Class对象.我们可以猜想下jvm加载类的过程:class文件有规定的格式,jvm去解析class文件流,读magi ...

  6. JVM学习(二)JVM加载类

    一.什么是类的加载 类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个java.lang.Class对象,用来封装类在方法区内的数据结构 ...

  7. JVM笔记11-类加载器和OSGI

    一.JVM 类加载器: 一个类在使用前,如何通过类调用静态字段,静态方法,或者new一个实例对象,第一步就是需要类加载,然后是连接和初始化,最后才能使用. 类从被加载到虚拟机内存中开始,到卸载出内存为 ...

  8. 深入JVM之类的加载过程

    类的加载—连接—初始化 加载:查找并加载类的字节码文件,从硬盘到内存. 类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个java.la ...

  9. java reflect 初始学习 动态加载类

    首先要理解Class类: 在java 的反射中,Class.forName("com.lilin.Office") 使用类的全名,这样获取,不仅仅表示了类的类类型,同时还代表着类的 ...

  10. [改善Java代码]使用forName动态加载类文件

    动态加载(Dynamic Loading)是指在程序运行时加载需要的类库文件,对Java程序来说,一般情况下,一个类文件在启动时或首次初始化时会被加载到内存中,而反射则可以在运行时再决定是否需要加载一 ...

随机推荐

  1. C语言循环坑 -- continue的坑

    文章目录 前言 一.continue语法 1.continue的作用 2.语法 二.大坑项目 题目 分析 正确写法 三.进坑调试 第一种 第二种 总结 前言 在使用continue和break时,会出 ...

  2. mysql怎样实现不重复插入数据

    mysql使用用insert往数据表中插入数据时,为了不重复插入数据,往往先查询一下该条数据是否已经存在,若不存在才进行插入操作. 而使用 insert if not exists语句,就不需重复做上 ...

  3. WPF 全局样式资源管理

    在WPF通常我们习惯于把样式直接写在控件属性上,例如: <TextBox x:Name="pluginPathTxt" Margin="0,0,0,0" ...

  4. 重返照片的原始世界:我为.NET打造的RAW照片解析利器

    重返照片的原始世界:我为.NET打造的RAW照片解析利器 如果你是我的老读者,你可能还记得,在2019年,我冒险进入了一片神秘的领域--用C#解析RAW格式的照片: 20191208 - 用.NET解 ...

  5. MySQL5.5+配置主从同步并结合ThinkPHP5设置分布式数据库

    前言: 本文章是在同处局域网内的两台windows电脑,且MySQL是5.5以上版本下进行的一主多从同步配置,并且使用的是集成环境工具PHPStudy为例.最后就是ThinkPHP5的分布式的连接,读 ...

  6. F-Beta-Score

    F1-Score相关概念 F1分数(F1 Score),是统计学中用来衡量二分类(或多任务二分类)模型精确度的一种指标.它同时兼顾了分类模型的准确率和召回率. F1分数可以看作是模型准确率和召回率的一 ...

  7. 概率dp_C++详解

    引入 概率 DP 用于解决概率问题与期望问题,建议先对概率和期望的内容有一定了解.一般情况下,解决概率问题需要顺序循环,而解决期望问题使用逆序循环,如果定义的状态转移方程存在后效性问题,还需要用到 高 ...

  8. WLAN-AC+AP,动态负载均衡用户量,避免某一个AP负载过重

    组网图形 动态负载均衡简介 负载均衡功能主要功能就是平衡WLAN网络中AP的负载,充分地保证每个STA的带宽.当有一个新的STA加入网络时,动态负载均衡动态将AC将所有上报该STA的AP动态组成一个组 ...

  9. QA|如何获取元素属性值|网页计算器自动化测试实战

    一般来说 类似于<value>123</value>这样的元素,我们获取元素值是用.text获取,但有时这个值不是写在这里,而是作为标签的属性值写进去的,此时我们就需要获取属性 ...

  10. C++算法之旅、06 基础篇 | 第三章 图论

    常用代码模板3--搜索与图论 - AcWing DFS 尽可能往深处搜,遇到叶子节点(无路可走)回溯,恢复现场继续走 数据结构:stack 空间:需要记住路径上的点,\(O(h)\). BFS使用空间 ...