FullGC和字符串去重你明白吗?

Full GC

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

FullGC和字符串去重你明白吗?

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的新特性自动完成字符串去重。

FullGC和字符串去重你明白吗?

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.Stringif (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);}}

FullGC和字符串去重你明白吗?

上一篇:JVM运行时内存划分


下一篇:还原FullGc翻车现场