《菜鸟读并发》多线程程序问题如何调试?

2020年2月28日 17点热度 0条评论 来源: 大师兄啊
  1. jstat
  2. jstack
  3. jmap
  4. netstat(非jvm命令)
  1. jstat 查看Jvm的堆栈信息

    能够查看eden,survivor,old,perm等heap的capacity,utility信息,对于查看系统是不是有能存泄漏以及参数设置是否合理有不错的意义

  2. jstack 查看jvm当前的thread dump的

    可以看到当前Jvm里面的线程状况,这个对于查找blocked线程比较有意义

  3. jmap .

    查看jvm当前的heap dump的,可以看出当前jvm中各种对象的数量,所占空间等等。

    尤其值得一提的是这个命令可以到处一份binary heap dump的bin文件,这个文件能够直接用

    Eclipse Memory Anayliser来分析,并找出潜在的内存泄漏的地方。

  4. 还有一个比较有用的非jvm命令–netstat

    通过这个命令可以看到linux系统当前在各个端口的链接状态,比如查看数据库连接数等等

jstat

jstat分析的内容(指标)

新生代对象增长的速率
Young GC的触发频率
Young GC的耗时
老年代对象增长的速率 Full
GC的触发频率
Full GC的耗时
每次Young GC后有多少对象是存活下来的
每次Young GC过后有多少对象进入了老年代

目的

  1. 通过这些指标,我们可以轻松的分析当前系统的运行情况
  2. 判断当前系统的内存使用压力以及GC压力
  3. 还有就是内存分配是否合理,为可能要做的优化做准备

常用的jstat命令

jps:找出你们的Java进程的PID
jstat -gc pid:查看Java进程(JVM)的内存和GC情况

可以显示gc的信息,查看gc的次数,及时间等。

运行这个命令之后会看到如下列:

  1. SOC:这是From Survivor区的大小.

  2. S1C:这是To Survivor区的大小

  3. SOU:这是From Survivor区当前使用的内存大小

  4. S1U:这是To Survivor区当前使用的内存大小

  5. EC:这是Eden区的大小

  6. EU:这是Eden区当前使用的内存大小

  7. OC:这是老年代的大小

  8. OU:这是老年代当前使用的内存大小

  9. MC:这是方法区(永久代、元数据区)的大小

  10. MU:这是方法区(永久代、元数据区)的当前使用的内存大小

  11. YGC:这是系统运行迄今为止的Young GC次数

  12. YGCT:这是Young GC的耗时

  13. FGC:这是系统运行迄今为止的Full GC次数

  14. FGCT:这是Full GC的耗时

  15. GCT:这是所有GC的总耗时

其中最重要的是最后五项,分别是young gc的次数,young gc的时间,full gc的次数,full gc的时间,gc的总时间

jstat -gccapacity pid:堆内存分析

可以显示,VM内存中三代(young,old,perm)对象的使用和占用大小,

如:PGCMN显示的是最小perm的内存使用量,PGCMX显示的是perm的内存最大使用量,PGC是当前新生成的perm内存占用量,PC是但前perm内存占用量。其他的可以根据这个类推,
OC是old内纯的占用量。

jstat -gcnewcapacity pid:年轻代对象的信息及其占用量
stat -gcoldcapacity pid:old代对象的信息及其占用量
jstat -gcpermcapacity pid:perm对象的信息及其占用量
jstat -gcnew pid:年轻代对象的信息
jstat -gcold pid:old代对象的信息
jstat -gcutil pid: 统计gc信息统计
jstat -class pid:显示加载class的数量,及所占空间等信息
jstat -compiler pid:显示VM实时编译的数量等信息
stat -printcompilation pid: 当前VM执行的信息

一些术语的中文解释:

  1. SOC:这是From Survivor区的大小.

  2. S1C:这是To Survivor区的大小

  3. SOU:这是From Survivor区当前使用的内存大小

  4. S1U:这是To Survivor区当前使用的内存大小

  5. EC:这是Eden区的大小

  6. EU:这是Eden区当前使用的内存大小

  7. OC:这是老年代的大小

  8. OU:这是老年代当前使用的内存大小

  9. MC:这是方法区(永久代、元数据区)的大小

  10. MU:这是方法区(永久代、元数据区)的当前使用的内存大小

  11. YGC:这是系统运行迄今为止的Young GC次数

  12. YGCT:这是Young GC的耗时

  13. FGC:这是系统运行迄今为止的Full GC次数

  14. FGCT:这是Full GC的耗时

  15. GCT:这是所有GC的总耗时

  16. NGCMN:年轻代(young)中初始化(最小)的大小 (字节)

  17. NGCMX:年轻代(young)的最大容量 (字节)

  18. NGC:年轻代(young)中当前的容量 (字节)

  19. OGCMN:old代中初始化(最小)的大小 (字节)

  20. OGCMX:old代的最大容量 (字节)

  21. OGC:old代当前新生成的容量 (字节)

  22. PGCMN:perm代中初始化(最小)的大小 (字节)

  23. PGCMX:perm代的最大容量 (字节)

  24. PGC:perm代当前新生成的容量 (字节)

  25. S0:年轻代中第一个survivor(幸存区)已使用的占当前容量百分比

  26. S1:年轻代中第二个survivor(幸存区)已使用的占当前容量百分比

  27. E:年轻代中Eden(伊甸园)已使用的占当前容量百分比

  28. O:old代已使用的占当前容量百分比

  29. P:perm代已使用的占当前容量百分比

  30. S0CMX:年轻代中第一个survivor(幸存区)的最大容量 (字节)

  31. S1CMX:年轻代中第二个survivor(幸存区)的最大容量 (字节)

  32. ECMX:年轻代中Eden(伊甸园)的最大容量 (字节)

  33. DSS:当前需要survivor(幸存区)的容量 (字节)(Eden区已满)

  34. TT:持有次数限制

  35. MTT :最大持有次数限制

使用
分析线上的JVM进程,最想要知道的信息包括如下

  • 新生代对象增长的速率
  • Young GC的触发频率
  • Young GC的耗时
  • 老年代对象增长的速率
  • Full GC的触发频率 Full
  • GC的耗时
  • 每次Young GC后有多少对象是存活下来的
  • 每次Young GC过后有多少对象进入了老年代

根据 GC优化jvm,合理分配内存空间,尽可能让对象留在年轻代不进入老年代,避免发生频繁的Full GC。这就是对JVM最好的性能优化了!

查看新生代对象增长速率: jstat -gc PID 1000 10

  • 他的意思就是每隔1秒钟更新出来最新的一行jstat统计信息,一共执行10次jstat统计
  • 通过这个命令,你可以非常灵活的对线上机器通过固定频率输出统计信息,观察每隔一段时间的jvm中的Eden区对象占用变化
  • 比如给大家举个例子,执行这个命令之后,第一秒先显示出来Eden区使用了200MB内存,第二秒显示出来的那行统计信息里,Eden区使用了205MB内存,第三秒显示出来的那行统计信息里,发现Eden区使用 了209MB内存,以此类推
  • 此时你可以轻易的推断出来,这个系统大概每秒钟会新增5MB左右的对象。
  • 而且这里大家可以根据自己系统的情况灵活多变的使用,比如你们系统负载很低,不一定每秒都有请求,那么可以把上面的1秒钟调整为1分钟,甚至10分钟,去看你们系统每隔1分钟或者10分钟大概增长多少对象
  • 还有就是一般系统都有高峰和日常两种状态,比如系统高峰期用的人很多,此时你就应该在系统高峰期去用上述命令看看高峰期的对象增长速率。然后你再得在非高峰的日常时间段内看看对象的增长速率

每次Young GC的平均耗时

  • jstat会 告诉你迄今为止系统已经发生了多少次Young GC以及这些Young GC的总耗时。
  • 比如系统运行24小时后共发生了260次Young GC,总耗时为20s。 那么平均下来每次Young GC大概就耗时几十毫秒的时间。

Young GC的触发频率和每次耗时

  • 因为系统高峰和日常时候的对象增长速率你都知道了,那么非常简单就可以推测出来高峰期多久发生一次Young GC,日常期多久发生一次Young GC。
  • 比如你Eden区有800MB内存,那么发现高峰期每秒新增5MB对象,大概高峰期就是3分钟会触发一次Young GC。日常期每秒新增0.5MB对象,那么日常期大概需要半个小时才会触发一次Young GC。

每次Young GC后有多少对象会存活下来,以及有多少对象会进入老年代

  • 每次Young GC后有多少对象会存活下来?
    每次Young GC过后有多少对象会存活下来,这个没法直接看出来,但是有办法可以大致推测出来。
    之前我们已经推算出来高峰期的时候多久发生一次Young GC,比如3分钟会有一次Young GC
    那么此时我们可以执行下述jstat命令: jstat -gc PID 180000 10。这就相当于是让他每隔三分钟执行一次统计,连续执行10次。
    可以观察下,每隔三分钟之后发生了一次Young GC,此时Eden、 Survivor、 老年代的对象变化。
    正常来说,Eden区 肯定会在几乎放满之后重新变得里面对象很少,比如800MB的空间就使用了几十MB。Survivor区肯定会放入一些存活对象,老年代可能会增长一些对象占用。

  • 观察老年代的对象增长速率
    从一个正常的角度来看,老年代的对象是不太可能不停的快速增长的,因为普通的系统其实没那么多长期存活的对象
    比如每隔3分钟一次Young GC,每次会有50MB对象进入老年代, 这就是年代对象的增长速率,每隔3分钟增长50MB。
    如果你发现比如每次Young GC过后,老年代对象都要增长几十MB,那很有可能就是你一次Young GC过后存活对象太多了
    存活对象太多,可能导致放入Survivor区域之后触发了动态年龄判定规则进入老年代,也可能是Survivor区域放不下了,所以大部分存活对象进入老年代。
    如果你的老年代每次在Young GC过后就新增几百KB,或者几MB的对象,这个还算情有可缘,但是如果老年代对象快速增长,那一定是不正常的。

  • 实际上Survivor区域里的和进入老年代的对象,都是存活的。

Full GC的触发时机和耗时

  • 知道了老年代对象的增长速率,那么Full GC的触发时机就很清晰了,比如老年代总共有800MB的内存,每隔3分钟新增50MB对象,那么大概每小时就会触发一次Full GC。
  • 然后可以看到jstat打印出来的系统运行起劲为止的Full GC次数以及总耗时,比如一共执行了10次Full GC,共耗时30s,每次Full GC大概就是需要耗费3s左右。

多线程程序问题如何调试?

  • 出了 Bug 基本上都是靠日志,靠线程dump来跟踪问题
  • 分析线程dump的一个基本功就是分析线程状态,大部分的死锁、饥饿、活锁问题都需要跟踪分析线程的状态。
  • 你可以通过jstack命令或者VisualVM这个可视化工具将JVM所有的线程栈信息导出来,完整的线程栈信息不仅包括线程的当前状态、调用栈,还包括了锁的信息
  • 例如一个死锁的程序,导出的线程栈明确告诉我发生了死锁,并且将死锁线程的调用栈信息清晰地显示出来了
  • 导出线程栈,分析线程状态是诊断并发问题的一个重要工具

top命令

可以通过top命令查看各个进程的cpu使用情况,默认按cpu使用率排序

上图中可以看出pid为23344的java进程占用了较多的cpu资源;

通过top -Hp 23344可以查看该进程下各个线程的cpu使用情况


上图中可以看出pid为25077的线程占了较多的cpu资源

jstack命令

jstack用于打印出给定的Java进程ID,线程pid或core file或远程调试服务的Java堆栈信息,如果是在64位机器上,需要指定选项"-J-d64",Windows的jstack使用方式只支持以下的这种方式:jstack [-l] pid

