JVM 下篇:性能监控与调优

文章目录

  • 性能优化三部曲
    • 第1步(发现问题):性能监控
      • 何时需要性能监控
    • 第2步(排查问题):性能分析
    • 第3步(解决问题):性能调优
    • 性能评价/测试指标
  • JVM监控及诊断工具命令行
    • jps
    • jstat
    • jinfo
    • jmap
      • 导出内存镜像文件
      • 显示堆内存相关信息
      • 其它作用
    • jhat
    • jstack
    • jcmd
    • jstatd
  • JVM监控及诊断工具GUI
    • Jconsole
    • Visual VM
    • eclipse MAT
      • 获取dump文件
      • 分析堆dump文件
      • 浅堆与深堆
      • 对象实际大小
    • 内存泄漏
      • 内存泄漏与内存溢出的关系
      • 泄漏的分类
      • Java中内存泄漏的8种情况
        • 静态集合类
        • 单例模式
        • 内部类持有外部类
        • 各种连接,如数据库连接、网络连接和IO连接等
        • 变量不合理的作用
        • 改变哈希值
        • 缓存泄漏
        • 监听器和回调
        • 案例
    • Jprofiler
      • 概述
      • 数据采集
      • 内存视图
      • Heap Walker
      • 线程视图
      • 监视器&锁 Monitors& locks
    • Arthas
      • 启动
      • 退出
      • 相关诊断指令
        • 基础指令
        • JVM相关
        • class/ classloader相关
        • monitor/ watch/ trace相关
        • 其它
    • JMC
      • Java Flight Recorder
        • 事件类型
        • 启动方式
    • Flame Graphs(火焰图)
  • JVM运行时参数
    • JVM参数选项类型
      • 标准参数选项
      • -X 参数选项
      • -XX参数选项(重要)
    • 添加JVM参数选项
    • 常用的JVM参数选项
      • 打印设置的XX选项及值
      • 堆、栈、方法区等内存大小设置
        • 堆内存
        • 方法区
        • 直接内存
      • OutofMemory相关的选项
      • 垃圾收集器相关选项
        • 查看默认垃圾收集器
        • Serial回收器
        • ParNew回收器
        • Parallel回收器
        • CMS回收器
        • G1回收器
        • 怎么选择垃圾回收器
        • GC日志相关选项
          • 常用参数
          • 其他参数
      • 其他参数
    • 通过Java代码获取JVM参数
  • 分析 GC 日志
    • GC日志参数
    • GC日志格式
      • GC分类
      • GC 日志分类
        • Minor GC 日志
        • FulI GC 日志
      • GC 日志结构分析
        • 垃圾收集器
        • GC前后情况
        • GC时间
      • 日志分析
        • Minor GC 日志解析
        • Full GC 日志解析
    • GC日志分析工具
      • GCeasy
      • GCViewer

性能优化三部曲

第1步(发现问题):性能监控

一种以非强行或者入侵方式收集或査看应用运营性能数据的活动监控通常是指一种在生产、质量评估或者开发环境下实施的带有预防或主动性的活动

当应用相关干系人提出性能问题却没有提供足够多的线索时,首先我们需要进行性能监控,随后是性能分析。

何时需要性能监控

  • GC频繁
  • CPU Load过高
  • OOM
  • 内存泄漏
  • 死锁
  • 程序响应时间较长

第2步(排查问题):性能分析

一种以侵入方式收集运行性能数据的活动,它会影响应用的吞吐量或响应性

性能分析是针对性能问题的答复结果,关注的范围通常比性能监控更加集中。

性能分析很少在生产环境下进行,通常是在质量评估、系统测试或者开发环境下进行,是性能监控之后的步骤。

  • 打印GC日志,通过 GCviewer或者 http://gceasy.io来分析日志信息
  • 灵活运用 命令行工具, jstack,jmap, jinfo等
  • dump出堆文件,使用内存分析工具分析文件
  • 便用阿里 Arthas,或 jconsole, JVisualVM来实时查看JVM状态
  • jstack查看堆栈信息

第3步(解决问题):性能调优

一种为改善应用响应性或昋吐量而更改参数、源代码、属性配置的活动,性能调优。是在性能监控、性能分析之后的活动

  • 适当增加内存,根据业务背景选择垃圾回收器
  • 优化代码,控制内存使用
  • 增加机器,分散节点压力
  • 合理设置线程池线程数量
  • 使用中间件提高程序效率,比如缓存,消息队列等

性能评价/测试指标

  1. 停顿时间(或响应时间)

    1. 暂停时间:执行垃圾收集时,程序的工作线程被暂停的时间。设置: -XX: MaxGCPauseMillis
  2. 吞吐量

    1. 对单位时间内完成的工作量请求的量度
    2. 在GC中:运行用户代码的时间占总运行时间的比例(总运行时间:程序的运行时间+内存回收的时间)
      吞吐量为:1-1/(1+n) -XX: GCTimeRatio=n
  3. 并发数

    同一时刻,对服务器有实际交互的请求数

  4. 内存占用
    Java堆区所占的内存大小

JVM监控及诊断工具命令行

可能造成Java应用出现性能问题的因素非常多,例如线程控制、磁盘读写、数据库访问
网络I/O、垃圾收集等。想要定位这些问题,一款优秀的性能诊断工具必不可少。

体会1:使用数据说明问题,使用知识分析问题,使用工具处理问题

体会2:无监控、不调优!

jps

查看正在运行的ava进程

显示指定系统内所有的 HotSpot虚拟机进程(查看虚拟机进程信息),可用于查询正在运行的虚拟机进程。

说明:对于本地虚拟机进程来说,进程的本地虚拟机ID与操作系统的进程ID是一致的,是唯一的。

它的基本使用语法为:

jps [options] [hostid]

我们还可以通过追加参数,来打印额外的信息

  • -q:仅仅显示LVID(local virtual machine id),即本地虚拟机唯一id。不显示主类的名称等
  • -l:输出应用程序主类的全类名或如果进程执行的是jar包,则输出jar完整路径
  • -m:输出虚拟机进程启动时传递给主类main()的参数
  • -v:列出虚拟机进程启动时的JVM参数。比如:-Xms20m -Xmx50m是启动程序指定的jvm参数。
    说明:以上参数可以综合使用。

补充:
如果某Java进程关闭了默认开启的 UsePerfData参数(即使用参数-XX:-UsePerfData),那么jps命令(以及jstat)将无法探知该Java进程。

jstat

查看JVM统计信息

jstat( JVM Statistics Monitoring Tool):用于监视虚拟机各种运行状态信息的命令行工具。

它可以显示本地或者远程虚拟机进程中的类装载、内存、垃圾收集、JIT编译等运行数据。

在没有GUI图形界面,只提供了纯文本控制台环境的服务器上,它将是运行期定位虚拟机性能问题的首选工具。常用于检测垃圾回收问题以及内存泄漏问题。

基本使用语法 :
在这里插入图片描述

  • interval参数
    用于指定输出统计数据的周期,单位为毫秒。即:查询间隔
  • count参数
    用于指定查询的总次数
  • -t 参数 可以在输出信息前加上一个 Timestamp列,显示程序的运行时间,单位:秒
    • 我们可以比较Java进程的启动时间以及总GC时间(GCT列),或者两次测量的间隔时间以及总GC时间的增量,来得出GC时间占运行时间的比例。
      如果该比例超过28%,则说明目前堆的压力较大;如果该比例超过99%,则说明堆里几乎没有可用空间,随时都可能抛出OOM异常。
  • -h参数
    可以在周期性数据输出时,输出多少行数据后输出一个表头信息
    查看命令相关参数:

jstat -h或 jstat -help

选项 option可以由以下值构成

  • 类装载相关的:

    • -class:显示 Classloader的相关信息:类的装载、卸载数量、总空间类装载所消耗的时间等
  • 垃圾回收相关的:

    • -gc:显示与GC相头的堆信息。包括Eden区、两个 Survivor区、老年代、 永久代等的容量、已用空间、GC时间合计等信息。
    • -gccapacity:显示内容与-gc基本相同,但输出主要关注Java堆各个区使用到的最大、最小空间。
    • -gcutil:显示内容与-gc基本相同,但输出主要关注已使用空间占总空间的百分比。
    • -gccause:与- gcutil功能一样,但是会额外输出导致最后一次或当前正在发生的GC产生的原因。
    • -gcnew:显示新生代GC状况
    • -gcnewcapacity:显示内容与- gcnew基本相同,输出主要关注使用到的最大、最小空间
    • -geold:显示老年代GC状况
  • JIT相关的:

    • -compiler:显示JIT编译器编译过的方法、耗时等信息
    • -printcompilation:输出已经被JIT编译的方法

jinfo

实时查看和修改JVM配置参数

jinfo( Configuration Info for Java)

查看虚拟机配置参数信息,也可用于调整虚拟机的配置参数

在很多情况下,Java应用程序不会指定所有的Java虚拟机参数。而此时,开发人员可能不知道某一个具体的Java虚拟机参数的默认值。在这种情况下,可能需要通过查找文档获取某个参数的默认值。这个査找过程可能是非常艰难的。但有了jnfo具,开发人员可以很方便地找到
Java虚拟机参数的当前值

基本使用语法
jinfo [options] pid
说明:java进程ID必须要加上

JVM 下篇:性能监控与调优_第1张图片
查看

  • jinfo -sysprops PID 可以查看由 System. getPrrperties()取得的参数
  • jinfo -flags PID 查看曾经赋过值的一些参数
  • jinfo -flag 具体参数PID 查看某个Java进程的具体参数的值

修改

Jinfo不仅可以查看运行时某一个Java虚拟机参数的实际取值,甚至可以在运行时修改部分数,并使之立即生效。
但是,并非所有参数都支持动态修改。参数只有被标记为 manageable的flag可以被实时修改

其实,这个修改能力是极其有限的。
可以查看被标记为 manageable的参数

Java -XX: +PrintFlagsFinal -version |grep manageable

JVM 下篇:性能监控与调优_第2张图片

  • 针对 boolean类型 jinfo -flag [+|-] 具体参数PD
  • 针对非 boolean类型 jinfo -flag 具体参数=具体参数值 PID

拓展

  • java -XX:+PrintFlagsInitial 看所有JVM参数启动的初始值
  • java -XX:+PrintFlagsFinal 查看所有JVM参数的最终值
  • java -XX:+PrintCommandLineFlags。查看那些已经被用户或者JVN设置过的详细的XX参数的名称和值

jmap

导出内存映像文件&内存使用情况

jmap( JVM Memory Map):作用一方面是获取dump文件(堆转储快照文件,二进制文件)
它还可以获取目标Java进程的内存相关信息,包括Java堆各区域的使用情况、堆中对象的统计信息、类加载信息等。

开发人员可以在控制台中输入命令“jmap -help”查阅jmap工具的具体使用方式和一些标准选项配置。

