Jvm命令操作解释

介绍了分析java进程或线程的步骤?jstack,jstat,jinfo等命令的用法

分析步骤命令

# 检测运行状态
$ top free

# 查看进程信息
$ ps -ef | grep cc-doc [pid]

# 查看进程堆栈
$ jstack [-l] [-F] pid > 1.txt
$ sz 1.txt

# 列出线程信息,找到占用cpu\时间高的线程tid
$ ps -mp pid -o THREAD,tid,time

# 用linux命令tid转为16进制
$ printf "%x" tid
# ee2f

# 查看线程的堆栈信息,-A30显示后30行
$ jstack pid | grep ee2f -A30

jstat

“jstat(JVM Statistics Monitoring Tool)是用于监视虚拟机各种运行状态信息的命令行工具。它可以显示本地或者远程[1]虚拟机进程中的类装载、内存、垃圾收集、JIT编译等运行数据,在没有GUI图形界面,只提供了纯文本控制台环境的服务器上,它将是运行期定位虚拟机性能问题的首选工具。”

$ jstat [option vmid] [interval[s|ms] [count]]]
$ jstat -gcutil pid 250 20
$ jstat -gc pid 250 20 //250ms间隔,刷新20次

jstack 堆栈跟踪工具

“jstack(Stack Trace for Java)命令用于生成虚拟机当前时刻的线程快照(一般称为threaddump或者javacore文件)”
“线程快照就是当前虚拟机内每一条线程正在执行的方法堆栈的集合,生成线程快照的主要目的是定位线程出现长时间停顿的原因,如线程间死锁、死循环、请求外部资源导致的长时间等待等都是导致线程长时间停顿的常见原因。线程出现停顿的时候通过jstack来查看各个线程的调用堆栈,就可以知道没有响应的线程到底在后台做些什么事情,或者等待着什么资源”

# jstack -l 3500 
# jstack -lm 3500 > a.txt

jinfo

“jinfo(Configuration Info for Java)的作用是实时地查看和调整虚拟机各项参数。使用jps命令的-v参数可以查看虚拟机启动时显式指定的参数列表,但如果想知道未被显式指定的参数的系统默认值,除了去找资料外,就只能使用jinfo的-flag选项进行查询了”

$jinfo -flags pid
$jinfo pid

jmap java内存映像工具

“jmap(Memory Map for Java)命令用于生成堆转储快照(一般称为heapdump或dump文件)
如果不使用jmap命令,要想获取Java堆转储快照,还有一些比较“暴力”的手段:譬如在第2章中用过的-XX:+HeapDumpOnOutOfMemoryError参数,可以让虚拟机在OOM异常出现之后自动生成dump文件,通过-XX:+HeapDumpOnCtrlBreak参数则可以使用[Ctrl]+[Break]键让虚拟机生成dump文件,又或者在Linux系统下通过Kill-3命令发送进程退出信号“吓唬”一下虚拟机,也能拿到dump文件”

“jmap的作用并不仅仅是为了获取dump文件,它还可以查询finalize执行队列、Java堆和永久代的详细信息,如空间使用率、当前用的是哪种收集器等。”

$ jmap -dump:format=b,file=dump.bin 21561 # 导出虚拟机dump文件
$ jmap -heap # 显示虚拟机堆信息

# jmap -heap 21561
输出:
Attaching to process ID 21561, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.77-b03

using thread-local object allocation.
Parallel GC with 4 thread(s)

Heap Configuration:
MinHeapFreeRatio = 0
MaxHeapFreeRatio = 100
MaxHeapSize = 1073741824 (1024.0MB)
NewSize = 89128960 (85.0MB)
MaxNewSize = 357564416 (341.0MB)
OldSize = 179306496 (171.0MB)
NewRatio = 2
SurvivorRatio = 8
MetaspaceSize = 21807104 (20.796875MB)
CompressedClassSpaceSize = 1073741824 (1024.0MB)
MaxMetaspaceSize = 17592186044415 MB
G1HeapRegionSize = 0 (0.0MB)

Heap Usage:
PS Young Generation
Eden Space:
capacity = 135790592 (129.5MB)
used = 47917952 (45.6981201171875MB)
free = 87872640 (83.8018798828125MB)
35.28812364261583% used
From Space:
capacity = 47710208 (45.5MB)
used = 229376 (0.21875MB)
free = 47480832 (45.28125MB)
0.4807692307692308% used
To Space:
capacity = 49283072 (47.0MB)
used = 0 (0.0MB)
free = 49283072 (47.0MB)
0.0% used
PS Old Generation
capacity = 463470592 (442.0MB)
used = 202372448 (192.99740600585938MB)
free = 261098144 (249.00259399414062MB)
43.66457149453832% used

22290 interned Strings occupying 2157896 bytes.

