对象的结构
一般来说,对象由头(head)和域(field)两个部分组成,如下图所示
其中head主要包含了对象的大小,种类以及一些gc需要信息。field主要用于存储对象中可访问的部分,也就是我们平时看到的对象的方法变量之类的
对象的生与死
目前主要的对象判活算法有 可达性分析算法 和 引用计数算法
- 可达性分析算法通过从 GC Roots 开始向下搜索,如果一个对象没有在引用链上存在时,则判定对象可以被回收
- 引用计数算法通过给对象添加一个引用计数器,通过引用计数判断对象是否存活,由于引用计数算法无法解决循环引用的问题,主流的Java虚拟机都没有采用这种方法。但是由于其实现方便,效率高的特点,可以作为一种辅助手段存在
对象的生活环境
就像人类的社会有资本主义和社会主义的划分,对象的生活环境也不相同。目前常见的垃圾回收算法有 标记清除算法(mark-sweep),标记压缩算法(mark-compact),复制算法(copying)
- mark-sweep分为 mark 和 sweep 两个阶段:首先标记出所有需要回收的对象,然后统一回收这部分对象。
- mark-compact在标记阶段与mark-sweep相同,但是后续会将对象向堆的一端移动,并清除边界外的所有对象。
- copying将可用内存分成两个部分,一块内存使用完了之后将活着的对象移动到另一块内存,再把使用过的内存一次清理掉。
时间开销
- mark-sweep: mark阶段与活对象的数量成正比,sweep阶段与整堆大小成正比(不需要移动对象,开销比compact小)
- mark-compact: mark阶段与活对象的数量成正比,compact阶段与活对象的大小成正比
- copying: 与活对象成正比
空间开销
- mark-sweep: 小(产生碎片)
- mark-compact: 小(不产生碎片)
- copying: 通常需要活对象的2倍大小(不产生碎片)
分配对象开销
mark-sweep由于有内存碎片的存在,分配对象需要通过查询当前内存使用情况,选择一块适合的内存分配,因此mark-sweep在分配内存的时候存在额外的时间开销。而mark-compact和copying分配对象不需要考虑这种情况
对象的辈分
一般情况下将Java堆分为 新生代,老年代
对象的分配优先放在新生代,新生代在一段时间内会有大量的临时对象产生,因此存活的对象比较少,比较适合copying算法。对象在经过一定次数的copying gc之后会升级到老年代。老年代一般采用mark-sweep和mark-compact算法,正常情况下运行的是mark-sweep,在内存碎片达到一定程度的时候启动mark-compact。大对象直接存放到老年代。
参考: