博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
读书笔记《深入理解java虚拟机》——对象的创建
阅读量:5953 次
发布时间:2019-06-19

本文共 4118 字,大约阅读时间需要 13 分钟。

head

本节讲解对象的创建过程必须在某种具体的虚拟机上才有意义,笔者使用HotSpot虚拟机为例,HotSpot虚拟机是Sun JDK和Open JDK中所带的虚拟机,也是目前使用最多的虚拟机。

创建对象

以下为普通java对象,不包括数组和Class对象。

1 创建的类是谁

当虚拟机执行 new 指令时会根据指令参数去类常量池中查找类的符号引用,该new命令参数的类型为CONSTANT_Class_info,然后它指向常量池中一个UTF-8的字面量。 检查这个类是否已经加载,链接,初始化。如果没有就先执行类的加载过程。

// 以下是类反编译后常量池和方法 (篇幅有限,有省略)Constant pool:   #1 = Methodref          #2.#21         // java/lang/Object."
":()V #2 = Class #22 // java/lang/Object #3 = Class #23 // io/stephen/Application #4 = Utf8
#21 = NameAndType #4:#5 // "
":()V #22 = Utf8 java/lang/Object 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 java/lang/Object #2为参数指向常量池中的#2,#2为Class的符号引用,它又指向#22的字面量 3: dup 4: invokespecial #1 // Method java/lang/Object."
":()V 7: astore_1 8: return复制代码

2 分配内存

所有的类在加载到虚拟机后便可确定它的大小,这时为这对象在堆中开辟空间即可。这时有两种情况:

1)堆中内存是规整的,既被使用的内存都在一边,未使用的内存在另外一边。两者之间有个指针作为分界点,为对象分配内存时就是把这个指针往未使用的内存挪动一块和该对象大小相同的一块区域,这种方式叫指针碰撞。

2)堆中的内存不规整,已使用的和未使用的内存相互交叉时,虚拟机要维护一个列表,记录那块内存是被使用的,哪块未使用,在分配时从列表中找出一块能容纳下这个对象的内存块分给该对象,然后更新列表的记录,这种方式叫做空闲列表。

可以看出这两种方式的选择由堆内存是否整齐为条件,那堆内存是否整齐又由采用的垃圾收集器是否是压缩整理决定。像Serial、ParNew这种基于压缩算法的垃圾收集器采用的是指针碰撞式。像CMS这种基于标记-清除算法的采用空闲列表式。

另外一个问题是及时保证new对象时候的线程安全性。因为可能出现虚拟机正在给对象A分配内存,指针还没有来得及修改,对象B又同时使用了原来的指针来分配内存的情况。对此虚拟机也有两种方案:

1)采用了CAS配上失败重试的方式保证更新更新操作的原子性。

2)把内存分配按照线程划分,给每个线程开辟一块内存称为本地线程分配缓存(TLAB),线程在创建对象时就分配在TLAB里,当TLAB用完时才需要同步锁定。可通过-XX:+/-UseTLAB参数设定是否使用此方式。

3 内存初始化

分配结束后,虚拟机要将分配的内存空间都初始化为零值(不包括对象头)。如果使用的TLAB模式,这个过程也可以提前daoTLAB分配时进行。这步的操作保证了对象字段在java代码中可以不赋初始值直接使用。程序能访问到这些字段的数据类型所对应的零值。

4 对象设置

对对象进行必要的设置,例如这个对象是哪个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的GC分代年龄等信息,这些信息存放在对象的对象头中。

5 执行init

new指令后会接着执行invokespecial指令,在上面字节码中可以看到该指令,它带有参数#1 可根据其查看要执行的方法为 init。init方法会将类中所有的非静态的字段和代码块收集到它的方法体中进行初始化。如下例:

public class Application {	private int n = 2;	private BigDecimal b = new BigDecimal(6);	{		n++;	}	public Application() {		// init() 方法在此执行		System.out.println(n);		System.out.println(b);	}	public static void main(String[] args) {		Application a = new Application();	}}复制代码

反编译后的字节码(主要看构造方法):

public io.stephen.dubbo.Application();    Code:      stack=4, locals=1, args_size=1         0: aload_0                                // 将this入栈         1: invokespecial #1                  // Method java/lang/Object."
":()V 调用init方法 4: aload_0 5: iconst_2 // 将常量2加载到操作数栈 6: putfield #2 // Field n:I 操作数栈顶元素出栈,并赋值给n 9: aload_0 10: new #3 // class java/math/BigDecimal // 创建bigDecimal对象 13: dup // 将栈顶元素赋值一份压入栈顶 14: bipush 6 // 将常量6压入操作数栈 16: invokespecial #4 // Method java/math/BigDecimal."
":(I)V // 执行BigDecimal的init方法 19: putfield #5 // Field b:Ljava/math/BigDecimal; //弹出栈顶的6赋值给bigDecimal方法参数 22: aload_0 23: dup 24: getfield #2 // Field n:I //访问字段 n 27: iconst_1 // 将常量1压入栈 28: iadd //将栈中头两个元素出栈,并相加,然后把结果从新入栈。 29: putfield #2 // Field n:I // 取栈顶也就是数字3赋值给n // 以下为打印语句,已省略 32: getstatic #6 // Field java/lang/System.out:Ljava/io/PrintStream; 35: aload_0 36: getfield #2 // Field n:I 39: invokevirtual #7 // Method java/io/PrintStream.println:(I)V 52: return // 结束方法执行将操作数栈顶的元素返回给调用者复制代码

现在这个对象创建完毕。终于可以歇歇了。

转载于:https://juejin.im/post/5bfe57716fb9a049d81b8dcf

你可能感兴趣的文章
CentOS7 64位小型操作系统的安装
查看>>
线程互互斥锁
查看>>
KVM虚拟机&openVSwitch杂记(1)
查看>>
win7下ActiveX注册错误0x80040200解决参考
查看>>
《.NET应用架构设计:原则、模式与实践》新书博客--试读-1.1-正确认识软件架构...
查看>>
2013 Linux领域年终盘点
查看>>
linux学习之查看程序端口占用情况
查看>>
相逢在栀枝花开的季节
查看>>
linux下git自动补全命令
查看>>
Ubuntu14.04LTS更新源
查看>>
Linux报“Unknown HZ value! (288) Assume 100”错误
查看>>
mysql多实例实例化数据库
查看>>
我的友情链接
查看>>
golang xml和json的解析与生成
查看>>
javascript 操作DOM元素样式
查看>>
Android 内存管理 &Memory Leak & OOM 分析
查看>>
【查找算法】基于存储的查找算法(哈希查找)
查看>>
JavaWeb网上图书商城完整项目--day02-10.提交注册表单功能之页面实现
查看>>
记录一下这次web实训的两个网站
查看>>
POJ-1830 开关问题 高斯消元
查看>>