内存堆管理器GenCollectedHeap分配对象内存及Gc触发

     前面在介绍内存堆管理器的抽象层CollectedHeap时着重提到过, Java对象的内存分配主要涉及到其三个核心方法,这三个方法分别表示: 一.从线程的本地分配缓冲区中分配临时内存; 二.从内存堆中分配临时内存; 三.从内存堆中分配永久性内存. 对于基于内存分代的内存堆管理器GenCollectedHeap来说, 它对Java对象内存分配请求的处理是由核心组件: Gc策略CollectorPolicy来执行的.内存堆管理器GenCollectedHeap的默认Gc策略实现是MarkSweepPolicy, 所以本文将通过MarkSweepPolicy来介绍GenCollectedHeap是如何给Java对象分配所需内存的.

class CollectedHeap : public CHeapObj {
    ...

    virtual HeapWord* allocate_new_tlab(size_t size);

    virtual HeapWord* mem_allocate(size_t size,
                                 bool* gc_overhead_limit_was_exceeded) = 0;
    
    virtual HeapWord* permanent_mem_allocate(size_t size) = 0;

    ...
}

1.普通对象内存分配

    GenCollectedHeap给普通对象分配内存或者给某一线程分配一块本地分配缓冲区在本质上都是一样的,它都认为这块内存都具有临时性, 毕竟线程也是会把它的本地分配缓冲区中的内存分配给临时对象,而不会把它们分配给永久性对象.归根结底, 这些内存要么从年青代中分配,要么从旧生代中分配

/**
 * 为某一线程申请一块本地分配缓冲区
 */
HeapWord* GenCollectedHeap::allocate_new_tlab(size_t size) {
  bool gc_overhead_limit_was_exceeded;
  return collector_policy()->mem_allocate_work(size /* size */,
                                               true /* is_tlab */,
                                               &gc_overhead_limit_was_exceeded);
}

/**
 * (内存分配的核心方法)
 * 从内存堆中分配指定大小的内存块(不是为线程的本地分配缓冲区分配内存空间)
 */
HeapWord* GenCollectedHeap::mem_allocate(size_t size,
                                         bool* gc_overhead_limit_was_exceeded) {
  return collector_policy()->mem_allocate_work(size,
                                               false /* is_tlab */,
                                               gc_overhead_limit_was_exceeded);
}
这里捎带提一下gc_overhead_limit_was_exceeded参数的作用,它表示这次内存分配是否发生了Gc并且Gc的时间超过了设置的时间设置.这个设计主要是为了那些对延迟敏感的场景,也就是当gc_overhead_limit_was_exceeded为true时,给上层应用抛出OOM的异常以便其作出合适的处理.

MarkSweepPolicy对临时对象的分配策略是:

1.(无锁式)年青代快速分配
2.(加锁式)
  1).抢占内存堆全局锁
  2).如果请求的内存大小>年青代内存容量 || Gc被触发但无法被执行 || 增量式Gc会失败, 则依次尝试从年青代-老年代分配内存否则,只从年青代分配内存
  3).如果Gc被触发但目前还无法被执行:
     a).如果某一内存代还可扩展其内存容量,则依次从老年代-年青代尝试扩展内存分配
     b).释放内存堆全局锁,并等待Gc被执行完成
  4).释放内存堆全局锁,触发一次GC操作请求,并等待其被执行或放弃
  5).如果Gc被放弃或由于Gc锁被禁止执行,则回到1
  6).如果Gc超时,返回NULL,否则返回分配的内存块
具体代码如下:

/**
 * 分配指定大小的内存空间,基于内存分代的分配策略(轮寻式)
 *
 * @param size 申请的内存空间大小
 * @param is_tlab 	false: 从内存堆中分配内存空间
 * 					true: 从当前线程的本地分配缓冲区中分配内存空间
 * @gc_overhead_limit_was_exceeded Full Gc是否超时
 *
 */
