synchronized关键字所生成的字节码详细分析
在之前已经将如下这样的源文件对应的字节码文件完整的分析完了,如下:
这次再来写一个内容稍丰富一点的类,准备再来从头至尾的来分析一下,对其字节码的理解进一步巩固,如下:
然后用javap -verbose来查看一下反编译信息:
xiongweideMacBook-Pro:jvm_lectue xiongwei$ cd out/production/classes/ xiongweideMacBook-Pro:classes xiongwei$ javap -verbose com/jvm/bytecode/MyTest2.class Classfile /Users/xiongwei/Documents/workspace/IntelliJSpace/jvm_lectue/out/production/classes/com/jvm/bytecode/MyTest2.class Last modified Sep 11, 2018; size 831 bytes MD5 checksum 22c52151c374d39068e39938d1b3e72c Compiled from "MyTest2.java" public class com.jvm.bytecode.MyTest2 minor version: 0 major version: 52 flags: ACC_PUBLIC, ACC_SUPER Constant pool: #1 = Methodref #10.#34 // java/lang/Object."<init>":()V #2 = String #35 // Welcome #3 = Fieldref #5.#36 // com/jvm/bytecode/MyTest2.str:Ljava/lang/String; #4 = Fieldref #5.#37 // com/jvm/bytecode/MyTest2.x:I #5 = Class #38 // com/jvm/bytecode/MyTest2 #6 = Methodref #5.#34 // com/jvm/bytecode/MyTest2."<init>":()V #7 = Methodref #5.#39 // com/jvm/bytecode/MyTest2.setX:(I)V #8 = Methodref #40.#41 // java/lang/Integer.valueOf:(I)Ljava/lang/Integer; #9 = Fieldref #5.#42 // com/jvm/bytecode/MyTest2.in:Ljava/lang/Integer; #10 = Class #43 // java/lang/Object #11 = Utf8 str #12 = Utf8 Ljava/lang/String; #13 = Utf8 x #14 = Utf8 I #15 = Utf8 in #16 = Utf8 Ljava/lang/Integer; #17 = Utf8 <init> #18 = Utf8 ()V #19 = Utf8 Code #20 = Utf8 LineNumberTable #21 = Utf8 LocalVariableTable #22 = Utf8 this #23 = Utf8 Lcom/jvm/bytecode/MyTest2; #24 = Utf8 main #25 = Utf8 ([Ljava/lang/String;)V #26 = Utf8 args #27 = Utf8 [Ljava/lang/String; #28 = Utf8 myTest2 #29 = Utf8 setX #30 = Utf8 (I)V #31 = Utf8 <clinit> #32 = Utf8 SourceFile #33 = Utf8 MyTest2.java #34 = NameAndType #17:#18 // "<init>":()V #35 = Utf8 Welcome #36 = NameAndType #11:#12 // str:Ljava/lang/String; #37 = NameAndType #13:#14 // x:I #38 = Utf8 com/jvm/bytecode/MyTest2 #39 = NameAndType #29:#30 // setX:(I)V #40 = Class #44 // java/lang/Integer #41 = NameAndType #45:#46 // valueOf:(I)Ljava/lang/Integer; #42 = NameAndType #15:#16 // in:Ljava/lang/Integer; #43 = Utf8 java/lang/Object #44 = Utf8 java/lang/Integer #45 = Utf8 valueOf #46 = Utf8 (I)Ljava/lang/Integer; { java.lang.String str; descriptor: Ljava/lang/String; flags: public static java.lang.Integer in; descriptor: Ljava/lang/Integer; flags: ACC_PUBLIC, ACC_STATIC public com.jvm.bytecode.MyTest2(); descriptor: ()V flags: ACC_PUBLIC Code: stack=2, locals=1, args_size=1 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: aload_0 5: ldc #2 // String Welcome 7: putfield #3 // Field str:Ljava/lang/String; 10: aload_0 11: iconst_5 12: putfield #4 // Field x:I 15: return LineNumberTable: line 3: 0 line 4: 4 line 6: 10 LocalVariableTable: Start Length Slot Name Signature 0 16 0 this Lcom/jvm/bytecode/MyTest2; 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 #5 // class com/jvm/bytecode/MyTest2 3: dup 4: invokespecial #6 // Method "<init>":()V 7: astore_1 8: aload_1 9: bipush 8 11: invokevirtual #7 // Method setX:(I)V 14: bipush 20 16: invokestatic #8 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer; 19: putstatic #9 // Field in:Ljava/lang/Integer; 22: return LineNumberTable: line 11: 0 line 13: 8 line 15: 14 line 16: 22 LocalVariableTable: Start Length Slot Name Signature 0 23 0 args [Ljava/lang/String; 8 15 1 myTest2 Lcom/jvm/bytecode/MyTest2; public void setX(int); descriptor: (I)V flags: ACC_PUBLIC Code: stack=2, locals=2, args_size=2 0: aload_0 1: iload_1 2: putfield #4 // Field x:I 5: return LineNumberTable: line 19: 0 line 20: 5 LocalVariableTable: Start Length Slot Name Signature 0 6 0 this Lcom/jvm/bytecode/MyTest2; 0 6 1 x I static {}; descriptor: ()V flags: ACC_STATIC Code: stack=1, locals=0, args_size=0 0: bipush 10 2: invokestatic #8 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer; 5: putstatic #9 // Field in:Ljava/lang/Integer; 8: return LineNumberTable: line 8: 0 } SourceFile: "MyTest2.java"
由于这次的类中声明有静态变量,所以标红处多了一个static块,下面将“-verbose”参数去掉可以看到一个更精简的信息,如下:
其中注意此时有一个setX()方法的,那如果将setX()方法访问修饰符由“public”改为"private",看会发生啥:
为啥呢?其实并非私有的方法在字节码信息中就不存在了,而是用javap命令需要再加一个参数才能看到私有的方法,如下:
由于此篇讨论的话题是关于synchronized关键字在字节码中的含义,所以接下来咱们给setX()方法加一个同步锁然后再来分析其字节码,在加之前先来看下不带synchronized所对应字节码的信息,如下:
接下来给方法加上synchronized,如下:
然后看一下此时对应的字节码会发生啥变化:
貌似网上有很多资料看到对于同步方法在字节码中表现通常会有“moniterenter”和“moniterexit”,但是为啥咱们自己通过javap -verbose查看不到呢?因为修饰的是实例方法,也就是给当前对象上锁,表现则是在方法说明之上而非代码中,如果改为修饰方法中的代码的话则就会可以看到啦,如下:
当然上面的这个是对参数str进行上锁,实际没啥意义,因为str是一个可变的值,正常的做法应该是对一个不可变的对象进行上锁,这里只是为了说明问题,下面再来看一下字节码信息:
private void test(java.lang.String); descriptor: (Ljava/lang/String;)V flags: ACC_PRIVATE Code: stack=3, locals=4, args_size=2 0: aload_1 1: dup 2: astore_2 3: monitorenter 4: getstatic #10 // Field java/lang/System.out:Ljava/io/PrintStream; 7: ldc #11 // String hello world~ 9: iconst_0 10: anewarray #12 // class java/lang/Object 13: invokevirtual #13 // Method java/io/PrintStream.printf:(Ljava/lang/String;[Ljava/lang/Object;)Ljava/io/PrintStream; 16: pop 17: aload_2 18: monitorexit 19: goto 27 22: astore_3 23: aload_2 24: monitorexit 25: aload_3 26: athrow 27: return Exception table: from to target type 4 19 22 any 22 25 22 any LineNumberTable: line 23: 0 line 24: 4 line 25: 17 line 26: 27 LocalVariableTable: Start Length Slot Name Signature 0 28 0 this Lcom/jvm/bytecode/MyTest2; 0 28 1 str Ljava/lang/String; StackMapTable: number_of_entries = 2 frame_type = 255 /* full_frame */ offset_delta = 22 locals = [ class com/jvm/bytecode/MyTest2, class java/lang/String, class java/lang/Object ] stack = [ class java/lang/Throwable ] frame_type = 250 /* chop */ offset_delta = 4
确实是看到了monitorenter,monitorexit了,其加了同步锁在字节码的流程可以大致分析一下:
另外synchronized还可以修饰静态方法,下面写一个:
很显然它锁的是整个class对象,下面也来看一下它对应的字节码信息:
由于synchronized修饰的是方法,而非它里面的代码,所以在javap中只看到了方法签名上有同步信息。
关于同步锁需要了解一下如下概念:
重入锁:所谓重入锁就是说同一个线程可以访问多个同步方法,比如说:
访问一次同步方法其引用计数就加1,像上面的引用计数就会变为2,而当setB()执行完之后,则引用计数就会减为1,再setX()执行完则引用计数会减为0。
非重入锁:是指多个线程而言的,比如一个线程访问了一个同步方法,另一个线程再访问同一个对象的该方法则会阻塞。