序
本文主要研究一下springboot1.x及2.x的JvmGcMetrics的区别
springboot1.x的jvm gc metrics
springboot1.x没有JvmGcMetrics这个类,它在SystemPublicMetrics中简单采集了jvm的几个指标
spring-boot-actuator-1.5.9.RELEASE-sources.jar!/org/springframework/boot/actuate/endpoint/SystemPublicMetrics.java
/**
* Add JVM heap metrics.
* @param result the result
*/
protected void addHeapMetrics(Collection> result) {
MemoryUsage memoryUsage = ManagementFactory.getMemoryMXBean()
.getHeapMemoryUsage();
result.add(newMemoryMetric("heap.committed", memoryUsage.getCommitted()));
result.add(newMemoryMetric("heap.init", memoryUsage.getInit()));
result.add(newMemoryMetric("heap.used", memoryUsage.getUsed()));
result.add(newMemoryMetric("heap", memoryUsage.getMax()));
}
/**
* Add JVM non-heap metrics.
* @param result the result
*/
private void addNonHeapMetrics(Collection> result) {
MemoryUsage memoryUsage = ManagementFactory.getMemoryMXBean()
.getNonHeapMemoryUsage();
result.add(newMemoryMetric("nonheap.committed", memoryUsage.getCommitted()));
result.add(newMemoryMetric("nonheap.init", memoryUsage.getInit()));
result.add(newMemoryMetric("nonheap.used", memoryUsage.getUsed()));
result.add(newMemoryMetric("nonheap", memoryUsage.getMax()));
}
/**
* Add garbage collection metrics.
* @param result the result
*/
protected void addGarbageCollectionMetrics(Collection> result) {
List garbageCollectorMxBeans = ManagementFactory
.getGarbageCollectorMXBeans();
for (GarbageCollectorMXBean garbageCollectorMXBean : garbageCollectorMxBeans) {
String name = beautifyGcName(garbageCollectorMXBean.getName());
result.add(new Metric("gc." + name + ".count",
garbageCollectorMXBean.getCollectionCount()));
result.add(new Metric("gc." + name + ".time",
garbageCollectorMXBean.getCollectionTime()));
}
}
这个gc的count及time是个累积量
springboot2.x的JvmGcMetrics
springboot2.x改为使用micrometer来进行metrics采集,其中gc相关的在JvmGcMetrics
micrometer-core-1.0.3-sources.jar!/io/micrometer/core/instrument/binder/jvm/JvmGcMetrics.java
/**
* Record metrics that report a number of statistics related to garbage
* collection emanating from the MXBean and also adds information about GC causes.
*
* @see GarbageCollectorMXBean
*/
@NonNullApi
@NonNullFields
public class JvmGcMetrics implements MeterBinder {
private static final Logger logger = LoggerFactory.getLogger(JvmGcMetrics.class);
private boolean managementExtensionsPresent = isManagementExtensionsPresent();
private Iterable tags;
@Nullable
private String youngGenPoolName;
@Nullable
private String oldGenPoolName;
public JvmGcMetrics() {
this(emptyList());
}
public JvmGcMetrics(Iterable tags) {
for (MemoryPoolMXBean mbean : ManagementFactory.getMemoryPoolMXBeans()) {
if (isYoungGenPool(mbean.getName()))
youngGenPoolName = mbean.getName();
if (isOldGenPool(mbean.getName()))
oldGenPoolName = mbean.getName();
}
this.tags = tags;
}
@Override
public void bindTo(MeterRegistry registry) {
AtomicLong maxDataSize = new AtomicLong(0L);
Gauge.builder("jvm.gc.max.data.size", maxDataSize, AtomicLong::get)
.tags(tags)
.description("Max size of old generation memory pool")
.baseUnit("bytes")
.register(registry);
AtomicLong liveDataSize = new AtomicLong(0L);
Gauge.builder("jvm.gc.live.data.size", liveDataSize, AtomicLong::get)
.tags(tags)
.description("Size of old generation memory pool after a full GC")
.baseUnit("bytes")
.register(registry);
Counter promotedBytes = Counter.builder("jvm.gc.memory.promoted").tags(tags)
.baseUnit("bytes")
.description("Count of positive increases in the size of the old generation memory pool before GC to after GC")
.register(registry);
Counter allocatedBytes = Counter.builder("jvm.gc.memory.allocated").tags(tags)
.baseUnit("bytes")
.description("Incremented for an increase in the size of the young generation memory pool after one GC to before the next")
.register(registry);
if (this.managementExtensionsPresent) {
// start watching for GC notifications
final AtomicLong youngGenSizeAfter = new AtomicLong(0L);
for (GarbageCollectorMXBean mbean : ManagementFactory.getGarbageCollectorMXBeans()) {
if (mbean instanceof NotificationEmitter) {
((NotificationEmitter) mbean).addNotificationListener((notification, ref) -> {
final String type = notification.getType();
if (type.equals(GarbageCollectionNotificationInfo.GARBAGE_COLLECTION_NOTIFICATION)) {
CompositeData cd = (CompositeData) notification.getUserData();
GarbageCollectionNotificationInfo notificationInfo = GarbageCollectionNotificationInfo.from(cd);
if (isConcurrentPhase(notificationInfo.getGcCause())) {
Timer.builder("jvm.gc.concurrent.phase.time")
.tags(tags)
.tags("action", notificationInfo.getGcAction(), "cause", notificationInfo.getGcCause())
.description("Time spent in concurrent phase")
.register(registry)
.record(notificationInfo.getGcInfo().getDuration(), TimeUnit.MILLISECONDS);
} else {
Timer.builder("jvm.gc.pause")
.tags(tags)
.tags("action", notificationInfo.getGcAction(),
"cause", notificationInfo.getGcCause())
.description("Time spent in GC pause")
.register(registry)
.record(notificationInfo.getGcInfo().getDuration(), TimeUnit.MILLISECONDS);
}
GcInfo gcInfo = notificationInfo.getGcInfo();
// Update promotion and allocation counters
final Map before = gcInfo.getMemoryUsageBeforeGc();
final Map after = gcInfo.getMemoryUsageAfterGc();
if (oldGenPoolName != null) {
final long oldBefore = before.get(oldGenPoolName).getUsed();
final long oldAfter = after.get(oldGenPoolName).getUsed();
final long delta = oldAfter - oldBefore;
if (delta > 0L) {
promotedBytes.increment(delta);
}
// Some GC implementations such as G1 can reduce the old gen size as part of a minor GC. To track the
// live data size we record the value if we see a reduction in the old gen heap size or
// after a major GC.
if (oldAfter < oldBefore || GcGenerationAge.fromName(notificationInfo.getGcName()) == GcGenerationAge.OLD) {
liveDataSize.set(oldAfter);
final long oldMaxAfter = after.get(oldGenPoolName).getMax();
maxDataSize.set(oldMaxAfter);
}
}
if (youngGenPoolName != null) {
final long youngBefore = before.get(youngGenPoolName).getUsed();
final long youngAfter = after.get(youngGenPoolName).getUsed();
final long delta = youngBefore - youngGenSizeAfter.get();
youngGenSizeAfter.set(youngAfter);
if (delta > 0L) {
allocatedBytes.increment(delta);
}
}
}
}, null, null);
}
}
}
}
//......
}
可以看到gc相关部分改为使用jmx的NotificationEmitter机制来更新数据;gc cause采用了Timer类型来采集。由于micrometer支持tag,所以这里JvmGcMetrics给gc cause添加了action(
比如end of minor GC
)及cause(比如G1 Evacuation Pause
)两个tag,以支持更细粒度的gc pause指标统计。
小结
springboot1.x的gc time及count是个累积量,而springboot2.x使用micrometer的JvmGcMetrics,其gc的pause指标就变成Timer类型。Timer类型的话,相当于Meter加上Histogram,Meter记录的是瞬时值,而Histogram会对这些瞬时值进行分布统计。除此之外springboot2.x版本还给gc pause打上了action及cause的tag,支持更细粒度的指标统计查询。
doc
- micrometer timers
- JVM and System Metrics
- springboot的metrics
- 聊聊springboot2的micrometer