bilibili-深入理解JVM 虚拟机 学习笔记
P6 _常量的本质含义与反编译及助记符详解(6)
虚拟机参数小笔记:
-XX:+ 表示开启 option 选项
-XX:- 表示关闭 option 选项
-XX:= 表示 option 选项的值设置为 value
例如:
-XX:+TraceClassLoading 开启打印加载的类列表,用于追踪类的加载信息,默认是不开启的;
package new_package.jvm.p6;
public class MyTest2 {
public static void main(String[] args) {
System.out.println(MyParent2.TEXT);
}
}
class MyParent2 {
public static final String TEXT = "hello world";
static {
System.out.println("MyParent2 static block");
}
}
// 执行结果
// hello world
思考:为何没有打印 MyParent2 static block ?
常量(public static final xxx xxx = xxx)在编译阶段会存到调用这个常量的方法所在的类的常量池
中;
本质上, MyTest2 类并没有直接引用定义常量的类(MyParent2),因此不会触发定义常量的类的初始化;
这里指的是将常量放到 MyTest2 的常量池中,之后 MyTest2 与 MyParent2 不会有引用关系了,MyParent2.class 的存在与否也不会影响 MyTest2.class 的执行。
通过反编译可以看出些更多的信息:
javap -c target.classes.new_package.jvm.p6.MyTest2
public class new_package.jvm.p6.MyTest2 {
public new_package.jvm.p6.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
}
通过 3: ldc #4
这一行可以看到, MyTest2.class 并没有引用其他类,而是直接将String hello world
推送至栈顶;
ldc
为 jvm 助记符,详情请查看 java虚拟机规范中关于 ldc 的表述
package new_package.jvm.p6;
public class MyTest3 {
public static void main(String[] args) {
System.out.println(MyParent3.CONTEXT);
}
}
class MyParent3 {
public static String CONTEXT = "MyParent2 context";
static {
System.out.println("MyParent2 static block");
}
}
// MyParent2 static block
// MyParent2 context
javap -c target.classes.new_package.jvm.p6.MyTest3
public class new_package.jvm.p6.MyTest3 {
public new_package.jvm.p6.MyTest3();
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: getstatic #3 // Field new_package/jvm/p6/MyParent3.CONTEXT:Ljava/lang/String;
6: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
9: return
}
用 MyTest3 和 MyTest2 进行对比可以发现,MyTest3 中 3: getstatic #3
并没有直接使用本地字符串缓存,而是引用指向了 MyParent3.CONTEXT;
了解一些其他的助记符:
package new_package.jvm.p6;
public class MyTest4 {
public static void main(String[] args) {
int a = MyParent4.a;
int b = MyParent4.b;
int c = MyParent4.c;
int d = MyParent4.d;
int e = MyParent4.e;
short s = MyParent4.s;
float f = MyParent4.f;
//System.out.println(String.format("%d %d %d %d %s %f", a, b, c, d, s, f));
}
}
class MyParent4 {
public static final String TEXT = "hello world";
public static final int a = 1;
public static final int b = 5;
public static final int c = 127;
public static final int d = 128;
public static final int e = 168434;
public static final short s = 200;
public static final float f = 200F;
}
javap -c target.classes.new_package.jvm.p6.MyTest4
public class new_package.jvm.p6.MyTest4 {
public new_package.jvm.p6.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: istore_1
2: iconst_5
3: istore_2
4: bipush 127
6: istore_3
7: sipush 128
10: istore 4
12: ldc #3 // int 168434
14: istore 5
16: sipush 200
19: istore 6
21: ldc #4 // float 200.0f
23: fstore 7
25: return
}
助记符:
ldc 表示将 int、float、string 类型的常量值从常量池推送到栈顶
iconst_x (x 表示 1~5) 将常量值 x 推送至栈顶
bipush 将单字节(-128~127)常量值推送到栈顶
sipush 将一个短整型(-32768~32767)常量值推送到栈顶
更多信息与助记符参考虚拟机规范中的第六章 Java虚拟机指令集
P7 _编译期常量与运行期常量的区别及数组创建本质分析(7)
package new_package.jvm.p7;
import java.util.UUID;
public class Test3 {
public static void main(String[] args) {
System.out.println(Parent3.UU);
}
}
class Parent3 {
public static String UU = UUID.randomUUID().toString();
static {
System.out.println("Parent3 static block");
}
}
// Parent3 static block
// 9438293f-3b78-499f-a585-2aa512a97137
javap -c target.classes.new_package.jvm.p7.Test3
public class new_package.jvm.p7.Test3 {
public new_package.jvm.p7.Test3();
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: getstatic #3 // Field new_package/jvm/p7/Parent3.UU:Ljava/lang/String;
6: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
9: return
}
当一个常量的值在编译期间无法确定,那么常量值就不会放到调用类的常量池中;
程序运行时 Test4 会主动使用常量所在类 Parent4 ,所以 Parent4 会被初始化;
package new_package.jvm.p7;
public class Test4 {
public static void main(String[] args) {
// Parent4 a1 = new Parent4();
// Parent4 a2 = new Parent4();
Parent4[] arr = new Parent4[2];
}
}
class Parent4 {
static {
System.out.println("Parent4 static block");
}
}
// 无输出
public class new_package.jvm.p7.Test4 {
public new_package.jvm.p7.Test4();
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_2
1: anewarray #2 // class new_package/jvm/p7/Parent4
4: astore_1
5: return
}
package new_package.jvm.p7;
public class Test4 {
public static void main(String[] args) {
Parent4 a1 = new Parent4();
System.out.println(a1.getClass());
Parent4[] arr = new Parent4[2];
System.out.println(arr.getClass());
Parent4[][] arr2 = new Parent4[2][1];
System.out.println(arr2.getClass());
System.out.println(arr.getClass().getSuperclass());
System.out.println(arr2.getClass().getSuperclass());
int[] ints = new int[1];
System.out.println(ints.getClass());
}
}
class Parent4 {
}
// class new_package.jvm.p7.Parent4
// class [Lnew_package.jvm.p7.Parent4;
// class [[Lnew_package.jvm.p7.Parent4;
// class java.lang.Object
// class java.lang.Object
// class [I
public class new_package.jvm.p7.Test4 {
public new_package.jvm.p7.Test4();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: new #2 // class new_package/jvm/p7/Parent4
3: dup
4: invokespecial #3 // Method new_package/jvm/p7/Parent4."<init>":()V
7: astore_1
8: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
11: aload_1
12: invokevirtual #5 // Method java/lang/Object.getClass:()Ljava/lang/Class;
15: invokevirtual #6 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
18: iconst_2
19: anewarray #2 // class new_package/jvm/p7/Parent4
22: astore_2
23: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
26: aload_2
27: invokevirtual #5 // Method java/lang/Object.getClass:()Ljava/lang/Class;
30: invokevirtual #6 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
33: iconst_2
34: iconst_1
35: multianewarray #7, 2 // class "[[Lnew_package/jvm/p7/Parent4;"
39: astore_3
40: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
43: aload_3
44: invokevirtual #5 // Method java/lang/Object.getClass:()Ljava/lang/Class;
47: invokevirtual #6 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
50: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
53: aload_2
54: invokevirtual #5 // Method java/lang/Object.getClass:()Ljava/lang/Class;
57: invokevirtual #8 // Method java/lang/Class.getSuperclass:()Ljava/lang/Class;
60: invokevirtual #6 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
63: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
66: aload_3
67: invokevirtual #5 // Method java/lang/Object.getClass:()Ljava/lang/Class;
70: invokevirtual #8 // Method java/lang/Class.getSuperclass:()Ljava/lang/Class;
73: invokevirtual #6 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
76: iconst_1
77: newarray int
79: astore 4
81: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
84: aload 4
86: invokevirtual #5 // Method java/lang/Object.getClass:()Ljava/lang/Class;
89: invokevirtual #6 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
92: return
}
对于数组实例来说,其类型是由 jvm 在运行期动态生成的,表示为[Lnew_package.jvm.p7.Parent4
这种形式;
助记符:
anewarray 创建一个引用类型
的数组,并将其引用值压入栈顶()
newarray 创建一个指定的原生类型
的数组,并将其引用值压入栈顶(int,char,float,double,boolean,short,long,byte)
P8 _接口初始化规则与类加载器准备阶段和初始化阶段的重要意义分析(8)
package new_package.jvm.p8;
import java.util.Random;
public class MyTest5 {
public static void main(String[] args) {
System.out.println(MyParent5.b);
}
}
interface MyParent5 {
// 如果是一个确定值,则会直接放到调用类的常量池中,就不会再初始化该父接口了
int a = new Random().nextInt(10);
}
interface MyChild5 extends MyParent5 {
// 注意:接口中的参数,默认为 public static final
int b = 12;
}
删除 MyParent5.class 依然能执行不报错;
当一个接口在初始化时,并不要求其父接口也初始化;
只有在真正使用父接口的时候,才会初始化(如引用父接口中的常量);
package new_package.jvm.p8;
public class MyTest6 {
public static void main(String[] args) {
Singleton singleton = Singleton.getInstance();
System.out.println(Singleton.c1);
System.out.println(Singleton.c2);
}
}
class Singleton {
public static int c1;
//public static int c2 = 0;
private static Singleton singleton = new Singleton();
private Singleton() {
c1++;
c2++; // 体现了准备阶段的意义,有一个初始值就可以使用了,否则会有异常
// System.out.println(c2);
}
public static int c2 = 0;
public static Singleton getInstance() {
return singleton;
}
}
// 1
// 0