diff --git a/_posts/2024-09-20-g1-conc-mark-mark.md b/_posts/2024-09-20-g1-conc-mark-mark.md
index b4900f1..22cb160 100644
--- a/_posts/2024-09-20-g1-conc-mark-mark.md
+++ b/_posts/2024-09-20-g1-conc-mark-mark.md
@@ -47,32 +47,34 @@ class G1ConcurrentMark {
## 三色标记算法
-在标记阶段 G1 采用三色标记算法([Tri-color marking](https://en.wikipedia.org/wiki/Tracing_garbage_collection#Tri-color_marking)),从 GC root 开始遍历对象,如图开始时只有与 gc root 直接关联的对象是黑色的,随着并发标记的进行,所有与 GC root 直接关联或者间接关联的对象都会变成黑色。
+在标记阶段 G1 采用三色标记算法([Tri-color marking](https://en.wikipedia.org/wiki/Tracing_garbage_collection#Tri-color_marking)),从 GC root 开始遍历对象。
+
+如图,开始时只有与 gc root 直接关联的对象是灰色的,随着并发标记的进行,所有与 GC root 直接关联或者间接关联的对象都会变成黑色。
注意这里说的对象染色并不是标记对象的某个属性为黑色或者灰色,以 G1 为例:
-- 并发标记前:在 Young GC 阶段,将与之 gc root 关联的对象标记在 mark_bitmap 中,同理在 root region scan 阶段也会标记与之直接关联的对象。当对象已经在 mark_bitmap 标记,称对象被标记为黑色。此时除此之外的对象全部为白色对象,就是还没有遍历到的对象。
+- 并发标记前:在 Young GC 阶段,将与之 gc root 关联的对象标记在 mark_bitmap 中,同理在 root region scan 阶段也会标记与之直接关联的对象。当对象已经在 mark_bitmap 标记,由于对象的引用还未被处理此时称对象被标记为灰色。此时除此之外的对象全部为白色对象,就是还没有遍历到的对象。
-- 并发标记中:G1 从 heap 开始的位置起开始遍历,当遍历到的对象已经被标记,就会将它的属性引用加入到 `_task_queue` 中,称在队列中的对象标记为灰色。当处理任务队列时,对象会被拿出来在 mark_bitmap 上标记,此时对象被标记黑色,并且遍历对象引用属性加入到队列中。
+- 并发标记中:G1 从 heap 开始的位置起开始遍历,当遍历到的对象已经被标记,则会遍历它的属性引用并加入到 `_task_queue` 中,称在队列中的对象标记为黑色。
+ 当处理任务队列时,对象会被拿出来在 mark_bitmap 上标记,此时对象被标记灰色,此后遍历对象引用属性加入到队列中,此时对象为黑色。
-- 并发标记后活者的对象都已经在 `mark_bitmap` 标记,为黑色对象,死对象未被标记,为白色对象。
+- 并发标记后活者的对象都已经在 `mark_bitmap` 标记,并且对象的所有应用都已经被遍历,为黑色对象,死对象未被标记,为白色对象。
-由此可知,只有在标记过程中有灰色对象,标记之前和以后都只有黑色和白色对象,即标记之前和之后任务队列都为空。
+由此可知,遍历开始时只有灰色对象和白色对象,遍历结束后只有黑色和白色对象。
## 并发标记
-### todo
+并发标记在代码的名称叫做 `mark from roots`,主要逻辑框架封装在 G1CMTask::do_marking_step` 方法中。
```cpp
-//-> subphase_mark_from_roots
-//->G1ConcurrentMark::mark_from_roots
+G1ConcPhaseTimer p(_cm, "Concurrent Mark From Roots");
+
+//-> subphase_mark_from_roots ->G1ConcurrentMark::mark_from_roots
class G1CMConcurrentMarkingTask : public WorkerTask {
G1ConcurrentMark* _cm;
-
-public:
void work(uint worker_id) {
{
G1CMTask* task = _cm->task(worker_id);
@@ -83,3 +85,307 @@ public:
} } }
}
```
+
+核心模块是一个 `do-while` 循环。
+
+```cpp
+do{
+ //1. process region between botton and tams
+ //2. process task queue
+
+ while (!has_aborted() && _curr_region == nullptr && !_cm->out_of_regions()){
+ G1HeapRegion* claimed_region = _cm->claim_region(_worker_id);//get region to scan
+ }
+}while(_curr_region != nullptr && !has_aborted())
+```
+
+并发标记的代码逻辑很多,下面分功能模块介绍。
+
+### drain satb buffers
+
+#### SATB
+
+G1 使用 SATB 来保证 GC 线程与用户线程并发执行的正确性,而 SATB 是写前屏障(write-pre barrier)来实现。
+
+SATB 机制将在并发标记过程中 “消失的对象” 全部标记为黑色,然后以此为根,开始扫描。它能确保对象不会被误删除(最终标记为黑色),但也会导致真正应该消失的对象在本轮标记中存活,需要等待下一轮并发标记才能被回收。参考《深入理解 Java 虚拟机》
+
+```cpp
+inline void ModRefBarrierSet::AccessBarrier::
+oop_store_in_heap(T* addr, oop value) {
+ BarrierSetT *bs = barrier_set_cast(barrier_set());
+ bs->template write_ref_field_pre(addr); //写前屏障
+ Raw::oop_store(addr, value);
+ bs->template write_ref_field_post(addr);
+}
+```
+
+写前屏障会首先判断 SATB 是否开启,开启状态下会将对象指针加入到 `SATBMarkQueue` 队列中。
+
+```cpp
+//write_ref_field_pre->enqueue
+inline void G1BarrierSet::enqueue(T* dst) {
+ G1SATBMarkQueueSet& queue_set = G1BarrierSet::satb_mark_queue_set();
+ if (!queue_set.is_active()) return;
+
+ T heap_oop = RawAccess::oop_load(dst);
+ if (!CompressedOops::is_null(heap_oop)) {
+ SATBMarkQueue& queue = G1ThreadLocalData::satb_mark_queue(Thread::current());
+ queue_set.enqueue_known_active(queue, CompressedOops::decode_not_null(heap_oop));
+ }
+}
+```
+
+在并发标记开始的时候,会设置 `SATBMarkQueueSet` 的状态,并为每个线程设置 `queue` 的容量。
+
+```cpp
+//start_concurrent_cycle->set_active_all_threads
+void SATBMarkQueueSet::set_active_all_threads(bool active, bool expected_active) {
+ _all_active = active;
+
+ Threads::threads_do(&closure);
+}
+
+class SetThreadActiveClosure : public ThreadClosure {
+ SATBMarkQueueSet* _qset; bool _active;
+public:
+ SetThreadActiveClosure(SATBMarkQueueSet* qset, bool active) :
+ _qset(qset), _active(active) {}
+ virtual void do_thread(Thread* t) {
+ SATBMarkQueue& queue = _qset->satb_queue_for_thread(t);
+ if (_active) {
+ assert(queue.is_empty(), "queues should be empty when activated");
+ } else {
+ queue.set_index(queue.current_capacity());
+ }
+ queue.set_active(_active);
+ }
+}
+```
+
+#### drain satb
+
+`_task->make_reference_grey` 在 mark_map 中标记当前对象,标记成功则将对象加入到队列中,后续会遍历对象所有引用。
+
+标记失败的情况:
+
+1. 对象地址大于 tams ,即对象不在本次需要遍历的范围内,不在此范围的对象是并发过程中用户线程产生的新对象,这些对象都是活对象。
+2. 对象已经被标记,不需要再次标记,保证所有任务都能够完成。
+
+```cpp
+//->drain_satb_buffers -> SATBMarkQueueSet::apply_closure_to_completed_buffer
+class G1CMSATBBufferClosure : public SATBBufferClosure {
+ virtual void do_buffer(void** buffer, size_t size) {
+ for (size_t i = 0; i < size; ++i) {
+ do_entry(buffer[i]);
+ } }
+
+ void do_entry(void* entry) const {
+ _task->increment_refs_reached();
+ oop const obj = cast_to_oop(entry);
+ _task->make_reference_grey(obj);
+ }
+}
+//make_reference_grey -> push -> _task_queue->push(task_entry)
+inline bool G1CMTask::make_reference_grey(oop obj) {
+ if (!_cm->mark_in_bitmap(_worker_id, obj)) {
+ return false;
+ }
+ if (is_below_finger(obj, global_finger)) {
+ push(entry);
+ }
+}
+```
+
+如果队列已满,则移动部分本地队列任务到全局栈中,然后再将当前任务加入到本地队列中。
+
+```cpp
+inline void G1CMTask::push(G1TaskQueueEntry task_entry) {
+ if (!_task_queue->push(task_entry)) {
+ move_entries_to_global_stack();
+ bool success = _task_queue->push(task_entry);
+ }
+}
+```
+
+### mark humongous region
+
+对于数组对象和普通对象有不同的处理,本节只看普通对象。
+
+```cpp
+if(_curr_region->is_humongous() && mr.start() == _curr_region->bottom()) {
+ if (_mark_bitmap->is_marked(mr.start())) {
+ // The object is marked - apply the closure
+ bitmap_closure.do_addr(mr.start());
+ }
+}
+
+bool G1CMBitMapClosure::do_addr(HeapWord* const addr) {
+ // We move that task's local finger along.
+ _task->move_finger_to(addr);
+ _task->scan_task_entry(G1TaskQueueEntry::from_oop(cast_to_oop(addr)));
+ return !_task->has_aborted();
+}
+
+//scan_task_entry -> process_grey_task_entry
+_words_scanned += obj->oop_iterate_size(_cm_oop_closure);; //源码中就多了一个分号
+```
+
+`_cm_oop_closure` 在 `do_marking_step` 开始执行的时候被设置,`make_reference_grey` 处理方式上文已经提到过。
+
+
+```cpp
+G1CMOopClosure cm_oop_closure(_g1h, this);
+set_cm_oop_closure(&cm_oop_closure);
+
+inline void G1CMOopClosure::do_oop_work(T* p) {
+ _task->deal_with_reference(p);
+}
+// G1CMTask::deal_with_reference -> make_reference_grey
+```
+
+如果全局栈耗尽,则会重新启动流程,在 `do_marking_step` 方法中首先执行清空队列和栈的逻辑。
+
+```cpp
+for (uint iter = 1; true; ++iter) {
+ // Check if we need to restart the marking loop.
+ if (!mark_loop_needs_restart()) break;
+ log_info(gc, marking)("Concurrent Mark Restart for Mark Stack Overflow (iteration #%u)", iter);
+}
+
+// ...then partially drain the local queue and the global stack
+drain_local_queue(true);
+drain_global_stack(true);
+```
+
+> If it overflows, then the marking phase should restart and iterate over the bitmap to identify grey objects.
+
+
+### mark normal region
+
+对于普通 region,则遍历 region 区域,找到其中被标记的对象,然后逐个遍历对象的所有引用,其次标记这些引用,最后将这些引用加入到队列中。`make_reference_grey` 处理方式上文已经提到过。
+
+```cpp
+if (_mark_bitmap->iterate(&bitmap_closure, mr))
+
+inline bool G1CMBitMap::iterate(G1CMBitMapClosure* cl, MemRegion mr) {
+ BitMap::idx_t const end_offset = addr_to_offset(mr.end());
+ BitMap::idx_t offset = _bm.find_first_set_bit(addr_to_offset(mr.start()), end_offset);
+
+ while (offset < end_offset) {
+ HeapWord* const addr = offset_to_addr(offset);
+ if (!cl->do_addr(addr)) { return false; }
+ size_t const obj_size = cast_to_oop(addr)->size();
+ offset = _bm.find_first_set_bit(offset + (obj_size >> _shifter), end_offset);
+ }
+ return true;
+}
+```
+
+### drain local queue
+
+依次从本地队列中取出任务处理,后续流程前文已经介绍过,不在赘述。
+
+参数 `partially` 表示处理部分任务还是全部任务,留下部分是为了让其他工作线程来窃取。
+
+```cpp
+void G1CMTask::drain_local_queue(bool partially) {
+ uint target_size;
+ if (partially) { target_size = GCDrainStackTargetSize; }
+ else { target_size = 0; }
+
+ G1TaskQueueEntry entry;
+ bool ret = _task_queue->pop_local(entry);
+ while (ret) {
+ scan_task_entry(entry);
+ if (_task_queue->size() <= target_size || has_aborted()) {
+ ret = false;
+ } else {
+ ret = _task_queue->pop_local(entry);
+ } }
+}
+
+//scan_task_entry->process_grey_task_entry
+```
+
+### drain global stack
+
+`get_entries_from_global_stack` 方法批量从全局栈中获取任务,然后将任务加入到本地队列中,然后 `drain_local_queue` 清空本地队列的任务。
+
+```cpp
+void G1CMTask::drain_global_stack(bool partially) {
+ while (!has_aborted() && _cm->mark_stack_size() > target_size) {
+ if (get_entries_from_global_stack()) {
+ drain_local_queue(partially);
+ } }
+}
+
+bool G1CMTask::get_entries_from_global_stack() {
+ G1TaskQueueEntry buffer[G1CMMarkStack::EntriesPerChunk];
+ if (!_cm->mark_stack_pop(buffer)) { return false; }
+ // We did actually pop at least one entry.
+ for (size_t i = 0; i < G1CMMarkStack::EntriesPerChunk; ++i) {
+ G1TaskQueueEntry task_entry = buffer[i];
+ if (task_entry.is_null()) { break; }
+ bool success = _task_queue->push(task_entry);
+ }
+ return true;
+}
+```
+
+### claim region
+
+`_cm->claim_region` 为当前线程分配 region,然后 `setup_for_region` 初始化相关属性。
+
+```cpp
+while (!has_aborted() && _curr_region == nullptr && !_cm->out_of_regions()) {
+ G1HeapRegion* claimed_region = _cm->claim_region(_worker_id);
+ if (claimed_region != nullptr) {
+ setup_for_region(claimed_region);
+ }
+}
+```
+
+### 窃取任务
+
+窃取任务的逻辑和收集阶段类似,不在赘述。
+
+```cpp
+while (!has_aborted()) {
+ G1TaskQueueEntry entry;
+ if (_cm->try_stealing(_worker_id, entry)) {
+ scan_task_entry(entry);
+ drain_local_queue(false);
+ drain_global_stack(false);
+ } else {
+ break;
+ }
+}
+```
+
+## 总结
+
+本文从三色标记算法开始,讲解了 G1 遍历所有 region(bottom 到 tams) ,并且在 mark map 中标记对象的全过程。这块代码看似很多,不过主要逻辑还是很清楚,希望本文对读者有用。
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/assets/conc-mark/conc-mark-tri-color.png b/assets/conc-mark/conc-mark-tri-color.png
index df59a31..153a6ef 100644
Binary files a/assets/conc-mark/conc-mark-tri-color.png and b/assets/conc-mark/conc-mark-tri-color.png differ