1. 类加载机制 在如下几种情况下,Java 虚拟机将结束生命周期
执行了 System.exit() 方法
程序正常执行结束
程序在执行的过程中遇到了异常或则错误而异常终止
由于操作系统出现了错误,导致 Java 虚拟机进程结束
Java 程序对类的使用方式可以分为两种:
主动使用
创建类的实例
访问某个类或接口的静态变量,或则对该静态变量赋值
调用类的静态方法
反射(如 Class.forName(“com.cuzz.Test”))
初始化一个子类
Java 虚拟机启动时被标明为启动类的类
被动使用
所有的 Java 虚拟机实现必须在每个类或接口被 Java 程序首次主动使用时才初始化他们
我们来看一段代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public class MyTest01 { public static void main(String[] args) { System.out.println(Child1.str); } } class Parent1 { public static String str = "hello world"; static { System.out.println("Parent1 static block"); } } class Child1 extends Parent1 { static { System.out.println("Child1 static block"); } }
输出
1 2 Parent1 static block hello world
对于静态代码块,只有定义该字段的类才会被初始化 ,这个 Child1.str 是子类调用父类的静态字段,所以子类不会被初始化,父类才会被初始化,这是对 Parent1 的主动使用,对于这个例子只是用了 Child1 的名字,并没有主动使用 Child1 这个类
我们在来看看有没有被加载到虚拟机中,在 VM options : -XX:+TraceClassLoading 在运行
1 2 3 4 5 6 7 ... [Loaded com.cuzz.jvm.classloader.Parent1 from file:/E:/project/learn-demo/demo-10-jvm-lecture/out/production/classes/] [Loaded com.cuzz.jvm.classloader.Child1 from file:/E:/project/learn-demo/demo-10-jvm-lecture/out/production/classes/] Parent1 static block hello world [Loaded java.lang.Shutdown from E:\deployer\jdk8\jre\lib\rt.jar] [Loaded java.lang.Shutdown$Lock from E:\deployer\jdk8\jre\lib\rt.jar]
发现这两个类已经被加载到虚拟中
再看一个例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public class MyTest01 { public static void main(String[] args) { System.out.println(Child1.str2); } } class Parent1 { public static String str = "hello world"; static { System.out.println("Parent1 static block"); } } class Child1 extends Parent1 { public static String str2 = "welcome"; static { System.out.println("Child1 static block"); } }
输出
1 2 3 Parent1 static block Child1 static block welcome
当我们初始一个子类,我们会先初始化父类,所以会线输出父类的静态代码块
如果我们加上 final 变为常量
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 /** * @Author: zzxx * @Date: 2020/4/10 19:16 * @Description: */ public class MyTest02 { public static void main(String[] args) { System.out.println(Parent2.str); } } class Parent2 { public static final String str = "hello world"; static { System.out.println("Parent2 static block"); } }
输出
常量在编译阶段会存入到调用这个常量的方法所在的类的常量池中(也就是说会存入MyTest02这个类中),本质上,调用类并没有直接引用到定义常量的类,因此并不会触发定义常量的类的初始化
注意:这里指的是将常量存放到了 MyTest02 的常量池中,之后 MyTest02 与 Parent2 就没有任何关系了,甚至我们可以将 Parent 的 class 文件删除
我们进入 classes 目录下使用:javap -c com.cuzz.jvm.classloader.MyTest02 命令反编译一下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 Compiled from "MyTest02.java" public class com.cuzz.jvm.classloader.MyTest02 { public com.cuzz.jvm.classloader.MyTest02();// (1) Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return public static void main(java.lang.String[]); Code: 0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 3: ldc #4 (2) // String hello world (3) 5: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 8: return }
1.是构造方法
2.ldc 助记符表示将 int,float 或 String 类型的值从常量池中推送至栈顶
3.可以看出Parent2.str 已经转化为 hello world
注:当 int 取值-15采用iconst指令,取值-128127采用 bipush 指令,取值-3276832767采用 sipush 指令,取值-21474836482147483647采用 ldc 指令
我们在看一个例子
1 2 3 4 5 6 7 8 9 10 11 public class MyTest03 { public static void main(String[] args) { System.out.println(Parent3.str); } } class Parent3 { public static final String str = UUID.randomUUID().toString(); static { System.out.println("Parent3 static block"); } }
输出
1 2 Parent3 static block bee2f54d-8960-46d0-b5d7-02666fcf4a14
相比于上一个例子,我们发现输出了静态代码块,说明 Parent3 这个类被初始化了,当一个常量的值并非编译期间可以确定的,那么器值就不会放到调用类的常量池中,这是在程序运行时,会导致主动使用这个常量所在的类,会导致这给类初始化
再看一个例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public class MyTest04 { public static void main(String[] args) { Parent4[] parent4s = new Parent4[1]; System.out.println("---------"); System.out.println(parent4s.getClass()); System.out.println(parent4s.getClass().getSuperclass()); System.out.println("---------"); int[] ints = new int[1]; System.out.println(ints.getClass()); System.out.println(ints.getClass().getSuperclass()); } } class Parent4 { static { System.out.println("Parent4 static block"); } }
输出
1 2 3 4 5 6 7 8 --------- class [Lcom.cuzz.jvm.classloader.Parent4; class java.lang.Object --------- class [I class java.lang.Object Process finished with exit code 0
对于数组实例来说,其类型是由 JVM 在运行期动态生成的,表示为 [Lcom.cuzz.jvm.classloader.Parent4 这种形式,动态生成的类型,其父类型就是 Object
对于数组来说,JavaDoc 经常将构成的数组元素称为 Component,实际上就是将数组降低一个维度的类型
我们使用 javap -c com.cuzz.jvm.classloader.MyTest04 进行反编译
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 public class com.cuzz.jvm.classloader.MyTest04 { public com.cuzz.jvm.classloader.MyTest04(); Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return public static void main(java.lang.String[]); Code: 0: iconst_1 1: anewarray #2 // class com/cuzz/jvm/classloader/Parent4 4: astore_1 5: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream; 8: ldc #4 // String --------- 10: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 13: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream; 16: aload_1 17: invokevirtual #6 // Method java/lang/Object.getClass:()Ljava/lang/Class; 20: invokevirtual #7 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V 23: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream; 26: aload_1 27: invokevirtual #6 // Method java/lang/Object.getClass:()Ljava/lang/Class; 30: invokevirtual #8 // Method java/lang/Class.getSuperclass:()Ljava/lang/Class; 33: invokevirtual #7 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V 36: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream; 39: ldc #4 // String --------- 41: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 44: iconst_1 45: newarray int 47: astore_2 48: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream; 51: aload_2 52: invokevirtual #6 // Method java/lang/Object.getClass:()Ljava/lang/Class; 55: invokevirtual #7 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V 58: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream; 61: aload_2 62: invokevirtual #6 // Method java/lang/Object.getClass:()Ljava/lang/Class; 65: invokevirtual #8 // Method java/lang/Class.getSuperclass:()Ljava/lang/Class; 68: invokevirtual #7 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V 71: return }
里面有两个助记符
anewarray :表示创建一个引用类型的(如类、接口、数组)数组,并将其值压入栈顶
newarray:表示创建一个指定的原始类型(如int、float、char等)数组,并将其引用值压入栈顶
下一个例子
1 2 3 4 5 6 7 8 9 10 11 12 public class MyTest05 { public static void main(String[] args) { System.out.println(Child5.j); } } interface Parent5 { int i = 5; } interface Child5 extends Parent5 { int j = 55; }
编译之后我们把 Parent5.class 文件删掉,还能打印出 55,说明当一个接口在初始化时,并不要求其父接口都完成初始化,如果我们把 Child5.class 文件也删掉,也能打印出 55,原来接口中的修饰符默认为 public static final 说明接口中的值是一个常量 ,不需要加载到 JVM 中,也就没有初始化。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public class MyTest05 { public static void main(String[] args) { System.out.println(Child5.j); } } interface Parent5 { public static Thread thread = new Thread() { { System.out.println("Parent5 static block"); } }; } class Child5 implements Parent5 { public static int j = 55; }
此时也也是输出 55 ,也没有初始化 Child5 接口 Parent5
下一例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 public class MyTest06 { public static void main(String[] args) { Singleton singleton = Singleton.newSingleton(); System.out.println(Singleton.counter1); System.out.println(Singleton.counter2); } } class Singleton { public static int counter1; private static Singleton singleton = new Singleton(); private Singleton() { counter1++; // counter1 = 1 counter2++; // counter2 = 1 } public static int counter2 = 0; // 此时又把值赋值为 0 public static Singleton newSingleton() { return singleton; } }
此时输出
为什么会这样呢,准备阶段 counter1 和 counter2 的初始值都是 0 ,初始化阶段从上往下赋值,后面 counter2 又赋值为 0
我们再来回顾一下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public class MyTest09 { static { System.out.println("MyTest09 static block"); } public static void main(String[] args) { System.out.println(Child9.j); } } class Parent9 { public static int i = 9; static { System.out.println("Parent9 static block"); } } class Child9 extends Parent9 { public static int j = 99; static { System.out.println("Child9 static block"); } }
输出
1 2 3 4 MyTest09 static block Parent9 static block Child9 static block 99
我们多输出点信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 public class MyTest09 { static { System.out.println("MyTest09 static block"); } public static void main(String[] args) { Parent9 parent9; // 不会初始化 System.out.println("-------------"); parent9 = new Parent9(); System.out.println("-------------"); System.out.println(Parent9.i); System.out.println("-------------"); System.out.println(Child9.j); } } class Parent9 { public static int i = 9; static { System.out.println("Parent9 static block"); } } class Child9 extends Parent9 { public static int j = 99; static { System.out.println("Child9 static block"); } }
输出结果
1 2 3 4 5 6 7 8 MyTest09 static block ------------- Parent9 static block ------------- 9 ------------- Child9 static block 99
在看一个例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public class MyTest12 { public static void main(String[] args) throws Exception{ ClassLoader classLoader = ClassLoader.getSystemClassLoader(); Class<?> clazz = classLoader.loadClass("com.cuzz.jvm.classloader.CL"); System.out.println("--------------"); clazz = Class.forName("com.cuzz.jvm.classloader.CL"); } } class CL { static { System.out.println("CL static block"); } }
输出
1 2 -------------- CL static block
说明调用 ClassLoader 类的 loadClass 方法加载一个类,并不是对类的主动使用,不会导致类的初始化,而通过 Class.forName 方法是通过反射机制,会对类初始化