jhat 内存快照分析工具

“JDK提供jhat(JVM Heap Analysis Tool)命令与jmap搭配使用,来分析jmap生成的堆转储快照”
“一般都不会去直接使用jhat命令来分析dump文件,主要原因有二:一是一般不会在部署应用程序的服务器上直接分析dump文件,即使可以这样做,也会尽量将dump文件复制到其他机器[1]上进行分析,因为分析工作是一个耗时而且消耗硬件资源的过程,既然都要在其他机器进行,就没有必要受到命令行工具的限制了;另一个原因是jhat的分析功能相对来说比较简陋,后文将会介绍到的VisualVM,以及专业用于分析dump文件的Eclipse Memory Analyzer、IBM HeapAnalyzer[2]等工具,都能实现比jhat更强大更专业的分析功能”

$ jhat dump.bin //一般用不到

可视化监测工具 jconsole,jvisualvm

  • “其中JConsole是在JDK 1.5时期就已经提供的虚拟机监控工具,而VisualVM在JDK 1.6 Update7中才首次发布,现在已经成为Sun(Oracle)主力推动的多合一故障处理工具[1],并且已经从JDK中分离出来成为可以独立发展的开源项目。JConsole(Java Monitoring and Management Console)是一种基于JMX的可视化监视、管理工具。”

启动命令,mac、windows可以在机器上直接启动

$ jconsole
$ jvisualvm
  • “VisualVM(All-in-One Java Troubleshooting Tool)是到目前为止随JDK发布的功能最强大的运行监视和故障处理程序,并且可以预见在未来一段时间内都是官方主力发展的虚拟机故障处理工具”, “VisualVM在JDK 1.6 update 7中才首次出现,但并不意味着它只能监控运行于JDK 1.6上的程序,它具备很强的向下兼容能力,甚至能向下兼容至近10年前发布的JDK 1.4.2平台[1]”

补充知识

  1. Java运行时内存分为5个部分:
    • 程序计数器(Program Counter Register) 当前线程所执行的字节码的行号指示器,是线程私有的
    • java虚拟机栈(VM stack) 描述的是java方法执行时的内存模型,每个方法在执行的同时会创建一个栈帧,用于存储局部变量表、操作数栈、动态链接、方法出口等。每个方法调用直到执行完成,就对应着一个栈在虚拟机中从入栈到出栈的过程。经常有人把java内存分为栈内存和堆内存,栈就是现在讲的虚拟机栈,或者说是其中的局部变量表部分。线程私有的,生命周期与线程相同。如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError,允许动态扩展但是内存不够时,会抛出OutOfMemoryError
    • 本地方法栈(Native Method Stack) 与Vm stack相似,不过他用于为本地方法服务
    • java堆(Heap) 被所有线程共享的区域,启动JVM时初始化,所有对象实例以及数组都要在堆上分配。它是垃圾回收器管理的主要区域,也称为GC堆。可以分为新生代和老年代,再细致一点有Eden空间、From Survivor空间、To Survivor空间。堆内存不够时,会抛出OutOfMemory异常
    • 方法区(Method Area) 各个线程共享的区域,用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。运行时常量池属于方法区的一部分。

