类加载
在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 #用于追踪类加载信息并打印出来
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FxtoNi3M-1604999076017)(https://i.loli.net/2020/11/10/ApDYrx2VcP58Bz9.png)]
运行,查看控制台打印的信息
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RY7whlXT-1604999076022)(https://i.loli.net/2020/11/10/pglJLtGNmBzsoZe.png)]
可以看出,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这个是虚拟机在编译的时候相当于动态代理给我们实现的