JVM(四)_性能监控与调优

不定期补充、修正、更新;欢迎大家讨论和指正

本文主要根据尚硅谷的视频学习,建议移步观看,其他参考资料会在使用时贴出链接
尚硅谷宋红康JVM全套教程(详解java虚拟机)
由于JVM的知识是互相穿插的,比如学习字节码会接触到运行时数据区的知识,学习堆区又会接触到GC的知识,所以建议先看视频对JVM有个完整的概念,本文只是作为学习笔记来复习,不适合入门学习。

JVM官方文档
The Java® Virtual Machine SpecificationJava SE 8 Edition

JVM(一)_类加载系统和字节码
JVM(二)_运行时数据区
JVM(三)_执行引擎
JVM(四)_性能监控与调优

前言

虚拟机(Virtual Machine)指通过软件模拟的具有完整硬件系统功能的、运行在一个完全隔离环境中的完整计算机系统。在实体计算机中能够完成的工作在虚拟机中都能够实现。在计算机中创建虚拟机时,需要将实体机的部分硬盘和内存容量作为虚拟机的硬盘和内存容量。每个虚拟机都有独立的CMOS、硬盘和操作系统,可以像使用实体机一样对虚拟机进行操作。

广义上来看,我们可以将具屏蔽底层细节,专注本层功能的都视为虚拟机,比如计算机组成原理的多级层次结构的计算机结构,我们可以把M4(高级语言机器)视为具有高级语言编译功能的机器,但M4并不是实际的机器,只是人们感到存在的一台高级语言功能的机器。同理,Word、Excel也可以视为处理文字、表格等的虚拟机。
在这里插入图片描述
狭义上,虚拟机可以分为系统虚拟机、程序虚拟机。

系统虚拟机是指通过软件模拟的具有完整硬件系统功能的、运行在一个完全隔离环境中的完整计算机系统,是对物理计算机的仿真,比如VMware、Visual Box等,这样就可以在Windows上运行Linux等系统,虽然看上去我们操作另一个系统,实际上是在操作软件。

程序虚拟机是专门为了某个计算机应用而设计的,比如要学的JVM。

JVM是Java Virtual Machine(Java虚拟机)的缩写,JVM是一种用于计算设备的规范,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。
引入Java语言虚拟机后,Java语言在不同平台上运行时不需要重新编译。Java语言使用Java虚拟机屏蔽了与具体平台相关的信息,使得Java语言编译程序只需生成在Java虚拟机上运行的目标代码(字节码),就可以在多种平台上不加修改地运行。
在这里插入图片描述
在这里插入图片描述

如果问世界上最好的语言是什么,各语言的程序员肯定吵得不可开交,但是最好的虚拟机,毫无疑问是JVM。
JVM从Java7开始就不只是服务Java语言,其他语言只要符合JSR-292规范,由编译器编译成JVM规范的字节码文件,就都能在JVM上运行。
因此Java平台多语言混合编程正成为主流,各个领域使用不同的语言,比如一个项目中,并行处理用Clojure编写,展示层用JRuby/Ralis,中间层用Java,各语言的交互不成问题,最终都在JVM上执行。

在这里插入图片描述

主要虚拟机

(我也不太了解,都是照抄视频中的,以后有机会学习再详细讲讲)

  1. Sun Classic VM
    1996年在java1.0由sun公司发布,是世界上第一款商用的java虚拟机。
    该虚拟机内部只提供了解释器,性能差(这也是造成Java运行效率比C/C++差固有印象的原因,现在的虚拟机一般是解释器和即时编译器(JIT)搭配执行),如果需要JIT,就需要外挂,但是解释器和即时编译器不能同时工作。该虚拟机在JDK1.4时候时被淘汰。

  2. Exact VM
    为了解决上一个虚拟机解释器和JIT不能同时工作的问题,JDK1.2时,sun提供了此虚拟机
    该虚拟机主要提供了准确式内存管理功能(Exact Memory Management :虚拟机知道内存中某个位置的数据是什么类型)、热点探测、编译器与解释器混合工作模式,是现代高性能虚拟机的雏形。
    但只在Solaris平台短暂使用,因为很快就被后来的HotSpot虚拟机取代。

  3. Hotspot VM
    最初由一家小公司Longview Technologizes设计,1997年被sun收购,2009年,sun公司被甲骨文oracle收购,JDK1.3时候,HotSpot成为默认虚拟机。是目前三大主流商用虚拟机之一,占绝对的主导地位,也是在此学习的虚拟机。
    HotSpot的名字就是他的热点代码探测技术,通过计数器找到最具编译价值代码,触发即时编译或栈上替换通过编译器与解释器协同工作,在优化响应时间和最佳执行性能中取得平衡。

  4. JRockit
    三大商用虚拟机之一,由BEA公司开发,专注服务器端应用,因为不太关注程序启动速度,所以JRockit内部不包括解析器实现,全部代码靠即时编译器编译后执行,因此也是目前世界上最快的JVM(因为都是编译器工作),在2008年BEA被Oracle收购(世界五百强不是吹的,Java就不说了,旗下的Oracle,Mysql数据库都是它家的)。

  5. IBM J9
    三大商用虚拟机之一,IBM Technology for java Virtual Machine 简称IT4J,内部代号J9,市场定位与HotSpot接近,服务器端、桌面应用,嵌入式等多用途,广泛应用于IBM的各种Java产品。号称速度最快,因为在自家平台测试,和IOS一样,与自家产品高度契合,当然效率也高。2017年开源,命名位OpenJ9,交给Eclipse基金会管理。

  6. Graal Vm
    2018年4月,Oracle Labs新公开了一项黑科技:Graal VM,从它的口号“Run Programs Faster Anywhere”就能感觉到一颗蓬勃的野心,这句话显然是与1995年Java刚诞生时的“Write Once,Run Anywhere”在遥相呼应。
    Graal VM被官方称为“Universal VM”和“Polyglot VM”,这是一个在HotSpot虚拟机基础上增强而成的跨语言全栈虚拟机,可以作为“任何语言”的运行平台使用,这里“任何语言”包括了Java、Scala、Groovy、Kotlin等基于Java虚拟机之上的语言,还包括了C、C++、Rust等基于LLVM的语言,同时支持其他像JavaScript、Ruby、Python和R语言等等。Graal VM可以无额外开销地混合使用这些编程语言,支持不同语言中混用对方的接口和对象,也能够支持这些语言使用已经编写好的本地库文件。
    Graal Vm野心极大,有一统所有虚拟机的目标,如果HotSpot被取代,最有可能的就是这款虚拟机,让我们拭目以待。

除了以上虚拟机,还有KVM、CDC、Azul VM、Liquid VM、Apache Harmony、Microsoft VM、TaobaoVM、Dalvik VM等

