java中有两种特殊的方法<init>,<clinit>
1,当java类中,存在用static修饰的静态类型字段,或static块,编译器便会生成<clinit>
2,当java类中定义了构造方法,或其他非static类成员变量被赋了初始值,编译器便会生成<init>
代码
public class Test{
private Integer i=3;
private static int a=90;
public void add(int a,int b){
Test test=this;
int z=a+b;
int x=3;
}
public static void main(String[] args){
Test test=new Test();
test.add(2,3);
}
}
javac Test.java
javap -verbose Test.class 如下图所示
java成员变量,静态属性,都在常量池中
Classfile /Users/robin/Test.class
Last modified 2018-11-16; size 570 bytes
MD5 checksum 179494093ddd1f13d97f138f5162f8a8
Compiled from "Test.java"
public class Test
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #8.#24 // java/lang/Object."<init>":()V
#2 = Methodref #25.#26 // java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
#3 = Fieldref #4.#27 // Test.i:Ljava/lang/Integer; //成员变量引用
#4 = Class #28 // Test
#5 = Methodref #4.#24 // Test."<init>":()V
#6 = Methodref #4.#29 // Test.add:(II)V
#7 = Fieldref #4.#30 // Test.a:I
#8 = Class #31 // java/lang/Object
#9 = Utf8 i
#10 = Utf8 Ljava/lang/Integer;
#11 = Utf8 a
#12 = Utf8 I
#13 = Utf8 <init>
#14 = Utf8 ()V
#15 = Utf8 Code
#16 = Utf8 LineNumberTable
#17 = Utf8 add
#18 = Utf8 (II)V
#19 = Utf8 main
#20 = Utf8 ([Ljava/lang/String;)V
#21 = Utf8 <clinit>
#22 = Utf8 SourceFile
#23 = Utf8 Test.java
#24 = NameAndType #13:#14 // "<init>":()V
#25 = Class #32 // java/lang/Integer
#26 = NameAndType #33:#34 // valueOf:(I)Ljava/lang/Integer;
#27 = NameAndType #9:#10 // i:Ljava/lang/Integer;
#28 = Utf8 Test
#29 = NameAndType #17:#18 // add:(II)V
#30 = NameAndType #11:#12 // a:I
#31 = Utf8 java/lang/Object
#32 = Utf8 java/lang/Integer
#33 = Utf8 valueOf
#34 = Utf8 (I)Ljava/lang/Integer;
{
public Test();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=1, args_size=1
0: aload_0 //加载变量表中的this 入栈
1: invokespecial #1 //调用常量池1位置父类的init方法 // Method java/lang/Object."<init>":()V
4: aload_0 //加载变量表中的this 入栈
5: iconst_3 //加载3入栈
6: invokestatic #2 //调用常量池2位置的静态方法 //Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
9: putfield #3 //放到常量池3位置 // Field i:Ljava/lang/Integer;
12: return
public void add(int, int);
descriptor: (II)V
flags: ACC_PUBLIC
Code:
stack=2, locals=6, args_size=3
0: aload_0
1: astore_3
2: iload_1
3: iload_2
4: iadd
5: istore 4
7: iconst_3
8: istore 5
10: return
LineNumberTable:
line 7: 0
line 8: 2
line 9: 7
line 10: 10
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=3, locals=2, args_size=1
0: new #4 // class Test
3: dup
4: invokespecial #5 // Method "<init>":()V
7: astore_1
8: aload_1
9: iconst_2
10: iconst_3
11: invokevirtual #6 // Method add:(II)V
14: return
LineNumberTable:
line 13: 0
line 14: 8
line 15: 14
static {};
descriptor: ()V
flags: ACC_STATIC
Code:
stack=1, locals=0, args_size=0
0: bipush 90
2: putstatic #7 // Field a:I
5: return
LineNumberTable:
line 4: 0
}
jdb命令打断点调试
UseCompressedOops 取消指针压缩功能
1,jdb -XX:+UseSerialGC -Xmx10m -XX:-UseCompressedOops
2,stop in Test.add 表示在Test类的add方法设断点
3, run Test 开始启动Test类的main方法,Test的main运行后,jdb会在add方法的第一行代码上暂停
4,next,会进入到add方法的第二句代码
执行jps,查看进程号 ,sudo java -cp sa-jdi.jar sun.jvm.hotspot.HSDB
1,查看堆内存 universe
universe
Heap Parameters:
Gen 0: eden [0x000000011f800000,0x000000011f9458e0,0x000000011fab0000) space capacity = 2818048, 47.31899527616279 used
from [0x000000011fab0000,0x000000011fab0000,0x000000011fb00000) space capacity = 327680, 0.0 used
to [0x000000011fb00000,0x000000011fb00000,0x000000011fb50000) space capacity = 327680, 0.0 usedInvocations: 0
Gen 1: old [0x000000011fb50000,0x000000011fb50000,0x0000000120200000) space capacity = 7012352, 0.0 usedInvocations: 0
hsdb>
2,搜索实例 scanoops
果然找到了Test的实例 地址 0x000000011f941720 scanoops 命令会显示该实例在JVM 内部对应的instanceOop的内存首地址
hsdb> scanoops 0x000000011f800000 0x0000000120200000 Test
0x000000011f941720 Test
3,选择tools->inspect
4, 查看类的<init>,<clinit>
tools-> class brower
问题:jvm为什么需要编译器自动生成这2个方法?
答:1,<init>方法等同于类的构造函数,在遇到new指令时,自然会调用该方法
2,java类中存在static成员变量,或者有static{}的代码块时,jvm加载java类时,会触发<clinit>方法,完成static成员变量的初始化,或者执行static{}的代码逻辑
<init>详解
在上面的Test例子中,并没有显式定义构造方法,但是编译器还是生成了一个构造方法,
1,如果我们显式的定义了一个构造方法
public class Test{
private Integer i=3;
private static int a=90;
public Test(){
short s=8;
}
public void add(int a,int b){
Test test=this;
int z=a+b;
int x=3;
}
public static void main(String[] args){
Test test=new Test();
test.add(2,3);
}
修改之前的
public Test();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=1, args_size=1
0: aload_0 //加载变量表中的this 入栈
1: invokespecial #1 //调用常量池1位置父类的init方法 // Method java/lang/Object."<init>":()V
4: aload_0 //加载变量表中的this 入栈
5: iconst_3 //加载3入栈
6: invokestatic #2 //调用常量池2位置的静态方法 //Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
9: putfield #3 //放到常量池3位置 // Field i:Ljava/lang/Integer;
12: return
查看字节码
{
public Test();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=2, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: aload_0
5: iconst_3
6: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
9: putfield #3 // Field i:Ljava/lang/Integer;
12: bipush 8 //将8载入操作数栈
14: istore_1 //存入变量表1位置
15: return
}
结论:即使定义了一个构造方法,也不会影响<init>方法,依然会将成员变量的初始化指令加到构造方法的字节码指令中
2,加入一段{}代码块
public class Test{
private Integer i=3;
private static int a=90;
{
System.out.println("i="+i);
}
public Test(){
short s=8;
}
public Test(int x){
x=12;
}
public void add(int a,int b){
Test test=this;
int z=a+b;
int x=3;
}
public static void main(String[] args){
Test test=new Test();
test.add(2,3);
}
}
字节码内容
{
public Test();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=3, locals=2, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: aload_0
5: iconst_3
6: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
9: putfield #3 //赋给成员变量值 // Field i:Ljava/lang/Integer;
//这一段就是{}的执行字节码
12: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
15: new #5 // class java/lang/StringBuilder
18: dup
19: invokespecial #6 // Method java/lang/StringBuilder."<init>":()V
22: ldc #7 // String i=
24: invokevirtual #8 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
27: aload_0
28: getfield #3 // Field i:Ljava/lang/Integer;
31: invokevirtual #9 // Method java/lang/StringBuilder.append:(Ljava/lang/Object;)Ljava/lang/StringBuilder;
34: invokevirtual #10 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
37: invokevirtual #11 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
40: bipush 8 // short s=8
42: istore_1
43: return
public Test(int);
descriptor: (I)V
flags: ACC_PUBLIC
Code:
stack=3, locals=2, args_size=2
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: aload_0
5: iconst_3
6: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
9: putfield #3 //赋给成员变量值 // Field i:Ljava/lang/Integer;
12: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
15: new #5 // class java/lang/StringBuilder
18: dup
19: invokespecial #6 // Method java/lang/StringBuilder."<init>":()V
22: ldc #7 // String i=
24: invokevirtual #8 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
27: aload_0
28: getfield #3 // Field i:Ljava/lang/Integer;
31: invokevirtual #9 // Method java/lang/StringBuilder.append:(Ljava/lang/Object;)Ljava/lang/StringBuilder;
34: invokevirtual #10 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
37: invokevirtual #11 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
40: bipush 12
42: istore_1
43: return
总结,从上面的代码,可以看出{}代码的字节码,在两个构造方法都有,进一步地说明,通过new指令,调用构造方法时,都会执行{}代码块
3,加入一个父类 BaseClass.java
public abstract class BaseClass{
protected long plong=12l;
static{
Integer i=30;
}
}
{
protected long plong;
descriptor: J
flags: ACC_PROTECTED
public BaseClass();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=3, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: aload_0
5: ldc2_w #2 // long 12l
8: putfield #4 // Field plong:J
11: return
static {};
descriptor: ()V
flags: ACC_STATIC
Code:
stack=1, locals=1, args_size=0
0: bipush 30
2: invokestatic #5 //Integer i=30; // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
5: astore_0 //保存到本地变量表中
6: return
}
查看字节码Test.class
{
public Test();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=2, args_size=1
0: aload_0
1: invokespecial #1 // Method BaseClass."<init>":()V
4: aload_0
5: iconst_3
6: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
9: putfield #3 // Field i:Ljava/lang/Integer;
12: bipush 8
14: istore_1
15: return
public Test(int);
descriptor: (I)V
flags: ACC_PUBLIC
Code:
stack=2, locals=2, args_size=2
0: aload_0
1: invokespecial #1 // Method BaseClass."<init>":()V
4: aload_0
5: iconst_3
6: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
9: putfield #3 // Field i:Ljava/lang/Integer;
12: bipush 12
14: istore_1
15: return
static {};
descriptor: ()V
flags: ACC_STATIC
Code:
stack=1, locals=0, args_size=0
0: bipush 90
2: putstatic #7 // Field a:I
5: return
结论:子类的构造方法,都会先去调用父类的<init>方法,
解释{}与static{}的作用域
1,{}块:
能够访问java类的非静态成员变量,和静态成员变量,但是其内部定的变量不能被外部所访问,但是{}块的局部变量在编译期会放到本地变量表,而本地变量表又是方法相关的,但是{}块并不一个方法,java编译器将{}块嵌入到<init>方法,因此这个本地变量表,就是java类的构造方法的,
测试当有多个{}块
public class Test extends BaseClass {
private Integer i=3;
private static int a=90;
{
int a=1;
int b=2;
int c=a+b;
}
{
int d=3;
int e=4;
int f=e-d;
}
public Test(){
short s=8;
}
}
{
public Test();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=4, args_size=1
0: aload_0
1: invokespecial #1 // Method BaseClass."<init>":()V
4: aload_0
5: iconst_3
6: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
9: putfield #3 //成员变量赋值 // Field i:Ljava/lang/Integer;
//第一个{}代码块
12: iconst_1
13: istore_1 // int a=1
14: iconst_2
15: istore_2 // int b=2
16: iload_1
17: iload_2
18: iadd
19: istore_3 // int c=a+b
//第二个{}代码块
20: iconst_3
21: istore_1 // int d=3
22: iconst_4
23: istore_2 // int e=4
24: iload_2
25: iload_1
26: isub
27: istore_3 // int f=e-d
28: bipush 8
30: istore_1 //short s=8;
31: return
第一{}中的a的slot是1,第二个{}的d的slot也是1,那不是存在复盖了,这样下次引用不就是错的吗?
由此可见,java类有多个{}块是,编译器并不是简单地将其合并到构造函数,而是将其做为一个独立的普通的java方法,因此多个{}块不受影响
2,static{}块
仅能访问java类的静态成员变量
static{}块所定的局部变量不能被 外部访问
clinit方法:
不具有继承性,因为它是在类加载过程中被调用,而父类与子类是分别加载的,当父类加载完之后,父类中的static成员变量初始化,static{}代码块已经执行完成,所以没有必要在子类加载时再执行一次
同样试验多个static{}块
public class Test extends BaseClass {
private Integer i=3;
private static int a=90;
static {
int a=1;
int b=2;
int c=a+b;
}
static {
int d=3;
int e=4;
int f=e-d;
}
public Test(){
short s=8;
}
public void add(int a,int b){
Test test=this;
int z=a+b;
int x=3;
}
public static void main(String[] args){
Test test=new Test();
test.add(2,3);
}
}
static {};
descriptor: ()V
flags: ACC_STATIC
Code:
stack=2, locals=3, args_size=0
0: bipush 90
2: putstatic #7 // Field a:I
5: iconst_1
6: istore_0
7: iconst_2
8: istore_1
9: iload_0
10: iload_1
11: iadd
12: istore_2
13: iconst_3
14: istore_0
15: iconst_4
16: istore_1
17: iload_1
18: iload_0
19: isub
20: istore_2
21: return
}