四、对象如何创建,布局?如何访问数据
对象的内存分配
对象的创建过程
Jvm读到new指令,
先去方法区(类常量池中)查看是否有对应的类符号,并检查该类是否被加载,
if 加载,JVM为新生对象分配内存
else 加载类,为对象分配内存
内存分配完,JVM将内存空间值初始化为0值
对象头信息记录,这个对象是哪个类的实例,如何找到类元数据信息,对象哈希码值,对象的GC分代信息记录到对象头中;
new之后执行init操作
JVM内存分配的方式
指针碰撞Bump the pointer分配内存
如果堆内存使用是很规整的,如图,一边是已使用;一边是未使用;那分配内存就是把指针向空闲空间那边移动一段距离,这种分配方式成为指针碰撞
适用范围:堆内存使用很规整
空闲列表分配内存
JVM必须维护一个内存使用列表,记录内存使用情况。分配时到列表中找到一个足够大的内存来划分给对象实例,并更新列表的记录。这种分配方法叫空闲列表!
适用范围:堆内存使用散乱,不规整!
选择
在使用Serial、 ParNew等带Compact过程的收集器时,系统采用的分配算法是指针碰撞,否则,使用CMS这种基于Mark-Sweep算法的收集器时,通常采用空闲列表。
并发创建对象时的内存分配问题
2个线程,1个指针,a线程分配的A对象的内存可能没分配完,b线程分配B对象内存时却发现a线程移动了内存中的指针!就会产生并发问题!如何解决?
两个方案:
1)分配内存空间的动作做同步处理
2)把内存分配的动作按照线程划分在不同空间之中执行
每个线程在java堆中预先分配一小块内存,称为本地线程缓冲区Thread Local Allocation Buffer-TLAB;哪个线程要分配内存,就在TLAB上分配。TLAB用完,并重新分配新的TLAB时,才需要同步!
虚拟机是否使用TLAB,可以通过-XX:+/-UseTLAB 参数设定
Java对象在内存中的结构
HotSpot虚拟机中,对象在堆内存中存储,结构分别是:
1)对象头(Header)
markword:存储对象运行时数据,如哈希码,GC分代粘了,锁状态标志,线程持有的锁,偏向线程ID,偏向时间戳等
长度:依计算机位数而定,
32/64位计算机的markword分别为32bit(4字节)和64bit(8字节);
Class对象指针:对象指向它所属类的元数据的指针;
2)实例数据(Instance Data)
3)对齐填充(Padding)。占位符,让对象的大小必须是8字节的整数倍。对象头部分正好是8字节的倍数(1倍或者2倍);因此当对象实例数据部分没有对齐时,就需要对齐填充来补全。
http://www.cnblogs.com/duanxz/p/4967042.html
一个对象占多大内存空间?
对象占用空间是8字节对齐的
对象头:32位 4字节/ 64位 8字节
Class对象指针: 32位 4字节/ 64位 8字节?
实例数据:按照基本类型所占字节计算
对齐填充:
复合对象:对象持有对象
reference引用: 8字节,在栈内存
固定的是:对象头16字节,一个对象最少16字节;
符合对象,32字节
http://yueyemaitian.iteye.com/blog/2033046
http://www.cnblogs.com/zhanjindong/p/3757767.html
实例数据分配策略
HotSpot虚拟机默认的分配策略为:
Longs/doubles、ints、shorts/chars、bytes/booleans、oops(Ordinary Object Pointers);相同宽度的字段总是被分配到一起。
在满足这个前提条件的情况下,在父类中定义的变量会出现在子类之前。
如何访问堆中对象?
必须通过栈区的reference索引,找到堆中的对象。
JVM虚拟机规范定义reference的作用,reference是指向对象的一个引用,但是引用如何实现却未说明!取决于不同的虚拟机实现!
句柄访问对象
通过指针访问对象
Java语法对应的内存位置
对象-堆内存
基本类型变量-线程栈内存
基本类型变量值-线程栈内存
引用类型变量-线程栈内存
引用类型变量值-堆内存 包括数组,对象
局部变量-线程栈内存 Thread local
方法参数-线程栈内存 Thread local
对象分配规则
1.对象优先分配在Eden区
如果Eden区没有足够的空间时,虚拟机执行一次Minor GC。
2.大对象直接进入老年代
大对象包括:
大数组,如 private static final int _1MB = 1024 * 1024;
byte[] allocation1= new byte[2 * _1MB];
长字符串,如java io里接收一行很长的line,大对象,并且生命周期很短,但是却在Old代了!占用old内存
(大对象是指需要大量连续内存空间的对象)。这样做的目的是避免在Eden区和两个Survivor区之间发生大量的内存拷贝。通过参数-XX:PretenureSizeThreshold=3145728KB控制,超过3145728KB的对象直接进入Old
3.长期存活的对象进入老年代。
虚拟机为每个对象定义了一个年龄计数器,对象每熬过了1次Minor GC对象的年龄加1,达到阀值对象进入老年区。通过参数-XX:MaxTenuringThreshold=15(默认)控制。
4.动态判断对象的年龄。
如果Survivor区中相同年龄的所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象可以直接进入老年代,无需等到MaxTenuringThreshold要求的年龄数。
5.空间分配担保。
每次进行Minor GC时,JVM会检查老年代剩余最大连续空间是否大于新生代所有对象总和
如果大于,则可以Minor GC
否则,检查是否设置HandlePromotionFailure参数,是否允许担保,
如(-XX:+HandlePromotionFailure)
如果设置允许担保,则检查老年代最大连续空间是否大于历次晋升到老年代对象的平均大小,如果大于,则尝试一次Minor GC,失败也要Full GC
如果小于,则直接Full GC
如果不允许担保,则直接Full GC;
5的担保是为了4的情况准备的!