HotSpot

提起HotSpot VM,相信所有Java程序员都知道,它是Sun JDK和OpenJDK中所带的虚拟机,也是目前使用范围最广的Java虚拟机。
但不一定所有人都知道的是,这个目前看起来“血统纯正”的虚拟机在最初并非由Sun公司开发,而是由一家名为“Longview Technologies”的小公司设计的;
甚至这个虚拟机最初并非是为Java语言而开发的,它来源于Strongtalk VM
而这款虚拟机中相当多的技术又是来源于一款支持Self语言实现“达到C语言50%以上的执行效率”的目标而设计的虚拟机,
Sun公司注意到了这款虚拟机在JIT编译上有许多优秀的理念和实际效果,在1997年收购了Longview Technologies公司,从而获得了HotSpot VM。

HotSpot VM既继承了Sun之前两款商用虚拟机的优点(如前面提到的准确式内存管理),也有许多自己新的技术优势,
如它名称中的HotSpot指的就是它的热点代码探测技术(其实两个VM基本上是同时期的独立产品,HotSpot还稍早一些,HotSpot一开始就是准确式GC,
而Exact VM之中也有与HotSpot几乎一样的热点探测。
为了Exact VM和HotSpot VM哪个成为Sun主要支持的VM产品,在Sun公司内部还有过争论,HotSpot打败Exact并不能算技术上的胜利),
HotSpot VM的热点代码探测能力可以通过执行计数器找出最具有编译价值的代码,然后通知JIT编译器以方法为单位进行编译。
如果一个方法被频繁调用,或方法中有效循环次数很多,将会分别触发标准编译和OSR(栈上替换)编译动作。
通过编译器与解释器恰当地协同工作,可以在最优化的程序响应时间与最佳执行性能中取得平衡,而且无须等待本地代码输出才能执行程序,
即时编译的时间压力也相对减小,这样有助于引入更多的代码优化技术,输出质量更高的本地代码。

在2006年的JavaOne大会上,Sun公司宣布最终会把Java开源,并在随后的一年,陆续将JDK的各个部分(其中当然也包括了HotSpot VM)在GPL协议下公开了源码,
并在此基础上建立了OpenJDK。这样,HotSpot VM便成为了Sun JDK和OpenJDK两个实现极度接近的JDK项目的共同虚拟机。

在2008年和2009年,Oracle公司分别收购了BEA公司和Sun公司,这样Oracle就同时拥有了两款优秀的Java虚拟机:JRockit VM和HotSpot VM。
Oracle公司宣布在不久的将来(大约应在发布JDK 8的时候)会完成这两款虚拟机的整合工作,使之优势互补。
整合的方式大致上是在HotSpot的基础上,移植JRockit的优秀特性,譬如使用JRockit的垃圾回收器与MissionControl服务,
使用HotSpot的JIT编译器与混合的运行时系统。–摘自《深入理解Java虚拟机:JVM高级特性与最佳实践》

整体结构

以下为HotSpot VM的大致结构,源代码编译成字节码文件(Class files,因此前面应还有一个编译过程),字节码文件通过类加载系统加载到JVM中,JVM所管理的内存为运行时数据区,执行引擎从运行时数据区获取数据,再通过执行引擎对这些数据进行处理,最终运行在操作系统上。
在这里插入图片描述
根据以上结构,对JVM分为四部分学习:

  1. 字节码和类加载系统
  2. 运行时数据区和本地方法接口
  3. 执行引擎
  4. 性能监控与调优

以下是更为详细的结构图

  • 类加载子系统(Class Loader SubSystem):由加载(Loading)->链接(Linking)->初始化(Initialization)三部分完成
  • 运行时数据区(Runtime Data Areas):包含方法区(Method Area)、堆区(Heap Area)、栈区(Stack Area)、PC寄存器(PC Registers)、本地方法栈(Native Method Stack)
  • 执行引擎(Execution Engine):解释器(Interpreter)、及时编译器(JIT Compiler)、分析器(Profiler)、垃圾回收器(Garbage Collection)

在这里插入图片描述

命令行工具

相信大家还记得JDK、JRE、JVM的关系,JDK除了提供最基础的javac编译器,还提供了许多基于命令行对JVM性能监控的工具。
在这里插入图片描述
这里要学习的命令行工具有

  • jps
  • jstat
  • jinfo
  • jmap
  • jhat
  • jstack
  • jcmd
  • jstatd

这些工具在jdk下的/bin目录下可以找到

在这里插入图片描述
或者在/lib目录下,有个tools.jar包

在这里插入图片描述
解压出来的/sun/lib目录下就是这些工具
在这里插入图片描述

jps

官方文档:Java Platform, Standard Edition Tools Reference-jps

jps(Java Process Status)用于显示正在运行的JVM进程的简单信息。
为了方便测试,我们可以用等待输入来阻塞进程
在这里插入图片描述
一般来说该程序在JVM的ID(后面为了方便都称为vmid)和操作系统的进程ID是一致的且唯一的
在这里插入图片描述

输入jps - h --help查看帮助
[-mlvV]表示这些参数可以一起使用,[hostid]选项显然是用于远程连接的,该选项放到最后和jstatd学习
在这里插入图片描述

  • q:仅显示LVMID(local virtual machine id,本地虚拟机id),不显示主类的名称。就是仅显示前面的vmid
    在这里插入图片描述

  • l:输出应用程序主类的的全类名,如果进程执行的是jar包,则输出jar包的完整路径
    在这里插入图片描述

  • m:输出JVM进程启动时传递给主类main()的参数
    main()作为一个方法自然也是需要由调用者调用
    在这里插入图片描述
    我们可以在运行配置的程序参数配置传入的参数,由于main()方法固定接收String[] args类型的参数,我们随便传个字符串就行
    在这里插入图片描述
    -

  • v:列出JVM启动时的JVM参数

在这里插入图片描述
在这里插入图片描述

如果Java进程关闭了默认开启的UserPerfData参数,即使用参数-XX:-UsePerfData,那么jps(还有下面要学习的jstat)就无法查看该进程
在这里插入图片描述

在这里插入图片描述
虽然在任务管理器是可以看到该进程的(12560)
在这里插入图片描述

jstat

官方文档:Java Platform, Standard Edition Tools Reference-jstat

jstat(JVM Statistics Monitoring Tool,JVM统计监测工具(一开始还以为是status的缩写)),用于监控JVM各种运行状态信息,它可以显示本地或者远程JVM进程的类装载、内存、垃圾收集、JIT编译等运行数据。

在没有GUI的场合下(比如远程连接服务器,一般都是纯命令行模式),jstat是运行期定位虚拟机性能问题的主要工具,常用于检测垃圾收集问题以及泄漏问题。

