源码分析:Java堆的创建

本文深入探讨了Java堆的创建过程,包括虚拟机如何根据配置选择堆的实现方式,如ParallelScavengeHeap、G1CollectedHeap和GenCollectedHeap。堆被分为新生代和老年代,采用分代收集策略以优化垃圾回收效率。文章详细介绍了不同垃圾回收策略如MarkSweepPolicy、ConcurrentMarkSweepPolicy及其在不同场景下的应用。此外,还阐述了64位JVM的指针压缩技术以及堆空间的分配与初始化细节。
摘要由CSDN通过智能技术生成

虚拟机在内存中申请一片区域,由虚拟机自动管理,用来满足应用程序对象分配的空间需求,即堆空间。

由于程序运行的局部特性,程序创建的大多数对象都具有非常短的生命周期,而程序也会创建一些生命周期特别长的对象。简单的复制收集器无论对象的生命周期是长是短,都会进行复制操作。而生命周期较长的对象在多次垃圾回收期间内并不会被回收,这就使得这些对象被来回复制而使得算法性能大大下降。

分代收集把堆分为多个子堆,分别用来存放不同寿命的对象。新生对象空间的将经历最频繁的垃圾回收,而对于经历了若干次垃圾收集后仍然存活的对象,将成长为成熟对象,并移动到成熟对象的子堆中,而对老生代子堆的垃圾回收就不会像新生对象子堆那么频繁。

HotSpot的堆空间分为新生代(YoungGen)和老年代(OldGen,此外还有位于非堆空间的永久代,但在Java8中将移除永久代),新生代又分为Eden区和2个Survivor区(From/To)用以进行复制收集垃圾对象。

对Java堆和对象的分析将从Java堆的创建开始,然后分析Java对象的分配与垃圾回收。

一、堆的实现方式
 
在虚拟机的创建初始化过程中,通过调用Universe的成员函数initialize_heap()将完成Java堆的初始化。在Universe模块下的初始化将根据虚拟机选项来选择堆的具体实现方式:

1.若虚拟机配置UseParallelGC,则Java堆的堆类型为ParallelScavengeHeap(并行收集堆)

//定义在/hotspot/src/share/vm/memory/universe.cpp中
if (UseParallelGC) {
   
#ifndef SERIALGC
    Universe::_collectedHeap = new ParallelScavengeHeap();
#else  // SERIALGC
    fatal("UseParallelGC not supported in java kernel vm.");
#endif // SERIALGC

  }

2.若虚拟机配置UseG1GC,那么将选择堆类型为G1CollectedHeap,垃圾收集策略将使用专用的G1CollectorPolicy(垃圾优先收集)策略

 else if (UseG1GC) {
#ifndef SERIALGC
    G1CollectorPolicy* g1p = new G1CollectorPolicy_BestRegionsFirst();
    G1CollectedHeap* g1h = new G1CollectedHeap(g1p);
    Universe::_collectedHeap = g1h;
#else  // SERIALGC
    fatal("UseG1GC not supported in java kernel vm.");
#endif // SERIALGC

  }

3.否则,虚拟机将使用GenCollectedHeap(分代收集堆)

Universe::_collectedHeap = new GenCollectedHeap(gc_policy);

各个堆实现类的类关系如下:
在这里插入图片描述

对于默认情况下的堆实现,还要根据配置选择垃圾回收策略gc_policy来构造一个GenCollectedHeap,这里根据虚拟机配置选择不同的GC策略:

(1).若虚拟机配置UseSerialGC,那么将使用MarkSweepPolicy(标记-清除)策略

GenCollectorPolicy *gc_policy;

    if (UseSerialGC) {
   
      gc_policy = new MarkSweepPolicy();
    }

(2).若虚拟机配置UseConcMarkSweepGC和UseAdaptiveSizePolicy,那么将ASConcurrentMarkSweepPolicy(自适应并发标记-清除)策略,若没有指定UseAdaptiveSizePolicy,虚拟机将默认使用ConcurrentMarkSweepPolicy(并发标记-清除)策略

else if (UseConcMarkSweepGC) {
   
#ifndef SERIALGC
      if (UseAdaptiveSizePolicy) {
   
        gc_policy = new ASConcurrentMarkSweepPolicy();
      } else {
   
        gc_policy = new ConcurrentMarkSweepPolicy();
      }

(3).若没有进行配置,虚拟机将默认使用MarkSweepPolicy策略

else {
    // default old generation
      gc_policy = new MarkSweepPolicy();
    }

如下表所示:

在这里插入图片描述

其中垃圾回收策略类的关系如下图:
在这里插入图片描述

4.接下来是相应实现的堆的初始化

jint status = Universe::heap()->initialize();
  if (status != JNI_OK) {
   
    return status;
  }

5.堆空间初始化完成后,是LP64平台上的指针压缩以及TLAB的相关内容 。

通常64位JVM消耗的内存会比32位的大1.5倍,这是因为在64位环境下,对象将使用64位指针,这就增加了一倍的指针占用内存开销。从JDK 1.6 update14开始,64 bit JVM正式支持了 -XX:+UseCompressedOops 选项来压缩指针,以节省内存空间。

指针压缩的地址计算如下:

addr = <narrow_oop_base> + <narrow_oop> << 3 + <field_offset>

若堆寻址空间小于4GB(2^32)时,直接使用32位的压缩对象指针< narrow_oop >就可以找到该对象
若堆寻址空间大于4GB(2^32)但小于32GB时,就必须借助偏移来获得真正的地址(对象是8字节对齐的)。
若堆寻址空间大于32GB时,就需要借助堆的基址来完成寻址了,< narrow_oop_base >为堆的基址,< field_offset >为一页的大小。

(1).若heap的地址空间的最大地址大于OopEncodingHeapMax(32GB),则设置基础地址为当前堆的起始地址-页大小,设置偏移为LogMinObjAlignmentInBytes(3),即使用普通的对象指针压缩技术

if ((uint64_t)Universe::heap()->reserved_region().end() > OopEncodingHeapMax) {
      // Can't reserve heap below 32Gb.
      Universe::set_narrow_oop_base(Universe::heap()->base() - os::vm_page_size());
      Universe::set_narrow_oop_shift(LogMinObjAlignmentInBytes);
}

(2).否则设置基础地址为0

else {
   
      Universe::set_narrow_oop_base(0);
      //...
      }

若heap的地址空间的最大地址大于NarrowOopHeapMax(4GB,小于32GB),则设置偏移为LogMinObjAlignmentInBytes(默认为3),即使用零基压缩技术,否则设置偏移为0,即直接使用压缩对象指针进行寻址

if((uint64_t)Universe::heap()->reserved_region(
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值