1、对象的实例化内存布局与访问定位
1.1、对象实例化的几种方式
1)new:最常见的方式,静态方法,xxxBuild/xxxFactory的静态方法 2)Class的newInstance():反射的方式,只能调用空参构造器,权限必须是public 3)Constructor的newInstance(xxx):反射的方式,可以调用空参、带参构造器,权限没有要求 4)clone:不调用任何构造器,当前类需要实现clonable接口,实现clone() 5)反序列化:从文件中或网络中获取一个对象的二进制流 6)第三方库Objenesis
1.2、字节码角度看对象的创建过程
public class Demo03 { public static void main(String[] args) { Object obj = new Object(); } }
javap -v -p Demo03.class > Demo03.txt
Classfile /D:/workspaces/eclipse201812_workspace/demo/bin/com/oy/Demo03.class Last modified 2021-10-10; size 454 bytes MD5 checksum 74095293acc6e6bda439cfc9bb4a6aa6 Compiled from "Demo03.java" public class com.oy.Demo03 minor version: 0 major version: 52 flags: ACC_PUBLIC, ACC_SUPER Constant pool: #1 = Class #2 // com/oy/Demo03 #2 = Utf8 com/oy/Demo03 #3 = Class #4 // java/lang/Object #4 = Utf8 java/lang/Object #5 = Utf8 <init> #6 = Utf8 ()V #7 = Utf8 Code #8 = Methodref #3.#9 // java/lang/Object."<init>":()V #9 = NameAndType #5:#6 // "<init>":()V #10 = Utf8 LineNumberTable #11 = Utf8 LocalVariableTable #12 = Utf8 this #13 = Utf8 Lcom/oy/Demo03; #14 = Utf8 main #15 = Utf8 ([Ljava/lang/String;)V #16 = Utf8 args #17 = Utf8 [Ljava/lang/String; #18 = Utf8 obj #19 = Utf8 Ljava/lang/Object; #20 = Utf8 MethodParameters #21 = Utf8 SourceFile #22 = Utf8 Demo03.java { public com.oy.Demo03(); descriptor: ()V flags: ACC_PUBLIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokespecial #8 // Method java/lang/Object."<init>":()V 4: return LineNumberTable: line 10: 0 LocalVariableTable: Start Length Slot Name Signature 0 5 0 this Lcom/oy/Demo03; 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 #3 // class java/lang/Object 3: dup 4: invokespecial #8 // Method java/lang/Object."<init>":()V 7: astore_1 8: return LineNumberTable: line 13: 0 line 14: 8 LocalVariableTable: Start Length Slot Name Signature 0 9 0 args [Ljava/lang/String; 8 1 1 obj Ljava/lang/Object; MethodParameters: Name Flags args } SourceFile: "Demo03.java"
1.3、对象创建的六个步骤
1)判断对象对应的类是否加载、链接、初始化 2)为对象分配内存,如果内存规整,指针碰撞;如果内存不规整,虚拟机需要维护一个空闲列表。 3)处理并发安全问题,采用CAS失败重试、区域加锁保证更新的原子性,每个线程预先分配一块TLAB,通过-XX:+/-UseTLAB参数来设定。 4)初始化分配到的空间,所有属性设置默认值,保证对象实例字段在不显示赋值时可以直接使用。 5)设置对象的对象头。 6)执行init方法进行初始化。在Java程序的视角看来,初始化才正式开始。初始化成员变量,执行实例化代码块,调用类的构造方法,并把堆内对象的首地址赋值给引用变量。因此一般来说(由字节码中是否跟随有invokespecial指令所决定),new指令之后会接着就是执行方法,把对象按照程序员的意愿进行初始化,这样一个真正可用的对象才算完全创建出来。
public class Person { /* * 给对象属性赋 * 1)属性的默认初始化 ==> 2)显示初始化 ==> 3)代码块中赋值 ==> 4)构造器中赋值 */ private int id = 111; { id = 222; } public Person() { id = 333; } { id = 444; } public static void main(String[] args) { Person p = new Person(); System.out.println(p.id); } }
javap -v -p Person.class > Person.txt
Classfile /D:/workspaces/eclipse201812_workspace/demo/bin/com/oy/Person.class Last modified 2021-10-10; size 638 bytes MD5 checksum 6beef92d37323367617e3d8ea8aa9f6e Compiled from "Person.java" public class com.oy.Person minor version: 0 major version: 52 flags: ACC_PUBLIC, ACC_SUPER Constant pool: #1 = Class #2 // com/oy/Person #2 = Utf8 com/oy/Person #3 = Class #4 // java/lang/Object #4 = Utf8 java/lang/Object #5 = Utf8 id #6 = Utf8 I #7 = Utf8 <init> #8 = Utf8 ()V #9 = Utf8 Code #10 = Methodref #3.#11 // java/lang/Object."<init>":()V #11 = NameAndType #7:#8 // "<init>":()V #12 = Fieldref #1.#13 // com/oy/Person.id:I #13 = NameAndType #5:#6 // id:I #14 = Utf8 LineNumberTable #15 = Utf8 LocalVariableTable #16 = Utf8 this #17 = Utf8 Lcom/oy/Person; #18 = Utf8 main #19 = Utf8 ([Ljava/lang/String;)V #20 = Methodref #1.#11 // com/oy/Person."<init>":()V #21 = Fieldref #22.#24 // java/lang/System.out:Ljava/io/PrintStream; #22 = Class #23 // java/lang/System #23 = Utf8 java/lang/System #24 = NameAndType #25:#26 // out:Ljava/io/PrintStream; #25 = Utf8 out #26 = Utf8 Ljava/io/PrintStream; #27 = Methodref #28.#30 // java/io/PrintStream.println:(I)V #28 = Class #29 // java/io/PrintStream #29 = Utf8 java/io/PrintStream #30 = NameAndType #31:#32 // println:(I)V #31 = Utf8 println #32 = Utf8 (I)V #33 = Utf8 args #34 = Utf8 [Ljava/lang/String; #35 = Utf8 p #36 = Utf8 MethodParameters #37 = Utf8 SourceFile #38 = Utf8 Person.java { private int id; descriptor: I flags: ACC_PRIVATE public com.oy.Person(); descriptor: ()V flags: ACC_PUBLIC Code: stack=2, locals=1, args_size=1 0: aload_0 1: invokespecial #10 // Method java/lang/Object."<init>":()V 4: aload_0 5: bipush 111 7: putfield #12 // Field id:I id字段显示初始化为111 10: aload_0 11: sipush 222 14: putfield #12 // Field id:I id字段代码块赋值222 17: aload_0 18: sipush 444 21: putfield #12 // Field id:I id字段代码块赋值444 24: aload_0 25: sipush 333 28: putfield #12 // Field id:I id字段构造器赋值333 31: return LineNumberTable: line 17: 0 line 11: 4 line 14: 10 line 22: 17 line 18: 24 line 19: 31 LocalVariableTable: Start Length Slot Name Signature 0 32 0 this Lcom/oy/Person; 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 #1 // class com/oy/Person 3: dup 4: invokespecial #20 // Method "<init>":()V 7: astore_1 8: getstatic #21 // Field java/lang/System.out:Ljava/io/PrintStream; 11: aload_1 12: getfield #12 // Field id:I 15: invokevirtual #27 // Method java/io/PrintStream.println:(I)V 18: return LineNumberTable: line 26: 0 line 27: 8 line 28: 18 LocalVariableTable: Start Length Slot Name Signature 0 19 0 args [Ljava/lang/String; 8 11 1 p Lcom/oy/Person; MethodParameters: Name Flags args } SourceFile: "Person.java"
1.4、对象的内存布局
1.5、对象的访问定位
JVM是如何通过栈帧中的对象引用访问到其内部的对象实例的呢?定位,通过栈上reference访问
对象访问方式主要有两种:1)句柄访问 2)直接指针(HotSpot采用)。句柄访问好处:reference中存储稳定句柄地址,对象被移动(垃圾收集时移动对象很普遍)时只会改变句柄中实例数据指针即可,reference本身不需要被修改。
2、执行引擎
---
----