JVM监控java.lang.management简介
java.lang.management是JDK自带的一套工具库。通过工厂类ManagementFactory对外提供一套管理接口,提供JVM和JVM所运行操作系统的各种信息,例如:内存使用情况、GC情况和操作系统版本等。基于以上信息,可以辅助我们对定位问题或者性能调优提供数据支撑。Management同时允许从本地和远程(JMX)对正在运行的JVM进行监视和管理。
MXBean简介
根据ManagementFactory的javadoc,可以看出它提供了大量的工厂方法,使得我们可以通过调用这些方法来获取JVM各组件相关的JavaBean,通过这些JavaBean就可以获取到对应组件的运行状态数据。而这些JavaBean是各个服务启动时自动注册好的,运行数据也是提前计算好的,所以我们并不会因为获取相关数据,而导致额外消耗资源进行计算。
这些JavaBean被称之为MBean或者MXBean。MBean和MXBean两者的区别在于:MXBean是一个特殊的MBean,它的数据类型被限制为开放类型,基本上是原始类型、字符串及其组合。由于这些限制,MXBean可以在不加载类的情况下使用,这使得它们甚至可以与非Java客户机进行互操作。
这个特性对于我们进行通用封装是十分重要的。
使用案例1:
JVM频繁的FullGC或者Java服务发生了OOM,但是JVM进程并没有自动退出。这种场景下,仅仅在操作系统层面对JVM进程进行监控是不能及时发现相关问题的。可以使用MemoryMXBean和GarbageCollectorMXBean来获取相关信息;并结合其他监控或者定时执行机制,实现告警等功能。
实现代码:
public class MemoryMXBeanTest {
private static volatile MemoryMXBean memoryMBean;
private static volatile List<GarbageCollectorMXBean> garbageCollectorMXBeanList;
/**
* 获取memoryMBean
*/
private static void initMemoryMXBean() {
synchronized (MemoryMXBeanTest.class) {
if (memoryMBean == null) {
try {
memoryMBean = AccessController.doPrivileged(
new PrivilegedExceptionAction<MemoryMXBean>() {
@Override
public MemoryMXBean run() throws DrsException {
return ManagementFactory.getMemoryMXBean();
}
});
} catch (Exception exp) {
log.error("", exp);
}
}
}
}
/**
* 获取GarbageCollectorMXBean
*/
private static void initGarbageCollectorMXBean() {
synchronized (MemoryMXBeanTest.class) {
if (garbageCollectorMXBeanList == null) {
try {
garbageCollectorMXBeanList = AccessController.doPrivileged(
new PrivilegedExceptionAction<List<GarbageCollectorMXBean>>() {
@Override
public List<GarbageCollectorMXBean> run() throws DrsException {
return ManagementFactory.getGarbageCollectorMXBeans();
}
});
} catch (Exception exp) {
log.error("", exp);
}
}
}
}
public static MemoryMXBean getMemoryMBean() {
if (memoryMBean == null) {
initMemoryMXBean();
}
return memoryMBean;
}
public static List<GarbageCollectorMXBean> getGCMXBeanList() {
if (garbageCollectorMXBeanList == null) {
initGarbageCollectorMXBean();
}
return garbageCollectorMXBeanList;
}
private Map<String, Object> getMemStatus() {
Map<String, Object> memStatus = new HashMap<String, Object>();
// 堆内存
memStatus.put("Heap mem Committed", getMemoryMBean().getHeapMemoryUsage().getCommitted());
memStatus.put("Heap mem Init", getMemoryMBean().getHeapMemoryUsage().getInit());
memStatus.put("Heap mem Max", getMemoryMBean().getHeapMemoryUsage().getMax());
memStatus.put("Heap mem Used", getMemoryMBean().getHeapMemoryUsage().getUsed());
// 堆内存使用率
memStatus.put("Heap mem Used Rato",
(getMemoryMBean().getHeapMemoryUsage().getUsed() * 100 / getMemoryMBean().getHeapMemoryUsage().getMax()) + "%");
// 堆外内存
memStatus.put("Non-Heap mem Committed", getMemoryMBean().getNonHeapMemoryUsage().getCommitted());
memStatus.put("Non-Heap mem Init", getMemoryMBean().getNonHeapMemoryUsage().getInit());
memStatus.put("Non-Heap mem Used", getMemoryMBean().getNonHeapMemoryUsage().getUsed());
return memStatus;
}
private Map<String, Object> getGcStatus() {
Map<String, Object> gcStatus = new HashMap<String, Object>();
if (!CollectionUtils.isEmpty(getGCMXBeanList())) {
// 不同的垃圾回收器
for (GarbageCollectorMXBean gcMXBean : getGCMXBeanList()) {
gcStatus.put(gcMXBean.getName() + "-" + Arrays.toString(gcMXBean.getMemoryPoolNames()) + "-count", gcMXBean.getCollectionCount());
gcStatus.put(gcMXBean.getName() + "-" + Arrays.toString(gcMXBean.getMemoryPoolNames()) + "-time", gcMXBean.getCollectionTime());
}
}
return gcStatus;
}
public void print() {
log.info("jvm mem status: {}", GsonUtils.toJson(getMemStatus()));
log.info("jvm gc status: {}", GsonUtils.toJson(getGcStatus()));
}
}
运行结果:
2020-03-09 08:24:30,001 jvm mem status: {"Non-Heap mem Used":201168408,"Heap mem Used":710231248,"Heap mem Used Rato":"48%","Heap mem Committed":1467482112,"Non-Heap mem Init":2555904,"Non-Heap mem Committed":207945728,"Heap mem Init":1474297856,"Heap mem Max":1467482112}
2020-03-09 08:24:30,001 jvm gc status: {"PS Scavenge-[PS Eden Space, PS Survivor Space]-count":210,"PS MarkSweep-[PS Eden Space, PS Survivor Space, PS Old Gen]-time":842,"PS MarkSweep-[PS Eden Space, PS Survivor Space, PS Old Gen]-count":4,"PS Scavenge-[PS Eden Space, PS Survivor Space]-time":2213}
使用案例2
有业务功能大量并发,同时占用了大量数据库连接;从而导致连接池中可用连接长时间无空闲的话,就会导致业务持久化操作异常。下面就以使用Druid数据库连接池为例,通过Druid自带的MXbean获取连接池相关信息。
PS:
1、多数Java连接池都会注册MXbean并记录重要监控数据,具体实现和数据线略有差别;
2、druid自带monitor;需要配置额外的数据库进行存储,可参见:https://github.com/alibaba/druid/wiki/druid-monitor%E8%AE%BE%E8%AE%A1
通过JMX连接JVM,可以看到DruidDataSource下有两个数据源,他们的ObjectName为:com.alibaba.druid:type=DruidDataSource,id=XXX,每个数据源的MXBean中各有一组Attribute记录相关监控项。
实现代码:
public class DruidMXBeanTest {
private static volatile MBeanServer mBeanServer = ManagementFactory.getPlatformMBeanServer();
private static void initMBeanServer() {
synchronized (DruidMXBeanTest.class) {
if (mBeanServer == null) {
try {
mBeanServer = AccessController.doPrivileged(
new PrivilegedExceptionAction<MBeanServer>() {
@Override
public MBeanServer run() throws DrsException {
return ManagementFactory.getPlatformMBeanServer();
}
});
} catch (Exception exp) {
log.error("", exp);
}
}
}
}
private static MBeanServer getMBeanServer() {
if (mBeanServer == null) {
initMBeanServer();
}
return mBeanServer;
}
public static Set<ObjectInstance> queryMBeans(String objectName) {
try {
if (getMBeanServer() != null) {
return getMBeanServer().queryMBeans(new ObjectName(objectName), null);
}
} catch (Exception e) {
log.error("", e);
}
return null;
}
public static Map<String, Object> getAttributes(String objectName, List<String> attrNameList) {
try {
return getAttributes(new ObjectName(objectName), attrNameList);
} catch (Exception e) {
log.error("", e);
}
return null;
}
public static Map<String, Object> getAttributes(ObjectName objectName, List<String> attrNameList) {
Map<String, Object> result = new HashMap<String, Object>();
if (CollectionUtils.isEmpty(attrNameList) || getMBeanServer() == null) {
return result;
}
try {
AttributeList attributeList = getMBeanServer().getAttributes(objectName, attrNameList.toArray(new String[] {}));
for (int i = 0; i < attrNameList.size(); i++) {
result.put(attrNameList.get(i), i < attributeList.size() ? ((Attribute) attributeList.get(i)).getValue() : "");
}
return result;
} catch (Exception e) {
log.error("", e);
}
return result;
}
private Map<String, Map<String, Object>> getDruidDataSourceStatus() {
Map<String, Map<String, Object>> druidDataSourceMap = new HashMap<String, Map<String, Object>>();
try {
// 结果数量不固定并且ID每次随机,使用带通配符的ObjectName进行批量查询
Set<ObjectInstance> druidDataSourceSet = queryMBeans("com.alibaba.druid:type=DruidDataSource,id=*");
if (druidDataSourceSet != null) {
// 一个数据源一组attribute
druidDataSourceSet.stream().forEach(objectInstance -> {
Map<String, Object> druidDataSource = new HashMap<String, Object>();
/**
* 需要获取到的监控项
*
* druid更多相关监控项可以在com.alibaba.druid.pool.DruidDataSource中查看(注意使用时需要大写驼峰格式)
*/
List<String> attrList = new ArrayList<String>() {
{
// 数据源用户名
add("Username");
// 最大连接池数量
add("MaxActive");
// 最小连接池数量
add("MinIdle");
// 连接池中空闲连接数
add("PoolingCount");
// 连接池中正在使用的连接数
add("ActiveCount");
}
};
druidDataSource = getAttributes(objectInstance.getObjectName(), attrList);
druidDataSourceMap.put(String.valueOf(druidDataSource.get("Username")), druidDataSource);
});
}
} catch (Exception e) {
log.error("", e);
}
return druidDataSourceMap;
}
public void print() {
log.info("druid dataSource status: {}", GsonUtils.toJson(getDruidDataSourceStatus()));
}
}
运行结果:
2020-03-09 08:24:30,002 druid dataSource status: {"db1":{"Username":"db1","MaxActive":20,"MinIdle":5,"ActiveCount":0,"PoolingCount":5},"db2":{"Username":"db2","MaxActive":20,"MinIdle":10,"ActiveCount":1,"PoolingCount":9}}
总结
很多开源中间件都会使用Management方式进行自定义扩展,记录相关运行状况数据;例如Kafka、logback等,都可以使用类似方式获取数据。
参考资料:
https://www.jianshu.com/p/5d854245051d
https://baike.baidu.com/item/java.lang.management/5179868?fr=aladdin
http://www.voidcn.com/article/p-cwctaeex-bsq.html
https://zimt8.com/questions/16275207/
https://docs.oracle.com/javase/tutorial/jmx/mbeans/mxbeans.html
https://docs.oracle.com/javase/8/docs/api/java/lang/management/package-summary.html
- 点赞
- 收藏
- 关注作者
评论(0)