1、线程控制 2、磁盘读写 3、数据库访问 4、网络I/O 5、垃圾收集2、为什么需要调优
1、防止出现OOM《关于OOM可以参考》 2、解决OOM 3、减少Full GC出现的频率3、性能优化的步骤
1、性能监控4、简单命令行工具一种以非强行或者入侵方式收集或查看应用运营性能数据的操作。 监控通常是指一种在生产、质量评估或者开发环境下实施的带有预防或主动性的操作。 当应用出现性能问题却没有足够多的线索时,首先需要进行性能监控,再进行性能分析。 通常发现的问题:频繁GC、CPU过高、OOM、内存泄露、死锁、响应时间过长等。 2、性能分析
一种以侵入方式收集运行性能数据的操作,它会影响应用的吞吐量或响应性。 性能分析是针对性能问题的答复结果,关注的范围通常比性能监控更加集中。 性能分析很少在生产环境下进行,通常是在质量评估、系统测试或者开发环境中进行,是性能监控之后的步骤。 分析常见方式有:
打印GC日志,通过GCviewer或者http://gceasy.io来分析异常信息。 灵活运用命令行工具、jstack、jmap、jinfo等。 dump出堆文件,使用内存分析工具分析文件。 使用阿里Arthas、jconsole、JVisualVM来实时查看JVM状态。 jstack查看堆栈信息。
3、性能调优:一种为改善应用响应性或吞吐量而更改参数、源代码、属性配置的操作,性能调优是在性能监控、性能分析之后的操作。 常见操作:
适当增加内存,根据业务背景选择垃圾回收器。 优化代码,控制内存使用。 增加机器,分散节点压力。 合理设置线程池线程数量。 使用中间件提高程序效率,比如缓存、消息队列等。 注意:具体问题需要具体分析。
4、性能指标《可以参考垃圾回收的GC的性能指标评估》
在jdk安装的bin目录中,有一系列的辅助工具,用来获取目标JVM不同方面、不同层次的信息,帮助开发人员很好地解决Java应用程序的一些问题。二、jps:查看正在运行的Java程序 1、jps介绍
1、jps(Java Process Status):显示指定系统内所有的HotSpot虚拟机进程(查看虚拟机进程信息),可用于查询正在运行的虚拟机进程。 2、对于本地虚拟机进程来说,进程的本地虚拟机ID与操作系统的进程ID是一致的,也是唯一的。2、jps语法参数
1、基本语法:jsp [options] [hostid] 2、options参数说明三、jstat:查看JVM统计信息 1、jstat介绍
说明:以上参数可以综合使用 注意:如果某Java进程关闭了默认开启的UsePerfData参数(即使用参数-XX:-UsePerfData),那么jps命令就无法查到该Java进程。 3、hostid参数说明
RMI注册表中注册的主机名,如果想要远程监控远程主机上的Java程序,需要安装jstatd。 对于具有更严格的安全实践的网络场所而言,可能使用一个自定义的策略文件来显示对特定的可信主机或网络的访问,尽管这种技术容易受到IP地址欺诈攻击。 如果安全问题无法使用一个定制的策略文件来处理,那么最安全的操作是不运行jstatd服务器,而是在本地使用jstat和jps工具。
1、jstat(JVM Statistics Monitoring Tool):用于监听虚拟机各种运行状态信息的命令行工具,可以显示本地或者远程虚拟机进程中的类装载、内存、垃圾收集、JIT编译等运行数据。 2、在没有GUI图形界面,只提供了纯文本控制台环境的服务器上,它将是运行期定位虚拟机性能问题的首选工具,常用于检测垃圾回收问题以及内存泄露问题。官方文档2、jstat语法参数
1、基本语法:jstat -3、option参数详细说明
1、类装载相关的:4、垃圾回收相关参数使用举例-class:显示ClassLoader的相关信息:类的装载、卸载数量、总空间、类装载所消耗的时间等 2、垃圾回收相关的:
-gc:显示与GC相关的堆信息。包括Eden区、两个Survivor区、老年代、永久代等的容量、已用空间、GC时间合计等信息 -gccapacity:显示内容与-gc基本相同,但输出主要关注Java堆各个区域使用到的最大最小空间 -gcutil:显示内容与-gc基本相同,但输出主要关注已使用空间占总空间的百分比 -gccause:与-gcutil功能一样,但是会额外输出导致最后一次或当前正在发生发生的GC的产生原因 -gcnew:显示新生代GC情况 -gcnewcapacity:显示内容与-gcnew基本相同,输出主要关注使用到的最大最小空间 -geold:显示老年代GC情况 3、JIT相关的:
-compiler:显示JIT编译器编译过的方法、耗时等信息 -printcompilation:输出已经被JIT编译的方法
public class GCTest {
public static void main(String[] args) {
ArrayList list = new ArrayList<>();
for (int i = 0; i < 1000; i++) {
//每次创建100kb大小的数组
byte [] bytes = new byte[1024 * 100];
list.add(bytes);
try {
Thread.sleep(120);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
1、-gc参数使用举例5、补充说明表头列含义说明:
S0C/S1C:第一个与第二个幸存者区的大小(字节) S0U/S1U:第一个与第二个幸存者区已使用的大小(字节) EC/EU:Eden空间的大小与Eden空间已使用大小(字节) OC/OU:老年代的大小与老年代已使用的大小(字节) MC/MU:方法区的大小与方法区已使用的大小(字节) CCSC/CCSU:压缩类空间的大小与压缩类空间已使用的大小(字节) YGC:从应用程序启动到采样时young gc的次数 YGCT:从应用程序启动到采样时young gc消耗时间(秒) FGC:从应用程序启动到采样时full gc的次数 FGCT:从应用程序启动到采样时的full gc的消耗时间(秒) GCT:从应用程序启动到采样时gc的总时间 2、-gcutil参数使用举例:在堆溢出之前会发生FULL GC
表头列含义说明:
S0/S1:代表幸存者0区与幸存者1区占比情况 E:代表伊甸园区占比情况 O:代表老年代占比情况 M:代表方法区占比情况 CCS:代表压缩类占比情况 YGC/YGCT:代表年轻代垃圾回收的次数与年轻代进行垃圾回收需要的时间 FGC/FGCT:代表代表Full GC的次数与Full GC的时间 GCT:代表垃圾回收的总时间 3、-gccause参数使用举例
表头参数说明:
LGCC/GCC:代表垃圾回收的原因 其他列含义说明参考-gcutil参数的使用举例
1、-t参数补充说明四、jinfo:实时查看和修改JVM配置参数 1、jinfo介绍可以比较Java进程的启动时间以及总GC时间(GCT列),或者两次测量的间隔时间以及总GC时间的增量,来得出GC时间占运行时间的比例。 如果该比例超过20%,则说明目前堆的压力较大;如果该比例超过90%,则说明堆里几乎没有可用空间,随时可能OMM。
2、jstat还可以用来判断是否出现内存泄漏
在长时间运行的Java程序中,可以运行jstat命令连续获取多行性能数据,并取这几行数据中的OU列(即已占用的老年代内存)的最小值。(抽样方法) 每隔一段较长时间重复一次上述操作,来获取到多组OU的最小值。可以画成折线图,如果这些值呈上涨趋势,则说明该Java程序的老年代内存已使用量在不断上涨,也就意味着无法回收的对象在不断增加,因此很有可能会造成内存泄漏。
1、jinfo(Configuration Info for Java):用于查看虚拟机配置参数信息,也可以调整虚拟机的配置参数(立即生效)。 2、在很多情况下,Java应用程序不会指定所有的Java虚拟机参数。而此时,开发人员可能不知道某一个具体的Java虚拟机参数的默认值,此时可以通过jinfo命令可以很方便的查看Java虚拟机参数的当前值。2、jinfo语法参数
1、基本语法:jinfo [options] pid 2、options参数说明:3、option相关参数使用举例3、注意:对参数进行修改的时候,会立即生效,但是并非所有的参数都支持动态修改。参数只有被标记为manageable的可以被实时修改
参数名 说明 no option 不带任何可选参数,直接jinfo pid,输出全部的参数和系统属性 -flag name 输出对应name的参数 -flag +name 开启name对应的参数,只有被标记为manageable的参数才可以被动态修改 -flag -name 关闭name对应的参数,只有被标记为manageable的参数才可以被动态修改 -flag name=value 设定name对应的参数 -flags 输出全部的参数 -sysprops 输出系统参数 可以通过命令在linux上查看被标记为manageable的参数:java -XX:+PrintFlagsFinal -version | grep manageable
1、-sysprops:可以查看由System.getProperties()取得的参数4、补充说明2、-flags:查看赋过值的一些参数
3、-flag name:查看具体参数的值,比如查看使用的垃圾收集器
4、-flag [+-]name 或 -flag name=value:动态的开启或者关闭某个参数,或者给数值类型的参数设定值
1、java -XX:+PrintFlagsInitial:查看所有JVM参数启动的初始值 2、java -XX:+PrintFlagsFinal:查看所有JVM参数的最终值 3、java -XX:+PrintCommandLineFlags:查看哪些已经被用户或者JVM设置过的详细的XX参数的名称和值五、jmap:导出内存映像文件与内存使用情况 1、jmap介绍
1、jmap(JVM Memory Map):作用一方面是获取dump文件(堆转储快照文件,二进制文件),它还可以获取目标Java进程的内存相关信息,包括Java堆各区域的使用情况、堆中对象的统计信息、类加载信息等。2、jmap语法参数
1、基本语法有三种情况:3、option重要参数的详细说明jmap [option] pid jmap [option] pid
> jmap [option] [server_id@] 2、option参数说明: 3、executable
参数名 说明 -dump 生成dump文件 -finalizerinfo 显示在F-Queue中等待的Finalizer线程执行finalize方法的对象(linux中有效) -heap 输出整个堆空间的详细信息,包括GC的使用、堆配置信息,以及内存的使用信息等 -histo 输出堆空间中对象的统计信息,包括类、实例数量和合计容量 -permstat 以ClassLoader为统计口径输出永久代的内存状态信息(linux中有效) -F 当虚拟机进程对-dump选项没有任何响应时,强制执行生成dump文件(linux中有效) -h | -help jmap的帮助命令 -J 传递参数给jmap启动的JVM,例如-J -Xms60m :代表可执行的代码,比如使用 > 文件名称来指定生成的dump文件存放位置 4、[server_id@]<···>:是为远程连接准备的
1、-dump:生成Java堆转储快照dump文件,它还有如下几个可选参数:4、关于Heap Dump介绍-dump:live:只保存堆中的存活对象 -dump:format=b:以什么格式保存到文件(b表示二进制,还可以Json) -dump:file=
:保存的文件名 示例:jmap -dump:live,format=b,file=heap.hprof 2、-heap:输出整个堆空间的详细信息,包括GC的使用、堆配置信息,以及内存的使用信息等 3、-histo:输出堆空间中对象的统计信息,包括类、实例数量和合计容量,还有一个可选参数: -histo:live:只统计堆中的存活对象
1、Heap Dump又叫做堆存储文件,指一个Java进程在某个时间点的内存快照。Heap Dump在触发内存快照的时候会保存此刻的信息如下:5、手动方式导出内存映像文件所有的对象信息,包括对象实例、成员变量、存储于栈中的基本类型值和存储于堆中的其他对象的引用值。 所有的类信息,包括ClassLoader、类名称、父类、静态变量等。 GCRoot到所有这些对象的引用路径。 线程信息,包括线程的调用栈及此线程的线程局部变量(TLS)。 2、说明
通常在写Heap Dump文件前会触发一次Full GC,所以Heap Dump文件里保存的都是FullGC后留下的对象信息。 生成dump文件比较耗时 3、注意:
自动输出dump文件会在它之前触发一次Full GC,而手动不会在Full GC之后生成dump文件。 使用手动方式生成dump文件,一般指令执行之后就会生成,不用等到快出现OOM的时候。 使用自动方式生成dump文件,当出现OOM之前先生成dump文件。
1、手动方式导出内存映像文件:jmap -dump:format=b,file=
jmap -dump:live,format=b,file= 2、说明 filename是文件名称,而.hprof是后缀名,<***>代表该值可以省略 format=b表示生成的是标准的dump文件,用来进行格式限定
public class JMap {
public static void main(String[] args) {
ArrayList list = new ArrayList<>();
for (int i = 0; i < 1000; i++) {
//每次创建100kb大小的数组
byte [] bytes = new byte[1024 * 100];
list.add(bytes);
try {
Thread.sleep(120);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
1、运行程序,打开cmd6、自动方式导出内存映像文件
1、当程序发生OOM退出系统时,一些瞬间信息都会随着程序的终止而消失,而重现OOM问题往往比较困难或者耗时,自动导出dump文件就很重要了。 2、设置JVM启动参数自动生成内存映像文件:7、显示堆内存相关信息-XX:+HeapDumpOnOutOfMemoryError:在程序发生OOM时,导出应用程序的当前堆快照 -XX:HeapDumpPath=
:指定dump文件的生成的位置 3、设置JVM启动参数
1、jmap -heap pid只是时间点上的堆信息,而jstat可以添加参数,可以指定时间动态观察数据改变情况,而图形化界面工具(例如:jvisualVM)可以直观明了的用图表的方式动态展示出相关信息。8、补充说明2、jmap -histo pid输出当前时刻内存中的堆中对象的同级信息,包括类、实例数量和合计容量
3、缺点:不能连续进行监控
1、由于jmap将访问堆中的所有对象,为了保证在此过程中不被应用程序干扰,jmap需要借助安全点机制,让所有线程停留在不改变堆中数据的状态。也就是说,由jmap导出的堆快照必订是安全点位置的,可能会导致基于该堆快照的分析结果存在偏差。 2、假设在编译生成的机器码中,某些对象的生命周期在两个安全点之间,那么:live参数将无法探知到这些对象。 3、如果某一个线程长时间无法跑到安全点,jmap会一直等待下去。 4、与jstat不同是垃圾回收器会主动将jstat所需要的摘要数据保存至固定位置之中,jstat直接读取即可。六、jhat:JDK自带堆分析工具 1、jhat介绍
1、jhat命令在JDK9及其之后就被移除了,官方建议使用jvisualVM代替jhat,所以简单了解即可 2、jhat(Java Heap Analysis Tool):是Sun JDK提供的,与jmap命令搭配使用,用于分析jmap生成的dump文件(堆转储快照)。jhat内置了一个微型的HTTP/HTML服务器,生成dump文件的分析结果后,可以在浏览器中查看分析结果。 3、使用jhat命令,就启动了一个http服务,生成之后会返回一个端口号,通过http://localhost:端口号即可访问2、jhat语法参数
1、基本语法:jhat [option] dumpFile 2、dumpFile:表示dump文件的地址及名称 3、option参数说明:3、使用示例
参数名 说明 -stack false|true 关闭或打开对象分配调用栈跟踪 -refs false|true 关闭或打开对象引用跟踪 -port 设置jhat的HTTP Server的端口号,默认7000 -exclude 执行对象查询时需要排除的数据成员 -baseline 指定一个基准堆转储 -debug 设置debug级别,可选0,1,2三个级别 -version 启动后显示版本就退出 -J 传入启动参数,如:-J -Xms60m
1、对之前导出的dump文件进行分析七、jstack:打印JVM中线程快照 1、jstack介绍2、浏览器访问http://localhost:7000查看分析结果
分析结果默认以包为单位进行分组显示,分析内存泄漏问题主要会使用到其中的“Heap Histogram”(与jmap-histo功能一样)与OQL页签的功能,前者可以找到内存中总容量最大的对象,后 者是标准的对象查询语言,使用类似SQL的语法对内存中的对象进行查询统计。
1、jstack(JVM Stack Trace):用于生成虚拟机指定进程当前时刻的线程快照(虚拟机堆栈跟踪)。线程快照就是当前虚拟机内每一条线程正在执行的的方法堆栈的集合。 2、生成线程快照的目的通常是定位线程出现长时间停顿的原因,如线程间死锁、死循环、请求外部资源导致的长时间挂起等,都是导致线程长时间停顿的常见原因。线程出现停顿时通过jstack来查看各个线程的调用堆栈, 就可以获知没有响应的线程到底在后台做些什么事情,或者等待着什么资源。 3、在线程dump文件中,要留意下面几种状态2、jstack语法参数死锁(Deadlock) 等待资源(Waiting on condition) 等待获取监视器(Waiting on monitor entry) 阻塞(Blocked) 执行(Runnable) 暂停(Supended) 对象等待中(Object.wait()或者TIMED_WAITING) 停止(Parked)
1、基本语法:jstack [option] pid 2、option参数说明:3、使用示例
参数名 说明 -F 当正常输出的请求不被响应时,强制输出线程堆栈 -l 除堆栈外,显示关于锁的附加信息 -m 如果调用到本地方法的话,可以显示C/C++的堆栈
public class DeadLock {
public static void main(String[] args) {
Object A = new Object();
Object B = new Object();
Thread t1 = new Thread(()->{
synchronized (A) {
System.out.println(new Date() + "【"+ Thread.currentThread().getName() + "】 - Lock A");
try {
sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (B) {
System.out.println(new Date() + "【"+ Thread.currentThread().getName() + "】 - Lock B");
System.out.println(new Date() + "【"+ Thread.currentThread().getName() + "】 - 操作...");
}
}
},"t1");
Thread t2 = new Thread(()->{
synchronized (B) {
System.out.println(new Date() + "【"+ Thread.currentThread().getName() + "】 - Lock B");
try {
sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (A) {
System.out.println(new Date() + "【"+ Thread.currentThread().getName() + "】 - Lock A");
System.out.println(new Date() + "【"+ Thread.currentThread().getName() + "】 - 操作...");
}
}
},"t2");
t1.start();
t2.start();
}
}
1、运行程序,打开cmd,首先通过jps获取到当前运行程序的进程id,再通过jstack命令查看线程堆栈结果2、查看部分结果
3、接着往下看分析结果
public class ThreadSleep {
public static void main(String[] args) {
System.out.println(">>>开始");
try {
//线程休眠10分钟
Thread.sleep(1000 * 60 * 10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(">>>结束");
}
}
1、运行程序之后,通过jstack命令查看线程堆栈结果
public class ThreadSyn {
public static void main(String[] args) {
Number number = new Number();
Thread thread1 = new Thread(number);
Thread thread2 = new Thread(number);
thread1.setName("线程1");
thread2.setName("线程2");
thread1.start();
thread2.start();
}
}
class Number implements Runnable {
private int number = 1;
@Override
public void run() {
while (true) {
synchronized (this) {
if (number <= 100) {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":" + number);
number++;
} else {
break;
}
}
}
}
}
1、运行程序之后,通过jstack命令查看线程堆栈结果4、补充:通过代码方式监控线程
1、从JDK 5起,java.lang.Thread类新增了一个getAllStackTraces()方法用于获取虚拟机中所有线程的StackTraceElement对象。使用这个方法可以通过简单的几行代码完成jstack的大部分功能
public class AllStackTrace {
public static void main(String[] args) {
//获取虚拟机中所有线程的StackTraceElement对象
Map allStackTraces = Thread.getAllStackTraces();
for (Map.Entry threadEntry : allStackTraces.entrySet()) {
Thread thread = threadEntry.getKey();
StackTraceElement[] value = threadEntry.getValue();
System.out.println("Thread name is 【" + thread.getName() + "】");
for (StackTraceElement element : value) {
System.out.println("t" + element.toString());
}
}
}
}
5、jstack管理远程进程
1、需要在远程程序的启动参数中增加如下配置:八、jcmd:多功能命令行 1、jcmd介绍-Djava.rmi.server.hostname=IP Address:服务本机IP -Dcom.sun.management.jmxremote:开启jmx,jdk1.5之后默认开启了,可以省略 -Dcom.sun.management.jmxremote.ssl=false:不开启ssl通信 -Dcom.sun.management.jmxremote.authenticate=false:不开启验证 -Dcom.sun.management.jmxremote.port=12609:jmx的端口
1、在JDK1.7之后,新增了一个命令行工具jcmd。它是一个多功能工具,可以用来实现除了jstat之外的所有命令的功能,比如:用它来导出堆、内存使用、查看Java进程、导出线程信息、执行GC、JVM运行时间等。 2、jcmd拥有jmap的大部分功能,并且在Oracle的官网上也推荐使用jcmd命令替代jmap命令。2、jcmd语法参数
1、jcmd -l:列出所有的JVM进程 2、jcmd pid help:针对指定的进程,列出支持的所有命令 3、jcmd pid 具体命令:显示指定进程的指令命令的数据 4、具体命令有哪些的获取方式:通过jcmd pid help查询出来



