HotSpot虚拟机中的对象

1 对象的创建

这里的对象仅限于普通的java对象,不包括数组和Class对象。一般来说,创建对象通常仅仅通过一个new关键字。当虚拟机遇到一条new的指令时,首先会去检查这个指令所带的参数能不能在常量池中定位到某个类的符号引用,并且检查这个类是否已经加载完毕,当类加载检查通过后,就开始为对象分配内存了,就是上节所提到的,会在java堆中划分出一块确定大小的内存。
在分配内存的时候,主要考虑以下两种情况:一种情况是假设java虚拟机的内存是绝对规整的,即使用过的内存都在一边,未使用过的内存都在另一边,中间用一个指针来作为界线,这时为对象分配内存时,只需要将指针往未使用内存的方向移动与对象大小相等的距离即可,这种方式称之为“指针碰撞”;另外一种情况就是java虚拟机中的内存是零散分布的,这时就需要有一张表来记录那块内存是使用过的,哪块内存是没有使用的,当需要为对象分配内存时,就从中间挑选出一块足够大的内存为对象分配,并且更新以下记录的列表,这种方式叫做“空闲列表”。
分配完内存之后,虚拟机就需要对分配到的这块内存初始化为零值,然后对对象进行一些必要的设置,比如这个对象是哪个类的实例,对象的哈希码等信息。以上这些工作做完之后,站在虚拟机的角度来看,对象已经产生了,但是站在程序的角度来看,对象才刚开始创建,接下来就要执行方法了,让对象按照程序猿的想法进行初始化,至此一个真正可用的对象就产生了。

2 对象的内存布局

什么叫对象的内存布局?就是对象在内存中存储的布局。在HotSpot虚拟机中,对象在内存中存储的布局可分为3部分:对象头、实例数据和对齐填充。

2.1 对象头

对象头包含两部分信息,第一部分是用于存储对象自身的运行时数据,如哈希码、GC分代年龄、锁状态标志、线程持有的锁等,这些数据的长度在32位和64位的虚拟机中分别为32bit和64bit,官方称之为“Mark Word”。另一部分是类型指针,即对象指向它的类元数据指针,虚拟机是通过这个指针来确定该对象是哪个类的实例的,如果对象是一个java数组,那么对象头中还必须有一块记录数组长度的数据,因为虚拟机可以通过普通java对象的元数据信息确定java对象的大小,但是无法从数组的元数据信息中确定数组的大小。

2.2 实例数据

实例数据就是对象真正存储的有效信息,也是在程序中定义的各种类型的字段内容,无论是从父类中继承下来的,还是在子类中定义的,都需要记录下来。

2.3 对齐填充

这一部分的数据并不是必然存在的,它没有什么特别的含义,只是起着占位符的作用,由于HotSpot VM要求对象的大小必须是8字节的整数倍,而对象头部分正好是8字节的倍数,所以当对象实例数据部分不是8字节的整数倍时,就需要这部分的数据来填充补齐了。

3 对象的访问定位

创建对象就是为了使用它,java程序需要通过栈上的reference数据来操作堆上的具体对象。目前主流的访问方式有使用句柄和直接指针两种。
在这里插入图片描述
在这里插入图片描述
通过上面两张图可以看出,使用句柄访问对象时,需要在java堆中划分一块内存作为句柄池,句柄池里面存放的是对象实例和类型数据各自的地址,reference存储的是对象的句柄池地址,优点就是对象移动时,只需要改变句柄池中实例数据的地址就可以了,reference中存储的数据不变,缺点是需要两次指针定位的访问;使用直接指针访问对象时,就必须要考虑如何放置访问类型数据的相关信息了,reference中存储的就是对象的地址,优点是节省了一次指针定位的时间,缺点是对象移动时,reference中的数据也需要做相应的变化。

4 疑惑点
public class TestStringIntern {

	public static void main(String[] args) {
		String str1 = new StringBuilder("计算机").append("软件").toString();
		System.out.println(str1.intern() == str1);
		
		String str2 = new StringBuilder("ja").append("va").toString();
		System.out.println(str2.intern() == str2);
	}
}

上面这串代码在jdk1.6和jdk1.7执行的结果不一致,官方给出的解释是:在jdk1.6中,intern()方法会把首次遇到的字符串实例复制到永久代中,返回的也是永久代中这个字符串实例的引用,而用StringBuilder创建的字符串实例在Java堆上,所以必然不是同一个引用,将返回false。而jdk1.7中的intern()实现不会再复制实例,只是在常量池中记录首次出现的实例引用,因此intern()返回的引用和由StringBuilder创建的那个字符串实例是同一个。对str2比较返回false是因为“java”这个字符串在执行StringBuilder.toString()之前已经出现过,字符串常量池中已经有它的引用了,不符合首次出现的原则,而“计算机软件”这个字符串则是首次出现的,因此返回true。
前半部分还能理解,后半部分始终不理解,“java”字符串怎么会已经出现过了呢?难道说jdk7及以后的版本中,jvm默认堆中已经有“java”这个字符串了???希望有哪位大神给解释一下。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值