ParallelGC的内存分配与回收策略

前言

绝大多数情况下,对象在优先Eden中分配,当eden没有足够空间进行分配时,虚拟机将发起一次Minor GC;但是存在一个例外情况,eden内存不足,且待分配的对象占用内存不小于eden空间的一半,将直接分配到老年代,不会触发Minor GC

本文讲解基于JDK 8默认的垃圾收集器(Parallel Scavenge+Parallel Old)。

1. eden内存不足,待分配对象内存不小于eden空间的一半

设定JVM参数信息:-Xms200M(JVM初始分配的堆内存200MB)、-Xmx200M(JVM初始分配的堆内存最大200MB)、-Xmn100M(设置年轻代为100M),老年代100M。

/**
 * -XX:+PrintGCDetails -XX:SurvivorRatio=8 -Xms200M -Xmx200M -Xmn100M
 */
public class GCLog {
    private static int _10MB = 10 * 1024 * 1024;
    public static void main(String[] args) {
        byte[] memory1, memory2, memory3, memory4;
        memory1 = new byte[_10MB * 2];
        memory2 = new byte[_10MB * 2];
        memory3 = new byte[_10MB * 2];
        memory4 = new byte[_10MB * 4];
    }
}
Heap
 PSYoungGen      total 92160K, used 67998K [0x00000000f9c00000, 0x0000000100000000, 0x0000000100000000)
  eden space 81920K, 83% used [0x00000000f9c00000,0x00000000fde679e0,0x00000000fec00000)
  from space 10240K, 0% used [0x00000000ff600000,0x00000000ff600000,0x0000000100000000)
  to   space 10240K, 0% used [0x00000000fec00000,0x00000000fec00000,0x00000000ff600000)
 ParOldGen       total 102400K, used 40960K [0x00000000f3800000, 0x00000000f9c00000, 0x00000000f9c00000)
  object space 102400K, 40% used [0x00000000f3800000,0x00000000f6000010,0x00000000f9c00000)
 Metaspace       used 3440K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 374K, capacity 388K, committed 512K, reserved 1048576K

1.1 日志分析

我们指定了Java堆为200M,年轻代为100M,老年代100M,-XX:SurvivorRatio=8(eden:from :To = 8:1:1),即eden 81920K(80*1024K)、from 10240K、to 10240K,年轻代可用空间90M(Eden区+一个Survivor区);

  1. memory1对象、memory2对象 、memory3对象直接分配到eden区,各分配20M内存,年轻代剩余可用内存30M或更少(JVM运行时会占用堆空间)。
  2. 给memory4分配内存时,eden不足以为memory4分配需要的40M内存,于是把memory4对象通过老年代空间分配担保机制(年轻代没有足够内存为待分配对象分配内存,且待分配对象内存占用不小于eden内存的一半)分配到老年代。

HotSpot代码如下(部分省略):

