vlambda博客
学习文章列表

自从学了深入解析java虚拟机:FullGC和字符串去重后,我无敌了


Full GC

在设计G1时会极力避免Full GC(以下简称FGC),但是总有一些特殊情况,如果当前并发回收的速度跟不上对象分配的速度,那么需要G1启动后备方案进行FGC。早期G1的FGC使用单线程的标记整理算法,后来为了充分发挥多核处理器的优势,JEP 307提案为G1的FGC设计了多线程标记整理算法,此时多线程的FGC的线程数量可以由-XX:ParallelGCThreads控制。


G1的多线程FGC与Parallel GC的FGC类似,是一个全局STW的过程,G1使用线程组完成垃圾回收工作,整个阶段都不允许Mutator线程运行。FGC的实现位于G1FullCollector::collect(),如代码清单11-7所示:

代码清单11-7 G1 FGC

void G1FullCollector::collect() {
phase1_mark_live_objects();
phase2_prepare_compaction();
phase3_adjust_pointers();
phase4_do_compaction();
}

正如之前所说,FGC是一个标准的标记整理算法,每个步骤提交任务给线程池,使用多线程完成,尽量减少STW时间。触发FGC的场景有很多,举例如下:

Mixed GC中如果老年代回收的速度小于对象分配或晋升的速度,会触发FGC;

YGC最后会移动存活对象到其他分区,如果此时发现没有能容纳存活对象的Region,会触发FGC;

如果没有足够的Region容纳下Humongous对象,会触发FGC;

应用程序调用System.gc()也会触发FGC。

由于FGC的全局STW性,如果频繁发生FGC是比较糟糕的信号,它暗示应用程序的特性与当前的G1参数配置不能良好契合,需要开发者找到问题并进一步调优处理。

字符串去重

如果读者对虚拟机进行过Heap Dump(-XX:+
HeapDumpOnOutOfMemoryError或者jmap触发)操作,会观察到Java堆中占比最大的通常是一些byte[]对象,这些byte[]对象又通常是String的成员,即字符串对象在Java堆中占据极大比重,如果能发现重复的字符串并消除它们,会节省很大一部分内存。可以手动调用String.intern()消除重复的字符串,但这需要开发者了解哪些字符串可能发生重复,也可以使用G1的新特性自动完成字符串去重。


G1的YGC和FGC都可以触发字符串去重,只需要开启-XX:+UseStringDeduplication。在YGC的copy_to_survivor()过程中如果发现开启了自动去重选项,G1会调用
G1StringDedup::enqueue_from_evacuation()自动发现可以去重的字符串,如代码清单11-8所示:

代码清单11-8 选择重复字符串

bool G1StringDedup::is_candidate_from_evacuation(...) {
// 如果对象在Eden Region,并且类型是java.lang.String
if (from_young && java_lang_String::is_instance_inlined(obj)) {
// 如果对象将要复制到Survivor Region,并且年龄小于阈值
if (to_young && obj->age() == StringDeduplicationAgeThreshold) {
return true; // 作为候选项加入G1StringDedupQueue
}
// 如果对象将要晋升到Old Region,并且年龄小于阈值if (!to_young && obj->age() < StringDeduplicationAgeThreshold) {
return true; // 作为候选项加入G1StringDedupQueue
}
}
return false;
}
void G1StringDedup::enqueue_from_evacuation(...) {
if (is_candidate_from_evacuation(...)) {
G1StringDedupQueue::push(worker_id, java_string);
}
}

G1将所有存活对象从Eden复制到Survivor Region,所有从Eden晋升到Old Region并且年龄小于
-XX:StringDeduplicationAgeThreshold的对象都会被放入G1StringDedupQueue等待字符串去重线程处理。字符串去重线程即StringDedupThread,它在发现队列中存在去重候选项后会弹出对象,然后调用StringDedupTable::deduplicate,如代码清单11-9所示:

代码清单11-9
StringDedupTable::deduplicate

void StringDedupTable::deduplicate(...) {
// 如果java.lang.String的value字段为空,那么不处理
typeArrayOop value = java_lang_String::value(java_string);
if (value == NULL) {
stat->inc_skipped();
return;}
...
// 根据新对象的hash查找已有对象
typeArrayOop existing_value = lookup_or_add(value, latin1, hash);
// 如果新对象和已有对象是同一个,那么不处理
if (oopDesc::equals_raw(existing_value, value)) {
stat->inc_known();
return;
}
... // 如果是不同对象,但是包含的字符串相同,则处理它
if (existing_value != NULL) {
java_lang_String::set_value(java_string, existing_value);
stat->deduped(value, size_in_bytes);
}
}



本文给大家讲解的内容是深入解析java虚拟机:Full GC和字符串去重

  1. 觉得文章不错的朋友可以转发此文关注小编;

  2. 感谢大家的支持!