基本使用语法

  • jmap [option]
  • jmap [option]
  • jmap [option [server_id@]

JVM 下篇:性能监控与调优_第3张图片
说明:这些参数和linux下输入显示的命令多少会有不同,包括也受jdk版本的影响
JVM 下篇:性能监控与调优_第4张图片

导出内存镜像文件

手动方式

  • jmap -dump:format=b, file=
  • jmap -dump:live, format=b, file=

自动的方式

  • -XX: +HeapDumpOnOutOfMemoryError
  • -XX: HeapDumpPath=

显示堆内存相关信息

  • jmap -histo pid
  • jmap -heap pid

其它作用

  • jmap -permstat pid 查看系统的 Classloader信息
  • jmap -finalizerinfo 查看堆积在 finalizer队列中的对象

jhat

sun jdk提供的jhat命令与jmap命令搭配使用,用于分析jmap生成的 heap dump文件(堆转储快照)。jhat内置了一个微型的HTTP/HTML服务器,生成dump文件的分析结果后,用户可以在浏览器中查看分析结果(分析虚拟机转储快照信息)。

使用了jhat命令,就启动了一个http服务,端口是7000,即http://localhost:7000/ 就可以在浏览器里分析。
说明:jhat命令在JDK9、JDK10中己经被删除,官方建议用 VisualVM代替。
JVM 下篇:性能监控与调优_第5张图片

jstack

打印JVM中线程快照

线程快照就是当前虚拟机内指定进程的每一条线程正在执行的方法堆栈的集合

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

在 thread dump中,要留意下面几种状态

  • 死锁, Deadlock(重点关注)
  • 等待资源, Waiting on condition(重点关注)
  • 等待获取监视器, Waiting on monitor entry(重点关注)
  • 阻塞,Blocked(重点关注)
  • 执行中, Runnable
  • 暂停, Suspended

基本使用语法 :
jstack option pid

stack管理远程进程的话,需要在远程程序的启动参数中增加:
-Djava. rmi .server .hostname=…
-Dcom. sun. management. imxremote
-Dcom. sun. management. jmxremote. port=8888
-Dcom. sun management. jmxremote. authenticate=false
-Dcom. sun. management. imxremote ssl=false

jcmd

多功能命令行

在JDK1.7以后,新增了一个命令行工具jcmd。

它是一个多功能的工具,可以用来实现前面除了 jstat之外所有命令的功能。比如:用它来出堆、内存使用、查看Java进程、导出线程信息、执行GC、JVM运行时间等。

jcmd拥有jmap的大部分功能,并且在 Oracle的官方网站上也推荐使用jcmd命令代jmap命令

基本语法

  • jcmd -l 列出所有的JVM进程
  • jcmd pid help 针对指定的进程,列出支持的所有命令
  • jcmd pid 具体命令 显示指定进程的指令命令的数据

jstatd

远程主机信息收集

之前的指令只涉及到监控本机的Java应用程序,而在这些工具中,一些监控工具也支持对远程计算机的监控(如jps、 jstat)。为了启用远程监控,则需要配合使用 jstatd工具。

命令 jstatd是一个RMI服务端程序,它的作用相当于代理服务器,建立本地计算机与远程监控工具的通信。jstatd服务器将本机的Java应用程序信息传递到远程计算机。

JVM 下篇:性能监控与调优_第6张图片

JVM监控及诊断工具GUI

图形化综合诊断工具

  • JDK自带的工具
    • jconsole:JDK自带的可视化监控工具。查看Java应用程序的运行概况、监控堆信
      息、永久区(或元空间)使用情况、类加载情况等 位置:jdk\bin\ jconsole.exe
    • Visual VM: Visual VM是一个工具,它提供了一个可视界面,用于查看Java虚拟
      机上运行的基于Java技术的应用程序的详细信息。
      位置:jdk\ bin\jvisualvm.exe
    • JMC: Java Mission Control,内置 Java Flight Recorder。能够以极低的性
      能开销收集Java虚拟机的性能数据。
  • 第三方工具
    • MAT:MAT( Memory Analyzer Tool)是基于 Eclipse的内存分析工具,是一个快
      速、功能丰富的 Java heap分析工具,它可以帮助我们查找内存泄漏和减少内存消
      Eclipse的插件形式
    • JProfiler:商业软件,需要付费。功能强大。
    • Arthas: Alibaba开源的Java诊断工具。深受开发者喜爱。
    • Trace:Java运行时追踪工具。可以在不停机的情况下,跟踪指定的方法调用、构造函数调用和系统内存等信息。

Jconsole

启动:jdk/bin目录下,启动jconsole.exe命令即可
JVM 下篇:性能监控与调优_第7张图片

Visual VM

  • 是一个功能强大的多合一故障诊断和性能监控的可视化工具
  • 它集成了多个JDK命令行工具,使用 Visual VM可用于显示虚拟机进程及进程的配置和环境信息
    (jps, jinfo),监视应用程序的CPU、GC、堆、方法区及线程的信息( jistat、 jstack)等,甚至JConsole
  • 在JDK6 Update7以后, Visual VM便作为JDK的一部分发布( Visuals在JDK/bin目录下) 即:它完全免费。

JVM 下篇:性能监控与调优_第8张图片
功能

  1. 生成/读取堆内存快照
  2. 查看JVM参数和系统属性
  3. 查看运行中的虚拟机进程
  4. 生成/读取线程快照
  5. 程序资源的实时监控
  6. 其他功能
    • JMX代理连接
    • 远程环境监控
    • CPU分析和内存分析

eclipse MAT

MAT( Memory Analyzer Tool)工具是一款功能强大的Java堆内存分析器。可以用于查找内存泄漏以及查看内存消耗情况。

MAT可以分析 heap dump文件。在进行内存分析时,只要获得了反映当前设备内存映像的 hprof文件,
通过MAT打开就可以直观地看到当前的内存信息。
一般说来,这些内存信息包含

  • 所有的对象信息,包括对象实例、成员变量、存储于栈中的基本类型值和存储于堆中的其他对象的引用值
  • 所有的类信息,包括classloader、类名称、父类、静态变量等
  • GCRoot到所有的这些对象的引用路径
  • 线程信息,包括线程的调用栈及此线程的线程局部变量(TLS)
    说明1:缺点

MAT不是一个万能工具,它并不能处理所有类型的堆存储文件。但是比较主流的厂家和格式,例如
sun,HP,SAP所采用的 HPROF二进制堆存储文件,以及IBM的PHD堆存傾文件等都能被很好的解析

说明2
最吸引人的还是能够快速为开发人员生成内存泄漏报表,方便定位问题和分析问题。虽然MAT有如此强
大的功能,但是内存分析也没有简单到一键完成的程度,很多内存问题还是需要我们从MAT展现给我们
的信息当中通过经验和直觉来判断才能发现。

获取dump文件

方法一:通过 jmap工具生成,可以生成任意一个java进程的dump文件;
方法二:通过配置JVM参数生成。

  • 选项”-XX:+ HeapDumpOnoutofMemoryError”或"-XX:+ HeapDumpBeforeFullGC

  • 选项”-XX: HeapDumpPath"所代表的含义就是当程序出现 OutofMemory时,将会在相应的目录下

    生成一份dump文件。如果不指定选项“XX: HeapDumpPath”则在当前目录下生成dump文件。

对比:考虑到生产环境中几乎不可能在线对其进行分析,大都是采用离线分析,因此使用jmap+MAT工是最常见的组合

方法三:使用 VisualVM可以导出堆dump文件

方法四: 使用MAT既可以打开一个已有的堆快照,也可以通过MAT直接从活动Java程序中导出堆快照。

分析堆dump文件

  • histogram

    • 展示了各个类的实例数目以及这些实例heap或 Retainedheap的总和
  • thread overview

    • 查看系统中的Java线程
    • 查看局部变量的信息
  • 获得对象相互引用的关系

    • with outgoing references
    • with incoming references
  • 浅堆与深堆

    • shallow heap
    • retained heap
    • 对象实际大小
  • 支配树

浅堆与深堆

浅堆( Shallow Heap)是指一个对象所消耗的内存。在32位系统中,一个对象用会占据4个字节,一个int类型会占据4个字节,long型变量会占据8个字节,每个对象头需要占用8个字节。根据堆快照格式不同对象的大小可能会向8字节进行对齐。

深堆( (Retained Heap)
深堆是指对象的保留集中所有的对象的浅堆大小之和。
注意:浅堆指对象本身占用的内存,不包括其内部引用对象的大小。一个对象的深堆指只能通过该对象
访问到的(直接或间接)所有对象的浅堆之和,即对象被回收后,可以释放的真实空间。

对象实际大小

另外一个常用的概念是对象的实际大小。这里,对象的实际大小定义为一个对象所能触及的所有对象的
浅堆大小之和,也就是通常意义上我们说的对象大小。与深堆相比,似乎这个在日常开发中更为直观
被人接受,但实际上,这个概念和垃圾回收无关。

下图显示了一个简单的对象引用关系图,对象A引用了C和D,对象B引用了C和E。那么对象A的浅堆大
小只是A本身,不含C和D,而A的实际大小为A、C、D三者之和。而A的深堆大小为A与D之和,由于对
象C还可以通过对象B访问到,因此不在对象A的深堆范围内。
JVM 下篇:性能监控与调优_第9张图片
MAT提供了一个称为支配树( Dominator Tree)的对象图。支配树体现了对象实例间的支配关系。
对象引用图中,所有指向对象B的路径都经过对象A,则认为对象A支配对象B。如果对象A是离对象B最
近的一个支配对象,则认为对象A为对象B的直接支配者。支配树是基于对象间的引用图所建立的,它
以下基本性质:

  • 对象A的子树(所有被对象A支配的对象集合)表示对象A的保留集( retained set),即深堆
  • 如果对象A支配对象B,那么对象A的直接支配者也支配对象B。
  • 支配树的边与对象引用图的边不直接对应

如下图所示:左图表示对象引用图,右图表示左图所对应的支配树。对象A和B由根对象直接支配,庄
在到对象c的路径中,可以经过A,也可以经过B,因此对象C的直接支配者也是根对象。对象F与对象
相互引用,因为到对象F的所有路径必然经过对象D,因此,对象D是对象F的直接支配者。而到对象
的所有路径中,必然经过对象C,即使是从对象F到对象D的引用,从根节点出发,也是经过对象C的
D的直接支配者为对象C

JVM 下篇:性能监控与调优_第10张图片
同理,对象E支配对象G。到达对象H的可以通过对象D,也可以通过对象E,因此对象D和E都不能支配对
象H,而经过对象C既可以到达D也可以到达E,因此对象C为对象H的直接支配者。
在MAT中,单击工具栏上的对象支配树按钮,可以打开对象支配树视图。

内存泄漏

可达性分析算法来判断对象是否是不再使用的对象,本质都是判断一个对象是否还被引用。那么对于
这种情况下,由于代码的实现不同就会出现很多种内存泄漏问题(让W误以为此对象还在引用中,无法回收,造成内存泄漏)

JVM 下篇:性能监控与调优_第11张图片
严格来说,有对象不会再被程序用到了,但是GC又不能回收他们的情况,才叫内存泄漏
但实际情况很多时候一些不太好的实践(或疏忽)会导致对象的生命周期变得很长甚至导致OOM,也可
以叫做宽泛意义上的“内存泄漏"
JVM 下篇:性能监控与调优_第12张图片
对象X引用对象Y,X的生命周期比Y的生命周期长;
那么当Y生命周期结束的时候,X依然引用着,这时候,垃圾回收期是不会回收对象Y的;
如果对象X还引用着生命周期比较短的A、B、C,对象A又引用着对象a、b、c,这样就可能造成大量
无用的对象不能被回收,进而占据了内存资源,造成内存泄漏,直到内存溢出。

内存泄漏与内存溢出的关系

1.内存泄漏( memory leak)
申请了内存所了不释放,比如一共有1024M的内存,分配了521M的内存一直不回收,那么可以 用的内存只有521M了,仿佛泄露掉了一部分
通俗一点讲的话,内存泄漏就是【占着茅坑不拉shi】

2.内存溢出( out of memory)

申请内存时,没有足够的内存可以使用;
通俗一点儿讲,一个厕所就三个坑,有两个站着茅坑不走的(内存泄漏),剩下最后一个坑,厕所表示接待压力很大,这时候一下子来了两个人,坑位(内存)就不够了,内存泄漏变成内存溢出了。

可见,内存泄漏和内存溢出的关系:内存泄漏的增多,最终会导致内存溢出。

泄漏的分类

经常发生:发生内存泄漏的代码会被多次执行,每次执行,泄露一块内存;

偶然发生:在某些特定情况下才会发生
一次性:发生内存泄漏的方法只会执行一次;
隐式泄漏:一直占着内存不释放,直到执行结束;严格的说这个不算内存泄漏,因为最终释放掉了,但是如果执行时间特别长,也可能会导致内存耗尽。

Java中内存泄漏的8种情况

  1. 静态集合类
  2. 单例模式
  3. 内部类持有外部类回
  4. 各种连接,如数据库连接、网络连接和IO连接等
  5. 变量不合理的作用域
  6. 改变哈希值
  7. 缓存泄漏
  8. 监听器和回调

静态集合类

静态集合类,如 HashMap、 LinkedList等等。如果这些容器为静态的,那么它们的生命周期与JVM程
序一致,则容器中的对象在程序结束之前将不能被释放,从而造成内存泄漏。简单而言,长生命周期
对象持有短生命周期对象的引用,尽管短生命周期的对象不再使用,但是因为长生命周期对象持有它的

单例模式

单例模式,和静态集合导致内存泄露的原因类似,因为单例的静态特性,它的生命周期和JVM的生命
周期一样长,所以如果单例对象如果持有外部对象的引用,那么这个外部对象也不会被回收,那么就会造成内存泄漏。

内部类持有外部类

内部类持有外部类,如果一个外部类的实例对象的方法返回了一个内部类的实例对象
这个内部类对象被长期引用了,即使那个外部类实例对象不再被使用,但由于内部类持有外部类的实例
对象,这个外部类对象将不会被垃圾回收,这也会造成内存泄漏

各种连接,如数据库连接、网络连接和IO连接等

各种连接,如数据库连接、网络连接和IO连接等。
在对数据库进行操作的过程中,首先需要建立与数据库的连接,当不再使用时,需要调用close方法来
释放与数据库的连接。只有连接被关闭后,垃圾回收器才会回收对应的对象
否则,如果在访问数据库的过程中,对 Connection、 Statement或 Resultset不显性地关闭,将会造成大量的对象无法被回收,从而引起内存泄漏

变量不合理的作用

变量不合理的作用域。一般而言,一个变量的定义的作用范围大于其使用范围,很有可能会造成内存泄
漏。另一方面,如果没有及时地把对象设置为null,很有可能导致内存泄漏的发生。

public class UsingRandom{ 
 private String msg; 
 public void receiveMsg  (){
   readFromNet();//从网络中接受数据保存到msg中 
   saveDB();//把msg保存到数据库中 
  }
}

如上面这个伪代码,通过 readFromNet方法把接受的消息保存在变量msg中,然后调用 saveDB方法把msg的内容保存到数据库中,此时msg已经就没用了,由于msg的生命周期与对象的生命周期相同,此时msg还不能回收,因此造成了内存泄漏。

实际上这个msg变量可以放在 receiveMsg方法内部,当方法使用完,那么msg的生命周期也就结束此时就可以回收了。还有一种方法,在使用完msg后,把msg设置为null,这样垃圾回收器也会回收msg的内存空间

改变哈希值

改变哈希值,当一个对象被存储进 HashSet集合中以后,就不能修改这个对象中的那些参与计算哈希值 的字段了。

否则,对象修改后的哈希值与最初存储进 HashSet集合中时的哈希值就不同了,在这种情况下,即使在contains方法使用该对象的当前引用作为的参数去 Hashset集合中检索对象,也将返回找不到对象的结果,这也会导致无法从 HashSet集合中单独删除当前对象,造成内存泄漏

这也是 String为什么被设置成了不可变类型,我们可以放心地把 String存入 HashSet,或者把String当做 HashMap的key值;

缓存泄漏

内存泄漏的另一个常见来源是缓存,一旦你把对象引用放入到缓存中,他就很容易遗忘。比如:之前项目在一次上线的时候,应用启动奇慢直到夯死,就是因为代码中会加载一个中的数据到缓存(内存)
中,测试环境只有几百条数据,但是生产环境有几百万的数据

对于这个问题,可以使用 WeakHashMap代表缓存,此种Map的特点是,当除了自身有对key的引用外,此key没有其他引用那么此map会自动丢弃此值。

监听器和回调

内存泄漏第三个常见来是监听器和其他回调,如果客户端在你实现的API中注册回调,却没有显示的取消,那么就会积聚
需要确保回调立即被当作垃圾回收的最佳方法是只保存它的弱引用,例如将他们保存成为WeakHashMap中的键

案例

出栈操作

错误写法:

public object pop(){  
 if (size =s0) 
    throw new Emptystack Exception() 
   return elements[--size] 
}

elements[size] 位置的元素仍然在栈中没有回收,久而久之造成内存泄漏

正确写法

public Object pop(){
if (size =s0) 
   throw new EmptyStackException(); 
   Object result elements[--size]; 
   elements[size]= null; 
   return result 
}

Jprofiler

概述

在运行Java的时候有时候想测试运行时占用内存情况,这时候就需要使用测试工具耷看了。在
eclipse里面有Eclipse Memory Analyzer tool(MAT)插件可以测试,而在IDEA中也有这么一个
插件,就是 JProfiler。
Profiler是由ej- technologies公司开发的一款Java应用性能诊断工具。功能强大,但是收费

特点:

  • 使用方便、界面操作友好(简单且强大)
  • 对被分析的应用影响小(提供模板)
  • CPU, Thread, Memory分析功能尤其强大
  • 支持对jdbc,noSql,jsp, servlet, socket等进行分析
  • 支持多种模式(离线,在线)的分析
  • 支持监控本地、远程的JVM
  • 跨平台,拥有多种操作系统的安装版本

主要功能

  1. 方法调用:对方法调用的分析可以帮助您了了解应用程序正在做什么,并找到提高其性能的方法
  2. 内存分配 :通过分析堆上对象、引用链和垃圾收集能帮您修复内存泄漏问题,优化内存使用
  3. 线程和锁: JProfiler提供多种针对线程和锁的分析视图助您发现多程问题许多性能问题都发生在更高的语义级别上。
  4. 高级子系统:执行最慢的SQL语句。 JProfiler支持对这些子系统进行集成分析

数据采集

JVM 下篇:性能监控与调优_第13张图片
JProfiler数据采集方式分为两种:sampling(样本采集)和 Instrumentation(重构模式)

  1. Instrumentation:这是JProfier全功能模式。在class加载之曲, JProfiler把相关功能代
    码写入到需要分析的class的 bytecode中,对正在运行的jvm有一定影响。

    • 优点:功能强大。在此设置中,调用堆栈信息是准确的。
    • 缺点:若要分析的class较多,则对应用的性能影响较大,CPU开销可能很高(取决于Filter的控制)。因此使用此模式一般配合Filter使用,只对特定的类或包进行分析。
  2. Sampling:类似于样本统计,每隔一定时间(5ms)将每个线程栈中方法栈中的信息统计出来

    1. 优点:对CPU的开销非常低,对应用影响小(即使你不配置任何Filter)

    2. 缺点:一些数据/特性不能提供(例如:方法的调用次数、执行时间)

注:JProfiler本身没有指出数据的采集类型,这里的采集类型是针对方法调用的采集类型。因为JProfiler的绝大多数核心功能都依赖方法调用采集的数据,所以可以直接认为是JProfiler的数据采集类型。

内存视图

Live memory内存剖析:class/class instance的相关信息。例如对象的个数,大小,对象创建的方法执行栈,对象创建的热点

所有对象All Objects
显示所有加载的类的列表和在堆上分配的实例数。只有Java 1.5(JVMTI)才会显示此视图

在这里插入图片描述

  • 记录对象 Record Objects

    查看特定时间段对象的分配,并记录分配的调用堆栈。

  • 分配访问树All location CallTree

    显示一棵请求树或者方法、类、包或对已选择类有带注释的分配信息的]2EE组件。

  • 分配热点Allocation Hot Spots
    显示一个列表,包括方法、类、包或分配已选类的J2EE组件。你可以标注当前值并且显示差异值。对
    于每个热点都可以显示它的跟踪记录树。

  • 类追踪器Class Tracker

分析:内存中的对象的情况

  • 创建的Java对象:死循环、循环次数过多
  • 存在大的对象:读取文件时,byte应该边读边写。->如果长时间不写出的话,导致byte过大
  • 存在内存泄漏

Heap Walker

分析内存泄漏,默认没有开启

线程视图

JProfiler通过对线程历史的监控判断其运行状态,并监控是否有线程阻塞产生,还能将一个线程所管理的方法以树状形式呈现。对线程剖析。
线程历史 Thread History
显示一个与线程活动和线程状态在一起的活动时间表。
线程监控 Thread Monitor
显示一个列表,包括所有的活动线程以及它们目前的活动状况。
线程转储 Thread Dumps
显示所有线程的堆栈跟踪

线程分析主要关心三个方面:

  1. web容器的线程最大数。比如: Tomcat的线程容量应该略大于最大并发
  2. 线程阻塞
  3. 线程死锁

监视器&锁 Monitors& locks

监控和锁 Monitors& Locks所有线程持有锁的情况以及锁的信息。
观察JVM的内部线程并查看状态

  • 死锁探测图表 Current Locking Graph:显示JVM中的当前死锁图表。
  • 目前使用的监测器 Current Monitρrs:显示目前使用的监测器并且包括它们的关联线程。
  • 锁定历史图表 Locking History Graph:显示记录在JVM中的锁定历史。
  • 历史检测记录 Monitor History:显示重大的等待事件和阻塞事件的历史记录。
  • 监控器使用统计Monitor Usage Statistics:显示分组监测,线程和监测类的统计监测数据

Arthas

Jprofiler 与 Visual VM 这两款工具在业界知名度也比较高,他们的优点是可以图形界面上看到各维度的性能数据,使用者根
据这些数据进行综合分析,然后判断哪里出现了性能问题

但是这两款工具也有个缺点,都必须在服务端项目进程中配置相关的监控参数。然后工具通过远程连接到项目进程,获取相关的数据。这样就会带来一些不便,比如线上环境的网络是隔离的,本地的监控工具根本连不上线上环境

Arthas(阿尔萨斯)是Alibaba开源的Java诊断工具,深受开发者喜爱在线排查问题,无需重启;动态跟踪Java代码;实时监控JVM状态。
Arthas支持JDK6+,支持 Linux/Mac/ Windows,采用命令行交互模式,同时提供丰富的Tab自动补全功能,进一步方便进行问题的定位和诊断

当你遇到以下类似问题而束手无策时, Arthas可以帮助你解决:

  • 这个类从哪个jar包加载的?为什么会报各种类相关的 Exception
  • 我改的代码为什么没有执行到?难道是我没 commit?分支搞错了?
  • 遇到问题无法在线上 debug,难道只能通过加日志再重新发布吗?
  • 线上遇到某个用户的数据处理有问题,但线上同样无法 debug,线下无法重现!
  • 是否有一个全局视角来查看系统的运行状况?
  • 有什么办法可以监控到JVM的实时运行状态

启动

Arthas只是一个Java程序,所以可以直接用java -jar运行。

执行成功后, arthas提供了一种命令行方式的交互方式, arthas会检测当前服务器上的Java进程,
并将进程列表展示出来,用户输入对应的编号(1、2、3、4…)进行选择,然后回车。

方式1 java -jar arthas-boot jar

方式2:运行时选择Java 进程PID
java -jar arthas-boot. jar [PID]

退出

最后一行[ arthas@7457]$,说明打开进入了监控客户端,在这里就可以执行相关命令进行查看了

  • 使用quit\exit:退出当前客户端
  • 使用stop\ shutdown:关闭 arthas服务端,并退出所有客户端

相关诊断指令

基础指令

help–查看命令帮助信息
cat–打印文件内容,和linux里的cat命令类似
echo–打印参数,和Linux里的echo命令类似
grep–匹配查找,和Linux里的grep命令类似
tee–复制标准输入到标准输出和指定的文件,和Linux里的tee命令类似
pwd–返回当前的工作目录,和 linux命令类似
cls–清空当前屏幕区域
session–查看当前会话的信思
reset–重置增强类,将被 Arthas:增强过的类全部还原, Arthas服务端关闭时会重置所有增强过的类
version–输出当前目标Java进程所加载的 Arthas版本号
history–打印命令历史
quit–退出当前 Arthas客户端,其他 Arthas客户不受影响
stop–关闭 Arthas服务满,所有 Arthas客户端全部退出

keymap-- Arthas快捷键列表及自定义快捷键

JVM相关

dashboard–当前系统的实时数据面板
thread–查看当前JVM的线程堆栈信息
jvm–查看当前JVM的信息
sysprop–查看和修改JVM的系统属性
sysenv–查看JVM的环境变量
vmoption–查看和修改JVM里诊断相关的 option
perfcounter–查看当前JVM的 Perf Counter信息
logger–查看和修改 logger
getstatic–查看类的静态厘性
ognl–执行ognl表达式
mbean–查看 Mbean的信息
heapdump-- dump java heap,类似jmap命令的 heap dump功能

class/ classloader相关

sc–查看已加载类的方法信息
jad–反编译指定已加载类的源码
sm–查看已加载类的方法信息
mc–内存编译器,内存编译.java文件为.class文件
retransform–加载外部的,.class文件, retransform到JVM里
redefine–加载外部的.class文件, redefine到JVM里
dump–dump已加载类的 byte code到特定目录
classloader–查看classloader的继承树,urls,类加载信息,使用 classloader去 getResource

sc命令:查看JVM已加载的类信息

  • 常用参数
    class- pattern类名表达式匹配
    -d 输岀当前类的详细信息,包括这个类所加载的原始文件来源、类的声明、加载的classloader 等详细信息。如果一个类被多个 ClassLoader所加载,则会出现多次
    -E 开启正则表达式匹配,默认为通配符匹配
    -f 输出当前类的成员变量信息(需要配合参数-d一起使用)
    -x 指定输出静态变量时属性的遍历深度,默认为0,即直接使用 toString输出
  • 补充
    • class- pattern支持全限定名,如com.test.AAA,也支持com/test/AAA这样的格式这样,我们从异常堆栈里面把类名拷贝过来的时候,不需要在手动把/替换为.了
    • sc默认开启了子类匹配功能,也就是说所有当前类的子类也会被搜索岀来,想要精确的匹配,请打开。 options disable-sub-class true开关

sm命令:查看已加载类的方法信息

sm命令只能看到由当前类所声明(declaring)的方法,父类则无法看到。

常用参数:

class- pattern 类名表达式匹配
method- pattern 方法名表达式匹配
-d 展示每个方法的详细信息
-E 开启正则表达式匹配,默认为通配符匹配

jad命令:反编译指定己加类的源码

在 Arthas Console上,反编译出来的源码是带语法高亮的,阅读更方便

当然,反编译出来的java代码可能会存在语法错误,但不影响你进行阅读理解

mc redefine

mc命令:Memory Compiler/内存编译器,编译,Java文件生成,class

redefine命令:加载外部的.class文件, redefine jvm已加载的类

推荐使用 retransform命令

在这里插入图片描述
lassloader命令:查看 classloader的继承树,urls,类加载信息

  • 了解当前系统中有多少类加载器,以及每个加载器加载的类数量,帮助您判断是否有类加载器泄漏。
  • 常用参数
    • -t:查看 Classloader的继承树
    • -l:按类加载实例查看统计信息
    • -c:用classloader对应的 hashcode来查看对应的 Jar urls

monitor/ watch/ trace相关

monitor–方法的监控
watch–方法执行数据观测
trace–方法内部调用路径,并输出方法路径上的每个节点上耗时
stack–输出当前方法被调用的调用路径
tt–方法执行数据的时空隧道,记录下指定方法每次调用的入参和返回信息,并能对这些不同的时间下

monitor命令:方法执行监控
对匹配class- pattern/ method- pattern的类、方法的调用进行监控。涉及方法的调用次数执行时间、失败率等
monitor命令是一个非实时返回命令

常用参数:
class- pattern 类名表达式匹配
method- pattern 方法名表达式匹配
-c 统计周期,默认值为128秒

watch命令:方法执行数据观测

  • 让你能方便的观察到指定方法的调用情况。能观察到的范围为:返回值、抛出异常、入参,通过编写 groovy表达式进行对应变量的查看。
  • 常用参数:
    • class-pattern类名表达式匹配
    • method-pattern方法名表达式匹配
    • express观察表达式
    • condition-express条件表达式
    • -b在方法调用之前观察(默认关闭)
    • -e在方法异常之后观察(默认关闭)
    • -s在方法返回之后观察(默认关闭)
    • -f在方法结束之后(正常返回和异常返回)观察(默认开启)
    • -ⅹ指定输出结果的属性遍历深度,默认为

trace命令;

方法内部调用路径, 输出方法路径上的每个节点上耗时

补充说明:

  • trace命令能主动搜索Class- pattern/ method- pattern对应的方法调用路径,渲染和统计整个调用链路上的所有性能开销和追踪调用链路
  • trace能方便的帮助你定位和发现因Rτ高而导致的性能问题缺陷,但其每次只能跟踪一级方法的调用链路
  • trace在执行的过程中本身是会有一定的性能开销,在统计的报告中并未像JProfiler一样预先减去其自身的统计开销。所以这统计出来有些许的不准,渲染路径上调用的类、方法越多,性能偏差越大。但还是能让你看清一些事情的
  • 参数说明
    • class-pattern 类名表达式匹配
    • method-pattern 方法名表达式匹配
    • condition-express 条件表达式
    • -n 命令执行次数

stack命令

输出当前方法被调用的调用路径

  • 常用参数
    • class-pattern类名表达式匹配
    • method-pattern方法名表达式匹配
    • condition-express条件表达式
    • -n执行次数限制

tt命令:

方法执行数据的时空隧道,记录下指定方法每次调用的入参和返回信息,并能对这些不同的时间下调用进行观测。

常用参数:

  • -t 表明希望记录下类*Test的 print方法的每次执行情况
  • -n 3 指定你需要记录的次数,当达到记录次数时 Arthas会主动中断tt命令的记录过程避免人工操作无法停止的情况。
  • -s 筛选指定方法的调用信息
  • -i 参数后边跟着对应的 INDEX编号查看到它的详细信息
  • -p 重做一次调用通过-replay-times指定调用次数,通过 --replay-interval指定多次调用间隔(单位ms,默认1000ms)

其它

  • 使用>将结果重写到日志文件,使用&指令命令是后台运行, session断开不影响任务执行(生命周期默认为1天)
  • jobs:列出所有job
  • kill:强制终止任务
  • fg:将暂停的任务拉到前台执行
  • bg:将暂停的任务放到后台执行
  • grep:搜索满足年件的结果
  • plaintext:将命令的结果去除ANSI颜色
  • wc:按行统计输出结果
  • options:查看或设置 Arthas全局开关
  • profile:使用 async-profiler对应用采样,生成火焰图

JMC

Java Mission Control(简称JMC),Java官方提供的性能强劲的工具。是一个用于对Java应用程序进行管理、监视、概要分析和故障排除的工具套件。

它包含一个GUI客户端,以及众多用来收集Java虚拟机性能数据的插件,如JVM
Console(能够访问用来存放虚拟机各个子系统运行数据的 MXBeans),以及虚拟机内置的高效 profiling工具 Java Flight Recorder(JFR)。

JMC的另一个优点就是:采厅取样,而不是传统的代码植入技术,对应用性能的影响非常非常小,完全可以开着JMC来做压测(唯一影响可能是full gc多了)

启动

Mission Control位于%JAVA_HOME%/bin/ jmc.exe,打开这款软件。

Java Flight Recorder

Java Flight Recorder是JMC的其中一个组件。
Java Flight Recorder能够以极低的性能开销收集Java虚拟机的性能数据。
JFR的性能开销很小,在默认配置下平均低于1%。与其他工具相比,JFR能够直接访问虚拟机
内的数据,并且不会影响虚拟机的优化。因此,它非常适用于生产环境下满负荷运行的Java程
Java Flight Recorder和JDK Mission Control共同创建了一个完整的工具链。JDK Mission control可对 Java Flight Recorder连续收集低水平和详细的运行时信息进行高效详细的分析。

事件类型

当启用时,JFR将记录运行过程中发生的一系列事件。其中包括Java层面的事件,如线程事件、锁事件,以及Java虚拟机内部的事件,如新建对象、垃圾回收和即时编译事件。

按照发生时机以及持续时间来划分,JFR的事件共有四种类型,它们分别为以下四种

1.瞬时事件( Instant Event),用户关心的是它们发生与否,例如异常、线程启动事件。
2.持续事件( Duration Event),用户关心的是它们的持续时间,例如垃圾回收事件
3.计时事件( Timed Event),是时长超出指定阈值的持续事件。
4.取样事件( Sample Event),是周期性取样的事件

取样事件的其中一个常见例于便是方法抽样( Method Sampling),即每隔一段时间统计各个线程的栈轨迹。如果在这些抽样取得的栈轨迹中存在一个反复出现的方法,那么我们可以推测该方法是热点方法。

启动方式

方式1:使用 -XX: StartFlightRecording=参数
方式2:使用jcmd的JFR+子命令
方式3:JMC的JFR插件

Flame Graphs(火焰图)

在追求极致性能的场景下,了解你的程序运行过程中cpu在干什么很重要,火焰图就是一种非常直观的展示cpu在程序整个生命周期过程中时间分配的工具。
火焰图对于现代的程序员不应该陌生,这个工具可以非常直观的显示出调用栈中的CPU消耗瓶颈

JVM 下篇:性能监控与调优_第14张图片
火焰图,简单通过x轴横条宽度来度量时间指标,y轴代表线程栈的层次。

JVM运行时参数

JVM参数选项类型

标准参数选项

JVM 下篇:性能监控与调优_第15张图片
特点

​ 比较稳定,后续版本基本不会变化 以-开头

各种选项

运行java或者java-hep可以看到所有的标准选项

Hotspot JVM有两种模式

Hotspot JVM有两种模式,分别是 server和 client,分别通过- server和- client模

  1. 在32位 Windows系统上,默认使用 Client类型的JVM。 要想使用 Server模式,则机器配置至少有2个以上的CPU和26以上的物理内存。client模式适用于对内存要求较小的桌面应用程序,默认使用 Serial串行垃圾集器
  2. 64位机器上只支持 server模式的JVM,适用于需要大内存的应用程序,默认使用并行垃圾回收器

-X 参数选项

特点

  1. 非标准化参数
  2. 功能还是比较稳定的。但宫方说后续版本可能会变更
  3. 以-X开头

各种选项

运行java -X命令可以看到所有的X选项

  • -Xmixed 混合模式执行(默认) ; 让JIT根据程序运行的情况,有选择的执行

  • -Xint 仅解释模式执行;禁用JIT,所有字节码都被解释执行,这个模式的速度最慢的

  • -Xcomp 仅采用即时编译器模式 ;所有字节码第一次使用就都被编译成本地代码,然后再执行

  • -Xbootclasspath: <用;分隔的目录和zip/jar文件>设置搜索路径以引导类和资源

  • -Bootsclasspath/a: <用;分隔的目录和zip/jar文件> 附加在引导类路径末尾

  • -Xbootclasspath/p: <用;分隔的目录和zip/jar文件>置于引导类路径之前

  • -Xdiag 显示附加诊断消息

  • -Xnoclassgc 禁用类垃圾收集

  • -Xincgc 启用增量垃圾收集

  • -Xloggc: 将GC状态记录在文件中(带时间戳)

  • -Xbatch 禁用后台编译

  • -Xms 设置初始Java堆大小

  • -Xmx 设置最大Java堆大小

说明

-Xms 设置初始Java堆大小,等价于-XX:InitialHeapSize
-Xmx设置最大lava堆大小,等价于-XX:MaxHeapSize
-Xss设置Java线程堆栈大小,-XX:ThreadStackSize

-XX参数选项(重要)

特点

  1. 非标准化参数
  2. 使用的最多的参数类型
  3. 这类选项属于实验性,不稳定
  4. 以-XX开头

作用

用于开发和调试JVM

分类

  1. Boolean类型格式
    • -XX:+< option> 表示启用 option属性
    • -XX:-< option> 表示禁用 option属性
    • 举例:
      • -XX:-UseParallelGC 不选择垃圾收集器为并行收集器
      • -XX:+UseG1GC 表示启用G1收集器
      • -XX:+UseAdaptiveSizePolicy 自动选择年轻代区大小和相应的 Survivor区比例
    • 说明:因为有的指令默认是开启的,所以可以使用-关闭
  2. 非 Boolean类型格式( key-value类型)
    1. 子类型1:数值型格式-XX:< option>=
      1. number表示数值, number可以带上单位,比如:‘m’、‘M’表示兆,‘k’、‘K’表示 kb, ‘g’、'G’表示g(例如32k跟32768是一样的效果)
        例如 :
        1. -XX:NewSize=1024m 表示设置新生代初始大小为1024兆
        2. -XX:MaxGCPauseMillis=580 表示设置GC停顿时间:500毫秒
        3. -XX:GCTimeRatio=19 表示设置吞吐量
        4. -XX:NewRatio=2 表示新生代与老年代的比例
    2. 子类型2:非数值型格式-XX:
      1. 例如:-XX:HeapDumpPath=/usr/ local/ heapdump.hprof 用来指定heap转存文件的存储路径
  3. 说明 -XX:+PrintFlagsFinal
    1. 输出所有参数的名称和默认值
    2. 默认不包括 Diagnostic和 Experimental的参数
    3. 可以配合-XX:+UnlockDiagnosticVMOptions和-XX:UnlockExperimentaJVMOptions使用

添加JVM参数选项

JVM 下篇:性能监控与调优_第16张图片

常用的JVM参数选项

打印设置的XX选项及值

  • -XX: +PrintCommandLineFlags 可以让在程序运行前打印出用户手动设置或者MM自动设置的XX选项
  • -XX: +PrintFlagsInitial 表示打印出所有XX选项的默认值
  • -XX:+PrintFlagsFinal 表示打印出XX选项在运行程序时生效的值
  • -XX: +PrintVMOptions 打印JVM的参数

堆、栈、方法区等内存大小设置

  • -Xss 128k 设置每个线程的栈大小为128k ;等价于-XX:Threadstacksize=128

堆内存

  • -Xms5350m 等价于 -XX: InitialHeapSize , 设置JVM初始维内存为350M
  • -Xmx3550m 等价于-XX: MaxHeapSize,设置JVM最大堆内存为3550M
  • -Xmn2g
    • 设置年轻代大小为2G
    • 官方推荐配置为整个堆大小的3/8
  • -XX:NewSize=1024m 设置年轻代初始值为1024M
  • -XX:MaxNewSize=1024m 设置年轻代最大值为1024M
  • -XX:SurvivorRatio=8 设置年轻代中Eden区与一个Survivor的比值,默认为8
  • -XX:+UseAdaptiveSizePolicy 自动分配各区大小比例 ,默认是开启状态
  • -XX:NewRatio=4 设置老年代与年轻代(包括1个Eden和2个Survivor区)的比值
  • -XX:PretenuresizeThreadshold=1024 设置让大于此阈值的对象直接分配在老年代,单位为字节;只对serial、 ParNew收集器有效
  • -XX:MaxTenuringThreshold=15 新生代每次MinorGC后,还存活的对象年龄+1,当对象的年龄大于设置的这个值时就进入老年代默认值为15
  • -XX:+PrintTenuringDistribution 让JVM在每次MinorGC后打印出当前使用的Survivor中对象的年龄分布
  • -XX:TargetSurvivorRatio 表示MinorGC结束后Survivor区域中占用空间的期望比例

方法区

永久代

  • -XX:Permsize=256m 设置永久代初始值为256M

  • -XX:MaxPermSize=256m 设置永久代最大值为256M

元空间

  • -XX:MetaspaceSize 初始空间大小
  • -XX:MaxMetaspaceSize 最大空间,默认没有限制
  • -XX:+UseCompressedOops 压缩对象指针
  • XX:+UseCompressedClassPointers 压缩类指针
  • XX:CompressedClassSize 设置 Class Metaspace的大小,默认1G

直接内存

-XX:MaxDirectMemorySize 指定DirectMemory容量,若未指定,则默认与Java堆最大值一样

OutofMemory相关的选项

  • -XX:+HeapDumpOnOutOtMemoryError 表示在内存出现OOM的时候,把Heap转存(Dump)到文件以便后续分析
  • -XX:+HeapDumpBeforeFulIGC 表示在出现FullGC之前,生成Heap转储文件
  • -XX:HeapDumpPath= 指定heap转存文件的存储路径
  • -XX:OnOutOfMemoryError 指定一个可行性程序或者脚本的路径,当发生OOM的时候,去执行这个脚本
    • 例如:OOM时重启服务:-XX:OnOutOfMemoryError=/opt/Server/restart.sh

垃圾收集器相关选项

查看默认垃圾收集器

  • -XX:+PrintCommandLineFlags: 查看命令行相关参数(包含使用的垃圾收集器)
  • 使用命令行指令:jinfo -flag 相关垃圾回收器 参数进程ID

Serial回收器

Serial收集器作为 Hotspot中client模式下的默认新生代垃圾收集器。 Serial Old是运行在client模式下默认的老年代的垃圾回收器。

-XX:+UseSerialGC
指定年轻代和老年代都使用串行收集器。等价于新生代用 Serial GC,且老年代用 Serial Old GC。可以获得最高的单线程收集效率。

ParNew回收器

-XX:+UseParNewGC
手动指定使用 ParDew收集器执行内存回收任务。它表示年轻代使用并行收集器,不影响老 年代。

-XX:ParallelGCThreads=N
限制线程数量,默认开启和CPU数据相同的线程数。

Parallel回收器

吞吐量优先

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

CMS回收器

  • -XX:+UseConcMarkSweepGC 手动指定使用CMS收集器执行内存回收任务

    • 开启该参数后会自动将-XX: UseParNewGo打开。即: ParNew(Young区用)+CMS(Old区用)+ SerialOld的组合
  • -XX:CMSInitiatingOccupanyFraction 设置堆内存使用率的阀值,一旦达到该阀值,便开始进回收。

    • JDK5及以前版本的默认值为68,即当老年代的空间使用率达到68%时,会执行一次CMS回收
    • JDK6及以上版本默认值为92%
    • 如果内存增长缓慢,则可以设置一个稍大的值,大的阈值可以有效降低CMS的触发频率,减少老年代回收的次数可以较为明显地改善应用程序性能。反之,如果应用程序内存使用率增长很快,则应该降低这个阈值,以避免频繁触发老年代串行收集器。因此通过该选项便可以有降低Full GC的执行次数
  • -XX:UseCMSCompactAtFullCollection 用于指定在执行完Full GC后对内存空间进行压缩整理.以此避兔内存碎片的产生。不过由于内存压缩整理过程无法并发执行,所带来的问题就是停顿时间变得更长了

  • -XX:CMSFullGCsBeforeCompaction 设置在执行多少次Full GC后对内存空间进行压缩整理。

  • -XX:ParallelCMSThreads 设置CMS的线程数量

    • CMS默认启动的线程数是( ParallelGCThreads+3)/4,ParallelGCThreads是年轻代并行收集器的线程数。当CPU资源比较紧张时,受到CMS收集器线程的影响,应用程序的性能在垃圾回收阶段可能会非常糟糕。
  • -XX:ConcGCThreads: 设置并发垃圾收集的线程数,默认该值是基于ParallelGCThreads计算出

  • -XX:+UseCMSInitiatingOccupancyOnly: 是否动态可调,用这个参数可以使CMS一直按 CMSInitiatingOccupancyFraction设定的值启动

  • -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回收器

  • -XX: +UseG1GC 手动指定使用G1收集器执行内存回收任务。

  • -XX: G1HeapRegionsize 设置每个 Region的大小。值是2的幂,范围是1MB到32MB之间,目标是根据最小的Java堆大小划分出约20 48个区域。默认是堆内存的1/2008。

  • -XX: MaxGCPauseMillis 设置期望达到的最大GC停顿时间指标(JWM会尽力实现,但不保证达到)。默认值是208ms

  • -XX: ParallelGCThread 设置STW时GC线程数的值。最多设置为8

  • -XX: ConcGCThreads 设置并发标记的线程数。将n设置为并行垃圾回收线程数(ParallelGCThreads)的1/4左右

  • -XX: InitiatingHeapOccupancyPercent 设置触发并发GC周期的Java堆占用率阈值。超过此值,就触发GC。默认值是45

  • -XX: G1NewSizePercent、 -XX: G1MaxNewSizepercent 新生代占用整个堆内存的最小百分比(默认5%)、最大百分比(默认60% )

  • -XX: G1ReservePercent=10 保留内存区域,防止 to space( Survivor中的to区) 溢出

Mixed GC调优参数

  • -XX:InitiatingHeapOccupancy Percent: 设置堆占用率的百分比(0到100)达到这个数值的时候触发global concurrent marking(全局并发标记),默认为 45%。值为0表示间断进行全局并发标记。
  • -XX:G1MixedGCLiveThresholdPercent: 设置old区的 region被回收时候的对象占比,默认占用率为85%。只有old区的 region中存活的对象占用达到了这个百分比才会在 Mixed GC中被回收。
  • -XX:G1HeapwastePercent: 在global concurrent marking(全局并发标记)结束之后,可以知道所有的区有多少空间要被回收,在每次 young GC之后和再次发生 Mixed GC之前,会检查垃圾占比是否达到此参数,只有达到了,下次才会发生Mixed GC。
  • -XX:G1MixedGCCountTarget: 一次gloabl concurrent marking(全局并发标记)之后,最多执行 Mixed GC的次数,默认是8。
  • -XX:G1OldCSetRegionThresholdPercent: 设置 Mixed GC收集周期中要收集的 old region数的上限。默认值是Java堆的10%

怎么选择垃圾回收器

  • 优先调整堆的大小让JVM自适应完成
  • 如果内存小于18M,使用串行收集器
  • 如果是单核、单机程序,并且没有停顿时间的要求,串行收集器
  • 如果是多CPU、需要高吞吐量、允许停顿时间超过1秒,选择并行或者JVM自己选择
  • 如果是多CPU、追求低停顿时间,需快速响应(比如延迟不能超过1秒,如互联网应用 ),使用并发收集器。官方推荐G1,性能高。现在互联网的项目,基本都是使用G1
  • 特别说明:
    • 没有最好的收集器,更没有万能的收集;
    • 调优永远是针对特定场景、特定需求,不存在一劳永逸的收集器

GC日志相关选项

常用参数
  • -verbose:gc 输出gc日志信息,默认输出到标准输出
  • -XX:+PrintGC 等同于 -verbose: gc 表示打开简化的GC日志
  • -XX:+PrintGCDetails 在发生垃圾回收时打印内存回收详细的日志,并在进程退出时输出当前内存各区域分配情况;可以独立使用
  • -XX:+PrintGCTimeStamps 输出GC发生时的时间戳 ;不可以独立使用,需要配合-XX+PrintGCDetails使用
  • -XX:+PrintGCDateStamps 输出GC发生时的时间截(以日期的形式,如 013-0504T21:53:59234+0800)
  • -XX:+PrintHeapAtGC 每一次GC前和GC后,都打印堆信息,可以独立使用
  • -Xloggc: 把GC日志写入到一个文件中去,而不是打印到标准输出中
其他参数
  • -XX:+TraceClassLoading 监控类的加载
  • -XX:PrintGCApplicationStoppedTime 打印GC时线程的停顿时间
  • -XX+PrintGCApplicationConcurrentTime 圾收集之前打印出应用未中断的执行时间
  • -XX:+PrintReferenceGC 记录回收了多少种不同引用类型的引用
  • -XX:+PrintTenuringDistribution 让JVM在每次 MinorGC后打印出当前使用的 Survivor中对象的年龄分布
  • -XX:+UseGCLogFileRotation 启用GC日志文件的自动转储
  • -XX:NumberofGClogFiles=1日志文件的循环数目
  • -XX:GCLogFileSize= 1M 控制GC日志文件的大小

其他参数

  • -XX:+DisableExplicitGC 禁止 hotspot执行 System.gc(),默认禁用
  • -XX: ReservedSize=[g|m|k]、 -XX: Initial CodeCacheSize=[g|m|k] 指定代码缓存的大小;的代码
  • -XX+:UseCodecacheFlushing 使用该参数让Jvm放弃一些被编译的代码,避免代码缓存被占满时JVM切换到 Interpreted-only的情况
  • -XX:+DoEscapeAnalysis 开启逃逸分析
  • -XX:+UseBiasedLocking 开启偏向锁
  • -XX:+UseLargePages 开启使用大页面
  • -XX:+UseTLAB 使用TLAB,默认打开
  • -XX:+PrintTlAB 打印TLAB的使用情况
  • -XX:TLABSize 设置TLAB大小

通过Java代码获取JVM参数

Java提供了java.lang.management包用于监视和管理Java虚拟机和Java运行时中的其他组件, 它允许本地和远程监控和管理运行的Java虚拟机。其中 ManagementFactory这个类还是挺常用的另外还有 Runtime类也可以获取一些内存、CPU核数等相关的数据。

通过这些api可以监控我们的应用服务器的堆内存使用情况,设置一些阈值进行报警等处理。

JVM 下篇:性能监控与调优_第17张图片

分析 GC 日志

GC日志参数

  • -verbose:gc 输出gc日志信息,默认输出到标准输出
  • -XX:+PrintGC 等同于 -verbose: gc 表示打开简化的GC日志
  • -XX:+PrintGCDetails 在发生垃圾回收时打印内存回收详细的日志,并在进程退出时输出当前内存各区域分配情况;可以独立使用
  • -XX:+PrintGCTimeStamps 输出GC发生时的时间戳 ;不可以独立使用,需要配合-XX+PrintGCDetails使用
  • -XX:+PrintGCDateStamps 输出GC发生时的时间截(以日期的形式,如 013-05-04T21:53:59234+0800)
  • -XX:+PrintHeapAtGC 每一次GC前和GC后,都打印堆信息,可以独立使用
  • -Xloggc: 把GC日志写入到一个文件中去,而不是打印到标准输出中

GC日志格式

GC分类

针对 HotSpot VM的实现,它里面的GC按照回收区域又分为两大种类型:一种是部分收集(Partial GC ) 另一种是整堆收集(Full GC)

  • 部分收集:不是完整收集整个Java堆的垃圾收集。其中又分为:

    • 新生代收集( Minor gc/ Young GC):只是新生代(Eden\S0,S1)的垃圾收集
    • 老年代收集( Major GC/Old GC):只是老年代的垃圾收集。
      • 目前,只有 CMS GC会有单独收集老年代的行为
      • 注意:很多时候 Major GC.会和Full GC混淆使用,需要具体分辨是老年代回收还是整堆回收
  • 混合收集( Mixed gc):收集整个新生代以及部分老年代的垃圾收集

    • 目前,只有G1 GC会有这种行为
  • 整堆收集(Full GC):收集整个java堆和方法区的垃圾收集

哪些情况会触发Full GC?

  1. 老年代空间不足
  2. 方法区空间不足
  3. 显式调用 System.gc()
  4. Minor GC进入老年代的数据的平均大小大于老年代的可用内存
  5. 大对象直接进入老年代,而老年代的可用空间不足

GC 日志分类

Minor GC 日志

JVM 下篇:性能监控与调优_第18张图片

FulI GC 日志

JVM 下篇:性能监控与调优_第19张图片

GC 日志结构分析

垃圾收集器

  • 使用 Serial收集器在新生代的名字是 Default New Generation,因此显示的是"[ DefNew"
  • 使用 ParNeW收集器在新生代的名字会变成"[ ParDew",意思是"Parallel New Generation
  • 使用parallel Scavenge收集器在新生代的名字是"[ PSYoungGen",这里的JDK1.7使用的就 是 PSYoung Gen
  • 使用Parallel old Generation收集器在老年代的名字是"[ParoldGen
  • 使用G1收集器的话,会显示为" garbage- first heap"

GC前后情况

GC日志格式的规律一般都是:GC前内存占用一>GC后内存占用(该区域内存总大小)
PSYounGen:5986K->696K(8704K)] 5986K-9704K(9216K)