jvm内存区域详见: “深入理解Java虚拟机:JVM高级特性与最佳实践。” 第二章、2.2节、 Apple Books.

  1. HotSpot虚拟机常见的垃圾收集器:
  • “Serial收集器是最基本、发展历史最悠久的收集器,曾经(在JDK 1.3.1之前)是虚拟机新生代收集的唯一选择。大家看名字就会知道,这个收集器是一个单线程的收集器,但它的“单线程”的意义并不仅仅说明它只会使用一个CPU或一条收集线程去完成垃圾收集工作,更重要的是在它进行垃圾收集时,必须暂停其他所有的工作线程,直到它收集结束。”“Serial收集器对于运行在Client模式下的虚拟机来说是一个很好的选择”
  • “ParNew收集器其实就是Serial收集器的多线程版本,除了使用多条线程进行垃圾收集之外,其余行为包括Serial收集器可用的所有控制参数(例如:-XX:SurvivorRatio、-XX:PretenureSizeThreshold、-XX:HandlePromotionFailure等)、收集算法、Stop The World、对象分配规则、回收策略等都与Serial收集器完全一样,在实现上,这两种收集器也共用了相当多的代码。”
  • “Parallel Scavenge收集器是一个新生代收集器,它也是使用复制算法的收集器”;“Parallel Scavenge收集器的特点是它的关注点与其他收集器不同,CMS等收集器的关注点是尽可能地缩短垃圾收集时用户线程的停顿时间,而Parallel Scavenge收集器的目标则是达到一个可控制的吞吐量(Throughput)。所谓吞吐量就是CPU用于运行用户代码的时间与CPU总消耗时间的比值,即吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间),虚拟机总共运行了100分钟,其中垃圾收集花掉1分钟,那吞吐量就是99%。;停顿时间越短就越适合需要与用户交互的程序,良好的响应速度能提升用户体验,而高吞吐量则可以高效率地利用CPU时间,尽快完成程序的运算任务,主要适合在后台运算而不需要太多交互的任务。”
  • “Serial Old是Serial收集器的老年代版本,它同样是一个单线程收集器,使用“标记-整理”算法。这个收集器的主要意义也是在于给Client模式下的虚拟机使用”
  • “Parallel Old是Parallel Scavenge收集器的老年代版本,使用多线程和“标记-整理”算法。这个收集器是在JDK 1.6中才开始提供的,在此之前,新生代的Parallel Scavenge收集器一直处于比较尴尬的状态。原因是,如果新生代选择了Parallel Scavenge收集器,老年代除了Serial Old(PS MarkSweep)收集器外别无选择(还记得上面说过Parallel Scavenge收集器无法与CMS收集器配合工作吗?)。由于老年代Serial Old收集器在服务端应用性能上的“拖累”,使用了Parallel Scavenge收集器也未必能在整体应用上获得吞吐量最大化的效果,由于单线程的老年代收集中无法充分利用服务器多CPU的处理能力,在老年代很大而且硬件比较高级的环境中,这种组合的吞吐量甚至还不一定有ParNew加CMS的组合“给力”
  • “CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。目前很大一部分的Java应用集中在互联网站或者B/S系统的服务端上,这类应用尤其重视服务的响应速度,希望系统停顿时间最短,以给用户带来较好的体验。CMS收集器就非常符合这类应用的需求。;从名字(包含”Mark Sweep”)上就可以看出,CMS收集器是基于“标记—清除”算法实现的,它的运作过程相对于前面几种收集器来说更复杂一些,整个过程分为4个步骤,包括:初始标记(CMS initial mark)并发标记(CMS concurrent mark)重新标记(CMS remark)并发清除(CMS concurrent sweep)”
  • “G1是一款面向服务端应用的垃圾收集器。HotSpot开发团队赋予它的使命是(在比较长期的)未来可以替换掉JDK 1.5中发布的CMS收集器。与其他GC收集器相比,G1具备如下特点” :

    • “并行与并发:G1能充分利用多CPU、多核环境下的硬件优势,使用多个CPU(CPU或者CPU核心)来缩短Stop-The-World停顿的时间,部分其他收集器原本需要停顿Java线程执行的GC动作,G1收集器仍然可以通过并发的方式让Java程序继续执行。

    • 分代收集:与其他收集器一样,分代概念在G1中依然得以保留。虽然G1可以不需要其他收集器配合就能独立管理整个GC堆,但它能够采用不同的方式去处理新创建的对象和已经存活了一段时间、熬过多次GC的旧对象以获取更好的收集效果。

    • 空间整合:与CMS的“标记—清理”算法不同,G1从整体来看是基于“标记—整理”算法实现的收集器,从局部(两个Region之间)上来看是基于“复制”算法实现的,但无论如何,这两种算法都意味着G1运作期间不会产生内存空间碎片,收集后能提供规整的可用内存。这种特性有利于程序长时间运行[…]”

垃圾收集器详见: “深入理解Java虚拟机:JVM高级特性与最佳实践。” 第三章、3.5节、 Apple Books.

  1. 对象已死的判断:
    引用计数法:无法解决循环引用的问题
    可达性分析算法:判断从gcRoots是否可达(Java、c#等主流商用语言都用这个算法
    详见: “深入理解Java虚拟机:JVM高级特性与最佳实践。” 第三章、3.2节、

  2. 回收算法:
    标记清除;复制;标记整理;分代收集算法
    详见: “深入理解Java虚拟机:JVM高级特性与最佳实践。” 第三章、3.3节、

  3. 内存分配策略

  • 对象优先在新生代的Eden区分配,当Eden区没有足够空间进行分配时,虚拟机将发起一次Minor GC;
  • 大对象直接进入老年代;
  • 长期存活的对象直接进入老年代;
  1. Minor GC和Full GC有什么不一样吗?
  • 新生代GC(Minor GC):指发生在新生代的垃圾收集动作,因为Java对象大多都具备朝生夕灭的特性,所以Minor GC非常频繁,一般回收速度也比较快。

  • 老年代GC(Major GC/Full GC):指发生在老年代的GC,出现了Major GC,经常会伴随至少一次的Minor GC(但非绝对的,在Parallel Scavenge收集器的收集策略里就有直接进行Major GC的策略选择过程)。Major GC的速度一般会比Minor GC慢10倍以上。”