转载请注明原文地址:http://www.cnblogs.com/ygj0930/p/6535156.html
一:虚拟机中对象的创建
1:虚拟机遇到new指令时,在常量池检索是否有对应的符号引用,对应的类是否已加载、解析和初始化。没有则先加载对应的类文件到虚拟机。
2:加载类文件后,为新对象分配内存(内存大小在加载类后即可确定):有两种办法,取决于当前区域内存的情况
1)指针碰撞法:若内存是连续的,空闲内存和占用内存中间有一指针作为分界点,则分配内存时只需把指针往空闲区域移动相应大小即可;
2)空闲列表法:若内存中占用空间和空闲空间交错存在,则虚拟机维护一个表格记录各个空闲的块,在分配时从列表中找到足够大的空闲块并更新表格即可。
在多线程下为对象分配内存:把内存分配动作划分在不同的空间进行,每个线程在Java堆预先分配一小块内存作为自己的本地线程分配缓冲区(TLAB),之后哪个线程需要分配内存则在自己的TLAB中分配即可,用完了才需要在共享的堆中进行分配。而在共享的堆空间分配时,使用同步锁进行限制即可。
3:初始化分配的空间为零值。这一步保证了对象的实例在代码中无需赋初值即可使用。
4:为对象进行必要设置:把对象属于哪个类、元数据保存位置、哈希码、GC分代年龄等信息封装在对象头中。
5:对象初始化:执行init方法,根据程序员代码中的指令进行真正的赋值。
二:对象在内存中的存在形式
对象在内存中由三部分组成:对象头、实例数据、对齐填充。
对象头:包括两部分:
1)对象自身的运行时数据,包括:哈希码、GC分代年龄、锁状态标识、线程持有的锁、偏向线程ID等;
2)类型指针:指向对象所属类的元数据区域。
实例数据:对象真正的有效信息。
对齐填充:HotSpot虚拟机要求对象的起始地址必须是8字节的整数倍,因此对象的实例数据部分没有对齐时就需要对齐填充来补全。
三:对象的访问定位
我们通过方法中需要使用对象时,只需通过栈帧中的reference数据来访问堆中的对象内容即可。对象的访问有两种方法:句柄法、直接指针法。
句柄法:内存中划分一片区域作为句柄池,句柄池中的句柄包含了该对象到实例数据区域的指针以及到对象类型区域的指针。句柄法访问对象时,先通过reference定位到句柄池中该对象的句柄,然后由句柄中的两个指针分别访问该对象的具体实例数据和类型数据。
直接指针法:reference直接执行对象的实例数据区,而数据区中又有一个指针指向类型数据区域。
比较:句柄法比较文档,如果对象内容被移动的话,只需更改句柄池中的句柄即可,栈帧中的reference可以不改变;而直接指针法则速度更快。
四:对象已死?
判断对象是否还在内存中存活,有两种理论:
1)引用计数法:每当有一个引用执行该对象时,引用计数器+1.当计数器器的值为0时即说明对象没有被引用,已死亡。该理论的致命缺陷在于:两个对象互相引用时,彼此计数器值不为0,但又不再被访问,对象已无用却一直不会被回收。
2)可达性分析:通过一系列GCRoots对象作为起点,沿着这些节点的引用链进行检索。若一个对象没有任何引用链与GCRoots相连的话,即说明该对象不可达(不可用),可进行回收。GCRoots一般有:栈帧的引用对象、静态属性引用的对象、常量引用的对象、本地方法栈中引用的对象。