JVM 的基本知识常用的也就是类加载机制, 内存区域, 分配, OOM,GC,JVM 参数调优
几个链接自己看:
内存区域 & 类加载机制
分配策略 & 垃圾回收算法, 收集器
今天结合代码讲一讲常用的 java 自带工具讲解, 这些命令一般都是 jdk/lib/tools.jar 中. 用来监控诊断我们的 Java 环境.
官方说明: https://docs.oracle.com/en/java/javase/11/tools/
1. jps
显示当前用户的所有 java 进程的 PID 以及主类名
jps : 显示当前用户的所有 java 进程的 PID 以及主类名
jps -v : 打印传递给 Java 虚拟机的参数(如 - XX:+UnlockExperimentalVMOptions -XX:+UseZGC)
jps -m : 打印传递给主类的参数
jps -l : 打印模块名以及包名
默认开启(UsePerfData), 若加上 - XX:-UsePerfData 则无法找到进程.
2. jstack
功能 jstack 仅会打印线程的栈轨迹, 线程状态 (BLOCKED), 持有的锁(locked...) 以及正在请求的锁(waiting to lock ...), 而且还会分析出具体的死锁.
jstack pid : 查看线程情况
jstack -F pid : 正常输出不被响应时, 使用该指令
jstack -l pid : 除堆栈外, 显示关于锁的附件信息
3. jstat
功能 允许用户查看目标 Java 进程的类加载, 即时编译以及垃圾回收相关的信息. 它常用于检测垃圾回收 GC 问题以及内存泄问题.
显示进程中的类装载, 内存, 垃圾收集, JIT 编译等运行数据.
常用指令
jstat -class pid : 打印类装载, 类卸载, 总空间以及所消耗的时间
jstat -compiler pid : 打印即时编译相关的数据
jstat -printcompilation pid : 打印即时编译相关的数据
jstat -gc pid 1s 20 : 查询垃圾收集情况, 每 1 秒查询一次, 一共查询 20 次.
jstat -gccause pid : 额外输出上次 GC 原因
... 剩下的都是以 - gc 为前缀的子命令, 它们将打印垃圾回收相关的数据.
加上 -t 参数 每数据之前打印目标 Java 进程的启动时间
我们可以看到, 这两个 Survivor 区的容相等, 而且始终有一个 Survivor 区的内存使用为 0.
在这种情况下, Java 虚拟机会将这块内存区域回收, 并标记为可分配的状态. 这样子做的结果是, 堆中可能完全没有 Survivor 内存区域, 因而相应的 S1C 和 S1U 将会是 0.
我们可以比较 Java 进程的启动时间以及总 GC 时间(GCT ), 或者两次测的间隔时间以及总 GC 时间的增, 来得出 GC 时间占运时间的比.
如果该比超过 20%, 则说明目前堆的压较大; 如果该比超过 90%, 则说明堆几乎没有可用空间, 随时都可能抛出 OOM 异常.
jstat 还可以用来判断是否出现内存泄. 在长时间运的 Java 程序中, 我们可以运 jstat 命令连续获取多性能数据, 并取这几数据中 OU (即已占用的代内存) 的最小值.
然后, 我们每隔一段较长的时间重复一次上述操作, 来获得多组 OU 最小值. 如果这些值呈上涨趋势, 则说明该 Java 程序的代内存已使用在断上涨, 这意味着无法回收的对象在断增加, 因此很有可能存在内存泄.
CGC 和 CGCT, 它们分别代表并发 GC Stop-The-World 的次数和时间.
S0C: 年轻代中第一个 survivor(幸存区)的容量 (kb)
S1C: 年轻代中第二个 survivor(幸存区)的容量 (kb)
S0U: 年轻代中第一个 survivor(幸存区)目前已使用空间 (kb)
S1U: 年轻代中第二个 survivor(幸存区)目前已使用空间 (kb)
EC: 年轻代中 Eden(伊甸园)的容量 (kb)
EU: 年轻代中 Eden(伊甸园)目前已使用空间 (kb)
OC: 老年代的容量 (kb)
OU: 老年代目前已使用空间 (kb)
MC: 元空间的容量 (kb)
MU: 元空间目前已使用空间 (kb)
CCSC: 压缩类的容量 (kb)
CCSU: 压缩类目前已使用空间 (kb)
YGC: 年轻代垃圾回收次数
YGCT: 年轻代垃圾回收消耗时间
FGC: 老年代垃圾回收次数
FGCT: 老年代垃圾回收消耗时间
GCT: 垃圾回收消耗总时间
4. jmap
功能 生成堆转储快照(heapdump) 用户统计目标 Java 进程的堆中存放的 Java 对象, 并将它们导出成二进制文件. 查询 Java 堆和永久代的详细信息, 使用率, 使用大小, 查询 finalize 执行队列的信息
常用指令
jmap -heap pid : 打印 jvm heap 的情况
jmap -histo pid : 打印 jvm heap 的直方图. 其输出信息包括类名, 对象数量, 对象占用大小. 并按照内存使用从多至少的顺序排
jmap -histo:live pid : JVM 会先触发 gc, 然后再统计信息, 只统计堆中的存活对象的情况
jmap -dump:format=b,file=map.log pid: 将内存使用的详细情况输出到文件, 之后一般使用其他工具进行分析. 同样,-dump:live 只保存堆中的存活对象.
jmap -clstats pid : 打印被加载类的信息
jmap -finalizerinfo pid : 该子命令将打印所有待 finalize 的对象.
jmap -permstat pid : 打印 permanent generation heap 情况
我们通常会用 jmap -dump:live,format=b,file=filename.bin 命令, 将堆中所有存活对象导出至一个文件之中.
这 format=b 将使 jmap 导出与 hprof(在 Java 9 中已被移除),-XX:+HeapDumpAfterFullGC,-XX:+HeapDumpOnOutOfMemoryError 格式一致的文件. 这种格式的文件可以被其他 GUI 工具查看.
jmap(以及 jinfo,jstack 和 jcmd)依赖于 Java 虚拟机的 Attach API, 因此只能监控本地 Java 进程.
一旦开启 Java 虚拟机参数 DisableAttachMechanism(即使用参数 - XX:+DisableAttachMechanism), 基于 Attach API 的命令将无法执. 反过来说, 如果你想被其他进程监控, 那么你需要开启该参数.
5. jhat
功能 一般与 jmap 搭配使用, 用来分析 jmap 生成的堆转储文件.
由于有很多可视化工具 (Eclipse Memory Analyzer ,IBM HeapAnalyzer) 可以替代, 所以很少用. 不过在没有可视化工具的机器上也是可用的.
常用指令
jmap -dump:format=b,file=map.log pid : 将内存使用的详细情况输出到文件
jhat map.log : 解析 Java 堆转储文件, 并启动一个 web server
演示: https://www.cnblogs.com/baihuitestsoftware/articles/6406271.html
6. jinfo
功能 打印目标 Java 进程的配置参数
实时查看和调整虚拟机参数, 可以显示未被显示指定的参数的默认值(jps -v 则不能).
jinfo pid : 可用来查看目标 Java 进程的参数, 如传递给 Java 虚拟机的 - X(即输出中的 jvm_args),-XX 参数(即输出中的 VM Flags), 以及可在 Java 层面通过 System.getProperty 获取的 - D 参数(即输出中的 System Properties).
7. jcmd
可以用来实现前面除 jstat 之外所有命令的功能.
详见 : https://www.jianshu.com/p/388e35d8a09b
8. javap
javap 是一个能够将 class 文件反汇编成人类可读格式的工具. ASM 字节码操作: https://blog.csdn.net/ohcezzz/article/details/78416176
默认情况下 javap 会打印所有非私有的字段和方法,
-p 打印私有的字段和方法.
-v 打印所有信息.
-c 查阅方法对应的字节码
附:
一只懂 JVM 参数的狐狸 http://xxfox.perfma.com/
代码验证
- @Slf4j
- public class JvmTest {
- private byte[] memory;
- public JvmTest(byte[] memory) {
- this.memory = memory;
- }
- public static void main(String[] args) throws InterruptedException {
GC 测试();
- // 死循环();
- // 死锁();
- }
public static void GC 测试() throws InterruptedException {
- for (int i = 1; i <5; i++) {
- byte[] b = new byte[50 * 1024 * 1024];
- log.info("分配了 50M 空间给数组");
- Thread.sleep(10000);
- }
- // 方法区中常量引用对象 (虚拟机栈 (栈帧中的局部变量) 中引用的对象 - 方法区中的静态变量引用的对象 - 本地方法栈中 JNI(即一般说的 Native 方法)中引用的对象)
- JvmTest jvmTest = new JvmTest(new byte[50 * 1024 * 1024]);
- log.info("分配了 50M 空间给对象");
- log.info("调用了 System.gc()");
- System.gc();
- jvmTest = null;
- Thread.sleep(10000);
- log.info("调用了 System.gc()");
- System.gc();
- Thread.sleep(3000000);
- }
public static void 死循环() {
- while (true) {
- }
- }
public static void 死锁() {
- String obj1 = "obj1";
- String obj2 = "obj2";
- Runnable r1 = () -> {
- log.info("r1 running");
- while (true) {
- synchronized (obj1) {
- log.info("r1 lock obj1");
- try {
- // 获取 obj1 后先等一会儿, 让 Lock2 有足够的时间锁住 obj2
- Thread.sleep(3000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- synchronized (obj2) {
- log.info("r1 lock obj2");
- }
- }
- }
- };
- Runnable r2 = () -> {
- log.info("r2 running");
- while (true) {
- synchronized (obj2) {
- log.info("r2 lock obj2");
- try {
- Thread.sleep(3000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- synchronized (obj1) {
- log.info("r2 lock obj1");
- }
- }
- }
- };
- Thread a = new Thread(r1);
- Thread b = new Thread(r2);
- a.start();
- b.start();
- }
- }
来源: https://www.cnblogs.com/loveincode/p/10577550.html