如果java程序崩溃生成core文件,jstack工具可以用来获得core文件的java stack和native stack的信息,从而可以轻松地知道java程序是如何崩溃和在程序何处发生问题。

另外,jstack工具还可以附属到正在运行的java程序中,看到当时运行的java程序的java stack和native stack的信息, 如果现在运行的java程序呈现hung的状态,jstack是非常有用的。

jstack命令可以指定pid和输出的文件位置,主要用来获取thread的dump线程信息,包含了JVM中所有存活的线程,为了分析指定线程,必须找出对应线程的调用栈

步骤:

  1. 首先在top命令中,已经获取到了占用cpu资源较高的线程pid
  2. 将该pid转成16进制的值,在thread dump中每个线程都有一个nid,找到对应的nid即可;
  3. 隔段时间再执行一次jstack命令获取thread dump,区分两份dump是否有差别
  4. 在nid=0x246c的线程调用栈中,发现该线程一直在执行JstackCase类第33行的calculate方法,得到这个信息,就可以检查对应的代码是否有问题

需要注意的问题:

  • 不同的 JAVA虚机的线程 DUMP的创建方法和文件格式是不一样的,不同的 JVM版本, dump信息也有差别。

  • 在实际运行中,往往一次 dump的信息,还不足以确认问题。建议产生三次 dump信息,如果每次dump都指向同一个问题,我们才确定问题的典型性

命令格式:

$jstack [ option ] pid

$jstack [ option ] executable core

$jstack [ option ] [server-id@]remote-hostname-or-IP

参数说明:

pid: java应用程序的进程号,一般可以通过jps来获得;

executable:产生core dump的java可执行程序;

core:打印出的core文件;

remote-hostname-or-ip:远程debug服务器的名称或IP;

server-id: 唯一id,假如一台主机上多个远程debug服务;

示例:

$jstack –l 23561

线程分析:

一般情况下,通过jstack输出的线程信息主要包括:jvm自身线程、用户线程等。其中jvm线程会在jvm启动时就会存在。对于用户线程则是在用户访问时才会生成。

l jvm线程:

在线程中,有一些 JVM内部的后台线程,来执行譬如垃圾回收,或者低内存的检测等等任务,这些线程往往在JVM初始化的时候就存在,如下所示:

"Attach Listener" daemon prio=10 tid=0x0000000052fb8000 nid=0xb8f waiting on condition [0x0000000000000000]

java.lang.Thread.State: RUNNABLE

Locked ownable synchronizers:

    - None

destroyJavaVM" prio=10 tid=0x00002aaac1225800 nid=0x7208 waiting on condition [0x0000000000000000]

java.lang.Thread.State: RUNNABLE

Locked ownable synchronizers:

    - None

l 用户级别的线程

还有一类线程是用户级别的,它会根据用户请求的不同而发生变化。该类线程的运行情况往往是我们所关注的重点。而且这一部分也是最容易产生死锁的地方。

"qtp496432309-42" prio=10 tid=0x00002aaaba2a1800 nid=0x7580 waiting on    condition [0x00000000425e9000]
java.lang.Thread.State: TIMED_WAITING (parking)

    at sun.misc.Unsafe.park(Native Method)

    - parking to wait for  <0x0000000788cfb020> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)

    at java.util.concurrent.locks.LockSupport.parkNanos(LockSupport.java:198)

    at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.awaitNanos(AbstractQueuedSynchronizer.java:2025)

    at org.eclipse.jetty.util.BlockingArrayQueue.poll(BlockingArrayQueue.java:320)

    at org.eclipse.jetty.util.thread.QueuedThreadPool$2.run(QueuedThreadPool.java:479)

    at java.lang.Thread.run(Thread.java:662)

Locked ownable synchronizers:

    - None

从上述的代码示例中我们可以看到该用户线程的以下几类信息:

Ø 线程的状态:waiting on condition(等待条件发生)

Ø 线程的调用情况;

