对于jmx提供的垃圾回收器的次数,很多人的疑问点都是老年代的回收次数是否等于FGC的次数。下面我们就围绕这个问题来进行分析。
jmx的提供以及用法
api介绍
public interface GarbageCollectorMXBean extends MemoryManagerMXBean {
public long getCollectionCount();
public long getCollectionTime();
}
jmx提供了gc次数和gc时间的接口。他们分别返回gc总次数以及gc总时间。
for (GarbageCollectorMXBean garbageCollector : ManagementFactory.getGarbageCollectorMXBeans()) {
//todo
}
使用方式也比较简单。
认识垃圾回收器名称
static GCMemoryManager* get_copy_memory_manager();//Copy
static GCMemoryManager* get_parnew_memory_manager();//ParNew
static GCMemoryManager* get_g1YoungGen_memory_manager();//G1 Young Generation
static GCMemoryManager* get_psScavenge_memory_manager();//PS Scavenge
static GCMemoryManager* get_psMarkSweep_memory_manager();//PS MarkSweep
static GCMemoryManager* get_g1OldGen_memory_manager();//G1 Old Generation
static GCMemoryManager* get_msc_memory_manager();//MarkSweepCompact
static GCMemoryManager* get_cms_memory_manager();//ConcurrentMarkSweep
以上就是列举jdk1.8中的垃圾回收器以及名字,前面4个是年轻代的。后面4个是老年代的。hotspot在1.8中还都是分代回收。所以jvm启动后会带有2个垃圾回收器。需要根据名字来获取对应的数据。
查看数据来源
以上的manager都是GCMemoryManager的子类。时间和次数都在父类的成员变量中,接下来的逻辑基本都是在父类中实现的。子类主要是提供名字的和算法种类。
void MemoryService::gc_end(bool fullGC, bool recordPostGCUsage,
bool recordAccumulatedGCTime,
bool recordGCEndTime, bool countCollection,
GCCause::Cause cause) {
GCMemoryManager* mgr;
if (fullGC) {
mgr = (GCMemoryManager*) _major_gc_manager;
} else {
mgr = (GCMemoryManager*) _minor_gc_manager;
}
assert(mgr->is_gc_memory_manager(), "Sanity check");
// register the GC end statistics and memory usage
mgr->gc_end(recordPostGCUsage, recordAccumulatedGCTime, recordGCEndTime,
countCollection, cause);
}
这里有个判定,是fullgc的时候就会使用老年代垃圾回收器,次数和时间增加的逻辑就在 mgr->gc_end中。MemoryService::gc_end的调用也是很有意思的,写在了析构函数中。
TraceMemoryManagerStats::~TraceMemoryManagerStats() {
MemoryService::gc_end(_fullGC, _recordPostGCUsage, _recordAccumulatedGCTime,
_recordGCEndTime, _countCollection, _cause);
}
现在问题就转变成什么时候生成TraceMemoryManagerStats的对象(对象销毁就会调用析构函数)。下面我们开始跟踪老年代的调用次数
MarkSweepCompact
void GenCollectedHeap::do_collection(bool full,
bool clear_all_soft_refs,
size_t size,
bool is_tlab,
int max_level) {
...
TraceMemoryManagerStats tmms(_gens[i]->kind(),gc_cause());
...
}
msc就是老年代回收的时候,调用一次。是fullgc的次数。
PS MarkSweep
PS MarkSweep是垃圾回收器的名称,但是他有两种算法
psMarkSweep
单线程回收
bool PSMarkSweep::invoke_no_policy(bool clear_all_softrefs) {
...
TraceMemoryManagerStats tms(true /* Full GC */,gc_cause);
...
}
psParallelCompact
多线程回收
bool PSParallelCompact::invoke_no_policy(bool maximum_heap_compaction) {
...
TraceMemoryManagerStats tms(true /* Full GC */,gc_cause);
...
}
也在老年代垃圾回收器fullgc时进行增加次数
cms
TraceCMSMemoryManagerStats::TraceCMSMemoryManagerStats(CMSCollector::CollectorState phase, GCCause::Cause cause): TraceMemoryManagerStats() {
switch (phase) {
case CMSCollector::InitialMarking:
initialize(true /* fullGC */ ,
cause /* cause of the GC */,
true /* recordGCBeginTime */,
true /* recordPreGCUsage */,
false /* recordPeakUsage */,
false /* recordPostGCusage */,
true /* recordAccumulatedGCTime */,
false /* recordGCEndTime */,
false /* countCollection */ );
break;
case CMSCollector::FinalMarking:
initialize(true /* fullGC */ ,
cause /* cause of the GC */,
false /* recordGCBeginTime */,
false /* recordPreGCUsage */,
false /* recordPeakUsage */,
false /* recordPostGCusage */,
true /* recordAccumulatedGCTime */,
false /* recordGCEndTime */,
false /* countCollection */ );
break;
case CMSCollector::Sweeping:
initialize(true /* fullGC */ ,
cause /* cause of the GC */,
false /* recordGCBeginTime */,
false /* recordPreGCUsage */,
true /* recordPeakUsage */,
true /* recordPostGCusage */,
false /* recordAccumulatedGCTime */,
true /* recordGCEndTime */,
true /* countCollection */ );
break;
default:
ShouldNotReachHere();
}
}
cms就比较奇特了,他的+1条件有3种,InitialMarking,FinalMarking,Sweeping。
foreground
void CMSCollector::collect_in_foreground(bool clear_all_soft_refs, GCCause::Cause cause) {
checkpointRootsInitial(false);
...
checkpointRootsFinal(false, clear_all_soft_refs,
init_mark_was_synchronous);
···
sweep(false);
···
}
在foreground模式下,3中情况都有调用,也是就说一次完整的foreground会造成垃圾器次数+3。不过这种情况触发条件还是比较苛刻的。
background
void CMSCollector::collect_in_background(bool clear_all_soft_refs, GCCause::Cause cause) {
...
sweep(true);
...
}
background模式下只有一处调用。也就是说正常的cms回收,老年代垃圾回收器是+1的。
G1
bool G1CollectedHeap::do_collection(bool explicit_gc,
bool clear_all_soft_refs,
size_t word_size) {
...
TraceMemoryManagerStats tms(true /* Full GC */,gc_cause);
...
}
G1的fullgc的时候老年代回收器会+1。这么没有mixed的事情,也就是说,只要参数设置合理,G1基本上是看不到老年代回收次数的。
总结
以上展示了各种老年代垃圾回收器jmx回收次数的采集。我们可以发现除了cms外,老年代垃圾回收的采集次数就是fullgc的次数。cms的老年代垃圾回收器的次数和jstat的fgc的值也是对不上的。需要根据gc日志来进行分析,不要单纯的看回收次数。