HeapWord* ParallelScavengeHeap::mem_allocate( size_t size, bool* gc_overhead_limit_was_exceeded) {
  HeapWord* result = young_gen()->allocate(size);
  while (result == NULL) {
    {
      result = young_gen()->allocate(size);
      if (result != NULL) {
        return result;
      }

      result = mem_allocate_old_gen(size);
      if (result != NULL) {
        return result;
      }
  return result;
}

HeapWord* ParallelScavengeHeap::mem_allocate_old_gen(size_t size) {
  if (!should_alloc_in_eden(size) || GC_locker::is_active_and_needs_gc()) {
    // Size is too big for eden, or gc is locked out.
    return old_gen()->allocate(size);
  }
  return NULL;
}

inline bool ParallelScavengeHeap::should_alloc_in_eden(const size_t size) const
{
  const size_t eden_size = young_gen()->eden_space()->capacity_in_words();`
  return size < eden_size / 2;
}

2. 内存不足触发GC

修改memory4的内存占用为30M。

/**
 * -XX:+PrintGCDetails -XX:SurvivorRatio=8 -Xms200M -Xmx200M -Xmn100M
 */
public class GCLog {
    private static int _10MB = 10 * 1024 * 1024;
    public static void main(String[] args) {
        byte[] memory1, memory2, memory3, memory4;
        memory1 = new byte[_10MB * 2];
        memory2 = new byte[_10MB * 2];
        memory3 = new byte[_10MB * 2];
        memory4 = new byte[_10MB * 3];
    }
}
[GC (Allocation Failure) [PSYoungGen: 66359K->808K(92160K)] 66359K->62248K(194560K), 0.0239164 secs] [Times: user=0.08 sys=0.00, real=0.02 secs] 
[Full GC (Ergonomics) [PSYoungGen: 808K->0K(92160K)] [ParOldGen: 61440K->62173K(102400K)] 62248K->62173K(194560K), [Metaspace: 3431K->3431K(1056768K)], 0.0054227 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
Heap
 PSYoungGen      total 92160K, used 31539K [0x00000000f9c00000, 0x0000000100000000, 0x0000000100000000)
  eden space 81920K, 38% used [0x00000000f9c00000,0x00000000fbacce50,0x00000000fec00000)
  from space 10240K, 0% used [0x00000000fec00000,0x00000000fec00000,0x00000000ff600000)
  to   space 10240K, 0% used [0x00000000ff600000,0x00000000ff600000,0x0000000100000000)
 ParOldGen       total 102400K, used 62173K [0x00000000f3800000, 0x00000000f9c00000, 0x00000000f9c00000)
  object space 102400K, 60% used [0x00000000f3800000,0x00000000f74b7718,0x00000000f9c00000)
 Metaspace       used 3438K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 374K, capacity 388K, committed 512K, reserved 1048576K

2.2 日志分析

GC日志头部信息分解

结合上面GC日志的第一行信息的解析信息如下图所示。
MINOR GC
FULL GC

  1. 年轻代可用内存90M,memory1对象、memory2对象 、memory3对象直接分配到eden区,各分配20M内存,年轻代剩余可用内存30M或更少。
  2. 当在eden上分配内存失败并且待分配对象的大小并不符合在老年代分配的前提下,触发Minor GC,因此eden给memory4分配3M内存失败时触发了一次Minor GC。
  3. Minor GC后memory1、memory2、memory3均存活无法回收,同时由于from只有10M,不足以放下任何一个对象,所以这三个对象直接晋升到老年代。
  4. 然后PS GC检查老年代剩余容量是否满足历代晋升所需内存,不满足则进行Full GC(即平均晋升到老年代的大小>老年代剩余内存则触发Full GC),这也正是ParallelGC的一种悲观策略,所以触发了一次Full GC。

HotSpot部分相关代码如下:

// This method contains all heap specific policy for invoking scavenge.
// PSScavenge::invoke_no_policy() will do nothing but attempt to
// scavenge. It will not clean up after failed promotions, bail out if
// we've exceeded policy time limits, or any other special behavior.
// All such policy should be placed here.
//
// Note that this method should only be called from the vm_thread while
// at a safepoint!
bool PSScavenge::invoke() {
  assert(SafepointSynchronize::is_at_safepoint(), "should be at safepoint");
  assert(Thread::current() == (Thread*)VMThread::vm_thread(), "should be in vm thread");
  assert(!Universe::heap()->is_gc_active(), "not reentrant");

  ParallelScavengeHeap* const heap = (ParallelScavengeHeap*)Universe::heap();
  assert(heap->kind() == CollectedHeap::ParallelScavengeHeap, "Sanity");

  PSAdaptiveSizePolicy* policy = heap->size_policy();
  IsGCActiveMark mark;

  const bool scavenge_done = PSScavenge::invoke_no_policy();
  const bool need_full_gc = !scavenge_done ||
    policy->should_full_GC(heap->old_gen()->free_in_bytes());
  bool full_gc_done = false;

  if (UsePerfData) {
    PSGCAdaptivePolicyCounters* const counters = heap->gc_policy_counters();
    const int ffs_val = need_full_gc ? full_follows_scavenge : not_skipped;
    counters->update_full_follows_scavenge(ffs_val);
  }

  if (need_full_gc) {
    GCCauseSetter gccs(heap, GCCause::_adaptive_size_policy);
    CollectorPolicy* cp = heap->collector_policy();
    const bool clear_all_softrefs = cp->should_clear_all_soft_refs();

    if (UseParallelOldGC) {
      full_gc_done = PSParallelCompact::invoke_no_policy(clear_all_softrefs);
    } else {
      full_gc_done = PSMarkSweep::invoke_no_policy(clear_all_softrefs);
    }
  }

  return full_gc_done;
}

// If the remaining free space in the old generation is less that
// that expected to be needed by the next collection, do a full
// collection now.
bool PSAdaptiveSizePolicy::should_full_GC(size_t old_free_in_bytes) {

  // A similar test is done in the scavenge's should_attempt_scavenge().  If
  // this is changed, decide if that test should also be changed.
  bool result = padded_average_promoted_in_bytes() > (float) old_free_in_bytes;
  if (PrintGCDetails && Verbose) {
    if (result) {
      gclog_or_tty->print("  full after scavenge: ");
    } else {
      gclog_or_tty->print("  no full after scavenge: ");
    }
    gclog_or_tty->print_cr(" average_promoted " SIZE_FORMAT
      " padded_average_promoted " SIZE_FORMAT
      " free in old gen " SIZE_FORMAT,
      (size_t) average_promoted_in_bytes(),
      (size_t) padded_average_promoted_in_bytes(),
      old_free_in_bytes);
  }
  return result;
}

3. 长期存活的对象将进入老年代

-XX:MaxTenuringThreshold参数用于设置年轻代存活对象晋升老年代的年龄阈值,对象头中用4(bit)存储age,因此最大值是15,年龄的增加随着经历的GC次数而累加。

/**
 * -XX:+PrintGCDetails -XX:SurvivorRatio=8 -Xms200M -Xmx200M -Xmn100M -XX:MaxTenuringThreshold=1
 */
public class GCLog {
    private static int _1MB = 1024 * 1024;

    public static void main(String[] args) {
        byte[] memory1, memory2, memory3;
        memory1 = new byte[_1MB];
        memory2 = new byte[_1MB];
        for (int i = 0; i < 80; i++) {
            byte[] localBytes = new byte[_1MB];
        }
        memory3 = new byte[_1MB];
        for (int i = 0; i < 80; i++) {
            byte[] localBytes = new byte[_1MB];
        }
    }
}
[GC (Allocation Failure) [PSYoungGen: 81720K->2856K(92160K)] 81720K->2856K(194560K), 0.0045687 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [PSYoungGen: 84334K->1024K(92160K)] 84334K->3820K(194560K), 0.0309931 secs] [Times: user=0.03 sys=0.00, real=0.03 secs] 
Heap
 PSYoungGen      total 92160K, used 11437K [0x00000000f9c00000, 0x0000000100000000, 0x0000000100000000)
  eden space 81920K, 12% used [0x00000000f9c00000,0x00000000fa62b508,0x00000000fec00000)
  from space 10240K, 10% used [0x00000000ff600000,0x00000000ff700010,0x0000000100000000)
  to   space 10240K, 0% used [0x00000000fec00000,0x00000000fec00000,0x00000000ff600000)
 ParOldGen       total 102400K, used 2796K [0x00000000f3800000, 0x00000000f9c00000, 0x00000000f9c00000)
  object space 102400K, 2% used [0x00000000f3800000,0x00000000f3abb0b0,0x00000000f9c00000)
 Metaspace       used 3439K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 374K, capacity 388K, committed 512K, reserved 1048576K

3.1 日志分析

上面我们通过-XX:MaxTenuringThreshold=1设置年轻代存活对象晋升到老年代的年龄阈值为1;

  1. memory1、memory2各分配1M。
  2. 第一个for循环时,触发第一次Young GC,memory1、memory2此时的age+1。
  3. memory3分配1M内存。
  4. 第二个for循环时,触发第二次Young GC,此时memory1、memory2的age已经为1,直接晋升到老年代,memory3的age+1。

所以我们在日志中可以看到object space 102400K, 2% used


4. 动态对象年龄判定

-XX:TargetSurvivorRatio,用于设定survivor区的目标使用率,默认50。
-XX:+PrintTenuringDistribution,在每次Young GC时打印survivor区中对象的年龄分布。

/**
 * --XX:+PrintGCDetails -XX:SurvivorRatio=8 -Xms200M -Xmx200M -Xmn100M -XX:MaxTenuringThreshold=15 -XX:+PrintTenuringDistribution -XX:TargetSurvivorRatio=50
 */
public class GCLog {
    private static int _1MB = 1024 * 1024;

    public static void main(String[] args) {
        byte[] memory1, memory2, memory3, memory4, memory5;
        memory1 = new byte[_1MB];
        memory2 = new byte[_1MB];
        for (int i = 0; i < 400; i++) {
            byte[] localBytes = new byte[_1MB];
        }
        memory3 = new byte[_1MB];
        memory4 = new byte[_1MB];
        for (int i = 0; i < 170; i++) {
            byte[] localBytes = new byte[_1MB];
        }
        memory5 = new byte[_1MB];
    }
}
[GC (Allocation Failure) 
Desired survivor size 10485760 bytes, new threshold 7 (max 15)
[PSYoungGen: 81720K->2904K(92160K)] 81720K->2912K(194560K), 0.0021259 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) 
Desired survivor size 10485760 bytes, new threshold 7 (max 15)
[PSYoungGen: 84382K->2840K(92160K)] 84390K->2848K(194560K), 0.0041180 secs] [Times: user=0.03 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) 
Desired survivor size 10485760 bytes, new threshold 7 (max 15)
[PSYoungGen: 83910K->2840K(92160K)] 83918K->2848K(194560K), 0.0008004 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) 
Desired survivor size 10485760 bytes, new threshold 7 (max 15)
[PSYoungGen: 84315K->2808K(92160K)] 84323K->2816K(194560K), 0.0007789 secs] [Times: user=0.06 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) 
Desired survivor size 3145728 bytes, new threshold 6 (max 15)
[PSYoungGen: 84291K->2824K(92160K)] 84299K->2832K(194560K), 0.0035467 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) 
Desired survivor size 3145728 bytes, new threshold 5 (max 15)
[PSYoungGen: 84313K->2872K(99328K)] 84321K->4928K(201728K), 0.0036147 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) 
Desired survivor size 3670016 bytes, new threshold 4 (max 15)
[PSYoungGen: 98984K->0K(96768K)] 101040K->4852K(199168K), 0.0021621 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
Heap
 PSYoungGen      total 96768K, used 16118K [0x00000000f9c00000, 0x0000000100000000, 0x0000000100000000)
  eden space 96256K, 16% used [0x00000000f9c00000,0x00000000fabbd9b0,0x00000000ffa00000)
  from space 512K, 0% used [0x00000000ffa00000,0x00000000ffa00000,0x00000000ffa80000)
  to   space 3584K, 0% used [0x00000000ffc80000,0x00000000ffc80000,0x0000000100000000)
 ParOldGen       total 102400K, used 4852K [0x00000000f3800000, 0x00000000f9c00000, 0x00000000f9c00000)
  object space 102400K, 4% used [0x00000000f3800000,0x00000000f3cbd0d0,0x00000000f9c00000)
 Metaspace       used 3440K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 374K, capacity 388K, committed 512K, reserved 1048576K

4.1 日志分析

HotSpot部分源码如下:

uint ageTable::compute_tenuring_threshold(size_t survivor_capacity) {
  // 通过TargetSurvivorRatio计算期望值
  size_t desired_survivor_size = (size_t)((((double) survivor_capacity)*TargetSurvivorRatio)/100);
  // 用于累加各年龄段对象大小
  size_t total = 0;
  uint age = 1;
  assert(sizes[0] == 0, "no objects with age zero should be recorded");
  while (age < table_size) {
    total += sizes[age];
    // check if including objects of age 'age' made us pass the desired
    // size, if so 'age' is the new threshold
    if (total > desired_survivor_size) break;
    age++;
  }
  // age与MaxTenuringThreshold 取最小值
  uint result = age < MaxTenuringThreshold ? age : MaxTenuringThreshold;
  // 省略
  return result;
}
  • 5
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

人生逆旅我亦行人

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值