一种以非强行或者入侵方式收集或査看应用运营性能数据的活动监控通常是指一种在生产、质量评估或者开发环境下实施的带有预防或主动性的活动
当应用相关干系人提出性能问题却没有提供足够多的线索时,首先我们需要进行性能监控,随后是性能分析。
一种以侵入方式收集运行性能数据的活动,它会影响应用的吞吐量或响应性
性能分析是针对性能问题的答复结果,关注的范围通常比性能监控更加集中。
性能分析很少在生产环境下进行,通常是在质量评估、系统测试或者开发环境下进行,是性能监控之后的步骤。
一种为改善应用响应性或昋吐量而更改参数、源代码、属性配置的活动,性能调优。是在性能监控、性能分析之后的活动
停顿时间(或响应时间)
吞吐量
并发数
同一时刻,对服务器有实际交互的请求数
内存占用
Java堆区所占的内存大小
可能造成Java应用出现性能问题的因素非常多,例如线程控制、磁盘读写、数据库访问
网络I/O、垃圾收集等。想要定位这些问题,一款优秀的性能诊断工具必不可少。
体会1:使用数据说明问题,使用知识分析问题,使用工具处理问题
体会2:无监控、不调优!
查看正在运行的ava进程
显示指定系统内所有的 HotSpot虚拟机进程(查看虚拟机进程信息),可用于查询正在运行的虚拟机进程。
说明:对于本地虚拟机进程来说,进程的本地虚拟机ID与操作系统的进程ID是一致的,是唯一的。
它的基本使用语法为:
jps [options] [hostid]
我们还可以通过追加参数,来打印额外的信息
补充:
如果某Java进程关闭了默认开启的 UsePerfData参数(即使用参数-XX:-UsePerfData),那么jps命令(以及jstat)将无法探知该Java进程。
查看JVM统计信息
jstat( JVM Statistics Monitoring Tool):用于监视虚拟机各种运行状态信息的命令行工具。
它可以显示本地或者远程虚拟机进程中的类装载、内存、垃圾收集、JIT编译等运行数据。
在没有GUI图形界面,只提供了纯文本控制台环境的服务器上,它将是运行期定位虚拟机性能问题的首选工具。常用于检测垃圾回收问题以及内存泄漏问题。
jstat -h或 jstat -help
选项 option可以由以下值构成。
类装载相关的:
垃圾回收相关的:
JIT相关的:
实时查看和修改JVM配置参数
jinfo( Configuration Info for Java)
查看虚拟机配置参数信息,也可用于调整虚拟机的配置参数
在很多情况下,Java应用程序不会指定所有的Java虚拟机参数。而此时,开发人员可能不知道某一个具体的Java虚拟机参数的默认值。在这种情况下,可能需要通过查找文档获取某个参数的默认值。这个査找过程可能是非常艰难的。但有了jnfo具,开发人员可以很方便地找到
Java虚拟机参数的当前值
基本使用语法
jinfo [options] pid
说明:java进程ID必须要加上
修改
Jinfo不仅可以查看运行时某一个Java虚拟机参数的实际取值,甚至可以在运行时修改部分数,并使之立即生效。
但是,并非所有参数都支持动态修改。参数只有被标记为 manageable的flag可以被实时修改
其实,这个修改能力是极其有限的。
可以查看被标记为 manageable的参数
Java -XX: +PrintFlagsFinal -version |grep manageable
拓展
导出内存映像文件&内存使用情况
jmap( JVM Memory Map):作用一方面是获取dump文件(堆转储快照文件,二进制文件)
它还可以获取目标Java进程的内存相关信息,包括Java堆各区域的使用情况、堆中对象的统计信息、类加载信息等。
开发人员可以在控制台中输入命令“jmap -help”查阅jmap工具的具体使用方式和一些标准选项配置。
基本使用语法
说明:这些参数和linux下输入显示的命令多少会有不同,包括也受jdk版本的影响
手动方式
自动的方式
sun jdk提供的jhat命令与jmap命令搭配使用,用于分析jmap生成的 heap dump文件(堆转储快照)。jhat内置了一个微型的HTTP/HTML服务器,生成dump文件的分析结果后,用户可以在浏览器中查看分析结果(分析虚拟机转储快照信息)。
使用了jhat命令,就启动了一个http服务,端口是7000,即http://localhost:7000/ 就可以在浏览器里分析。
说明:jhat命令在JDK9、JDK10中己经被删除,官方建议用 VisualVM代替。
打印JVM中线程快照
线程快照就是当前虚拟机内指定进程的每一条线程正在执行的方法堆栈的集合
生成线程快照的作用:可用于定位线程出现长时间停顿的原因,如线程间死锁、死循环、请
外部资源导致的长时间等待等问题。这些都是导致线程长时间停顿的常见原因。当线程出现亻
顿时,就可以用 jstack显示各个线程调用的堆栈情况。
在 thread dump中,要留意下面几种状态
基本使用语法 :
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
多功能命令行
在JDK1.7以后,新增了一个命令行工具jcmd。
它是一个多功能的工具,可以用来实现前面除了 jstat之外所有命令的功能。比如:用它来出堆、内存使用、查看Java进程、导出线程信息、执行GC、JVM运行时间等。
jcmd拥有jmap的大部分功能,并且在 Oracle的官方网站上也推荐使用jcmd命令代jmap命令
基本语法
远程主机信息收集
之前的指令只涉及到监控本机的Java应用程序,而在这些工具中,一些监控工具也支持对远程计算机的监控(如jps、 jstat)。为了启用远程监控,则需要配合使用 jstatd工具。
命令 jstatd是一个RMI服务端程序,它的作用相当于代理服务器,建立本地计算机与远程监控工具的通信。jstatd服务器将本机的Java应用程序信息传递到远程计算机。
图形化综合诊断工具
启动:jdk/bin目录下,启动jconsole.exe命令即可
MAT( Memory Analyzer Tool)工具是一款功能强大的Java堆内存分析器。可以用于查找内存泄漏以及查看内存消耗情况。
MAT可以分析 heap dump文件。在进行内存分析时,只要获得了反映当前设备内存映像的 hprof文件,
通过MAT打开就可以直观地看到当前的内存信息。
一般说来,这些内存信息包含
MAT不是一个万能工具,它并不能处理所有类型的堆存储文件。但是比较主流的厂家和格式,例如
sun,HP,SAP所采用的 HPROF二进制堆存储文件,以及IBM的PHD堆存傾文件等都能被很好的解析
说明2
最吸引人的还是能够快速为开发人员生成内存泄漏报表,方便定位问题和分析问题。虽然MAT有如此强
大的功能,但是内存分析也没有简单到一键完成的程度,很多内存问题还是需要我们从MAT展现给我们
的信息当中通过经验和直觉来判断才能发现。
方法一:通过 jmap工具生成,可以生成任意一个java进程的dump文件;
方法二:通过配置JVM参数生成。
选项”-XX:+ HeapDumpOnoutofMemoryError”或"-XX:+ HeapDumpBeforeFullGC
选项”-XX: HeapDumpPath"所代表的含义就是当程序出现 OutofMemory时,将会在相应的目录下
生成一份dump文件。如果不指定选项“XX: HeapDumpPath”则在当前目录下生成dump文件。
对比:考虑到生产环境中几乎不可能在线对其进行分析,大都是采用离线分析,因此使用jmap+MAT工是最常见的组合
方法三:使用 VisualVM可以导出堆dump文件
方法四: 使用MAT既可以打开一个已有的堆快照,也可以通过MAT直接从活动Java程序中导出堆快照。
histogram
thread overview
获得对象相互引用的关系
浅堆与深堆
支配树
浅堆( 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的深堆范围内。
MAT提供了一个称为支配树( Dominator Tree)的对象图。支配树体现了对象实例间的支配关系。
对象引用图中,所有指向对象B的路径都经过对象A,则认为对象A支配对象B。如果对象A是离对象B最
近的一个支配对象,则认为对象A为对象B的直接支配者。支配树是基于对象间的引用图所建立的,它
以下基本性质:
如下图所示:左图表示对象引用图,右图表示左图所对应的支配树。对象A和B由根对象直接支配,庄
在到对象c的路径中,可以经过A,也可以经过B,因此对象C的直接支配者也是根对象。对象F与对象
相互引用,因为到对象F的所有路径必然经过对象D,因此,对象D是对象F的直接支配者。而到对象
的所有路径中,必然经过对象C,即使是从对象F到对象D的引用,从根节点出发,也是经过对象C的
D的直接支配者为对象C
同理,对象E支配对象G。到达对象H的可以通过对象D,也可以通过对象E,因此对象D和E都不能支配对
象H,而经过对象C既可以到达D也可以到达E,因此对象C为对象H的直接支配者。
在MAT中,单击工具栏上的对象支配树按钮,可以打开对象支配树视图。
可达性分析算法来判断对象是否是不再使用的对象,本质都是判断一个对象是否还被引用。那么对于
这种情况下,由于代码的实现不同就会出现很多种内存泄漏问题(让W误以为此对象还在引用中,无法回收,造成内存泄漏)
严格来说,有对象不会再被程序用到了,但是GC又不能回收他们的情况,才叫内存泄漏
但实际情况很多时候一些不太好的实践(或疏忽)会导致对象的生命周期变得很长甚至导致OOM,也可
以叫做宽泛意义上的“内存泄漏"
对象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)
申请内存时,没有足够的内存可以使用;
通俗一点儿讲,一个厕所就三个坑,有两个站着茅坑不走的(内存泄漏),剩下最后一个坑,厕所表示接待压力很大,这时候一下子来了两个人,坑位(内存)就不够了,内存泄漏变成内存溢出了。
可见,内存泄漏和内存溢出的关系:内存泄漏的增多,最终会导致内存溢出。
经常发生:发生内存泄漏的代码会被多次执行,每次执行,泄露一块内存;
偶然发生:在某些特定情况下才会发生
一次性:发生内存泄漏的方法只会执行一次;
隐式泄漏:一直占着内存不释放,直到执行结束;严格的说这个不算内存泄漏,因为最终释放掉了,但是如果执行时间特别长,也可能会导致内存耗尽。
静态集合类,如 HashMap、 LinkedList等等。如果这些容器为静态的,那么它们的生命周期与JVM程
序一致,则容器中的对象在程序结束之前将不能被释放,从而造成内存泄漏。简单而言,长生命周期
对象持有短生命周期对象的引用,尽管短生命周期的对象不再使用,但是因为长生命周期对象持有它的
单例模式,和静态集合导致内存泄露的原因类似,因为单例的静态特性,它的生命周期和JVM的生命
周期一样长,所以如果单例对象如果持有外部对象的引用,那么这个外部对象也不会被回收,那么就会造成内存泄漏。
内部类持有外部类,如果一个外部类的实例对象的方法返回了一个内部类的实例对象
这个内部类对象被长期引用了,即使那个外部类实例对象不再被使用,但由于内部类持有外部类的实例
对象,这个外部类对象将不会被垃圾回收,这也会造成内存泄漏
各种连接,如数据库连接、网络连接和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
}
在运行Java的时候有时候想测试运行时占用内存情况,这时候就需要使用测试工具耷看了。在
eclipse里面有Eclipse Memory Analyzer tool(MAT)插件可以测试,而在IDEA中也有这么一个
插件,就是 JProfiler。
Profiler是由ej- technologies公司开发的一款Java应用性能诊断工具。功能强大,但是收费
特点:
主要功能
JProfiler数据采集方式分为两种:sampling(样本采集)和 Instrumentation(重构模式)
Instrumentation:这是JProfier全功能模式。在class加载之曲, JProfiler把相关功能代
码写入到需要分析的class的 bytecode中,对正在运行的jvm有一定影响。
Sampling:类似于样本统计,每隔一定时间(5ms)将每个线程栈中方法栈中的信息统计出来
优点:对CPU的开销非常低,对应用影响小(即使你不配置任何Filter)
缺点:一些数据/特性不能提供(例如:方法的调用次数、执行时间)
注: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
分析:内存中的对象的情况
分析内存泄漏,默认没有开启
JProfiler通过对线程历史的监控判断其运行状态,并监控是否有线程阻塞产生,还能将一个线程所管理的方法以树状形式呈现。对线程剖析。
线程历史 Thread History
显示一个与线程活动和线程状态在一起的活动时间表。
线程监控 Thread Monitor
显示一个列表,包括所有的活动线程以及它们目前的活动状况。
线程转储 Thread Dumps
显示所有线程的堆栈跟踪
线程分析主要关心三个方面:
监控和锁 Monitors& Locks所有线程持有锁的情况以及锁的信息。
观察JVM的内部线程并查看状态
Jprofiler 与 Visual VM 这两款工具在业界知名度也比较高,他们的优点是可以图形界面上看到各维度的性能数据,使用者根
据这些数据进行综合分析,然后判断哪里出现了性能问题
但是这两款工具也有个缺点,都必须在服务端项目进程中配置相关的监控参数。然后工具通过远程连接到项目进程,获取相关的数据。这样就会带来一些不便,比如线上环境的网络是隔离的,本地的监控工具根本连不上线上环境
Arthas(阿尔萨斯)是Alibaba开源的Java诊断工具,深受开发者喜爱在线排查问题,无需重启;动态跟踪Java代码;实时监控JVM状态。
Arthas支持JDK6+,支持 Linux/Mac/ Windows,采用命令行交互模式,同时提供丰富的Tab自动补全功能,进一步方便进行问题的定位和诊断
当你遇到以下类似问题而束手无策时, Arthas可以帮助你解决:
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]$,说明打开进入了监控客户端,在这里就可以执行相关命令进行查看了
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快捷键列表及自定义快捷键
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功能
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已加载的类信息
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,类加载信息
monitor–方法的监控
watch–方法执行数据观测
trace–方法内部调用路径,并输出方法路径上的每个节点上耗时
stack–输出当前方法被调用的调用路径
tt–方法执行数据的时空隧道,记录下指定方法每次调用的入参和返回信息,并能对这些不同的时间下
monitor命令:方法执行监控
对匹配class- pattern/ method- pattern的类、方法的调用进行监控。涉及方法的调用次数执行时间、失败率等
monitor命令是一个非实时返回命令
常用参数:
class- pattern 类名表达式匹配
method- pattern 方法名表达式匹配
-c 统计周期,默认值为128秒
watch命令:方法执行数据观测
trace命令;
方法内部调用路径, 输出方法路径上的每个节点上耗时
补充说明:
stack命令
输出当前方法被调用的调用路径
tt命令:
方法执行数据的时空隧道,记录下指定方法每次调用的入参和返回信息,并能对这些不同的时间下调用进行观测。
常用参数:
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是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插件
在追求极致性能的场景下,了解你的程序运行过程中cpu在干什么很重要,火焰图就是一种非常直观的展示cpu在程序整个生命周期过程中时间分配的工具。
火焰图对于现代的程序员不应该陌生,这个工具可以非常直观的显示出调用栈中的CPU消耗瓶颈
火焰图,简单通过x轴横条宽度来度量时间指标,y轴代表线程栈的层次。
比较稳定,后续版本基本不会变化 以-开头
各种选项
运行java或者java-hep可以看到所有的标准选项
Hotspot JVM有两种模式
Hotspot JVM有两种模式,分别是 server和 client,分别通过- server和- client模
特点
各种选项
运行java -X命令可以看到所有的X选项
-Xmixed 混合模式执行(默认) ; 让JIT根据程序运行的情况,有选择的执行
-Xint 仅解释模式执行;禁用JIT,所有字节码都被解释执行,这个模式的速度最慢的
-Xcomp 仅采用即时编译器模式 ;所有字节码第一次使用就都被编译成本地代码,然后再执行
-Xbootclasspath: <用;分隔的目录和zip/jar文件>设置搜索路径以引导类和资源
-Bootsclasspath/a: <用;分隔的目录和zip/jar文件> 附加在引导类路径末尾
-Xbootclasspath/p: <用;分隔的目录和zip/jar文件>置于引导类路径之前
-Xdiag 显示附加诊断消息
-Xnoclassgc 禁用类垃圾收集
-Xincgc 启用增量垃圾收集
-Xloggc:
-Xbatch 禁用后台编译
-Xms
-Xmx
说明:
-Xms
-Xmx
-Xss
特点
作用
用于开发和调试JVM
分类
永久代
-XX:Permsize=256m 设置永久代初始值为256M
-XX:MaxPermSize=256m 设置永久代最大值为256M
元空间
-XX:MaxDirectMemorySize 指定DirectMemory容量,若未指定,则默认与Java堆最大值一样
Serial收集器作为 Hotspot中client模式下的默认新生代垃圾收集器。 Serial Old是运行在client模式下默认的老年代的垃圾回收器。
-XX:+UseSerialGC
指定年轻代和老年代都使用串行收集器。等价于新生代用 Serial GC,且老年代用 Serial Old GC。可以获得最高的单线程收集效率。
-XX:+UseParNewGC
手动指定使用 ParDew收集器执行内存回收任务。它表示年轻代使用并行收集器,不影响老 年代。
-XX:ParallelGCThreads=N
限制线程数量,默认开启和CPU数据相同的线程数。
吞吐量优先
-XX:+UseConcMarkSweepGC 手动指定使用CMS收集器执行内存回收任务
-XX:CMSInitiatingOccupanyFraction 设置堆内存使用率的阀值,一旦达到该阀值,便开始进回收。
-XX:UseCMSCompactAtFullCollection 用于指定在执行完Full GC后对内存空间进行压缩整理.以此避兔内存碎片的产生。不过由于内存压缩整理过程无法并发执行,所带来的问题就是停顿时间变得更长了
-XX:CMSFullGCsBeforeCompaction 设置在执行多少次Full GC后对内存空间进行压缩整理。
-XX:ParallelCMSThreads 设置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 这个阶段
-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调优参数
Java提供了java.lang.management包用于监视和管理Java虚拟机和Java运行时中的其他组件, 它允许本地和远程监控和管理运行的Java虚拟机。其中 ManagementFactory这个类还是挺常用的另外还有 Runtime类也可以获取一些内存、CPU核数等相关的数据。
通过这些api可以监控我们的应用服务器的堆内存使用情况,设置一些阈值进行报警等处理。
针对 HotSpot VM的实现,它里面的GC按照回收区域又分为两大种类型:一种是部分收集(Partial GC ) 另一种是整堆收集(Full GC)
部分收集:不是完整收集整个Java堆的垃圾收集。其中又分为:
混合收集( Mixed gc):收集整个新生代以及部分老年代的垃圾收集
整堆收集(Full GC):收集整个java堆和方法区的垃圾收集
哪些情况会触发Full GC?
GC日志格式的规律一般都是:GC前内存占用一>GC后内存占用(该区域内存总大小)
PSYounGen:5986K->696K(8704K)] 5986K-9704K(9216K)
中括号内:GC回收前年轻代堆大小,回收后大小,(年轻代堆总大小)
括号外:GC回收前年轻代和老年代大小,回收后大小,(年轻代和老年代总大小)
GC日志中有三个时间:user,sys和real
由于多核的原因,一般的GC事件中, real time是小于sys+ user time的,因为一般是多个线程并发的去做GC,所以 real time是要小于sys+ user time的。如果real>sys+user的话,则你的应用可能存在下列问题:IO负载非常重或者是CPU不够用。
GC日志看起来比较麻烦, GC日志可视化分析工具 GCeasy和 GCviewer等。通过GC日志可视化分析工具,我们可以很方便的看到JWM各个分代 的内存使用情况、垃圾回收次数、垃圾回收的原因、垃圾回收占用的时间、吞吐量等,这些指标在我们进行JVM调优的时候是很有用的
如果想把GC日志存到文件的话,是下面这个参数:
-Xloggc: /path/to/gc.log
然后就可以用一些工具去分析这些gc日志
官网地址 GCeasy是一款在线的GC日志分析器,可以通过GC日志分析进内存泄漏检测、GC暂停原因分析、JVM配置建议优化等功能,而且是可以免费使用的(有一些服务是收费的)。
GCViewer是一个免费的、离线的开源的分析小工具,用于可视化查看由SUN/Oracle,IBM,HP和BEA Java虚拟机产生的垃圾收集器的日志。
GCViewer用于可视化 Java VM选项-verbose:gc和.NET生成的数据-Xloggc: