Serial Collector

멀티 프로세서에서는 비효율적인 GC이지만, 100MB 정도로 작은 규모의 데이터셋을 사용하는 애플리케이션이라면 멀티 프로세서에서도 쓸만하다. Serial Collector는 VM이 운영체제와 하드웨어 환경에 따라 자동으로 선택하거나, -XX:+UseSerialGC 옵션으로 활성화할 수 있다.

Native 코드를 읽어보자

  • OpenJDK의 JVM C++ 코드를 읽으면서 내가 알고 있는 사실들을 확인해 보자.
  • 코드 출처는 @ 55739:7e2238451585 jdk-13+24
  • 읽다보면 의외의 깨달음을 얻게 될지도 모른다.

eden과 survivor의 max size 계산

_eden_space = new ContiguousSpace();
_from_space = new ContiguousSpace(); /* survivor (from 역할) */
_to_space   = new ContiguousSpace(); /* survivor (to 역할) */

if (_eden_space == NULL || _from_space == NULL || _to_space == NULL) {
  vm_exit_during_initialization("Could not allocate a new gen space");
}

// Compute the maximum eden and survivor space sizes. These sizes
// are computed assuming the entire reserved space is committed.
// These values are exported as performance counters.
uintx size = _virtual_space.reserved_size();
_max_survivor_size = compute_survivor_size(size, SpaceAlignment);
_max_eden_size = size - (2*_max_survivor_size); /* eden max 사이즈 계산 */

// allocate the performance counters

// Generation counters -- generation 0, 3 subspaces
_gen_counters = new GenerationCounters("new", 0, 3,
  min_size, max_size, &_virtual_space);
_gc_counters = new CollectorCounters(policy, 0);
_eden_counters = new CSpaceCounters(
    "eden", 0, _max_eden_size, _eden_space,
                                    _gen_counters);
_from_counters = new CSpaceCounters("s0", 1, _max_survivor_size, _from_space,
                                    _gen_counters);
_to_counters = new CSpaceCounters("s1", 2, _max_survivor_size, _to_space,
                                  _gen_counters);
  • survivor 두 개(s0, s1)는 같은 max 크기를 가진다.
  • eden 사이즈는 할당된 young 영역의 공간에서 두 개의 survivor size를 뺀 값으로 계산된다.

두 Survivor의 스왑

void DefNewGeneration::swap_spaces

void DefNewGeneration::swap_spaces() {
  /* from 과 to 를 swap 한다. */
  ContiguousSpace* s = from();
  _from_space        = to();
  _to_space          = s;
  eden()->set_next_compaction_space(from());
  // The to-space is normally empty before a compaction so need
  // not be considered.  The exception is during promotion
  // failure handling when to-space can contain live objects.
  from()->set_next_compaction_space(NULL);

  if (UsePerfData) {
    CSpaceCounters* c = _from_counters;
    _from_counters = _to_counters;
    _to_counters = c;
  }
}

Aging

oop DefNewGeneration::copy_to_survivor_space

oop DefNewGeneration::copy_to_survivor_space(oop old) {
  assert(is_in_reserved(old) && !old->is_forwarded(),
         "shouldn't be scavenging this oop");
  size_t s = old->size();   /* old 객체의 사이즈 */
  oop obj = NULL;

  // Try allocating obj in to-space (unless too old)
  if (old->age() < tenuring_threshold()) {
    /* old 객체의 age가 임계값 미만이면 survivor(to)영역에 객체의 크기만한 공간을 마련한다 */
    obj = (oop) to()->allocate_aligned(s);
  }

  // Otherwise try allocating obj tenured
  if (obj == NULL) {
    /* 공간이 마련되지 않았다면 old 영역으로 보내버린다(promotion) */
    obj = _old_gen->promote(old, s);
    if (obj == NULL) {
      handle_promotion_failure(old);
      return old;
    }
  } else {
    /* 공간이 마련됐다면 survivor(to) 영역으로 보낸다 */
    // Prefetch beyond obj
    const intx interval = PrefetchCopyIntervalInBytes;
    Prefetch::write(obj, interval);

    // Copy obj
    Copy::aligned_disjoint_words((HeapWord*)old, (HeapWord*)obj, s);

    // Increment age if obj still in new generation
    /* 아직 new gen에 있으므로 객체는 나이를 먹는다 */
    obj->incr_age();
    age_table()->add(obj, s);
  }

  // Done, insert forward pointer to obj in this header
  old->forward_to(obj);

  return obj;
}

License

  • 이 글에 포함된 OpenJDK 코드는 "The GNU General Public License (GPL)" 라이선스를 따릅니다.
  • 이 글에 포함된 OpenJDK 코드에 대해 들여쓰기, 줄바꿈 등을 수정하였으며 한국어가 포함된 주석은 제가 작성한 것입니다.