Skip to main content

Garbage-First Garbage Collection 简单概况

· 8 min read

Garbage-First Garbage Collection

Garbage-First简称g1算法,是java目前可选的一个gc算法。

目标: 在一定的软实时性条件下,保证整体的吞吐

算法构成:

  • 堆等大小: 整个内存堆被划分为相同大小的块。

The Garbage-First collector achieves these goals via sev- eral techniques. The heap is partitioned into a set of equal- sized heap regions, much like the train cars of the Mature- Object Space collector of Hudson and Moss [22]. However, whereas the remembered sets of the Mature-Object Space collector are unidirectional, recording pointers from older regions to younger but not vice versa, Garbage-First remem- bered sets record pointers from all regions (with some excep- tions, described in sections 2.4 and 4.6). Recording all ref- erences allows an arbitrary set of heap regions to be chosen for collection. A concurrent thread processes log records cre- ated by special mutator write barriers to keep remembered sets up-to-date, allowing shorter collections.

源码分析

  • Young gc
void G1YoungCollector::collect() {
// Do timing/tracing/statistics/pre- and post-logging/verification work not
// directly related to the collection. They should not be accounted for in
// collection work timing.

// The G1YoungGCTraceTime message depends on collector state, so must come after
// determining collector state.
G1YoungGCTraceTime tm(this, _gc_cause);

// JFR
G1YoungGCJFRTracerMark jtm(gc_timer_stw(), gc_tracer_stw(), _gc_cause);
// JStat/MXBeans
G1MonitoringScope ms(monitoring_support(),
false /* full_gc */,
collector_state()->in_mixed_phase() /* all_memory_pools_affected */);
// Create the heap printer before internal pause timing to have
// heap information printed as last part of detailed GC log.
G1HeapPrinterMark hpm(_g1h);
// Young GC internal pause timing
G1YoungGCNotifyPauseMark npm(this);

// Verification may use the workers, so they must be set up before.
// Individual parallel phases may override this.
set_young_collection_default_active_worker_threads();

// Wait for root region scan here to make sure that it is done before any
// use of the STW workers to maximize cpu use (i.e. all cores are available
// just to do that).
wait_for_root_region_scanning();

G1YoungGCVerifierMark vm(this);
{
// Actual collection work starts and is executed (only) in this scope.

// Young GC internal collection timing. The elapsed time recorded in the
// policy for the collection deliberately elides verification (and some
// other trivial setup above).
policy()->record_young_collection_start();

calculate_collection_set(jtm.evacuation_info(), _target_pause_time_ms);

G1RedirtyCardsQueueSet rdcqs(G1BarrierSet::dirty_card_queue_set().allocator());
G1PreservedMarksSet preserved_marks_set(workers()->active_workers());
G1ParScanThreadStateSet per_thread_states(_g1h,
&rdcqs,
&preserved_marks_set,
workers()->active_workers(),
collection_set()->young_region_length(),
collection_set()->optional_region_length(),
&_evac_failure_regions);

pre_evacuate_collection_set(jtm.evacuation_info(), &per_thread_states);

bool may_do_optional_evacuation = collection_set()->optional_region_length() != 0;
// Actually do the work...
evacuate_initial_collection_set(&per_thread_states, may_do_optional_evacuation);

if (may_do_optional_evacuation) {
evacuate_optional_collection_set(&per_thread_states);
}
post_evacuate_collection_set(jtm.evacuation_info(), &per_thread_states);

// Refine the type of a concurrent mark operation now that we did the
// evacuation, eventually aborting it.
_concurrent_operation_is_full_mark = policy()->concurrent_operation_is_full_mark("Revise IHOP");

// Need to report the collection pause now since record_collection_pause_end()
// modifies it to the next state.
jtm.report_pause_type(collector_state()->young_gc_pause_type(_concurrent_operation_is_full_mark));

policy()->record_young_collection_end(_concurrent_operation_is_full_mark, evacuation_failed());
}
TASKQUEUE_STATS_ONLY(print_taskqueue_stats());
TASKQUEUE_STATS_ONLY(reset_taskqueue_stats());
}