中括号内:GC回收前年轻代堆大小,回收后大小,(年轻代堆总大小)
括号外:GC回收前年轻代和老年代大小,回收后大小,(年轻代和老年代总大小)

GC时间

GC日志中有三个时间:user,sys和real

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

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

日志分析

Minor GC 日志解析

JVM 下篇:性能监控与调优_第20张图片

Full GC 日志解析

JVM 下篇:性能监控与调优_第21张图片

GC日志分析工具

GC日志看起来比较麻烦, GC日志可视化分析工具 GCeasy和 GCviewer等。通过GC日志可视化分析工具,我们可以很方便的看到JWM各个分代 的内存使用情况、垃圾回收次数、垃圾回收的原因、垃圾回收占用的时间、吞吐量等,这些指标在我们进行JVM调优的时候是很有用的

如果想把GC日志存到文件的话,是下面这个参数:
-Xloggc: /path/to/gc.log
然后就可以用一些工具去分析这些gc日志

GCeasy

官网地址 GCeasy是一款在线的GC日志分析器,可以通过GC日志分析进内存泄漏检测、GC暂停原因分析、JVM配置建议优化等功能,而且是可以免费使用的(有一些服务是收费的)。

GCViewer

GCViewer是一个免费的、离线的开源的分析小工具,用于可视化查看由SUN/Oracle,IBM,HP和BEA Java虚拟机产生的垃圾收集器的日志。

GCViewer用于可视化 Java VM选项-verbose:gc和.NET生成的数据-Xloggc:。它还计算与垃圾回收相关的性能指标(吞吐量,累积的暂停,最长的暂停等)。当通过更改世代大小或设置初始堆大小来调整特定应用程序的垃圾回收时,此功能非常有用

你可能感兴趣的:(JVM,性能优化,JVM,Java,Java,虚拟机)