HeapWord* GenCollectorPolicy::mem_allocate_work(size_t size, bool is_tlab,
		bool* gc_overhead_limit_was_exceeded) {

  GenCollectedHeap *gch = GenCollectedHeap::heap();

  debug_only(gch->check_for_valid_allocation_state());

  //确保当前JVM没有正在进行GC
  assert(gch->no_gc_in_progress(), "Allocation during gc not allowed");

  //先假设GC没有超时
  *gc_overhead_limit_was_exceeded = false;

  HeapWord* result = NULL;

  // Loop until the allocation is satisified,
  // or unsatisfied after GC.
  //通过重试的机制来确保内存分配成功
  for (int try_count = 1; /* return or throw */; try_count += 1) {
    HandleMark hm; // discard any handles allocated in each iteration

    /**
     * 1. 无锁式分配
     */

    //年青代必须支持无锁并发方式的内存分配
    Generation *gen0 = gch->get_gen(0);
    assert(gen0->supports_inline_contig_alloc(), "Otherwise, must do alloc within heap lock");

    //当前是否应该优先考虑从年青代分配内存
    if (gen0->should_allocate(size, is_tlab)) {
      //试图从年青代快速分配内存块
      result = gen0->par_allocate(size, is_tlab);
      if (result != NULL) {
        assert(gch->is_in_reserved(result), "result not in heap");
        return result;
      }
    }

    /**
     * 2.有锁式分配
     */

    unsigned int gc_count_before;  //记录当前发生的Gc次数
    {
      MutexLocker ml(Heap_lock);	//上内存堆的全局锁

      if (PrintGC && Verbose) {
        gclog_or_tty->print_cr("TwoGenerationCollectorPolicy::mem_allocate_work:"
                      " attempting locked slow path allocation");
      }

      // Note that only large objects get a shot at being
      // allocated in later generations.
      //当前是否应该只在年青代分配内存
      bool first_only = ! should_try_older_generation_allocation(size);

      //依次尝试从内存堆的各内存代中分配内存空间
      result = gch->attempt_allocation(size, is_tlab, first_only);
      if (result != NULL) {
        assert(gch->is_in_reserved(result), "result not in heap");
        return result;
      }

      if (GC_locker::is_active_and_needs_gc()) {	//当前其它线程已经触发了Gc
        if (is_tlab) {
          //当前线程是为本地分配缓冲区申请内存(进而再从本地分配缓冲区为对象分配内存),则返回NULL,
          //以让其直接从内存代中为对象申请内存
          return NULL;
        }

        if (!gch->is_maximal_no_gc()) {	//内存堆中的某一个内存代允许扩展其大小
          //在允许扩展内存代大小的情况下尝试从内存堆的各内存代中分配内存空间
          result = expand_heap_and_allocate(size, is_tlab);
          // result could be null if we are out of space
          if (result != NULL) {
            return result;
          }
        }

        // If this thread is not in a jni critical section, we stall
        // the requestor until the critical section has cleared and
        // GC allowed. When the critical section clears, a GC is
        // initiated by the last thread exiting the critical section; so
        // we retry the allocation sequence from the beginning of the loop,
        // rather than causing more, now probably unnecessary, GC attempts.
        JavaThread* jthr = JavaThread::current();
        if (!jthr->in_critical()) {
          MutexUnlocker mul(Heap_lock);
          //等待所有的本地线程退出并执行完Gc操作
          GC_locker::stall_until_clear();
          continue;
        } else {	//JNI的代码块
          if (CheckJNICalls) {
            fatal("Possible deadlock due to allocating while in jni critical section");
          }
          return NULL;
        }
      }

      //分配失败,决定触发一次GC操作
      gc_count_before = Universe::heap()->total_collections();
    }

    //触发一次Gc操作,将GC型JVM操作加入VMThread的操作队列中
    //Gc的真正执行是由VMThread或特型GC线程来完成的
    VM_GenCollectForAllocation op(size, is_tlab, gc_count_before);
    VMThread::execute(&op);

    if (op.prologue_succeeded()) {	//一次Gc操作已完成
      result = op.result();
      if (op.gc_locked()) {	//当前线程没有成功触发GC(可能刚被其它线程触发了),则继续重试分配
         assert(result == NULL, "must be NULL if gc_locked() is true");
         continue;  // retry and/or stall as necessary
      }

      // Allocation has failed and a collection
      // has been done.  If the gc time limit was exceeded the
      // this time, return NULL so that an out-of-memory
      // will be thrown.  Clear gc_overhead_limit_exceeded
      // so that the overhead exceeded does not persist.

      //本次Gc耗时是否超过了设置的GC时间上限
      const bool limit_exceeded = size_policy()->gc_overhead_limit_exceeded();
      const bool softrefs_clear = all_soft_refs_clear();
      //本次GC超时一定是进行了清除软引用的操作
      assert(!limit_exceeded || softrefs_clear, "Should have been cleared");

      //Gc超时
      if (limit_exceeded && softrefs_clear) {
        *gc_overhead_limit_was_exceeded = true;
        size_policy()->set_gc_overhead_limit_exceeded(false);
        if (op.result() != NULL) {
          CollectedHeap::fill_with_object(op.result(), size);
        }
        //Gc超时,给上层调用返回NULL,让其抛出内存溢出错误
        return NULL;
      }

      //分配成功则确保该内存块一定在内存堆中
      assert(result == NULL || gch->is_in_reserved(result), "result not in heap");
      return result;
    }

    //打印警告信息,应为当前分配内存一直失败(GC过于频繁)
    if ((QueuedAllocationWarningCount > 0) && (try_count % QueuedAllocationWarningCount == 0)) {
          warning("TwoGenerationCollectorPolicy::mem_allocate_work retries %d times \n\t size=%d %s", try_count, size, is_tlab ? "(TLAB)" : "");
    }

  }// for

}
从上面的代码可以看出,Java线程向内存堆申请内存空间但由于当前内存不足而触发Gc操作时,该线程只是创建一个VM_GenCollectForAllocation的JVM操作指令而自己并不去执行Gc操作.当然, 这个Gc操作指令最终会由一个名为" VM Thread"的JVM级线程调度执行.下面是上面源代码中设计到的一些细节.
     (1).当前可以从旧生代分配内存的判断条件:

/**
 * 当前是否需要尝试在旧生代分配内存
 * 		1).待分配的内存大小 > 年青代容量
 * 		2).当前内存堆需要一次GC
 * 		3).内存堆的增量式GC刚刚失败
 */
bool GenCollectorPolicy::should_try_older_generation_allocation(
        size_t word_size) const {
  GenCollectedHeap* gch = GenCollectedHeap::heap();
  size_t gen0_capacity = gch->get_gen(0)->capacity_before_gc();
  return  (word_size > heap_word_size(gen0_capacity))
         || GC_locker::is_active_and_needs_gc() || gch->incremental_collection_failed();
}
     (2).试图从内存堆的各内存代中依次尝试分配指定大小的内存块:

/**
 * 试图从内存堆的各内存代中依次尝试分配指定大小的内存块
 * @param first_only 	true: 只在年青代尝试分配
 * 						false: 先在年青代尝试分配,分配失败则再在旧生代中尝试分配
 */
HeapWord* GenCollectedHeap::attempt_allocation(size_t size,
                                               bool is_tlab,
                                               bool first_only) {
  HeapWord* res;
  for (int i = 0; i < _n_gens; i++) {
    if (_gens[i]->should_allocate(size, is_tlab)) {
      res = _gens[i]->allocate(size, is_tlab);
      if (res != NULL) return res;
      else if (first_only) break;
    }
  }

  // Otherwise...
  return NULL;
}
     (3).通过扩展内存堆中的某内存代容量的方式来尝试分配申请的内存块

/**
 * 通过扩展内存堆中的某内存代容量的方式来尝试分配申请的内存块
 * 优先扩展旧生代的内存容量
 */
HeapWord* GenCollectorPolicy::expand_heap_and_allocate(size_t size,
                                                       bool   is_tlab) {
  GenCollectedHeap *gch = GenCollectedHeap::heap();
  HeapWord* result = NULL;
  for (int i = number_of_generations() - 1; i >= 0 && result == NULL; i--) {
    Generation *gen = gch->get_gen(i);
    if (gen->should_allocate(size, is_tlab)) {
      result = gen->expand_and_allocate(size, is_tlab);
    }
  }

  assert(result == NULL || gch->is_in_reserved(result), "result not in heap");
  return result;
}

2.永久对象内存分配

    内存堆管理器GenCollectedHeap对永久性对象的内存分配请求处理并没有像分配普通对象那样依赖于MarkSweepPolicy, 它主要是将其转交给永久代内存管理器PermGen处理:

class SharedHeap : public CollectedHeap {
  
  ...

  PermGen* _perm_gen;	//永久代内存管理器

  ...

  HeapWord* permanent_mem_allocate(size_t size) {
    assert(perm_gen(), "NULL perm gen");
    return _perm_gen->mem_allocate(size);
  }

  ...

}

    本文主要介绍了GenCollectedHeap分配对象内存的一些策略,并没有介绍其内存各个内存代管理器是如何分配指定大小内存块的细节,而这些内容将会在介绍各种内存代管理器实现的时候详细谈到.


   

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值