学习命令行工具都应该-h看看有哪些参数和大致使用方法
jstat以及后面学习的工具,带有<>的参数都是必选的参数;带有[]的是可选的参数;[<>]是表示该参数可选,但是选择后<>的参数必选
在这里插入图片描述
根据必选的参数,即-<option>和vmid,所以该工具的基础用法就是jstat -<option> vmid
注意这里是-<option>,而不是-option,如果是-option直接输入即可,这表明有很多选项可选
可以从官方文档看到option的选项,而option选项自身输出的信息都不尽相同。所以这里并不打算直接学习这些选项,而是先以class选项为例来学习上面还没讲的其他参数。
在这里插入图片描述

class选项用于类加载器行为的监测:类的装载、卸载数量、总空间等

我们首先根据刚学的jps打印正在运行的java进程,再根据vmid使用jstat

在这里插入图片描述
根据官方文档
在这里插入图片描述

  • Loaded:加载类的数量
  • Bytes:加载类的大小(KB)
  • unloaded:卸载类的数量
  • Bytes:卸载类的大小
  • Time:类加载与卸载操作花费的时间

大致了解class选项后,我们回到jstat命令本身,学习其他参数

在这里插入图片描述

  • interval:用于指定输出统计数据的周期,即查询间隔,单位为毫秒

  • count:指定查询的总次数(不指定会一直执行)
    在这里插入图片描述

  • t:可以在输出信息前加上一个Timestamp时间戳列来显示程序的运行时间,单位秒
    在这里插入图片描述

  • h:可以在周期性数据输出时,输出多少行数据后输出一个表头信息
    在这里插入图片描述
    现在回到option选项上面,option可选的选项有:

  • class: 用于类加载器行为的监测:类的装载、卸载数量、总空间等

  • compiler:显示JIT编译器的统计信息

  • gc: 显示堆垃圾收集的相关的信息(逻辑上永久区/元空间也属于堆)

  • gccapacity: 显示的内容跟-gc基本相同,主要关注的是堆中各区域使用到的最大、最小空间

  • gccause: 和-gcutil类似,但会额外输出导致最后一次或当前正在发生的GC产生的原因

  • gcnew: 显示新生区

  • gcnewcapacity: 显示的内容跟-gcnew基本相同,主要关注的是新生区各区域使用到的最大、最小空间

  • gcold: 显示老年区和元空间的统计信息

  • gcoldcapacity: 显示的内容跟-gcold基本相同,主要关注的是老年区使用到的最大、最小空间

  • gcmetacapacity: 显示的内容跟-gcold基本相同,主要关注的是元空间使用到的最大、最小空间

  • gcutil:显示有关垃圾收集统计信息,和-gc类似,主要关注已使用空间占总空间的百分比

  • printcompilation:显示已被JIT编译的方法

对于编译器相关的选项
在这里插入图片描述
对于GC相关的选项,GC显示的信息比较全,直接学习GC即可
在这里插入图片描述

  • S0/1C: Survivor0/1区的大小(kB,后面都一样),E(den)C、O(ld)C、M(etaspace)C同理
  • S0/1U: Survivor0/1区已使用的容量,EU、OU、MU同理
  • CCSC: 压缩类空间的大小
  • CCSU: 压缩类空间已使用的容量
  • YGC: 从应用程序启动到采样时YGC的次数
  • YGCT: 从应用程序启动到采样时YGC的消耗时间(秒)
  • FGC: 从应用程序启动到采样时Full GC的次数
  • FGCT: 从应用程序启动到采样时Full GC的消耗时间(秒)
  • GCT: 从应用程序启动到采样时GC的总时间(YGCT+FGCT)