Ø 线程对资源的锁定情况;

线程的状态分析:

正如我们刚看到的那样,线程的状态是一个重要的指标,它会显示在线程每行结尾的地方。那么线程常见的有哪些状态呢?线程在什么样的情况下会进入这种状态呢?我们能从中发现什么线索?

通过thread dump分析线程状态

除了上述的分析,大多数情况下会基于thead dump分析当前各个线程的运行情况,如是否存在死锁、是否存在一个线程长时间持有锁不放等等。

在dump中,线程一般存在哪几种状态:
  • 1)RUNNABLE,线程处于执行中
    该状态表示线程具备所有运行条件,在运行队列中准备操作系统的调度,或者正在运行。

  • 2)BLOCKED,线程被阻塞

  • 3)Waiton condition
    该状态出现在线程等待某个条件的发生。具体是什么原因,可以结合stacktrace来分析。最常见的情况是线程在等待网络的读写,比如当网络数据没有准备好读时,线程处于这种等待状态,而一旦有数据准备好读之后,线程会重新激活,读取并处理数据。在 Java引入 NIO之前,对于每个网络连接,都有一个对应的线程来处理网络的读写操作,即使没有可读写的数据,线程仍然阻塞在读写操作上,这样有可能造成资源浪费,而且给操作系统的线程调度也带来压力。在 NIO里采用了新的机制,编写的服务器程序的性能和可扩展性都得到提高。
    如果发现有大量的线程都在处在 Wait on condition,从线程 stack看, 正等待网络读写,这可能是一个网络瓶颈的征兆。因为网络阻塞导致线程无法执行。一种情况是网络非常忙,几乎消耗了所有的带宽,仍然有大量数据等待网络读写;另一种情况也可能是网络空闲,但由于路由等问题,导致包无法正常的到达。所以要结合系统的一些性能观察工具来综合分析,比如 netstat统计单位时间的发送包的数目,如果很明显超过了所在网络带宽的限制 ; 观察 cpu的利用率,如果系统态的 CPU时间,相对于用户态的 CPU时间比例较高;如果程序运行在 Solaris 10平台上,可以用 dtrace工具看系统调用的情况,如果观察到 read/write的系统调用的次数或者运行时间遥遥领先;这些都指向由于网络带宽所限导致的网络瓶颈。
    另外一种出现 Wait on condition的常见情况是该线程在 sleep,等待 sleep的时间到了时候,将被唤醒。

  • 4)Waiton condition :Waitingfor monitor entry 和 in Object.wait()
    在多线程的 JAVA程序中,实现线程之间的同步,就要说说Monitor。Monitor是Java中用以实现线程之间的互斥与协作的主要手段,它可以看成是对象或者 Class的锁。每一个对象都有,也仅有一个 monitor。下面这个图,描述了线程和 Monitor之间关系,以及线程的状态转换图:

    从图中可以看出,每个 Monitor在某个时刻,只能被一个线程拥有,该线程就是 “Active Thread”,而其它线程都是 “Waiting Thread”,分别在两个队列 “ Entry Set”和 “Wait Set”里面等候。在 “Entry Set”中等待的线程状态是 “Waiting for monitorentry”,而在 “Wait Set”中等待的线程状态是“in Object.wait()”。
    先看 “Entry Set”里面的线程。我们称被 synchronized保护起来的代码段为临界区。当一个线程申请进入临界区时,它就进入了 “Entry Set”队列。对应的 code就像:
    synchronized(obj){

    }
    这时有两种可能性:
    该 monitor不被其它线程拥有,Entry Set里面也没有其它等待线程。本线程即成为相应类或者对象的 Monitor的 Owner,执行临界区的代码 。此时线程将处于Runnable状态;
    该 monitor被其它线程拥有,本线程在 Entry Set队列中等待。此时dump的信息显示“waiting for monitor entry”。

"Thread-0" prio=10 tid=0x08222eb0 nid=0x9 waiting for monitor entry [0xf927b000..0xf927bdb8] 

