关于JAVA虚拟机的dup指令的一个问题
public class Test {
public static void main(String[] args) {
Test t = new Test();
}
}
使用java反编译命令:javap -v Test.class(注释:这里只是main方法对应的字节码)
C:\Users\AnXiaole\Desktop>javap -v Test.class
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=2, args_size=1
0: new #2 //从内存中申请一块类 class com/hzcf/flagship/web/Test 的对象空间,将空间的地址放入栈顶
3: dup // 将第0步得到的类Test 的对象空间地址复制一份,放入栈顶.为什么要复制?这是因为:执行第4步(即下一步)的指令(即:调用Test()构造器时,是需要对象的引用的,即:是用Test()构造器对这个引用所代表的对象空间做初始化的.另外:解析#3时,也要用到它是属于哪个对象的引用.
4: invokespecial #3 // Method "<init>":()V //执行这一指令时,用到第3步中的引用.执行完成后,第3步的引用已经从栈中弹出了
7: astore_1 // 将此时的栈顶值(正是第0步放入的对象的引用,该引用的对象空间已由3-4两步初始化了)弹出,放入局部变量Test t中.
8: return
LineNumberTable:
line 30: 0
line 31: 8
将操作数栈的栈顶一个或两个元素出栈:pop、pop2
将栈最顶端的来两个数值互换:swap
虚拟机规范预定义的属性
(图来源于:《深入理解java虚拟机(第二版)》周志明著 表6-13)
内部类
非静态内部类B
public class A
{
class B // 非静态内部类
{
}
}
编译
使用javac编译后,生成两个类,一个A.class,另一个是A$B.class
javac A.java
反编译
使用javap反编译A.class:
Microsoft Windows [版本 10.0.14393]
(c) 2016 Microsoft Corporation。保留所有权利。
C:\Users\AnXiaole>cd Desktop
C:\Users\AnXiaole\Desktop>javap -v A.class
Classfile /C:/Users/AnXiaole/Desktop/A.class
Last modified 2017-12-18; size 220 bytes
MD5 checksum 275a40274293fa218e498c7cf8f8939d
Compiled from "A.java"
public class A
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #3.#13 // java/lang/Object."<init>":()V
#2 = Class #14 // A
#3 = Class #15 // java/lang/Object
#4 = Class #16 // A$B
#5 = Utf8 B
#6 = Utf8 InnerClasses
#7 = Utf8 <init>
#8 = Utf8 ()V
#9 = Utf8 Code
#10 = Utf8 LineNumberTable
#11 = Utf8 SourceFile
#12 = Utf8 A.java
#13 = NameAndType #7:#8 // "<init>":()V
#14 = Utf8 A
#15 = Utf8 java/lang/Object
#16 = Utf8 A$B
{
public A();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 1: 0
}
SourceFile: "A.java"
InnerClasses:
#5= #4 of #2; //B=class A$B of class A
C:\Users\AnXiaole\Desktop>
使用javap反编译A$B.class:
Microsoft Windows [版本 10.0.14393]
(c) 2016 Microsoft Corporation。保留所有权利。
C:\Users\AnXiaole>cd Desktop
C:\Users\AnXiaole\Desktop>javap -v A$B.class
Classfile /C:/Users/AnXiaole/Desktop/A$B.class
Last modified 2017-12-18; size 267 bytes
MD5 checksum ab8de5aa5a0ec80864506f590eac36f3
Compiled from "A.java"
class A$B
minor version: 0
major version: 52
flags: ACC_SUPER
Constant pool:
#1 = Fieldref #3.#13 // A$B.this$0:LA;
#2 = Methodref #4.#14 // java/lang/Object."<init>":()V
#3 = Class #16 // A$B
#4 = Class #19 // java/lang/Object
#5 = Utf8 this$0
#6 = Utf8 LA;
#7 = Utf8 <init>
#8 = Utf8 (LA;)V
#9 = Utf8 Code
#10 = Utf8 LineNumberTable
#11 = Utf8 SourceFile
#12 = Utf8 A.java
#13 = NameAndType #5:#6 // this$0:LA;
#14 = NameAndType #7:#20 // "<init>":()V
#15 = Class #21 // A
#16 = Utf8 A$B
#17 = Utf8 B
#18 = Utf8 InnerClasses
#19 = Utf8 java/lang/Object
#20 = Utf8 ()V
#21 = Utf8 A
{
final A this$0; // javac编译时,编译器自动给非静态内部类B添加了一个字段;
descriptor: LA; // 此字段为外部类的类型A;
flags: ACC_FINAL, ACC_SYNTHETIC // 此字段是final的(0x0010),并且是编译器自动添加出来的(ACC_SYNTHETIC 0x1000);
A$B(A); // B类有一个构造方法,参数为类型A
descriptor: (LA;)V
flags:
Code:
stack=2, locals=2, args_size=2
0: aload_0
1: aload_1
2: putfield #1 // Field this$0:LA;为B重的A字段赋值。
5: aload_0
6: invokespecial #2 // Method java/lang/Object."<init>":()V
9: return
LineNumberTable:
line 3: 0
}
SourceFile: "A.java"
InnerClasses:
#17= #3 of #15; //B=class A$B of class A
C:\Users\AnXiaole\Desktop>
静态内部类B
public class A
{
static class B // 静态内部类
{
}
}
编译
使用javac编译后,生成两个类,一个A.class,另一个是A$B.class
javac A.java
反编译
先使用javac编译(同上)。再使用javap反编译A.class:
Microsoft Windows [版本 10.0.14393]
(c) 2016 Microsoft Corporation。保留所有权利。
C:\Users\AnXiaole>cd Desktop
C:\Users\AnXiaole\Desktop>javap -v A.class
Classfile /C:/Users/AnXiaole/Desktop/A.class
Last modified 2017-12-18; size 220 bytes
MD5 checksum 8e47700d8b726d5939db637ebd069fd8
Compiled from "A.java"
public class A
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #3.#13 // java/lang/Object."<init>":()V
#2 = Class #14 // A
#3 = Class #15 // java/lang/Object
#4 = Class #16 // A$B
#5 = Utf8 B
#6 = Utf8 InnerClasses
#7 = Utf8 <init>
#8 = Utf8 ()V
#9 = Utf8 Code
#10 = Utf8 LineNumberTable
#11 = Utf8 SourceFile
#12 = Utf8 A.java
#13 = NameAndType #7:#8 // "<init>":()V
#14 = Utf8 A
#15 = Utf8 java/lang/Object
#16 = Utf8 A$B
{
public A();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 1: 0
}
SourceFile: "A.java"
InnerClasses:
static #5= #4 of #2; //B=class A$B of class A
C:\Users\AnXiaole\Desktop>
使用javap反编译A$B.class:
Microsoft Windows [版本 10.0.14393]
(c) 2016 Microsoft Corporation。保留所有权利。
C:\Users\AnXiaole>cd Desktop
C:\Users\AnXiaole\Desktop>javap -v A$B.class
Classfile /C:/Users/AnXiaole/Desktop/A$B.class
Last modified 2017-12-18; size 220 bytes
MD5 checksum 0e5880806b58919d834d99aa3edeb341
Compiled from "A.java"
class A$B
minor version: 0
major version: 52
flags: ACC_SUPER
Constant pool:
#1 = Methodref #3.#10 // java/lang/Object."<init>":()V
#2 = Class #12 // A$B
#3 = Class #15 // java/lang/Object
#4 = Utf8 <init>
#5 = Utf8 ()V
#6 = Utf8 Code
#7 = Utf8 LineNumberTable
#8 = Utf8 SourceFile
#9 = Utf8 A.java
#10 = NameAndType #4:#5 // "<init>":()V
#11 = Class #16 // A
#12 = Utf8 A$B
#13 = Utf8 B
#14 = Utf8 InnerClasses
#15 = Utf8 java/lang/Object
#16 = Utf8 A
{
A$B();
descriptor: ()V
flags:
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 3: 0
}
SourceFile: "A.java"
InnerClasses:
static #13= #2 of #11; //B=class A$B of class A
C:\Users\AnXiaole\Desktop>
可以看到,静态内部类的实例化方法A$B();变成无参的了(这里说的无参,是显式无参,即没有LA;类作为参数了,但args_size=1说明参数为this。)
之前接触内部类的时候,有个问题,正好可以用现在学的知识解释:内部类 - 和代总讨论
问题
关于同步指令的一些疑问
同步一段指令集序列通常使用Java语言中的synchronized语句块来表示的,Java虚拟机的指令集中有monitorenter和monitorexit两条指令来支持synchronized关键字的语义。
编译器必须确保无论方法通过何种方式完成,方法中调用过的每条monitorenter指令都必须执行其对应的monitorexit指令,而无论这个方法是正常结束还是异常结束。
——《深入理解java虚拟机(第二版)》周志明著 6.4.10同步指令
public class Test {
public void doSomething(String args) {
synchronized (args) {
System.out.println(111);
}
}
}
Microsoft Windows [版本 10.0.14393]
(c) 2016 Microsoft Corporation。保留所有权利。
C:\WorkSpaces>javap -v Test.class
Classfile /C:/WorkSpaces/Test.class
Last modified 2017-12-18; size 542 bytes
MD5 checksum 2cdcfdd7b89ea6adce87e50e042c739b
Compiled from "Test.java"
public class com.huizhongcf.util.Test
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #5.#19 // java/lang/Object."<init>":()V
#2 = Fieldref #20.#21 // java/lang/System.out:Ljava/io/PrintStream;
#3 = Methodref #22.#23 // java/io/PrintStream.println:(I)V
#4 = Class #24 // com/huizhongcf/util/Test
#5 = Class #25 // java/lang/Object
#6 = Utf8 <init>
#7 = Utf8 ()V
#8 = Utf8 Code
#9 = Utf8 LineNumberTable
#10 = Utf8 doSomething
#11 = Utf8 (Ljava/lang/String;)V
#12 = Utf8 StackMapTable
#13 = Class #24 // com/huizhongcf/util/Test
#14 = Class #26 // java/lang/String
#15 = Class #25 // java/lang/Object
#16 = Class #27 // java/lang/Throwable
#17 = Utf8 SourceFile
#18 = Utf8 Test.java
#19 = NameAndType #6:#7 // "<init>":()V
#20 = Class #28 // java/lang/System
#21 = NameAndType #29:#30 // out:Ljava/io/PrintStream;
#22 = Class #31 // java/io/PrintStream
#23 = NameAndType #32:#33 // println:(I)V
#24 = Utf8 com/huizhongcf/util/Test
#25 = Utf8 java/lang/Object
#26 = Utf8 java/lang/String
#27 = Utf8 java/lang/Throwable
#28 = Utf8 java/lang/System
#29 = Utf8 out
#30 = Utf8 Ljava/io/PrintStream;
#31 = Utf8 java/io/PrintStream
#32 = Utf8 println
#33 = Utf8 (I)V
{
public com.huizhongcf.util.Test();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 12: 0
public void doSomething(java.lang.String);
descriptor: (Ljava/lang/String;)V
flags: ACC_PUBLIC
Code:
stack=2, locals=4, args_size=2
0: aload_1 //将参数args对象入栈
1: dup // 复制栈顶元素(即args的引用)
2: astore_2 // 将栈顶元素存储到局变量表slot 2中(*作用:如果出现异常,那么栈顶就会放入异常对象。由于出现异常的时也要释放锁,所以先把锁放到slot 2中,出了异常的时候先从slot 2中获得锁然后释放掉锁monitorexit)
3: monitorenter // 以栈顶元素为锁,开始同步
4: getstatic #2 // 此三个指令用于调用System.out.println(111);
7: bipush 111 // 此三个指令用于调用System.out.println(111);
9: invokevirtual #3 // 此三个指令用于调用System.out.println(111);
12: aload_2 // 将局部变量slot 2的元素(即args)入栈
13: monitorexit // 退出同步
14: goto 22 // 方法正常结束,找转到22 return
17: astore_3 // 从这部开始是异常路径,见下面异常表的Target 13
18: aload_2 // 将局部变量slot 2的元素(即args)入栈
19: monitorexit // 退出同步
20: aload_3 // 将局部变量slot 3的元素(即异常对象)入栈
21: athrow // 把异常对象重新抛给此方法的调用者
22: return // 方法正常返回
Exception table:
from to target type
4 14 17 any // 如果从第4行(包含)到第14行(不报含)出现any异常,则跳转到第17行继续执行
17 20 17 any // 如果从第17行(包含)到第14行(不报含)出现any异常,则跳转到第17行继续执行
LineNumberTable:
line 15: 0
line 16: 4
line 17: 12
line 18: 22
StackMapTable: number_of_entries = 2
frame_type = 255 /* full_frame */
offset_delta = 17
locals = [ class com/huizhongcf/util/Test, class java/lang/String, class java/lang/Object ]
stack = [ class java/lang/Throwable ]
frame_type = 250 /* chop */
offset_delta = 4
}
SourceFile: "Test.java"