-JVM理解
类加载
在Java代码中,类型(class)的加载、连接与初始化过程都是在程序运行期间完成的
在cmd可以输入这两个命令查看jvm的情况
-jconsole
选择不安全连接
-jvisualvm
类的加载、连接与初始化
加载
-
查找并加载类的二进制数据
连接
-
验证:确保被加载的类的正确性
-
准备:为类的静态变量分配内存,并将其初始化为默认值(如public static int a = 1),在这一步只是为a分配了内存,且将其初始化为了0(int型默认值为0)
-
解析:把类中的符号引用转换为直接引用
初始化
-
为类的静态变量赋予正确的初始值(这时 a=1)
类的使用和卸载
-
使用
-
卸载
Java程序对类的使用方式可以分为两种
-
主动使用
-
被动使用
所有的Java虚拟机实现必须在每个类或接口被Java程序“首次主动使用”的时候才初始化他们
public class MyTest4 { public static void main(String[] args) { MyParent4 myParent4 = new MyParent4(); System.out.println(myParent4.getClass()); System.out.println("========="); MyParent4 myParent5 = new MyParent4(); } } class MyParent4{ static{ System.out.println("MyParent4 static block"); } }
MyParent4 static block class com.qyh.jvm.classloader.MyParent4 =========
可以看到只初始化了一次
主动使用(七种)
-
创建一个类的实例
-
访问某个类或接口的静态变量,或者对该静态变量赋值
-
调用类的静态方法
-
反射(如Class.forName("com.qyh.Test"))
-
初始化一个类的子类(初始化子类,就会初始化父类,如果父类还有父类那就一直往上初始化)
-
Java虚拟机启动时被标明为启动类的类(含Main方法)
-
JDK1.7开始提供的动态语言支持
除了以上七种情况,其它使用Java类的方式都被看做是对类的被动使用,不会导致类的初始化
类的加载
类的加载指得是将类的.class文件中的二进制数据读入内存中,将其放入在运行时数据区的方法区(JDK1.8之后移除了方法区,将其移至源空间,位于本地内存中),然后在内存中创建一个java.lang.Class对象(规范并未说明对象位于哪里,Hotspot虚拟机将其放在了方法区中)用来封装在方法区的数据结构
-
加载.class文件的方式
-
从本地系统中直接中加载
-
通过网络下载.class 文件
-
从zip,jar等归档文件中加载.class文件
-
从专有的数据库中提取.class 文件
-
将Java源文件动态编译为.class文件(如jsp中的java代码)
package com.qyh.jvm.classloader; public class MyTest1 { public static void main(String[] args) { // System.out.println(MyChild1.str2); System.out.println(MyChild1.str); } } class MyParent1{ public static String str="hello world"; static{ System.out.println("MyParent1 static block"); } } class MyChild1 extends MyParent1{ public static String str2 = "welcome"; static{ System.out.println("MyChild1 static block"); } }
运行结果
MyParent1 static block hello world
思考:虽然MyChild1没有初始化,但是此时MyChild1加载了么?
XX:+TraceClassLoading #用于追踪类加载信息并打印出来
运行,查看控制台打印的信息
可以看出,MyChild1,尽管没有初始化,但是仍然加载了的!
package com.qyh.jvm.classloader; public class MyTest1 { public static void main(String[] args) { System.out.println(MyChild1.str2); // System.out.println(MyChild1.str); } } class MyParent1{ public static String str="hello world"; static{ System.out.println("MyParent1 static block"); } } class MyChild1 extends MyParent1{ public static String str2 = "welcome"; static{ System.out.println("MyChild1 static block"); } }
运行结果
MyParent1 static block MyChild1 static block welcome
编译期常量与运行期常量的区别
package com.qyh.jvm.classloader; //常量在编译阶段会被存入到调用这个方法所在的类的常量池中,(str就放在了MyTest2的常量池中) //本质上,调用类并没有直接引用到定义常量的类,因此并不会触发定义常量的类的初始化 //运行结果仅仅为hello world //注意:这里只是将常量存放到了MyTest2的常量池中,之后的MyTest2与MyParent2就没有任何关系了 //甚至我们可以吧MyParent2的class文件删除掉,仍然可以运行出结果 public class MyTest2 { public static void main(String[] args) { System.out.println(MyParent2.str); } } class MyParent2{ public static final String str="hello world"; static { System.out.println("MyParent2 static block"); } }
为了验证删除class文件,我们用记事本编译,运行一次后,删除掉MyParent2的.class文件
反编译结果:
D:\Javaweb\jvm_study>cd out 系统找不到指定的路径。 D:\Javaweb\jvm_study>cd target D:\Javaweb\jvm_study\target>cd classes D:\Javaweb\jvm_study\target\classes>javap -c com.qyh.jvm.classloader.Mytest2 警告: 二进制文件com.qyh.jvm.classloader.Mytest2包含com.qyh.jvm.classloader.MyTest2 Compiled from "MyTest2.java" public class com.qyh.jvm.classloader.MyTest2 { public com.qyh.jvm.classloader.MyTest2(); 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 // String hello world 5: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 8: return }
关于助记符
package com.qyh.jvm.classloader; //常量在编译阶段会被存入到调用这个方法所在的类的常量池中,(str就放在了MyTest2的常量池中) //本质上,调用类并没有直接引用到定义常量的类,因此并不会触发定义常量的类的初始化 //运行结果仅仅为hello world //注意:这里只是将常量存放到了MyTest2的常量池中,之后的MyTest2与MyParent2就没有任何关系了 //甚至我们可以吧MyParent2的class文件删除掉,仍然可以运行出结果 //在Terminal反编译后 D:\Javaweb\jvm_study\target\classes>javap -c com.qyh.jvm.classloader.Mytest2 //查看到结果: /* 助记符: ldc 表示将int,float或是String类型的常量值从常量池推送至栈顶 bipush 表示将单字节(-128~127)的常量值推送至栈顶 sipush 表示将一个短整型常量值(-32768~32767)推送至栈顶 iconst_1 表示将int类型的1推送至栈顶(iconst_0 ~ iconst_5 表示int类型0~5 iconst_m1 表示将int类型-1推送至栈顶 超过5或者小于-1之后便会变成bipush以此推递下去 */ public class MyTest2 { public static void main(String[] args) { System.out.println(MyParent2.m); } } class MyParent2{ public static final String str="hello world"; public static final short s=127; public static final int i=128; // public static final int m=1; // public static final int m=-2; // public static final int m=-1; public static final int m=0; // public static final int m=5; // public static final int m=6; static { System.out.println("MyParent2 static block"); } }
反编译
D:\Javaweb\jvm_study\target\classes>javap -c com.qyh.jvm.classloader.Mytest2 警告: 二进制文件com.qyh.jvm.classloader.Mytest2包含com.qyh.jvm.classloader.MyTest2 Compiled from "MyTest2.java" public class com.qyh.jvm.classloader.MyTest2 { public com.qyh.jvm.classloader.MyTest2(); 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: sipush 128 6: invokevirtual #4 // Method java/io/PrintStream.println:(I)V 9: return } D:\Javaweb\jvm_study\target\classes>javap -c com.qyh.jvm.classloader.Mytest2 警告: 二进制文件com.qyh.jvm.classloader.Mytest2包含com.qyh.jvm.classloader.MyTest2 Compiled from "MyTest2.java" public class com.qyh.jvm.classloader.MyTest2 { public com.qyh.jvm.classloader.MyTest2(); 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: iconst_1 4: invokevirtual #4 // Method java/io/PrintStream.println:(I)V 7: return } D:\Javaweb\jvm_study\target\classes>javap -c com.qyh.jvm.classloader.Mytest2 警告: 二进制文件com.qyh.jvm.classloader.Mytest2包含com.qyh.jvm.classloader.MyTest2 Compiled from "MyTest2.java" public class com.qyh.jvm.classloader.MyTest2 { public com.qyh.jvm.classloader.MyTest2(); 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: iconst_2 4: invokevirtual #4 // Method java/io/PrintStream.println:(I)V 7: return } D:\Javaweb\jvm_study\target\classes>javap -c com.qyh.jvm.classloader.Mytest2 警告: 二进制文件com.qyh.jvm.classloader.Mytest2包含com.qyh.jvm.classloader.MyTest2 Compiled from "MyTest2.java" public class com.qyh.jvm.classloader.MyTest2 { public com.qyh.jvm.classloader.MyTest2(); 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: bipush 6 5: invokevirtual #4 // Method java/io/PrintStream.println:(I)V 8: return } D:\Javaweb\jvm_study\target\classes>javap -c com.qyh.jvm.classloader.Mytest2 警告: 二进制文件com.qyh.jvm.classloader.Mytest2包含com.qyh.jvm.classloader.MyTest2 Compiled from "MyTest2.java" public class com.qyh.jvm.classloader.MyTest2 { public com.qyh.jvm.classloader.MyTest2(); 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: iconst_m1 4: invokevirtual #4 // Method java/io/PrintStream.println:(I)V 7: return } D:\Javaweb\jvm_study\target\classes>javap -c com.qyh.jvm.classloader.Mytest2 警告: 二进制文件com.qyh.jvm.classloader.Mytest2包含com.qyh.jvm.classloader.MyTest2 Compiled from "MyTest2.java" public class com.qyh.jvm.classloader.MyTest2 { public com.qyh.jvm.classloader.MyTest2(); 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: bipush -2 5: invokevirtual #4 // Method java/io/PrintStream.println:(I)V 8: return } D:\Javaweb\jvm_study\target\classes>javap -c com.qyh.jvm.classloader.Mytest2 警告: 二进制文件com.qyh.jvm.classloader.Mytest2包含com.qyh.jvm.classloader.MyTest2 Compiled from "MyTest2.java" public class com.qyh.jvm.classloader.MyTest2 { public com.qyh.jvm.classloader.MyTest2(); 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: iconst_0 4: invokevirtual #4 // Method java/io/PrintStream.println:(I)V 7: return }
注意:
package com.qyh.jvm.classloader; import java.util.UUID; /* 当一个常量的值并非编译期间可以确定的,那么其值就不会被放到调用类的常量池中, 这时在程序运行时,会导致主动使用这个常量所在的类,显然会导致这个类初始化 */ public class MyTest3 { public static void main(String[] args) { System.out.println(MyParent3.str); } } class MyParent3{ public static final String str = UUID.randomUUID().toString(); static { System.out.println("MyParent3 static code"); } }
运行结果:
MyParent3 static code 0a9218e8-c54d-4318-961c-804869f64496
数组类型创建的本质分析
package com.qyh.jvm.classloader; /* 对于数组实例来说,其类型是由JVM在运行期动态生成的,表示为[Lcom.qyh.jvm.classloader.MyParent4 这种形式。动态生成的类型,其父类型就是Object。 对于数组来说,JavaDoc经常将构成数组的元素称为Component,实际上就是将数组降低一个维度后的类型。 助记符: anewarray 表示创建一个引用类型的(如:类、接口、数组)数组,并将其引用值压入栈顶 newarray 表示创建一个指定的原始类型(如:int、float、char等)的数组,并将其引用值压入栈顶 */ public class MyTest4 { public static void main(String[] args) { // MyParent4 myParent4 = new MyParent4(); // System.out.println(myParent4.getClass()); // System.out.println("========="); MyParent4[] myParent4s = new MyParent4[1]; System.out.println(myParent4s.getClass()); MyParent4[][] myParent4s1 = new MyParent4[1][1]; System.out.println(myParent4s1.getClass()); System.out.println(myParent4s.getClass().getSuperclass()); System.out.println(myParent4s1.getClass().getSuperclass()); System.out.println("==============="); int[] ints = new int[1]; System.out.println(ints.getClass()); System.out.println(ints.getClass().getSuperclass()); char[] chars = new char[1]; System.out.println(chars.getClass()); boolean[] b = new boolean[1]; System.out.println(b.getClass()); short[] s = new short[1]; System.out.println(s.getClass()); byte[] bytes = new byte[1]; System.out.println(bytes.getClass()); } } class MyParent4{ static{ System.out.println("MyParent4 static block"); } }
MyParent4 static block class com.qyh.jvm.classloader.MyParent4 ###################################################### class [Lcom.qyh.jvm.classloader.MyParent4; class [[Lcom.qyh.jvm.classloader.MyParent4; class java.lang.Object class java.lang.Object =============== class [I class java.lang.Object class [C class [Z class [S class [B
反编译:
D:\Javaweb\jvm_study\target\classes>javap -c com.qyh.jvm.classloader.Mytest4 警告: 二进制文件com.qyh.jvm.classloader.Mytest4包含com.qyh.jvm.classloader.MyTest4 Compiled from "MyTest4.java" public class com.qyh.jvm.classloader.MyTest4 { public com.qyh.jvm.classloader.MyTest4(); 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/qyh/jvm/classloader/MyParent4 4: astore_1 5: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream; 8: aload_1 9: invokevirtual #4 // Method java/lang/Object.getClass:()Ljava/lang/Class; 12: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V 15: iconst_1 16: iconst_1 17: multianewarray #6, 2 // class "[[Lcom/qyh/jvm/classloader/MyParent4;" 21: astore_2 22: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream; 25: aload_2 26: invokevirtual #4 // Method java/lang/Object.getClass:()Ljava/lang/Class; 29: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V 32: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream; 35: aload_1 36: invokevirtual #4 // Method java/lang/Object.getClass:()Ljava/lang/Class; 39: invokevirtual #7 // Method java/lang/Class.getSuperclass:()Ljava/lang/Class; 42: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V 45: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream; 48: aload_2 49: invokevirtual #4 // Method java/lang/Object.getClass:()Ljava/lang/Class; 52: invokevirtual #7 // Method java/lang/Class.getSuperclass:()Ljava/lang/Class; 55: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V 58: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream; 61: ldc #8 // String =============== 63: invokevirtual #9 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 66: iconst_1 67: newarray int 69: astore_3 70: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream; 73: aload_3 74: invokevirtual #4 // Method java/lang/Object.getClass:()Ljava/lang/Class; 77: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V 80: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream; 83: aload_3 84: invokevirtual #4 // Method java/lang/Object.getClass:()Ljava/lang/Class; 87: invokevirtual #7 // Method java/lang/Class.getSuperclass:()Ljava/lang/Class; 90: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V 93: iconst_1 94: newarray char 96: astore 4 98: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream; 101: aload 4 103: invokevirtual #4 // Method java/lang/Object.getClass:()Ljava/lang/Class; 106: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V 109: iconst_1 110: newarray boolean 112: astore 5 114: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream; 117: aload 5 119: invokevirtual #4 // Method java/lang/Object.getClass:()Ljava/lang/Class; 122: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V 125: iconst_1 126: newarray short 128: astore 6 130: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream; 133: aload 6 135: invokevirtual #4 // Method java/lang/Object.getClass:()Ljava/lang/Class; 138: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V 141: iconst_1 142: newarray byte 144: astore 7 146: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream; 149: aload 7 151: invokevirtual #4 // Method java/lang/Object.getClass:()Ljava/lang/Class; 154: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V 157: return }
结论:
只有主动引用才会初始化类,第一次new MyParent4可以初始化,并且打印对象的类,可以看到来自于com.qyh.jvm.classloader.MyParent4
当我们创建MyParent4的一维和二维数组的时候,并没有主动引用MyParent4这个类,通过getclass我们看到我们创建的实例来自[Lcom.XXX和[[Lcom.XXX这个是虚拟机在编译的时候相当于动态代理给我们实现的