at testthread.WaitThread.run(WaitThread.java:39) 
- waiting to lock <0xef63bf08> (a java.lang.Object) 
- locked <0xef63beb8> (a java.util.ArrayList) 
at java.lang.Thread.run(Thread.java:595) 

临界区的设置,是为了保证其内部的代码执行的原子性和完整性。但是因为临界区在任何时间只允许线程串行通过,这和我们多线程的程序的初衷是相反的。如果在多线程的程序中,大量使用 synchronized,或者不适当的使用了它,会造成大量线程在临界区的入口等待,造成系统的性能大幅下降。如果在线程 DUMP中发现了这个情况,应该审查源码,改进程序。

现在我们再来看现在线程为什么会进入 “Wait Set”。当线程获得了 Monitor,进入了临界区之后,如果发现线程继续运行的条件没有满足,它则调用对象(一般就是被 synchronized 的对象)的 wait() 方法,放弃了 Monitor,进入 “Wait Set”队列。只有当别的线程在该对象上调用了 notify() 或者 notifyAll() , “ Wait Set”队列中线程才得到机会去竞争,但是只有一个线程获得对象的Monitor,恢复到运行态。在 “Wait Set”中的线程, DUMP中表现为: in Object.wait(),类似于:

"Thread-1" prio=10 tid=0x08223250 nid=0xa in Object.wait() [0xef47a000..0xef47aa38] 

at java.lang.Object.wait(Native Method) 

- waiting on <0xef63beb8> (a java.util.ArrayList) 

at java.lang.Object.wait(Object.java:474) 

at testthread.MyWaitThread.run(MyWaitThread.java:40) 

- locked <0xef63beb8> (a java.util.ArrayList) 

at java.lang.Thread.run(Thread.java:595) 

仔细观察上面的 DUMP信息,你会发现它有以下两行:

² locked <0xef63beb8> (ajava.util.ArrayList)

² waiting on <0xef63beb8> (ajava.util.ArrayList)

这里需要解释一下,为什么先 lock了这个对象,然后又 waiting on同一个对象呢?让我们看看这个线程对应的代码:

synchronized(obj){

obj.wait();

}

线程的执行中,先用 synchronized 获得了这个对象的 Monitor(对应于 locked <0xef63beb8> )。当执行到 obj.wait(), 线程即放弃了 Monitor的所有权,进入 “wait set”队列(对应于 waiting on<0xef63beb8> )。

往在你的程序中,会出现多个类似的线程,他们都有相似的 dump也可能是正常的。比如,在程序中有多个服务线程,设计成从一个队列里面读取请求数据。这个队列就是 lock以及 waiting on的对象。当队列为空的时候,这些线程都会在这个队列上等待,直到队列有了数据,这些线程被notify,当然只有一个线程获得了 lock,继续执行,而其它线程继续等待。

实例1:多线程竞争synchronized锁

很明显:线程1获取到锁,处于RUNNABLE状态,线程2处于BLOCK状态

  1. locked <0x000000076bf62208>说明线程1对地址为0x000000076bf62208对象进行了加锁;
  2. waiting to lock <0x000000076bf62208> 说明线程2在等待地址为0x000000076bf62208对象上的锁
  3. waiting for monitor entry [0x000000001e21f000]说明线程1是通过synchronized关键字进入了监视器的临界区,并处于"Entry Set"队列,等待monitor,具体实现可以参考深入分析synchronized的JVM实现;
