黑马程序员技术交流社区
标题: 【上海校区】如何开发一款java应用运行时的监控程序? [打印本页]
作者: 孤尽 时间: 2018-11-1 20:30
标题: 【上海校区】如何开发一款java应用运行时的监控程序?
如何开发一款java应用运行时的监控程序?
前言 每个程序员都或多或少遇到过相当多的疑难杂症问题排查的时刻。我自己也是工作中遇到许多稀奇古怪的问题。最开始我们排查问题使用的是jprofiler。特别是使用jprofiler来排查调用链路的耗时问题。
前言每个程序员都或多或少遇到过相当多的疑难杂症问题排查的时刻。我自己也是工作中遇到许多稀奇古怪的问题。最开始我们排查问题使用的是jprofiler。特别是使用jprofiler来排查调用链路的耗时问题。如下图所示:
但是jprofiler只能用于排查一些本地的问题。对于一些生产环境的由于网络隔离在加上权限受限, jprofiler就不是那么好使了。这时候萌生了自己做个小工具的想法。同时参考了一些工具和apm的实现, 简单实现了所需的功能。
我们现在思考下, 假设要开发一个java程序的监控工具,比如包含以下功能, 都需要怎么实现?
[AppleScript] 纯文本查看 复制代码
1. 实时或周期性的获取java进程运行数据, 包括但不限于内存,线程,操作系统,GC等。
2. 如何在运行时知道一个class是被哪个classloader加载的?
3. 如何动态的知道一个方法的执行时间?(对于基础的排查性能问题很有用)
4. 如何动态知道一个方法被调用时候的完整调用栈?
5. 如何动态的知道一次调用下一个方法的入参,返回值?
...
基础部分在这个【基础部分】里, 我们可以很轻松的解决上边的问题1。这要感谢JDK5后提供的两大神器:Instrument和management。前置提供了应用程序之外的代理(agent)程序来监测和协助运行在JVM上的应用程序, 后者可以实时获取应用程序的实时运行数据。
Agent我们遇到的第一个问题, 是如何将自己的监控程序和目标进程关联起来。
比如我1个monitor.jar,里边包含了我们的监控程序, 如何和生产环境正在运行的tomcat进程进行关联?
答案是JDK提供的agent机制。简单来说只需要做以下事情:
[JavaFX] 纯文本查看 复制代码
1. 监控代码的jar中包含Agent-Class属性。该值的名字是自定义的agent类。
2. 该类必须实现如下的方法:
public static void agentmain(String agentArgs, Instrumentation inst);
3. 使用VirtualMachine vm = VirtualMachine.attach(targetPid)关联到目标进程
其中Instrumentation非常重要, 后续还会说明。关于Agent-Class属性可以通过maven-assembly-plugin插件来设置:
[Java] 纯文本查看 复制代码
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>single</goal>
</goals>
<phase>package</phase>
<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
<archive>
<manifestEntries>
<Premain-Class>xxx</Premain-Class>
<Agent-Class>xxx</Agent-Class>
<Can-Redefine-Classes>true</Can-Redefine-Classes>
<Can-Retransform-Classes>true</Can-Retransform-Classes>
</manifestEntries>
</archive>
</configuration>
</execution>
</executions>
</plugin>
Instrumentation核心内容都在java.lang.instrumentation包下, 主要的类有:
[Java] 纯文本查看 复制代码
public interface Instrumentation {
....
java.lang.instrument.Instrumentation
java.lang.instrument.ClassFileTransformer
....
}
ClassFileTransformer提供了类的转换功能, 可以对字节码进行修改。 而Instrumentation除了可以管理ClassFileTransformer之外, 还有一些其他功能。比如:
[Java] 纯文本查看 复制代码
getAllLoadedClasses()
该方法可以获取当前虚拟机加载的所有Class对象。记住是所有。那在这里问题2就很好解决了。
我只需要遍历这个结果集和给定的名字是否匹配即可。再通过getClassLoader()可以获取这个类到底被谁加载了。
同时, getProtectionDomain().getCodeSource().getLocation().getFile() 还可以获取到当前类
的具体路径, 对于排查问题会更有帮助。
线程使用情况
[Java] 纯文本查看 复制代码
java.lang.management.ThreadMXBean
以下方法比较有用:
getThreadCount() //线程数
getDaemonThreadCount() //daemon线程数
getPeakThreadCount() //峰值
getTotalStartedThreadCount() //启动过的线程数
操作系统
[Java] 纯文本查看 复制代码
java.lang.management.MemoryMXBean
java.lang.management.MemoryManagerMXBean
垃圾回收
[Java] 纯文本查看 复制代码
java.lang.management.GarbageCollectorMXBean
以下方法比较有用:
getName() // 收集器英文名称
getCollectionCount() // 该收集器收集总次数
getCollectionTime() // 该收集器收集总时间(ms)
注意:会有多个GarbageCollectorMXBean。
编译器
[Java] 纯文本查看 复制代码
java.lang.management.CompilationMXBean
以下方法比较有用:
getName() //返回JIT编译器名称
getTotalCompilationTime() //返回在编译上花费的累积耗费时间的近似值(以毫秒为单位)
注意:需要调用isCompilationTimeMonitoringSupported方法来确定是否支持编译期的监控。
类加载
[Java] 纯文本查看 复制代码
java.lang.management.ClassLoadingMXBean
以下方法比较有用:
getLoadedClassCount() //返回当前加载到 Java 虚拟机中的类的数量。
getTotalLoadedClassCount() //返回自 Java 虚拟机开始执行到目前已经加载的类的总数。
getUnloadedClassCount() //返回自 Java 虚拟机开始执行到目前已经卸载的类的总数。
isVerbose() //测试是否已为类加载系统启用了 verbose 输出。
运行时数据
[Java] 纯文本查看 复制代码
java.lang.management.RuntimeMXBean
以下方法比较有用:
getName() //返回表示正在运行的 Java 虚拟机的名称
getStartTime() //返回 Java 虚拟机的启动时间
getManagementSpecVersion() //返回正在运行的 Java 虚拟机实现的管理接口的规范版本。
getSpecName() //返回 Java 虚拟机规范名称。
getSpecVendor() //返回 Java 虚拟机规范供应商。
getSpecVersion() //返回 Java 虚拟机规范版本。
getVmName() //返回 Java 虚拟机实现名称。本机为Java HotSpot(TM) 64-Bit Server VM
getVmVendor() //返回 Java 虚拟机实现供应商
getVmVersion() //返回 Java 虚拟机实现版本
getInputArguments() //返回传入的JVM启动参数
getClassPath() //返回类路径
getBootClassPath() //返回bootstrap的path
进阶class文件格式对于理解JVM和深入理解Java语言, 学习并了解class文件的格式都是必须要掌握的功课。 原因很简单, JVM不会理解我们写的Java源文件, 我们必须把Java源文件编译成class文件, 才能被JVM识别, 对于JVM而言, class文件相当于一个接口, 理解了这个接口, 能帮助我们更好的理解JVM的行为;另一方面, class文件以另一种方式重新描述了我们在源文件中要表达的意思, 理解class文件如何重新描述我们编写的源文件, 对于深入理解Java语言和语法都是很有帮助的。
[Java] 纯文本查看 复制代码
https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html
会话保持会话保持有2个重要的参数。
- 会话保持时间, 常量。一般定义为10分钟就够了。
- 触摸时间, 每次发起请求会更新触摸时间
可以后台起daemon线程, 周期性检查触摸时间和当前时间的差值是否超过了会话保持时间。如果超过需要关闭连接。
通信协议既然确定了网络通信使用nio, 那我们务必要制定一套简单的通信协议, 能简明的告知服务端和客户端请求响应信息。这里我们拿dubbo来举例:
包装写到这里, 核心的内容应差不多了。剩下的就是一些边角料。但是可以使你的project更加优雅和健全。
命令行解析解析来自于命令行的参数并不是一件特别容易的事情。但是好在有优秀的工具:
当然这里同样要考虑自己项目的复杂度, 我们的目标是尽可能做一个精简的监控程序。
作者: 不二晨 时间: 2018-11-7 09:04
ヾ(◍°∇°◍)ノ゙
作者: 魔都黑马少年梦 时间: 2018-11-8 17:01
欢迎光临 黑马程序员技术交流社区 (http://bbs.itheima.com/) |
黑马程序员IT技术论坛 X3.2 |