真正复制的代码

evacuate_live_objects(


MAYBE_INLINE_EVACUATION
void G1ParScanThreadState::dispatch_task(ScannerTask task) {
verify_task(task);
if (task.is_narrow_oop_ptr()) {
do_oop_evac(task.to_narrow_oop_ptr());
} else if (task.is_oop_ptr()) { //oop 复制
do_oop_evac(task.to_oop_ptr());
} else {
do_partial_array(task.to_partial_array_task());
}
}

堆栈:

Thread 23 "GC Thread#4" hit Breakpoint 1, G1ParScanThreadState::do_copy_to_survivor_space (this=0x7fff7c000d90, region_attr=..., old=0x716809d28, old_mark=...) at /home/dai/jdk/src/hotspot/share/gc/g1/g1ParScanThreadState.cpp:443
443 assert(region_attr.is_in_cset(),
(gdb) bt
#0 G1ParScanThreadState::do_copy_to_survivor_space (this=0x7fff7c000d90, region_attr=..., old=0x716809d28, old_mark=...) at /home/dai/jdk/src/hotspot/share/gc/g1/g1ParScanThreadState.cpp:443
#1 0x00007ffff64ab3f6 in G1ParScanThreadState::copy_to_survivor_space (this=0x7fff7c000d90, region_attr=..., old=0x716809d28, old_mark=...)
at /home/dai/jdk/src/hotspot/share/gc/g1/g1ParScanThreadState.cpp:555
#2 0x00007ffff64de15e in G1ParCopyClosure<(G1Barrier)0, false>::do_oop_work<oopDesc*> (this=0x7fff7c001478, p=0x7ffff02e1cc8) at /home/dai/jdk/src/hotspot/share/gc/g1/g1OopClosures.inline.hpp:240
#3 0x00007ffff64dcbc3 in G1ParCopyClosure<(G1Barrier)0, false>::do_oop (this=0x7fff7c001478, p=0x7ffff02e1cc8) at /home/dai/jdk/src/hotspot/share/gc/g1/g1OopClosures.hpp:167
#4 0x00007ffff6546dd8 in chunk_oops_do (f=0x7fff7c001478, chunk=0x7ffff02e1cb0, chunk_top=0x7ffff02e1cd0 "\350\034\200\026\a") at /home/dai/jdk/src/hotspot/share/runtime/handles.cpp:100
#5 0x00007ffff6546e23 in HandleArea::oops_do (this=0x7ffff02e1c30, f=0x7fff7c001478) at /home/dai/jdk/src/hotspot/share/runtime/handles.cpp:108
#6 0x00007ffff6d85dd4 in Thread::oops_do_no_frames (this=0x7ffff02e1160, f=0x7fff7c001478, cf=0x7fff7c001520) at /home/dai/jdk/src/hotspot/share/runtime/thread.cpp:550
#7 0x00007ffff6d8a513 in JavaThread::oops_do_no_frames (this=0x7ffff02e1160, f=0x7fff7c001478, cf=0x7fff7c001520) at /home/dai/jdk/src/hotspot/share/runtime/thread.cpp:1968
#8 0x00007ffff6d85e28 in Thread::oops_do (this=0x7ffff02e1160, f=0x7fff7c001478, cf=0x7fff7c001520) at /home/dai/jdk/src/hotspot/share/runtime/thread.cpp:580
#9 0x00007ffff6d91359 in ParallelOopsDoThreadClosure::do_thread (this=0x7fff87dfaa00, t=0x7ffff02e1160) at /home/dai/jdk/src/hotspot/share/runtime/thread.cpp:3620
#10 0x00007ffff6d8c40b in Threads::possibly_parallel_threads_do (is_par=true, tc=0x7fff87dfaa00) at /home/dai/jdk/src/hotspot/share/runtime/thread.cpp:2545
#11 0x00007ffff6d8eac8 in Threads::possibly_parallel_oops_do (is_par=true, f=0x7fff7c001478, cf=0x7fff7c001520) at /home/dai/jdk/src/hotspot/share/runtime/thread.cpp:3626
#12 0x00007ffff64dea53 in G1RootProcessor::process_java_roots (this=0x7fffc9723df0, closures=0x7fff7c001470, phase_times=0x7fffb8001380, worker_id=1)
at /home/dai/jdk/src/hotspot/share/gc/g1/g1RootProcessor.cpp:183
#13 0x00007ffff64de78e in G1RootProcessor::evacuate_roots (this=0x7fffc9723df0, pss=0x7fff7c000d90, worker_id=1) at /home/dai/jdk/src/hotspot/share/gc/g1/g1RootProcessor.cpp:60
#14 0x00007ffff64f06b8 in G1EvacuateRegionsTask::scan_roots (this=0x7fffc9723f50, pss=0x7fff7c000d90, worker_id=1) at /home/dai/jdk/src/hotspot/share/gc/g1/g1YoungCollector.cpp:706
#15 0x00007ffff64f0632 in G1EvacuateRegionsBaseTask::work (this=0x7fffc9723f50, worker_id=1) at /home/dai/jdk/src/hotspot/share/gc/g1/g1YoungCollector.cpp:693
#16 0x00007ffff6e8bb7c in WorkerTaskDispatcher::worker_run_task (this=0x7ffff00a4c88) at /home/dai/jdk/src/hotspot/share/gc/shared/workerThread.cpp:67
#17 0x00007ffff6e8c074 in WorkerThread::run (this=0x7fffb800df30) at /home/dai/jdk/src/hotspot/share/gc/shared/workerThread.cpp:159
#18 0x00007ffff6d8557f in Thread::call_run (this=0x7fffb800df30) at /home/dai/jdk/src/hotspot/share/runtime/thread.cpp:358
#19 0x00007ffff6acc1e7 in thread_native_entry (thread=0x7fffb800df30) at /home/dai/jdk/src/hotspot/os/linux/os_linux.cpp:705
#20 0x00007ffff7c94ac3 in start_thread (arg=<optimized out>) at ./nptl/pthread_create.c:442
#21 0x00007ffff7d26a40 in clone3 () at ../sysdeps/unix/sysv/linux/x86_64/clone3.S:81

内存复制:

static void pd_disjoint_words(const HeapWord* from, HeapWord* to, size_t count) {
#ifdef AMD64
switch (count) {
case 8: to[7] = from[7];
case 7: to[6] = from[6];
case 6: to[5] = from[5];
case 5: to[4] = from[4];
case 4: to[3] = from[3];
case 3: to[2] = from[2];
case 2: to[1] = from[1];
case 1: to[0] = from[0];
case 0: break;
default:
(void)memcpy(to, from, count * HeapWordSize);
break;
}
#else
// Includes a zero-count check.
intx temp;
__asm__ volatile(" testl %6,%6 ;"
" jz 3f ;"
" cmpl $32,%6 ;"
" ja 2f ;"
" subl %4,%1 ;"
"1: movl (%4),%3 ;"
" movl %7,(%5,%4,1);"
" addl $4,%0 ;"
" subl $1,%2 ;"
" jnz 1b ;"
" jmp 3f ;"
"2: rep; smovl ;"
"3: nop "
: "=S" (from), "=D" (to), "=c" (count), "=r" (temp)
: "0" (from), "1" (to), "2" (count), "3" (temp)
: "memory", "cc");
#endif // AMD64
}

并发标记: D:\jdk\src\hotspot\share\gc\g1\g1ConcurrentMark.inline.hpp

inline bool G1ConcurrentMark::mark_in_bitmap(uint const worker_id, oop const obj) {
HeapRegion* const hr = _g1h->heap_region_containing(obj);

if (hr->obj_allocated_since_marking_start(obj)) {
return false;
}

// Some callers may have stale objects to mark above TAMS after humongous reclaim.
// Can't assert that this is a valid object at this point, since it might be in the process of being copied by another thread.
assert(!hr->is_continues_humongous(), "Should not try to mark object " PTR_FORMAT " in Humongous continues region %u above TAMS " PTR_FORMAT, p2i(obj), hr->hrm_index(), p2i(hr->top_at_mark_start()));

bool success = _mark_bitmap.par_mark(obj);
if (success) {
add_to_liveness(worker_id, obj, obj->size());
}
return success;
}

分配内存:

HeapWord*
G1CollectedHeap::mem_allocate(size_t word_size,
bool* gc_overhead_limit_was_exceeded) {
assert_heap_not_locked_and_not_at_safepoint();

if (is_humongous(word_size)) {
return attempt_allocation_humongous(word_size);
}
size_t dummy = 0;
return attempt_allocation(word_size, word_size, &dummy);
}
  product(uint, GCCardSizeInBytes, 512,                                     \
"Card table entry size (in bytes) for card based collectors") \
range(128, NOT_LP64(512) LP64_ONLY(1024)) \
constraint(GCCardSizeInBytesConstraintFunc,AtParse)


cartable 为什么是512

  _card_size = GCCardSizeInBytes;
_card_shift = log2i_exact(_card_size);
_card_size_in_words = _card_size / sizeof(HeapWord);
template <DecoratorSet decorators, typename BarrierSetT>
template <typename T>
inline void G1BarrierSet::AccessBarrier<decorators, BarrierSetT>::
oop_store_not_in_heap(T* addr, oop new_value) {
// Apply SATB barriers for all non-heap references, to allow
// concurrent scanning of such references.
G1BarrierSet *bs = barrier_set_cast<G1BarrierSet>(BarrierSet::barrier_set());
bs->write_ref_field_pre<decorators>(addr);
Raw::oop_store(addr, new_value);
}

写入barrier逻辑

写入barrier逻辑

void G1BarrierSetAssembler::oop_store_at(MacroAssembler* masm, DecoratorSet decorators, BasicType type,
Address dst, Register val, Register tmp1, Register tmp2, Register tmp3) {
bool in_heap = (decorators & IN_HEAP) != 0;
bool as_normal = (decorators & AS_NORMAL) != 0;

bool needs_pre_barrier = as_normal;
bool needs_post_barrier = val != noreg && in_heap;

Register rthread = LP64_ONLY(r15_thread) NOT_LP64(rcx);
// flatten object address if needed
// We do it regardless of precise because we need the registers
if (dst.index() == noreg && dst.disp() == 0) {
if (dst.base() != tmp1) {
__ movptr(tmp1, dst.base());
}
} else {
__ lea(tmp1, dst);
}

#ifndef _LP64
InterpreterMacroAssembler *imasm = static_cast<InterpreterMacroAssembler*>(masm);
#endif

NOT_LP64(__ get_thread(rcx));
NOT_LP64(imasm->save_bcp());

if (needs_pre_barrier) {
g1_write_barrier_pre(masm /*masm*/,
tmp1 /* obj */,
tmp2 /* pre_val */,
rthread /* thread */,
tmp3 /* tmp */,
val != noreg /* tosca_live */,
false /* expand_call */);
}
if (val == noreg) {
BarrierSetAssembler::store_at(masm, decorators, type, Address(tmp1, 0), val, noreg, noreg, noreg);
} else {
Register new_val = val;
if (needs_post_barrier) {
// G1 barrier needs uncompressed oop for region cross check.
if (UseCompressedOops) {
new_val = tmp2;
__ movptr(new_val, val);
}
}
BarrierSetAssembler::store_at(masm, decorators, type, Address(tmp1, 0), val, noreg, noreg, noreg);
if (needs_post_barrier) {
g1_write_barrier_post(masm /*masm*/,
tmp1 /* store_adr */,
new_val /* new_val */,
rthread /* thread */,
tmp3 /* tmp */,
tmp2 /* tmp2 */);
}
}
NOT_LP64(imasm->restore_bcp());
}

hread 2 "java" hit Breakpoint 1, 0x00007ffff6fa66d8 in G1BarrierSetAssembler::oop_store_at(MacroAssembler*, unsigned long, BasicType, Address, Register, Register, Register, Register) ()
from /usr/lib/jvm/java-21-openjdk-amd64/lib/server/libjvm.so
(gdb) bt
#0 0x00007ffff6fa66d8 in G1BarrierSetAssembler::oop_store_at(MacroAssembler*, unsigned long, BasicType, Address, Register, Register, Register, Register) ()
from /usr/lib/jvm/java-21-openjdk-amd64/lib/server/libjvm.so
#1 0x00007ffff7482012 in ModRefBarrierSetAssembler::store_at(MacroAssembler*, unsigned long, BasicType, Address, Register, Register, Register, Register) ()
from /usr/lib/jvm/java-21-openjdk-amd64/lib/server/libjvm.so
#2 0x00007ffff740992a in MacroAssembler::store_heap_oop(Address, Register, Register, Register, Register, unsigned long) ()
from /usr/lib/jvm/java-21-openjdk-amd64/lib/server/libjvm.so
#3 0x00007ffff77506ec in TemplateTable::aastore() ()
from /usr/lib/jvm/java-21-openjdk-amd64/lib/server/libjvm.so
#4 0x00007ffff7746b47 in Template::generate(InterpreterMacroAssembler*) ()
from /usr/lib/jvm/java-21-openjdk-amd64/lib/server/libjvm.so
#5 0x00007ffff7736a0c in TemplateInterpreterGenerator::generate_and_dispatch(Template*, TosState) ()
from /usr/lib/jvm/java-21-openjdk-amd64/lib/server/libjvm.so
#6 0x00007ffff77427f6 in TemplateInterpreterGenerator::set_vtos_entry_points(Template*, unsigned char*&, unsigned char*&, unsigned char*&, unsigned char*&, unsigned char*&, unsigned char*&, unsigned char*&, unsigned char*&, unsigned char*&) () from /usr/lib/jvm/java-21-openjdk-amd64/lib/server/libjvm.so
#7 0x00007ffff77370d7 in TemplateInterpreterGenerator::set_entry_points(Bytecodes::Code) () from /usr/lib/jvm/java-21-openjdk-amd64/lib/server/libjvm.so
#8 0x00007ffff773855b in TemplateInterpreterGenerator::generate_all() ()
--Type <RET> for more, q to quit, c to continue without paging--
from /usr/lib/jvm/java-21-openjdk-amd64/lib/server/libjvm.so
#9 0x00007ffff7735d45 in TemplateInterpreter::initialize_code() () from /usr/lib/jvm/java-21-openjdk-amd64/lib/server/libjvm.so
#10 0x00007ffff70f0007 in interpreter_init_code() () from /usr/lib/jvm/java-21-openjdk-amd64/lib/server/libjvm.so
#11 0x00007ffff70cc117 in init_globals2() () from /usr/lib/jvm/java-21-openjdk-amd64/lib/server/libjvm.so
#12 0x00007ffff7777839 in Threads::create_vm(JavaVMInitArgs*, bool*) () from /usr/lib/jvm/java-21-openjdk-amd64/lib/server/libjvm.so
#13 0x00007ffff719c702 in JNI_CreateJavaVM () from /usr/lib/jvm/java-21-openjdk-amd64/lib/server/libjvm.so
#14 0x00007ffff7f81123 in JavaMain () from /usr/lib/jvm/java-21-openjdk-amd64/bin/../lib/libjli.so
#15 0x00007ffff7f84f0d in ThreadJavaMain () from /usr/lib/jvm/java-21-openjdk-amd64/bin/../lib/libjli.so
#16 0x00007ffff7e233ec in start_thread (arg=<optimized out>) at ./nptl/pthread_create.c:444
#17 0x00007ffff7ea3a4c in clone3 () at ../sysdeps/unix/sysv/linux/x86_64/clone3.S:81

相关阅读