实例2:通过wait挂起线程
static class Task implements Runnable {
    @Override
    public void run() {
        synchronized (lock) {
            try {
                lock.wait();
                //TimeUnit.SECONDS.sleep(100000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

dump结果

线程1和2都处于WAITING状态

  1. 线程1和2都是先locked <0x000000076bf62500>,再waiting on <0x000000076bf62500>,之所以先锁再等同一个对象,是因为wait方法需要先通过synchronized获得该地址对象的monitor;

  2. waiting on <0x000000076bf62500>说明线程执行了wait方法之后,释放了monitor,进入到"Wait Set"队列,等待其它线程执行地址为0x000000076bf62500对象的notify方法,并唤醒自己,具体实现可以参考深入分析Object.wait/notify实现机制;

jmap

使用jmap为了展示java进程的内存映射信息,或者堆内存详情

  • 这两个工具可以帮助我们观察线上JVM中的对象分布,了 解到你的系统平时运行过程中,到底哪些对象占据了主角位置,他们占据了多少内存空间,让你对你的系统运行有更加细致的了解。

使用jmap了解系统运行时的内存区域对象占据占用情况

  • 其实如果单单只是要了解JVM的运行状况,然后去进行JVM GC优化,通常来说jstat就完全够用了
  • 但是有的时候可能我们会发现JVM新增对象的速度很快,然后就想要去看看,到底什么对象占据了那么多的内存。
  • 如果发现有的对象在代码中可以优化一下创建的时机,避免那种对象对内存占用过大,那么也许甚至可以去反过来优化一下代码。
  • 当然,其实如果不是出现OOM那种极端情况,也并没有那么大的必要去着急优化代码。
  • 上周的案例中就发现年轻代里总是有500kb左右的未知对象,大家是不是会很好奇?如果可以看到jvm中这500kb的对象到底是什么就好了

jmap用法(查看堆内存里的基本各个区域的情况): jmap -heap PID

  • 大致来说,这个信息会打印出来堆内存相关的一些参数设置;
  • 然后就是当前堆内存里的一些基本各个区域的情况比如Eden区总容量、已经使用的容量、剩余的空间容量,两个Survivor区的总容量、已经使用的容量和剩余的空间容量,老年代的总容量、已经使用的容量和剩余的容量。
  • 但是这些信息大家会想了,其实jstat已经有 了啊!对的,所以一般不会用jmap去看这些信息,毕竟他信息还没jstat全呢,因为没有gc相关的统计

jmap用法(了解系统运行时的对象分布):jmap -histo PID

  • 展示class的内存情况,展示的信息为编号,实例数,字节,类名
  • 打印出来的东西,他按照各种对象占用内存空间的大小降序排列,把占用内存最多的对象放在最上面
  • 如果你只是想要简单的了解一下当前jvm中的对象对内存占用的情况,只要直接用jmap -histo命令即可,非常好用,你可以快速了解到当前内存里到底是哪个对象占用了大量的内存空间。

生成堆内存转储快照:jmap -dump:live,format=b,file=dump.hprof PID

  • 但是如果你仅仅只是看一个大概,感觉就只是看看上述那些对象占用内存的情况,感觉还不够,想要来点深入而且仔细点的那就可以用jmap命令生成一个堆内存快照放到一个文件里去
  • 这个命令会在当前目录下生成一个dump.hrpof文件,这里是二进制的格式,你不能直接打开看的,他把这一时刻JVM堆内存里所有对象的快照放到文件里去了,供你后续去分析。

展示pid的整体堆信息:jmap -heap pid

说明如下

jhat用法(在浏览器中分析堆转出快照):jhat dump.hprof -port 7000

  • jhat内置 了web服务器,他会支持你通过浏览器来以图形化的方式分析堆转储快照使用如下命令即可启动jhat服务器
  • 可以指定自己想要的http端口号,默认是7000端口号
  • 接着你就在浏览器.上访问当前这台机器的7000端口号,就可以通过图形化的方式去分析堆内存里的对象分布情况了

dump

导出的文件可以供分析用,比如jhat或者mat,以便查找内存溢出原因

如果你觉得文章还不错,你的转发、分享、赞赏、点赞、留言就是对我最大的鼓励。 感谢您的阅读,我坚持原创,十分欢迎并感谢您的关注。

原创不易,欢迎转发,关注公众号“码农进阶之路”,获取更多面试题,源码解读资料!

    原文作者:大师兄啊
    原文地址: https://blog.csdn.net/qq_25843323/article/details/104565768
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系管理员进行删除。