根据下面代码来观察-gc输出的信息(手速不够的可以在循环前先睡个十多秒)
在这里插入图片描述
在这里插入图片描述
使用-interval参数动态地监测堆的情况,直至OOM
如果看得比较乱,主要看S0/1U、EU、OU、何时YGC和FGC即可;
对于各区域的容量大小
S0/1:E=1:8,和在JVM中设置的一致
N(S0+S1+Eden):O=1:2(2048+2048+16384)/(40960(默认)。
heapSize = 40960+16384 = 61440kb
61440kb/1024 = 60m,和设置的一致
在这里插入图片描述

得到以上信息后,我们可以通过两次测量的间隔时间以及总GC时间的增量,来得出GC时间占运行时间的比例。
如下(0.029-0.004)/15.3-12.3=0.83%,如果该比例超过20%,说明目前堆压力较大;如果超过90%,则说明堆里几乎没有可用空间,随时都可能抛出OOM。
在这里插入图片描述
根据以上信息还可以判断是否出现内存泄漏
在长时间运行的Java程序中,我们通过jstat获取多行数据,并取这几行数据中OU列(即老年区已用容量的最小值)
随后每隔一段较长的时间重复上述操作,来获取多组OU最小值,如果这些值呈现上涨趋势,这意味着无法收集的对象在不断增加,因此很有可能存在内存泄漏。

通过下面代码模拟多次忘记关闭IO流导致的内存泄漏问题(为了方便看结果,把堆大小设置小些)
在这里插入图片描述
可以看到OU列的数值一直在上升,这表明老年区的空间一直被使用,但是并没有进行FGC,这表明存在内存泄漏的问题。
在这里插入图片描述

jinfo

Java Platform, Standard Edition Tools Reference-jinfo

jinfo(Configuration Info for Java,Java配置信息),用于查看虚拟机配置参数信息,也可用于调整虚拟机的配置参数。

在很多情况下,Java程序并不会指定所有JVM参数,开发者可能不知道某一个JVM参数的默认值,这时就需要通过查找文档获取某个参数的默认值。使用jinfo,开发者这可以直接找到JMV参数的当前值,而不用麻烦地查找文档。

jinfo的使用比较简单,option选项的内容下面也给出了
在这里插入图片描述

  • sysprops:查看vmid的系统属性,也可以从程序中System.getProperties()来获取
    在这里插入图片描述
  • flags:查看JVM中被赋值参数的值(包括一些系统默认和手动设置的)
    在这里插入图片描述
    在这里插入图片描述
  • flag <具体参数> :查看vmid某具体参数的值
    在这里插入图片描述

jinfo不仅可以查看运行时某一个JVM参数的实际值,也可以在程序运行期间修改部分参数,并使之立即生效。但并非所有参数都支持动态修改,只有被标记为manageable的参数可以被实时修改。

可以通过 java -XX:+PrintFlagsFinal -version | findstr manageable命令查看哪些参数是被manageable修饰的(Linux下是grep manageable)

在这里插入图片描述
JVM参数的值分为布尔值和非布尔值,布尔值的参数 直接+/-参数名来开启/关闭相关参数即可
比如PrintGC 参数
在这里插入图片描述
对于非布尔值的参数,需要通过参数=参数值来设置
比如MaxHeapFreeRatio
在这里插入图片描述

jmap

Java Platform, Standard Edition Tools Reference-jmap

jmap(JVM Memory Map)主要用于生成dump文件((dump .vt 内存信息转储,转存)简单来说就是堆内存的二进制快照文件)、获取目标vmid内存相关信息,包括堆各区域的使用情况、堆中对象的统计信息、类加载信息等。
在这里插入图片描述

  • heap:打印堆概览
  • histo:直方图的形式显示堆中Java对象,支持:live子选项来仅显示堆中存活的对象
  • clstats:打印类加载的信息
  • finalizerinfo:
  • dump:生成vmid堆内存快照文件(主要功能)

-dump是jmap最主要的功能,所以先学习这个。
Heap Dump又叫堆存储文件,指一个Java进程在某一时间点的内存快照。Heap Dump在触发内存快照时会保存以下信息

  • All Objects
    Class,fields,primitive values and references
  • All Classes
    ClassLoader,name,super class,static fields
  • Garbage Collection Roots
    Objects defined to be reachable by the JVM
  • Thread Stacks and Local Variables
    The call-stacks of threads at the moment of the snapshot,and per-frame information about local objects

通常在生成dump文件前会触发一次FGC,所以dump文件保存的都是FGC留下的信息。
在这里插入图片描述

  • live:仅dump存活对象
  • all:dump所有对象,默认情况
  • format=b:二进制格式
  • file:dump文件的路径

在这里插入图片描述
这些生成的文件是二进制文件,需要用专门的工具来解析,比如后面学的jhat、JProfiler、VisualVM等

在这里插入图片描述
通过命令行来dump还是比较繁琐的(可以用脚本,这块偏向运维了),并且当发生OOM时,我们几乎来不及来手动生成dump文件,因此我们需要能自动dump的功能,尤其在OOM时自动导出dump文件。
JVM提供了两个参数选项来自动生成dump文件:
-XX:+HeapDumpOnOutOfMemoryError(在程序OOM时,导出dump文件)和-XX:HeapDumpPath(指定dump文件保存位置,不指定默认是项目路径下)
在这里插入图片描述

在这里插入图片描述
这两种生成dump文件并不是互斥的,应该根据情况使用合适的方式
在这里插入图片描述
-histo,以直方图的形式显示堆中的对象信息,也可以加:live子参数仅查看存活的对象
在这里插入图片描述

jhat

Java Platform, Standard Edition Tools Reference-jhat

jhat(JVM Heap Analysis Tool)是JDK提供用于分析dump文件的工具,和jmap搭配使用(在JDK 9中已被删除,官方建议使用VisualVM,所以你环境变量不是JDK 8以下,就不能直接使用jhat命令)

在这里插入图片描述
虽然参数很多,但都是可选的,必选的只有dump文件所在的路径
这里直接分析jmap生成的dump文件
jhat内置了一个微型的HTTP/HTML服务器,用户可以直接在浏览器查看分析结果,默认端口是7000

在这里插入图片描述
在这里插入图片描述
分析的结果很多,比如和jmap -histo命令一样的直方图,由于jhat已被弃用,而且信息太多,简单看看就行
在这里插入图片描述

在这里插入图片描述
比较有趣的是OQL查询
在这里插入图片描述
OQL语句跟SQL类似,用于查询对象。比如查询java.lang.string包下长度大于1000的字符串
在这里插入图片描述
在这里插入图片描述

jstack

Java Platform, Standard Edition Tools Reference-jstack

jstack(JVM Stack Trace):用于生成虚拟机指定进程当前时刻的线程快照(虚拟机堆栈跟踪)。线程快照就是当前虚拟机内指定进程的每一条线程正在执行的方法堆栈的集合。

线程快照可用于定位线程出现长时间停顿的原因,如线程间死锁、死循环、请求外部资源导致的长时间等待等问题。这些都是导致线程长时间停顿的常见原因。当线程出现停顿时,就可以用jstack显示各个线程调用的堆栈情况。

在线程生成的快照中,我们主要留意以下这些状态

  • 死锁(Deadlock)
  • 等待资源(Waiting on condition)
  • 等待获取监视器(Waiting on monitor entry)
  • 阻塞(Blocked)
  • 执行中(Runnable)
  • 暂停(Suspended)
  • 对象等待中(Object.wait() 或 TIMED_WAITING)
  • 停止(Parked)

在这里插入图片描述

  • F:当jstack pid未相应时(进程被挂起),强制dump线程快照
  • m:如果调用到本地方法的话,可以显示C/C++的堆栈(mixed模式,就是JVM默认的解释器和即时编译器同时工作的模式)
  • l:打印关于锁的附加信息

以上的参数并没有深入学习的必要,后面看看-l参数就行,学习jstack基础功能就足够了

通过下面代码模拟线程死锁

public class ThreadDeadLock {

    public static void main(String[] args) {

        StringBuilder s1 = new StringBuilder();
        StringBuilder s2 = new StringBuilder();

        new Thread(){
            @Override
            public void run() {

                synchronized (s1){

                    s1.append("a");
                    s2.append("1");

                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    synchronized (s2){

                        s1.append("b");
                        s2.append("2");

                        try {
                            Thread.sleep(100);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }


            }
        }.start();

        new Thread(){
            @Override
            public void run() {

                synchronized (s2){

                    s1.append("c");
                    s2.append("3");

                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    synchronized (s1){

                        s1.append("d");
                        s2.append("4");

                        try {
                            Thread.sleep(100);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }


            }
        }.start();
    }
}

在生成的线程快照可以看到我们手动创建的线程Thread-0/1以及它们的信息
在这里插入图片描述
除了我们手动创建的线程,当前进程还有一些其他线程,比如守护线程等
在这里插入图片描述
jstack分析快照发现死锁,并定位到发生死锁的线程
在这里插入图片描述
这里加上-l参数,查看锁的附加信息。每个线程下都多出了一些信息Locked ownable synchronizers
根据官方定义:一个可持有的同步器多半是线程独有并且使用了AbstractOwnableSynchronizer(或是其子类)去实现它的同步特性,ReentrantLock与ReentrantReadWriteLock就是Java平台提供的两个例子。(没看懂)

在这里插入图片描述

jcmd

Java Platform, Standard Edition Tools Reference-jcmd

jcmd是JDK 7后新增加的命令行工具,jcmd是多功能的工具,可以用来实现前面除了jstat之外所有命令的功能(白学了doge),比如导出dump文件、内存使用、查看Java进程、执行GC等

jcmd -h查看帮助
除了-h参数,jcmd还提供了-l参数和一大串的命令
在这里插入图片描述
-l参数比较简单,列出本地机器JVM的进程,和jps的功能差不多,并且显示的信息更多
在这里插入图片描述
jcmd <pid | main class> <command …|PerfCounter.print|-f file>的使用就看得比较头疼了,根据其他命令的经验,<>内的参数是必选的,|表示选择其中一个参数即可,也就是说我们可以 jcmd pid command的形式来使用
但想要了解用法还得结合它给出的一大串英文帮助

其大概意思是:可以通过"help"命令来看该pid下可使用的command(也就是说上面的command是有一堆选项,"help"也是其中一个),如果pid为0,会依次列出所有Java进程的可使用command。

如果没有指定选项,即仅使用jcmd命令,就和使用-l参数一样,列出当前Java进程
在这里插入图片描述
仅使用jcmd命令,跟jcmd -l效果一样
在这里插入图片描述
相信看到这里,就大概知道如何使用了,最基础的用法就是 jcmd pid command的形式,pid为某一Java进程号,command为该进程可以使用的命令,所以一般会先用jcmd pid help列出可使用的命令
在这里插入图片描述
比如使用VM.version命令
在这里插入图片描述
已经和jinfo -flag功能相同的VM.flags
在这里插入图片描述
从给出的command可以看到很多和jmap相同的功能,比如jmap -histo(GC.class_histogram)、jmap -dump(GC.heap_dump)等,所以官方也推荐使用jcmd代替jmap。
在这里插入图片描述

jstatd

图形化工具

命令行工具是可以获取Java进程的相关信息,但显然还是不如图形化方便,展示直观。另外命令行工具无法获取方法级别的分析数据,如方法间的调用关系、各方法的调用次数和调用时间等。
所以这里将会学习以下GUI工具(这些工具很多功能稍微摸索下就能了解,图太多占空间,这里对每种工具只进行一些复杂的功能和具体案例进行学习

  • jConsole
  • VisualVM
  • MAT
  • JProfiler
  • Arthas
  • Java Mission Control
  • Btrace
  • Flame Graphs

jConsole

jConsole是JDK自带的可视化监控工具,用于查看Java进程的运行概括、监控堆信息、元空间使用情况、类加载情况等。
在jdk/bin下就可以找到jconsole.exe可执行文件
在这里插入图片描述
启动后页面是这样的,选择要监控的进程连接即可(Jconsole所在的JDK尽量和程序所用的JDK版本一致,我环境变量里的是JDK 14,程序用的是JDK 8,然后就总是连接丢失)

在这里插入图片描述

  • 占坑

使用jconsole分析内存情况-JVM

VisualVM

JVM(二)_运行时数据区中就曾经用VisualVM的VisualGC学习堆区。

VisualVM 是一款免费的,集成了多个 JDK 命令行工具的可视化工具,提供强大的分析功能,对 Java 应用程序做性能分析和调优。这些功能包括生成和分析海量数据、跟踪内存泄漏、监控垃圾收集器、执行内存和 CPU 分析,同时它还支持在 MBeans 上进行浏览和操作。VisualVM 囊括了jConsole的大部分功能,所以可以用其取代jConsole。

在JDK 6 Update 7以后,VisualVM就称为JDK的一部分,也在jdk/bin目录下在这里插入图片描述
除了JDK自带的,也可以去官网直接下载
VisualVM
在这里插入图片描述
下载后解压即可
在这里插入图片描述
打开程序,选择某一个进程就可以进行分析工作了
在这里插入图片描述
VisualVM默认提供了五个功能,概述就是一些基本信息,自己看看就行
在这里插入图片描述

监视功能和JConsole的概览功能一致,用于监控CPU、类、线程、堆的信息
在这里插入图片描述
监视功能可以手动GC,也可以dump堆的快照

在这里插入图片描述

需要注意,快照目前只是在内存中,关掉软件快照也会丢失,所以要将dump文件持久化到硬盘中,在左侧右键dump文件另存为即可。
在这里插入图片描述
VisualVM有分析dump文件的功能,在文件->装载导入dump文件即可,文件类型要切换为hprof格式
在这里插入图片描述
在这里插入图片描述
和jmap -histo相同的功能
在这里插入图片描述

线程功能,这里使用的示例是jstack的线程死锁,可以看到VisualVM不仅有线程dump功能,而且可以直接检测到死锁
在这里插入图片描述
通过线程dump文件查看详细内容,同样的有需要应该持久化到硬盘中
在这里插入图片描述

  • 占坑

MAT

  • 占坑

JProfiler

直接进入官网下载即可
JProfiler
在这里插入图片描述
在会话->启动中心->快速Attach 选择进程就可以监控了
在这里插入图片描述
在这里插入图片描述

  • 占坑

Arthas

Arthas 用户文档

  • 占坑

Java Mission Control

  • 占坑

Btrace

  • 占坑

Flame Graphs

  • 占坑

JVM参数

在之前的学习中,就已经使用了很多JVM设置参数,但都是边学习边根据知识点使用相关的参数,在这里系统地学习这些参数的使用。

首先先了解如何使用这些参数
一种很熟悉的方式就是在IDEA(Eclipse没用过,应该方式差不多)中的虚拟机选项中配置
在这里插入图片描述
点击应用即可,这些参数会在程序重新运行时生效
在这里插入图片描述
另一种是在命令行纯原生的执行jar包,同时添加JVM参数即可
在这里插入图片描述
在这里插入图片描述
在jinfo中说过,在程序运行时也可以修改JVM参数,不过仅限于被manageable修饰的参数
在这里插入图片描述
在Tomcat运行war包也可以使用JVM参数,例如在catalina.bat添加以下配置

set "JAVA_OPTS"=-Xms512M -Xmx1024M

知道如何设置这些参数后,我们来具体学习这些参数,JVM参数选项分为三种

  • 标准参数选项
  • -X参数选项
  • -XX参数选项

标准参数选项
该类选项比较稳定,基本不会随着JDK版本的变化而改变
在命令行中java -h就可以看到有哪些选项
在这里插入图片描述
比如使用-version
在这里插入图片描述
和java -version输出的信息相同
在这里插入图片描述

-X参数选项
以-X开头的参数会随着JDK版本改变有些区别
使用java -X查看有哪些参数

在这里插入图片描述
最常用的就是下面三个参数,相信都使用过
在这里插入图片描述
-Xmixed和-Xint参数用于设置JVM执行引擎的工作模式,在 JVM(三)_执行引擎的解释器中有测试不同模式下的效率
在这里插入图片描述

-XX参数选项

以-XX开头的参数也会随着JDK版本的改变的稍有差异。这类参数应该是我们使用最多的参数了,也是主要学习的参数,常用于开发和调试JVM。

可以使用java -XX:+PrintFlagsFinal命令查看所有-XX类型的参数以及其默认值,在上面的jinfo中也用该命令来搜索被manageable修饰的参数
在这里插入图片描述
这类参数和jinfo -flag一样分为布尔类型格式和非布尔类型格式,布尔类型只要-XX:+/-选项来开启/关闭即可。
比如启用G1 GC
在这里插入图片描述

在这里插入图片描述
非布尔类型需要指定值-XX:选项=值
比如设置新生区与老年区的比列
在这里插入图片描述
有些参数设置大小数值后面可以添加大小单位,m/M、k/K、g/G等(都是字节单位),比如设置堆最大容量为600MB
在这里插入图片描述

值的类型也可以是字符串,不过比较少,比如在jmap中设置堆dump文件保存的位置
在这里插入图片描述

这类参数光列出来的就有700多个,肯定不能一个个讲完,这里按照以下分类学习

  • 堆、栈、方法区
  • OOM
  • GC
  • GC日志(GC日志的参数放到日志分析一起学习)
  • 其他常用

堆、栈、方法区

# 栈
-XX:ThreadStackSize=num #设置栈的大小,等价于-Xss<size>

# 堆
-XX:InitialHeapSize=size #堆初始内存大小,等价于-Xms<size>
-XX:MaxHeapSize=size #设置JVM最大堆内存,等价于-Xmx<size>
-XX:NewSize=size -XX:MaxNewSize=num #设置新生区大小,等价于-Xmn<size>
-XX:SurvivorRatio=num #设置新生区中Eden区与Survivor区的比值,默认为8,即S0:S1:Eden = 1:1:8
-XX:NewRatio=num #设置老年区与新生区的比例,默认为2,即n:o=1:2
-XX:+UseAdaptiveSizePolicy #自适应策略,设置大小比例自适应,默认开启
-XX:PretenureSizeThreadshold=size #设置让大于此阈值的对象直接分配在老年代,只对Serial、ParNew收集器有效
-XX:MaxTenuringThreshold=num #新生区对象晋升老年区的年龄阈值,默认为15
-XX:TargetSurvivorRatio #MinorGC结束后Survivor区占用空间的期望比例

# 方法区
-XX:MetaspaceSize/-XX:PermSize=size #设置元空间/永久区初始值
-XX:MaxMetaspaceSize/-XX:MaxPermSize=size #设置元空间/永久区最大值
-XX:+UseCompressedOops #使用压缩对象
-XX:+UseCompressedClassPointers #使用压缩类指针
-XX:CompressedClassSpaceSize #设置Klass Metaspace的大小,默认1G

# 直接内存
-XX:MaxDirectMemorySize #指定DirectMemory容量,默认等于Java堆最大值

如下
在这里插入图片描述

在这里插入图片描述
在之前学习中遇到一个问题,-XX:SurvivorRatio幸存者区和伊甸园区的默认比例是1:1:8,然而实际上却并不是,这是因为自适应机制的原因,该机制会自动分配新生区的内存

在这里插入图片描述
可以看到自适应机制是默认开启的(实际上即使关闭了还是一样,还是显式使用-XX:SurvivorRatio参数才能确保)
在这里插入图片描述

OOM

-XX:+HeapDumpOnOutOfMemoryError #内存出现OOM时生成Heap转储文件,两者互斥
-XX:+HeapDumpBeforeFullGC #出现FullGC时生成Heap转储文件,两者互斥
-XX:HeapDumpPath=<path> #指定heap转储文件的存储路径,默认当前目录
-XX:OnOutOfMemoryError=<path> #指定可行性程序或脚本的路径,当发生OOM时执行脚本

-XX:+HeapDumpOnOutOfMemoryError和-XX:HeapDumpPath=< path> 在jmap中使用过,这里就不再赘述。

对于-XX:+HeapDumpBeforeFullGC
在这里插入图片描述
每次进行FGC都会生成快照
在这里插入图片描述
在这里插入图片描述

GC

回顾七种常用的GC,GC相关的内容在JVM(三)_执行引擎学习

在这里插入图片描述

#Serial GC
-XX:+UseSerialGC  #使用Serial GC,该选项同时开启Serial和Serial Old GC,并没有单独开启Serial Old GC的选项
#ParNew GC
-XX:+UseParNewGC  #使用ParNew GC
-XX:ParallelGCThreads  #设置并行收集器的线程数。一般地,最好与CPU数量相等,以避免过多的线程数影响垃圾收集性能。
# Parallel GC
-XX:+UseParallelGC  #使用Parallel Scavenge GC
-XX:+UseParallelOldGC  #Parallel Old GC,与Parallel Scavenge GC互相激活
-XX:ParallelGCThreads #同上 设置并行收集器的线程数 
-XX:MaxGCPauseMillis  #设置垃圾收集器最大停顿时间(即STW的时间),单位是毫秒。
	#为了尽可能地把停顿时间控制在MaxGCPauseMills以内,收集器在工作时会调整Java堆大小或者其他一些参数。
	#对于用户来讲,停顿时间越短体验越好;但是服务器端注重高并发,整体的吞吐量。
	#所以服务器端适合Parallel,进行控制。该参数使用需谨慎。
-XX:GCTimeRatio  #垃圾收集时间占总时间的比例(1 / (N+1)),用于衡量吞吐量的大小
	#取值范围(0,100),默认值99,也就是垃圾收集时间不超过1%。
	#与前一个-XX:MaxGCPauseMillis参数有一定矛盾性。暂停时间越长,Radio参数就容易超过设定的比例。
-XX:+UseAdaptiveSizePolicy  #设置Parallel Scavenge收集器具有自适应调节策略。
#在这种模式下,年轻区的大小、Eden和Survivor的比例、晋升老年代的对象年龄等参数会被自动调整,已达到在堆大小、吞吐量和停顿时间之间的平衡点。
#在手动调优比较困难的场合,可以直接使用这种自适应的方式,仅指定虚拟机的最大堆、目标的吞吐量(GCTimeRatio)和停顿时间(MaxGCPauseMills),让虚拟机自己完成调优工作。

# CMS GC
-XX:+UseConcMarkSweepGC  #使用CMS GC。
	#开启该参数后会自动将-XX:+UseParNewGC打开。即:ParNew(Young区)+ CMS(Old区)+ Serial Old的组合
-XX:CMSInitiatingOccupanyFraction  #设置堆内存使用率的阈值,一旦达到该阈值,便开始进行收集。JDK5及以前版本的默认值为68,DK6及以上版本默认值为92%。
	#如果内存增长缓慢,则可以设置一个稍大的值,大的阈值可以有效降低CMS的触发频率,减少老年代收集的次数可以较为明显地改善应用程序性能。
	#反之,如果应用程序内存使用率增长很快,则应该降低这个阈值,以避免频繁触发老年代串行收集器。
	#因此通过该选项便可以有效降低Fu1l GC的执行次数。
-XX:+UseCMSInitiatingOccupancyOnly  #是否动态可调,使CMS一直按CMSInitiatingOccupancyFraction设定的值启动
-XX:+UseCMSCompactAtFullCollection  #用于指定在执行完Full GC后对内存空间进行压缩整理
	#以此避免内存碎片的产生。不过由于内存压缩整理过程无法并发执行,所带来的问题就是停顿时间变得更长了。
-XX:CMSFullGCsBeforeCompaction  #设置在执行多少次Full GC后对内存空间进行压缩整理。
-XX:ParallelCMSThreads  #设置CMS的线程数量。
	#CMS 默认启动的线程数是(ParallelGCThreads+3)/4,ParallelGCThreads 是年轻代并行收集器的线程数。
	#当CPU 资源比较紧张时,受到CMS收集器线程的影响,应用程序的性能在垃圾收集阶段可能会非常糟糕。
-XX:ConcGCThreads  #设置并发垃圾收集的线程数,默认该值是基于ParallelGCThreads计算出来的
-XX:+CMSScavengeBeforeRemark  #强制hotspot在cms remark阶段之前做一次minor gc,用于提高remark阶段的速度
-XX:+CMSClassUnloadingEnable #如果有的话,启用收集Perm 区(JDK8之前)
-XX:+CMSParallelInitialEnabled  #用于开启CMS initial-mark阶段采用多线程的方式进行标记 用于提高标记速度,在Java8开始已经默认开启
-XX:+CMSParallelRemarkEnabled  #用户开启CMS remark阶段采用多线程的方式进行重新标记,默认开启
-XX:+ExplicitGCInvokesConcurrent
-XX:+ExplicitGCInvokesConcurrentAndUnloadsClasses #这两个参数用户指定hotspot虚拟在执行System.gc()时使用CMS周期
-XX:+CMSPrecleaningEnabled  #指定CMS是否需要进行Pre cleaning阶段	

# G1 GC
-XX:+UseG1GC #手动指定使用G1收集器执行内存收集任务。
-XX:G1HeapRegionSize #设置每个Region的大小。
	值是2的幂,范围是1MB到32MB之间,目标是根据最小的Java堆大小划分出约2048个区域。默认是堆内存的1/2000。
-XX:MaxGCPauseMillis  #设置期望达到的最大GC停顿时间指标(JVM会尽力实现,但不保证达到)。默认值是200ms
-XX:ParallelGCThread  #设置STW时GC线程数的值。最多设置为8
-XX:ConcGCThreads  #设置并发标记的线程数。将n设置为并行垃圾收集线程数(ParallelGCThreads)的1/4左右。
-XX:InitiatingHeapOccupancyPercent #设置触发并发GC周期的Java堆占用率阈值。超过此值,就触发GC。默认值是45。
-XX:G1NewSizePercent  #新生代占用整个堆内存的最小百分比(默认5%)
-XX:G1MaxNewSizePercent  #新生代占用整个堆内存的最大百分比(默认60%)
-XX:G1ReservePercent=10  #保留内存区域,防止 to space(Survivor中的to区)溢出

其他常用

-XX:+DisableExplicitGC  #禁用hotspot执行System.gc(),默认禁用
-XX:ReservedCodeCacheSize=<n>[g|m|k]、-XX:InitialCodeCacheSize=<n>[g|m|k]  #指定代码缓存的大小
-XX:+UseCodeCacheFlushing  #放弃一些被编译的代码,避免代码缓存被占满时JVM切换到interpreted-only的情况
-XX:+DoEscapeAnalysis  #开启逃逸分析
-XX:+UseBiasedLocking  #开启偏向锁
-XX:+UseLargePages  #开启使用大页面
-XX:+PrintTLAB  #打印TLAB的使用情况
-XX:TLABSize  #设置TLAB大小  
-XX:StringTableSize #设置字符串常量池中HashTable的大小

GC日志

GC日志相关虚拟机参数如下

-XX:+PrintGC/-verbose:gc  #打印简要日志信息
-XX:+PrintGCDetails      #打印详细日志信息
-XX:+PrintGCTimeStamps  #打印程序启动到GC发生的时间,搭配-XX:+PrintGCDetails使用
-XX:+PrintGCDateStamps  #打印GC发生时的时间戳,搭配-XX:+PrintGCDetails使用
-XX:+PrintHeapAtGC  #打印GC前后的堆信息
-Xloggc:<file> #输出GC日志到指定路径下的文件中

根据以下代码使用相关参数(后面所有示例都以此来学习)
在这里插入图片描述

在这里插入图片描述
输出内容的含义稍后就会学习
在这里插入图片描述
对于-XX:+PrintHeapAtGC参数,每次GC后都会输出堆GC前和GC后的内存信息
在这里插入图片描述

-XX:+TraceClassLoading  #监控类的加载
-XX:+PrintGCApplicationStoppedTime  #打印GC时线程的停顿时间
-XX:+PrintGCApplicationConcurrentTime  #打印垃圾收集之前应用未中断的执行时间
-XX:+PrintReferenceGC #打印收集了多少种不同引用类型的引用
-XX:+PrintTenuringDistribution  #打印JVM在每次MinorGC后当前使用的Survivor中对象的年龄分布
-XX:+UseGCLogFileRotation #启用GC日志文件的自动转储
-XX:NumberOfGCLogFiles=1  #设置GC日志文件的循环数目
-XX:GCLogFileSize=1M  #设置GC日志文件的大小

在之前学习中,我们知道JVM对不同区域的GC策略分为

  • YGC/Minor GC,针对新生区
  • OGC/Major GC,针对老年区
  • Mixed GC,收集新生区和老年区
  • Full GC,整堆收集,包括方法区

对于YGC其输出信息结构如下

在这里插入图片描述
在这里插入图片描述

  • 2022-02-05T09:35:28.268+0800 为日期戳,是因为使用了-XX:+PrintGCDateStamps参数
  • 0.160为时间戳,是因为使用了-XX:+PrintGCTimeStamps参数
  • GC (Allocation Failure)表明本次引起GC的原因是因为在新生区中没有足够的空间能够存储新的数据

常见造成GC的原因有:

  • Allocation Failure:表明本次引起GC的原因是因为新生区中没有足够的区域存放需要分配的数据
  • Metadata GCThreshold:Metaspace区内存不足
  • FErgonomics:JVM自适应调整导致的GC
  • System:调用了System.gc()方法

对于FGC其输出信息结构如下
在这里插入图片描述
在这里插入图片描述

GC类型中,使用的垃圾收集器不同,显示的信息也不同

  • Serial收集器:显示 “DefNew”,即 Default New Generation
  • Serial Old收集器: 显示"Tenured"
  • ParNew收集器:显示 “ParNew”,即 Parallel New Generation
  • Parallel Scavenge收集器:显示"PSYoungGen"
  • Parallel Old收集器:显示"ParOldGen"
  • CMS 收集器:显示"CMS"

Serial和Serial Old
在这里插入图片描述
在这里插入图片描述
ParNew
在这里插入图片描述
Parallel Scavenge GC和Parallel Old GC
在这里插入图片描述
在这里插入图片描述
CMS
在这里插入图片描述

GC日志输出信息中有三个时间:user、sys、real
在这里插入图片描述

  • user:进程执行用户态代码(核心之外)所使用的时间。这是执行此进程所使用的实际CPU 时间,其他进程和此进程阻塞的时间并不包括在内。在垃圾收集的情况下,表示GC线程执行所使用的 CPU 总时间。
  • sys:进程在内核态消耗的 CPU 时间,即在内核执行系统调用或等待系统事件所使用的CPU 时间
  • real:程序从开始到结束所用的时钟时间。这个时间包括其他进程使用的时间片和进程阻塞的时间(比如等待 I/O 完成)。对于并行GC,这个数字应该接近(用户时间+系统时间)除以垃圾收集器使用的线程数。

由于多核的原因,一般的GC事件中,real time是小于sys time+user time的,因为一般是多个线程并发的去做GC,所以real time是要小于sys+user time的。如果real>sys+user的话,则你的应用可能存在下列问题:IO负载非常重或CPU不够用。

CMS GC
由于CMS GC的流程相比于其他GC复杂些,所以输出的日志信息也有区别
CMS和ParNew组合使用(CMS失效时,启用Serial Old GC备用方案),所以ParNew负责YGC
在这里插入图片描述
CMS的GC流程分为:初始标记、并发标记、重新标记、并发清理、重置线程(进程终止前触发,并不是每次GC后都重置)
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

#初始标记阶段
[GC (CMS Initial Mark) [1 CMS-initial-mark: 29545K(40960K)] 32027K(59392K), 0.0003969 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
#并发标记阶段
[CMS-concurrent-mark-start]
[CMS-concurrent-mark: 0.001/0.001 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[CMS-concurrent-preclean-start]
[CMS-concurrent-preclean: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[CMS-concurrent-abortable-preclean-start]
[CMS-concurrent-abortable-preclean: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
#重新(最终)标记阶段
[GC (CMS Final Remark) [YG occupancy: 14784 K (18432 K)][Rescan (parallel) , 0.0002213 secs][weak refs processing, 0.0000114 secs][class unloading, 0.0003448 secs][scrub symbol table, 0.0005292 secs][scrub string table, 0.0001575 secs][1 CMS-remark: 29545K(40960K)] 44329K(59392K), 0.0013693 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
#并发清除
[CMS-concurrent-sweep-start]
[GC (Allocation Failure) [ParNew: 18284K->18284K(18432K), 0.0000283 secs][CMS[CMS-concurrent-sweep: 0.001/0.001 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
 (concurrent mode failure): 29545K->40932K(40960K), 0.0181614 secs] 47829K->47733K(59392K), [Metaspace: 3374K->3374K(1056768K)], 0.0182834 secs] [Times: user=0.02 sys=0.00, real=0.02 secs] 
[Full GC (Allocation Failure) [CMS: 40932K->40929K(40960K), 0.0064782 secs] 59259K->59236K(59392K), [Metaspace: 3374K->3374K(1056768K)], 0.0065539 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
[Full GC (Allocation Failure) [CMS: 40929K->40912K(40960K), 0.0085502 secs] 59236K->59218K(59392K), [Metaspace: 3374K->3374K(1056768K)], 0.0085881 secs] [Times: user=0.02 sys=0.00, real=0.01 secs] 
#进程结束后进行线程重置
[CMS-concurrent-reset-start]
[CMS-concurrent-reset: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
  • G1收集器:显示”garbage-first heap“

和命令行工具一样,这些输出信息并不直观,如果有可以分析或者以图形化展示的工具就可以大大简化我们的工作
比如GC Eesy,GC Eesy是线上分析GC日志的网站GC Eesy

使用工具来分析日志,先使用-Xloggc: < file>将日志导出来
上传文件后就可以直接分析了
在这里插入图片描述
第一项信息展示的与JVM堆内存容量有关,Peak表示该区域内存曾使用的峰值,例如新生区的内存为17.5M,曾经一度使用到了17.4M,同理老年区内存为40M,一度使用到了39.97M,不难看出这种情况基本都发生了OOM
在这里插入图片描述
在原生GC日志中也可以找到相关的数据,新生区的Allocated:17920K/1024≈17.5M,Peak:17814K/1024≈17.4M
在这里插入图片描述
老年区的40925K/1024≈39.97M
在这里插入图片描述
Key Performance Indicators关键性能指示器,主要用于展示吞吐量和延迟
在这里插入图片描述
Throughput:吞吐量,是垃圾收集器的一个重要指标,运行用户代码的时间占总运行时间的比例(若总运行时间 = 程序的运行时间a + 内存收集的时间b,吞吐量则为a/(a+b)),越大越好。

这里默认使用的是Parallel Scavenge、Parallel Old GC,恰好该GC注重于吞吐量,所以吞吐量还是挺高的
在这里插入图片描述
切换到ParNew GC,吞吐量就低很多
在这里插入图片描述

Latency:延迟,也是GC很重要的性能指标,即执行垃圾收集时,程序的工作线程被暂停的时间,越小越好
Avg Pause GC Time:平均GC暂停时间,即STW时间
Max Pause GC Time:最大GC暂停时间

这里为Parallel Scavenge GC的延迟

在这里插入图片描述
切换到注重低延迟的CMS GC(啊这,你个废物(╯‵□′)╯︵┻━┻,怎么比Parallel Scavenge GC表现还差,也可能是测试代码的原因)
在这里插入图片描述

不同GC暂停时间所占的比例

在这里插入图片描述
Interactive Graphs交互式图表,为了显示得更加直观,这里稍微修改下代码
在这里插入图片描述
堆内存随GC的变化情况,红色三角为Full GC,Heap before GC同理
在这里插入图片描述
执行GC的时间段和类型
在这里插入图片描述

新生区随GC的变化过程,Old Gen、Meta Space、A&P同理
在这里插入图片描述

GC Statistics统计信息,应该都能看懂
在这里插入图片描述
GC Causes,导致GC的原因
在这里插入图片描述
使用的JVM参数
在这里插入图片描述
其他工具

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值