public class ThreadDemo {
public static void main(String[] args) {
for(int i = 0;i<10;i++){
final int j = i;
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("线程" + j);
}
}) .start();
}
}
}
javac -g ThreadDemo.java命令编译后生成两个class文件:ThreadDemo.class和ThreadDemo$1.class
字节码利用jdk提供的反编译工具:javap -v class文件
得到如下两段字节码:
Classfile /D:/work/workspace/util-demo/src/main/java/com/gsy/utildemo/thread/api/ThreadDemo.class
Last modified 2020-3-20; size 716 bytes
MD5 checksum 5837aed4e038d345b4e4089e35479b94
Compiled from "ThreadDemo.java"
public class com.gsy.utildemo.thread.api.ThreadDemo
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #8.#27 // java/lang/Object."":()V
#2 = Class #28 // java/lang/Thread
#3 = Class #29 // com/gsy/utildemo/thread/api/ThreadDemo$1
#4 = Methodref #3.#30 // com/gsy/utildemo/thread/api/ThreadDemo$1."":(I)V
#5 = Methodref #2.#31 // java/lang/Thread."":(Ljava/lang/Runnable;)V
#6 = Methodref #2.#32 // java/lang/Thread.start:()V
#7 = Class #33 // com/gsy/utildemo/thread/api/ThreadDemo
#8 = Class #34 // java/lang/Object
#9 = Utf8 InnerClasses
#10 = Utf8
#11 = Utf8 ()V
#12 = Utf8 Code
#13 = Utf8 LineNumberTable
#14 = Utf8 LocalVariableTable
#15 = Utf8 this
#16 = Utf8 Lcom/gsy/utildemo/thread/api/ThreadDemo;
#17 = Utf8 main
#18 = Utf8 ([Ljava/lang/String;)V
#19 = Utf8 j
#20 = Utf8 I
#21 = Utf8 i
#22 = Utf8 args
#23 = Utf8 [Ljava/lang/String;
#24 = Utf8 StackMapTable
#25 = Utf8 SourceFile
#26 = Utf8 ThreadDemo.java
#27 = NameAndType #10:#11 // "":()V
#28 = Utf8 java/lang/Thread
#29 = Utf8 com/gsy/utildemo/thread/api/ThreadDemo$1
#30 = NameAndType #10:#35 // "":(I)V
#31 = NameAndType #10:#36 // "":(Ljava/lang/Runnable;)V
#32 = NameAndType #37:#11 // start:()V
#33 = Utf8 com/gsy/utildemo/thread/api/ThreadDemo
#34 = Utf8 java/lang/Object
#35 = Utf8 (I)V
#36 = Utf8 (Ljava/lang/Runnable;)V
#37 = Utf8 start
{
public com.gsy.utildemo.thread.api.ThreadDemo();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."":()V
4: return
LineNumberTable:
line 3: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/gsy/utildemo/thread/api/ThreadDemo;
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=5, locals=3, args_size=1
0: iconst_0
1: istore_1
2: iload_1
3: bipush 10
5: if_icmpge 34
8: iload_1
9: istore_2
10: new #2 // class java/lang/Thread
13: dup
14: new #3 // class com/gsy/utildemo/thread/api/ThreadDemo$1
17: dup
18: iload_2
19: invokespecial #4 // Method com/gsy/utildemo/thread/api/ThreadDemo$1."":(I)V
22: invokespecial #5 // Method java/lang/Thread."":(Ljava/lang/Runnable;)V
25: invokevirtual #6 // Method java/lang/Thread.start:()V
28: iinc 1, 1
31: goto 2
34: return
LineNumberTable:
line 7: 0
line 8: 8
line 9: 10
line 14: 25
line 7: 28
line 17: 34
LocalVariableTable:
Start Length Slot Name Signature
10 18 2 j I
2 32 1 i I
0 35 0 args [Ljava/lang/String;
StackMapTable: number_of_entries = 2
frame_type = 252 /* append */
offset_delta = 2
locals = [ int ]
frame_type = 250 /* chop */
offset_delta = 31
}
class文件大致分为以下几部分:
文件元数据 类信息(类名、继承/实现关系、编译器版本、访问控制修饰符) 常量池 属性信息(私有属性信息不在内) 方法信息(签名、访问控制符、代码(指令、异常表、行号表(原码:字节码)、本地变量表、栈图))Classfile /D:/work/workspace/util-demo/src/main/java/com/gsy/utildemo/thread/api/ThreadDemo$1.class
Last modified 2020-3-22; size 950 bytes
MD5 checksum d0c952ff06f40d24aa9f6d4bc76af6b0
Compiled from "ThreadDemo.java"
final class com.gsy.utildemo.thread.api.ThreadDemo$1 implements java.lang.Runnable
minor version: 0
major version: 52
flags: ACC_FINAL, ACC_SUPER
Constant pool:
#1 = Fieldref #11.#31 // com/gsy/utildemo/thread/api/ThreadDemo$1.val$j:I
#2 = Methodref #12.#32 // java/lang/Object."":()V
#3 = Fieldref #33.#34 // java/lang/System.out:Ljava/io/PrintStream;
#4 = Class #35 // java/lang/StringBuilder
#5 = Methodref #4.#32 // java/lang/StringBuilder."":()V
#6 = String #36 // 绾跨▼
#7 = Methodref #4.#37 // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
#8 = Methodref #4.#38 // java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
#9 = Methodref #4.#39 // java/lang/StringBuilder.toString:()Ljava/lang/String;
#10 = Methodref #40.#41 // java/io/PrintStream.println:(Ljava/lang/String;)V
#11 = Class #42 // com/gsy/utildemo/thread/api/ThreadDemo$1
#12 = Class #43 // java/lang/Object
#13 = Class #44 // java/lang/Runnable
#14 = Utf8 val$j
#15 = Utf8 I
#16 = Utf8
#17 = Utf8 (I)V
#18 = Utf8 Code
#19 = Utf8 LineNumberTable
#20 = Utf8 LocalVariableTable
#21 = Utf8 this
#22 = Utf8 InnerClasses
#23 = Utf8 Lcom/gsy/utildemo/thread/api/ThreadDemo$1;
#24 = Utf8 run
#25 = Utf8 ()V
#26 = Utf8 SourceFile
#27 = Utf8 ThreadDemo.java
#28 = Utf8 EnclosingMethod
#29 = Class #45 // com/gsy/utildemo/thread/api/ThreadDemo
#30 = NameAndType #46:#47 // main:([Ljava/lang/String;)V
#31 = NameAndType #14:#15 // val$j:I
#32 = NameAndType #16:#25 // "":()V
#33 = Class #48 // java/lang/System
#34 = NameAndType #49:#50 // out:Ljava/io/PrintStream;
#35 = Utf8 java/lang/StringBuilder
#36 = Utf8 绾跨▼
#37 = NameAndType #51:#52 // append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
#38 = NameAndType #51:#53 // append:(I)Ljava/lang/StringBuilder;
#39 = NameAndType #54:#55 // toString:()Ljava/lang/String;
#40 = Class #56 // java/io/PrintStream
#41 = NameAndType #57:#58 // println:(Ljava/lang/String;)V
#42 = Utf8 com/gsy/utildemo/thread/api/ThreadDemo$1
#43 = Utf8 java/lang/Object
#44 = Utf8 java/lang/Runnable
#45 = Utf8 com/gsy/utildemo/thread/api/ThreadDemo
#46 = Utf8 main
#47 = Utf8 ([Ljava/lang/String;)V
#48 = Utf8 java/lang/System
#49 = Utf8 out
#50 = Utf8 Ljava/io/PrintStream;
#51 = Utf8 append
#52 = Utf8 (Ljava/lang/String;)Ljava/lang/StringBuilder;
#53 = Utf8 (I)Ljava/lang/StringBuilder;
#54 = Utf8 toString
#55 = Utf8 ()Ljava/lang/String;
#56 = Utf8 java/io/PrintStream
#57 = Utf8 println
#58 = Utf8 (Ljava/lang/String;)V
{
final int val$j;
descriptor: I
flags: ACC_FINAL, ACC_SYNTHETIC
com.gsy.utildemo.thread.api.ThreadDemo$1(int);
descriptor: (I)V
flags:
Code:
stack=2, locals=2, args_size=2
0: aload_0
1: iload_1
2: putfield #1 // Field val$j:I
5: aload_0
6: invokespecial #2 // Method java/lang/Object."":()V
9: return
LineNumberTable:
line 8: 0
LocalVariableTable:
Start Length Slot Name Signature
0 10 0 this Lcom/gsy/utildemo/thread/api/ThreadDemo$1;
public void run();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=3, locals=1, args_size=1
0: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
3: new #4 // class java/lang/StringBuilder
6: dup
7: invokespecial #5 // Method java/lang/StringBuilder."":()V
10: ldc #6 // String 线程
12: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
15: aload_0
16: getfield #1 // Field val$j:I
19: invokevirtual #8 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
22: invokevirtual #9 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
25: invokevirtual #10 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
28: return
LineNumberTable:
line 11: 0
line 12: 28
LocalVariableTable:
Start Length Slot Name Signature
0 29 0 this Lcom/gsy/utildemo/thread/api/ThreadDemo$1;
}
这里需要注意由于源代码在匿名内部类中使用了方法中的局部变量j,这里会给匿名内部类生成一个val$j属性,
并自动生成有参构造为属性赋值;(这里比较特别,使用匿名内部类是main方法,它是静态的,也没有访问外部
类任何属性,所以内部类不需要持有外部类对象引用,不需要内部类构造参数中加一个外部类对象的引用)扩展
一下,如果不仅使用了局部变量,还使用了外部类属性,会怎么样?实际上编译器会在外部内生成一个静态方法
(*) static access$$xxx(ThreadDemo),这个方法传入外部类对象引用,里面通过引用返回访问的属性;
同时构造内部类也会增加一个外部类ThreadDemo类型的引用,对应的构造方法会增加一个参数为ThreadDemo类
型的,完成属性赋值。
2.1 init(int)初始化方法
aload_0 加载slot_0位置的this引用到操作数栈;
iload_1 加载slot_1位置变量int型变量到操作数栈,这里不知道为什么,在本地变量表没有展示,实际上slot_1是方法的形参;
putfield #1 给属性赋值,用this访问属性,赋值为栈顶值1;弹出栈顶两个元素;
aload_0 加载slot_0位置的this引用到操作数栈;
invokespecial #2 调用父类的初始化方法;弹出栈顶元素;
return 返回我们尝试以匿名内部类为例解读了编译器生成的字节码文件(反编译后的),但这里面依然有许多的偶然性,需要大家探索;比如:可以将main方法改为一个非静态方法,就会发现内部类会增加一个外部类引用的属性,即使内部类没有访问外部类的任何属性;而本例比较特殊:匿名内部类使用在一个静态方法中,并且匿名内部类没有访问外部类的任何属性,所以在内部类中没有添加外部类属性;
3.结论与疑问 3.1结论 在方法中使用匿名内部类时,匿名内部类中访问方法的局部变量使用过为匿名内部类增加一个该类型属性,同时外部类会调用有参构造传入改值完成赋值来实现的; 如果是在非静态方法中使用匿名内部类,无论是否匿名内部类是否访问外部类属性,内部类都会增加一个外部类类型的属性,同时在外部类生成一个静态有参方法access$xx(外部类引用);匿名内部类访问外部类属性的就是通过这种方法 ,将外部类引用传入,通过返回值访问到外部类的属性; 3.2疑问 为什在本例中的匿名内部类的构造函数中,会出现先属性赋值后嗲用父类后构造的情况?