jvm-调优


当Java程序性能达不到⽬标,且架构上的优化、代码上的优化⼿段都已经穷尽时,通常需要调整垃圾回收器和JVM内存空间配置来进⼀步提⾼性能,这就是JVM调优。JVM性能调优是个⽼⽣常谈的话题,本将带您系统地探寻JVM调优。

本⽂通过理论加实践的⽅式逐步讲解JVM调优。共分四部分。

  • 第⼀部分:概述,主要讲解关于JVM调优的理论概念知识;
  • 第⼆部分:JVM调优⼯具,主要讲述在调优过程中所使⽤的⼯具以及命令;
  • 第三部分:JVM参数,主要讲述JVM主要调优参数;
  • 第四部分:案例分析,通过前三章的理论基础结合真实项⽬案例来看看如何进⾏调优。

1:概述

主要通过JVM基础、调优层次、调优指标、何时JVM调优、JVM调优⽬标、JVM调优原则以及JVM调优步骤介绍,以达到对JVM调优有个整体上的认识,后续通过具体的案例结合理论来看下如何进⾏调优。

1.1:JVM基础

在讲JVM调优之前,先简单回顾下JVM相关的基础知识,这⾥我们重点回顾下JAVA堆、垃圾回收器。这两块也是在JVM调优过程中重点关注的部分。

1.1.1:java堆

被所有线程共享,在虚拟机启动时创建,⽤来存放对象实例,⼏乎所有的对象实例都在这⾥分配内存。对于⼤多数应⽤来说,Java堆(Java Heap)是Java虚拟机所管理的内存中最⼤的⼀块。Java堆是垃圾收集器管理的主要区域,因此很多时候也被称做“GC堆”。如果从内存回收的⻆度看,由于现在收集器基本都是采⽤的分代收集算法,所以java堆中还可以细分为:新⽣代和⽼年代;新⽣代⼜有Eden空间、From Survivor空间、To Survivor空间三部分。java 堆不需要连续内存,并且可以通过动态增加其内存,增加失败会抛出 OutOfMemoryError 异常。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

1.1.2:垃圾回收

针对HotSpot VM的实现,它⾥⾯的GC其实准确分类只有两⼤种:

  • Partial GC:并不收集整个GC堆的模式。其中⼜分为
    • 新⽣代的回收:(Minor GC/Young GC),只收集新⽣代的GC
    • ⽼年代的回收:(Major GC/Old GC),只收集⽼年代的GC。(⽬前只有CMS的concurrent collection是这个模式,只收集⽼年代。)
    • Mixed GC:收集整个young gen以及部分old gen的GC。(只有G1有这个模式。)
  • Full GC:收集整个堆,包括young gen、old gen、perm gen(如果存在的话)等所有部分的模式。
    • Major GC通常是跟full GC是等价的,收集整个GC堆。但因为HotSpot VM发展了这么多年,外界对各种名词的解读已经完全混乱了,当有⼈说“major GC”的时候⼀定要问清楚他想要指的是上⾯的full GC还是old GC。

1.1.3:STW

Stop-the-World,简称STW,指的是GC事件发⽣过程中,会产⽣应⽤程序的停顿。停顿是产⽣时整个应⽤程序线程会被暂停,没有任何响应,有点像卡死的感觉,这个停顿称为STW。Stop-the-world意味着 JVM由于要执⾏GC⽽停⽌了应⽤程序(⽤户线程、⼯作线程)的执⾏,并且这种情形会在任何⼀种GC算法中发⽣。当Stop-the-world发⽣时,除了GC所需的线程以外,所有线程都处于等待状态直到GC任务完成。
在这里插入图片描述

STW事件和采⽤哪款GC⽆关,所有的GC都有这个事件。哪怕是G1也不能完全避免Stop-the-world情况发⽣,只能说垃圾回收器越来越优秀,回收效率越来越⾼,尽可能缩短了暂停时间。

STW是JVM在后台⾃动发起和⾃动完成的,在⽤户不可⻅的情况下,把⽤户正常的⼯作线程全部停掉。

随着应⽤程序越来越复杂,每次GC不能保证应⽤程序的正常运⾏。⽽经常造成STW的GC跟不上实际的需求,所以才需要不断对GC进⾏优化。事实上,GC优化很多时候就是指减少Stop-the-world发⽣的时间,从⽽使系统具有⾼吞吐 、低停顿的特点。

1.2:调优层次

性能调优包含多个层次,⽐如:架构调优、代码调优、JVM调优、数据库调优、操作系统调优等。架构调优和代码调优是JVM调优的基础,其中架构调优是对系统影响最⼤的。

1.3:调优指标

  • 吞吐量:运⾏⽤户代码的时间占总运⾏时间的⽐例 (总运⾏时间=程序的运⾏时间+内存回收的时间);
  • 暂停时间:执⾏垃圾收集时,
  • 内存占⽤:java堆区所占的内存⼤⼩;

注重吞吐量:吞吐量优先,意味着在单位时间内,STW的时间最短:0.2 + 0.2= 0.4 s
在这里插入图片描述

注重低延迟:暂停时间优先,意味这尽可能让单次STW的时间最短:0.1 + 0.1 + 0.1 + 0.1 + 0.1 = 0.5s
在这里插入图片描述
这三者共同构成⼀个”不可能三⻆“。三者总体的表现会随着技术进步⽽越来越好。⼀款优秀的收集器通常最多同时满⾜其中的两项。

简单来说,主要抓住两点:

  • 吞吐量
  • 暂停时间

在设计(或使⽤)GC算法时,必须确定我们的⽬标:⼀个GC算法只可能针对两个⽬标之⼀(即只专注于较⼤吞吐量或最⼩暂停时间),或尝试找⼀个⼆者的折衷。

现在标准,在最⼤吞吐量优先的情况下,降低停顿时间。

1.4: JVM调优原则

1.4.1: 优先原则

优先架构调优和代码调优,JVM优化是不得已的⼿段,⼤多数的Java应⽤不需要进⾏JVM优化

1.4.2: 堆设置

参数-Xms和-Xmx,通常设置为相同的值,避免运⾏时要不断扩展JVM内存,建议扩⼤⾄3-4倍FullGC后的⽼年代空间占⽤。

1.4.3: 垃圾回收器设置

GC 发展阶段:Serial => Parallel(并⾏) => CMS(并发) => G1 => ZGC

截⽌jdk1.8 ,⼀共有7款不同垃圾收集器。每⼀款不同的垃圾收集器都有不同的特点,在具体使⽤的时候,需要根据具体的情况选择不同的垃圾回收器

垃圾收集器分类作用位置使用方法特点使用场景
Serial串⾏新⽣代复制算法响应速度优先适⽤于单CPU环境下client模式
ParNew并⾏新⽣代复制算法响应速度优先多CPU环境下Server模式下与CMS配合
Parallel并⾏新⽣代复制算法吞吐量优先适⽤于后台运算⽽不需要太多交互的场景
Serial Old串⾏⽼年代标记-压缩响应速度优先适⽤于单CPU环境下client模式
Parallel Old并⾏⽼年代标记-压缩吞吐量优先适⽤于后台运算⽽不需要太多交互的场景
CMS并发⽼年代标记-清除响应速度优先适⽤于互联⽹或B/S业务
G1并发、并⾏新、老复制;标记-清除响应速度优先⾯向服务端应⽤

两个垃圾回收器之间有连线表示它们可以搭配使⽤,可选的搭配⽅案如下:
在这里插入图片描述
G1的适⽤场景

  • 1: ⾯向服务端应⽤,针对具有⼤内存、多处理器的机器。(在普通⼤⼩的堆⾥表现并不惊喜)
  • 2:最主要的应⽤是需要低GC延迟,并具有⼤堆的应⽤程序提供解决⽅案;
  • 3:在堆⼤⼩约6GB或更⼤时,可预测的暂停时间可以低于0.5秒;(G1通过每次只清理⼀部分⽽不是全部Region的增量式清理在保证每次GC停顿时间不会过⻓) 。
  • 4:⽤来替换掉JDK1.5中的CMS收集器,以下情况,使⽤G1可能⽐CMS好
    • 超过50% 的java堆被活动数据占⽤;
    • 对象分配频率或年代提升频率变化很⼤;
    • GC停顿时间过⻓(⼤于0.5⾄1秒)
  • 5:从经验上来说,整体⽽⾔:(这个临界点⼤概是在 6~8G 之间(经验值))
    • ⼩内存应⽤上,CMS ⼤概率会优于 G1;
    • ⼤内存应⽤上,G1 则很可能更胜⼀筹。

其他收集器适⽤场景

  • 如果你想要最⼩化地使⽤内存和并⾏开销,请选择Serial Old(⽼年代) + Serial(年轻代)
  • 如果你想要最⼤化应⽤程序的吞吐量,请选择Parallel Old(⽼年代) + Parallel(年轻代)
  • 如果你想要最⼩化GC的中断或停顿时间,请选择CMS(⽼年代) + ParNew(年轻代)

1.4.4:年轻代设置

参数-Xmn,1-1.5倍FullGC之后的⽼年代空间占⽤。

避免新⽣代设置过⼩,当新⽣代设置过⼩时,会产⽣两种⽐较明显的现象,⼀是minor GC次数频繁,⼆是可能导致 minor GC对象直接进⼊⽼年代。当⽼年代内存不⾜时,会触发Full GC。

避免新⽣代设置过⼤,当新⽣代设置过⼤时,会带来两个问题:⼀是⽼年代变⼩,可能导致Full GC频繁执⾏;⼆是 minor GC 执⾏回收的时间⼤幅度增加。

-Xmn ⾄于这个参数则是对 -XX:newSize、-XX:MaxnewSize两个参数的同时配置,也就是说如果通过-Xmn来配置新⽣代的内存⼤⼩,那么-XX:newSize = -XX:MaxnewSize = -Xmn,虽然会很⽅便,但需要注意的是这个参数是在JDK1.4版本以后才使⽤的。

1.4.4:年老代设置

注重低延迟的应⽤:

  • 1:年⽼代使⽤并发收集器,所以其⼤⼩需要⼩⼼设置,⼀般要考虑并发会话率和会话持续时间等⼀些参数;
  • 2:如果堆设置偏⼩,可能会造成内存碎⽚、⾼回收频率以及应⽤暂停;
  • 3:如果堆设置偏⼤,则需要较⻓的收集时间。

吞吐量优先的应⽤

⼀般吞吐量优先的应⽤都有⼀个很⼤的年轻代和⼀个较⼩的年⽼代。原因是,这样可以尽可能回收掉⼤部分短期对象,减少中期的对象,⽽年⽼代尽存放⻓期存活对象

1.4.6:⽅法区设置

基于jdk1.7版本,永久代:参数-XX:PermSize和-XX:MaxPermSize;
基于jdk1.8版本,元空间:参数 -XX:MetaspaceSize和-XX:MaxMetaspaceSize;
通常设置为相同的值,避免运⾏时要不断扩展,建议扩⼤⾄1.2-1.5倍FullGc后的永久带空间占⽤。

1.5 JVM调优步骤

1.5.1:监控分析

分析GC⽇志及dump⽂件,判断是否需要优化,确定瓶颈问题点。监控JVM的状况,可以使⽤jdk⾃带的命令或者第三⽅可视化⼯具查看其运⾏状态
如何⽣成GC⽇志
常⽤参数部分会详细讲解如何⽣成GC⽇志
如何产⽣dump⽂件

  • 1:JVM的配置⽂件中配置(JVM启动时增加两个参数)
#出现 OOME 时⽣成堆 dump:
-XX:+HeapDumpOnOutOfMemoryError
#⽣成堆⽂件地址:
-XX:HeapDumpPath=/home/hadoop/dump/
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
/*
-Xmx100m -Xms100m -XX:+HeapDumpOnOutOfMemoryError -
XX:HeapDumpPath=/Users/hadoop/Desktop
*/
public class OOMTest {
	 public static void main(String[] args) {
		 List<Picture> list = new ArrayList<>();
		 while (true){
			 try {
				Thread.sleep(20);
			 } catch (InterruptedException e) {
				e.printStackTrace();
			 }
			 list.add(new Picture(new Random().nextInt(1024 * 1024)));
		 }
	 }
}
class Picture{
	private byte[] pixels;
	public Picture(int length){
		this.pixels = new byte[length];
	}
}
  • 2.jmap⽣成
    发现程序异常前通过执⾏指令,直接⽣成当前JVM的dump⽂件,9257是指JVM的进程号
    • jmap -dump:file=⽂件名.dump [pid]
    • jmap -dump:format=b,file=testmap.dump 9257
import java.util.ArrayList;
import java.util.List;
/**
* -XX:+PrintCommandLineFlags 
*/
public class GCUseTest {
	 public static void main(String[] args) {
		 List<byte[]> list = new ArrayList<>();
		 while (true){
			 byte[] arr = new byte[100];
			 list.add(arr);
			 try {
				Thread.sleep(10000);
			 } catch (InterruptedException e) {
				e.printStackTrace();
			 }
		 }
	 }
}

第⼀种⽅式是⼀种事后⽅式,需要等待当前JVM出现问题后才能⽣成dump⽂件,实时性不⾼;
第⼆种⽅式在执⾏时,JVM是暂停服务的,所以对线上的运⾏会产⽣影响。所以建议第⼀种⽅式。

  • 3.第三⽅可视化⼯具⽣成

1.5.2:判断

如果各项参数设置合理,系统没有超时⽇志或异常信息出现,GC频率不⾼,GC耗时不⾼,那么没有必要进⾏GC优化,如果GC时间超过1-3秒,或者频繁GC,则必须优化。

遇到以下情况,就需要考虑进⾏JVM调优:

  • 系统吞吐量与响应性能不⾼或下降;
  • Heap内存(⽼年代)持续上涨达到设置的最⼤内存值;
  • Full GC 次数频繁;
  • GC 停顿时间过⻓(超过1秒);
  • 应⽤出现OutOfMemory 等内存异常;
  • 应⽤中有使⽤本地缓存且占⽤⼤量内存空间;

1.5.3:确定⽬标

调优的最终⽬的都是为了应⽤程序使⽤最⼩的硬件消耗来承载更⼤的吞吐量或者低延迟。jvm调优
主要是针对垃圾收集器的收集性能优化,减少GC的频率和Full GC的次数,令运⾏在虚拟机上的应⽤能
够使⽤更少的内存、⾼吞吐量、低延迟。

下⾯列举⼀些JVM调优的量化⽬标参考实例,注意:不同应⽤的JVM调优量化⽬标是不⼀样的。

  • 堆内存使⽤率 <= 70%
  • ⽼年代内存使⽤率<= 70%;
  • avgpause <= 1秒;
  • Full GC 次数0 或 avg pause interval >= 24⼩时 ;

1.5.4:调整参数

调优⼀般是从满⾜程序的内存使⽤需求开始的,之后是时间延迟的要求,最后才是吞吐量的要求,要基于这个步骤来不断优化,每⼀个步骤都是进⾏下⼀步的基础,不可逆⾏之。

1.5.5:对⽐调优前后指标差异

1.5.6:重复以上过程

1.5.7: 应⽤

找到合适的参数,先在单台服务器上试运⾏,然后将这些参数应⽤到所有服务器,并进⾏后续跟踪

2:JVM调优工具

2.1: jdk命令⾏

2.1.1: jsp

jps:Java Virtual Machine Process Status Tool
查看Java进程 ,相当于Linux下的ps命令,只不过它只列出Java进程。

jps :列出Java程序进程ID和Main函数名称
jps -q :只输出进程ID
jps -m :输出传递给Java进程(主函数)的参数
jps -l :输出主函数的完整路径
jps -v :显示传递给Java虚拟的参数

2.1.2: jstat

jstat:JVM Statistics Monitoring Tool

jstat可以查看Java程序运⾏时相关信息,可以通过它查看堆信息的相关情况

jstat -<options> [-t] [-h<lines>] <vmid> [<interval> [<count>]]
options:由以下值构成
-class:显示ClassLoader的相关信息
-compiler:显示JIT编译的相关信息
-gc:显示与GC相关信息
-gccapacity:显示各个代的容量和使⽤情况
-gccause:显示垃圾收集相关信息(同-gcutil),同时显示最后⼀次或当前正在发⽣的垃圾收集的诱发
原因
-gcnew:显示新⽣代信息
-gcnewcapacity:显示新⽣代⼤⼩和使⽤情况
-gcold:显示⽼年代信息
-gcoldcapacity:显示⽼年代⼤⼩
-gcpermcapacity:显示永久代⼤⼩
-gcutil:显示垃圾收集信息
-printcompilation:输出JIT编译的⽅法信息
-t:在输出信息前加上⼀个Timestamp列,显示程序的运⾏时间
-h:可以在周期性数据输出后,输出多少⾏数据后,跟着⼀个表头信息
interval:⽤于指定输出统计数据的周期,单位为毫秒
count:⽤于指定⼀个输出多少次数据
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
/**
-Xms500m -Xmx500m
*/
public class HeapInstanceTest {
	 byte[] buffer = new byte[new Random().nextInt(1024*200)];
	 public static void main(String[] args) {
	 List<HeapInstanceTest> list = new ArrayList<HeapInstanceTest>();
	 while (true){
		 list.add(new HeapInstanceTest());
		 try {
			Thread.sleep(100);
		 } catch (InterruptedException e) {
			e.printStackTrace();
		 }
	 }

	 }
}
  • 示例⼀: jstat -gc 7063 500 4
    下⾯输出的是GC相关信息,7063 是 进程ID ,采样时间间隔为500ms,采样数为4。
    在这里插入图片描述
S0C:年轻代中第⼀个survivor(幸存区)的容量 (字节)
S1C:年轻代中第⼆个survivor(幸存区)的容量 (字节)
S0U:年轻代中第⼀个survivor(幸存区)⽬前已使⽤空间 (字节)
S1U :年轻代中第⼆个survivor(幸存区)⽬前已使⽤空间 (字节)
EC :年轻代中Eden(伊甸园)的容量 (字节)
EU :年轻代中Eden(伊甸园)⽬前已使⽤空间 (字节)
OC :Old代的容量 (字节)
OU :Old代⽬前已使⽤空间 (字节)
MC:metaspace(元空间)的容量 (字节)
MU:metaspace(元空间)⽬前已使⽤空间 (字节)
CCSC:压缩类空间⼤⼩
CCSU:压缩类空间使⽤⼤⼩
YGC :从应⽤程序启动到采样时年轻代中gc次数
YGCT :从应⽤程序启动到采样时年轻代中gc所⽤时间(s)
FGC :从应⽤程序启动到采样时old代(全gc)gc次数
FGCT :从应⽤程序启动到采样时old代(全gc)gc所⽤时间(s)
GCT:从应⽤程序启动到采样时gc⽤的总时间(s)
  • 示例⼆:jstat -class 7737(类的装载信息)
Loaded : 已经装载的类的数量
Bytes : 装载类所占⽤的字节数
Unloaded:已经卸载类的数量
Bytes:卸载类的字节数
Time:装载和卸载类所花费的时间
  • 示例三:jstat -gcutil 7737 5s 5 (显示垃圾收集信息)
    在这里插入图片描述
S0 年轻代中第⼀个survivor(幸存区)已使⽤的占当前容量百分⽐
S1 年轻代中第⼆个survivor(幸存区)已使⽤的占当前容量百分⽐
E 年轻代中Eden(伊甸园)已使⽤的占当前容量百分⽐
O old代已使⽤的占当前容量百分⽐
M metaspace已使⽤的占当前容量百分⽐
CCS 压缩使⽤⽐例
YGC 从应⽤程序启动到采样时年轻代中gc次数
YGCT 从应⽤程序启动到采样时年轻代中gc所⽤时间(s)
FGC 从应⽤程序启动到采样时old代(全gc)gc次数
FGCT 从应⽤程序启动到采样时old代(全gc)gc所⽤时间(s)
GCT 从应⽤程序启动到采样时gc⽤的总时间(s)

2.1.3: jinfo

jinfo:Java Configuration Info
jinfo可以⽤来查看正在运⾏的java程序的扩展参数,甚⾄⽀持运⾏时,修改部分参数

jinfo [option] <pid>
-flag <name> to print the value of the named VM flag
-flag [+|-]<name> to enable or disable the named VM flag
-flag <name>=<value> to set the named VM flag to the given value
-flags to print VM flags
-sysprops to print Java system properties
<no option> to print both of the above
-h | -help to print this help message
/**
* -Xms10m -Xmx10m -XX:+PrintCommandLineFlags -XX:+UseConcMarkSweepGC
*/
public class GCUseTest {
	 public static void main(String[] args) {
		 List<byte[]> list = new ArrayList<>();
		 while (true){
			 byte[] arr = new byte[100];
			 list.add(arr);
			 try {
				Thread.sleep(10);
			 } catch (InterruptedException e) {
				e.printStackTrace();
			 }
		 }
	 }
}
  • 示例⼀:查看堆的最⼤值
~ jinfo -flag MaxHeapSize 8384
-XX:MaxHeapSize=10485760
  • 示例⼆:查看所有参数
~ jinfo -flags 8384
Attaching to process ID 8384, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.121-b13
Non-default VM flags: -XX:CICompilerCount=4 -XX:InitialHeapSize=10485760 -
XX:MaxHeapSize=10485760 -XX:MaxNewSize=3145728 -XX:MinHeapDeltaBytes=524288 -
XX:NewSize=3145728 -XX:OldSize=7340032 -XX:+UseCompressedClassPointers -
XX:+UseCompressedOops -XX:+UseFastUnorderedTimeStamps -XX:+UseParallelGC
Command line: -Xms10m -Xmx10m -Dfile.encoding=UTF-8
  • 示例三:查看使⽤的垃圾回收器
~ jinfo -flag UseParallelGC 8384
-XX:+UseParallelGC
➜ ~ jinfo -flag UseConcMarkSweepGC 8384
-XX:-UseConcMarkSweepGC
  • 示例四:设置⽇志打印
~ jinfo -flag PrintGCDetails 8384
-XX:-PrintGCDetails
➜ ~ jinfo -flag +PrintGCDetails 8384~ jinfo -flag PrintGCDetails 8384
-XX:+PrintGCDetails
➜ ~ jinfo -flag -PrintGCDetails 8384~ jinfo -flag PrintGCDetails 8384
-XX:-PrintGCDetails

2.1.4: jmap

jmap:Memory Map

jmap⽤来查看堆内存使⽤状况,⼀般结合jhat使⽤。

jmap语法格式如下:

Usage:
 jmap [option] <pid>
 (to connect to running process)
 jmap [option] <executable <core>
 (to connect to a core file)
 jmap [option] [server_id@]<remote server IP or hostname>
 (to connect to remote debug server)
where <option> is one of:
 <none> to print same info as Solaris pmap
 -heap to print java heap summary
 -histo[:live] to print histogram of java object heap; if the "live"
 suboption is specified, only count live objects
 -clstats to print class loader statistics
 -finalizerinfo to print information on objects awaiting finalization
 -dump:<dump-options> to dump java heap in hprof binary format
 dump-options:
 live dump only live objects; if not
specified,
 all objects in the heap are dumped.
 format=b binary format
 file=<file> dump heap to <file>
 Example: jmap -dump:live,format=b,file=heap.bin <pid>
 -F force. Use with -dump:<dump-options> <pid> or -histo
 to force a heap dump or histogram when <pid> does not
 respond. The "live" suboption is not supported
 in this mode.
 -h | -help to print this help message
 -J<flag> to pass <flag> directly to the runtime system

参数:

option: 选项参数。
pid: 需要打印配置信息的进程ID。
executable: 产⽣核⼼dump的Java可执⾏⽂件。
core: 需要打印配置信息的核⼼⽂件。
server-id: 可选的唯⼀id,如果相同的远程主机上运⾏了多台调试服务器,⽤此选项参数标识服务器。
remote server IP or hostname 远程调试服务器的IP地址或主机名。
option
no option: 查看进程的内存映像信息,类似 Solaris pmap 命令。
heap: 显示Java堆详细信息
histo[:live]: 显示堆中对象的统计信息
clstats:打印类加载器信息
finalizerinfo: 显示在F-Queue队列等待Finalizer线程执⾏finalizer⽅法的对象
dump:<dump-options>:⽣成堆转储快照
F: 当-dump没有响应时,使⽤-dump或者-histo参数. 在这个模式下,live⼦参数⽆效.
help:打印帮助信息
J<flag>:指定传递给运⾏jmap的JVM的参数
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
/**
-Xms500m -Xmx500m
*/
public class HeapInstanceTest {
	 byte[] buffer = new byte[new Random().nextInt(1024*200)];
	 public static void main(String[] args) {
		 List<HeapInstanceTest> list = new ArrayList<HeapInstanceTest>();
		 while (true){
			 list.add(new HeapInstanceTest());
			 try {
				Thread.sleep(100);
			 } catch (InterruptedException e) {
				e.printStackTrace();
			 }
		 }
	 }
}

示例⼀:显示Java堆详细信息

  • 命令:jmap -heap pid
  • 描述:显示Java堆详细信息
    打印⼀个堆的摘要信息,包括使⽤的GC算法、堆配置信息和各内存区域内存使⽤信息
~ jmap -heap 8985
Attaching to process ID 8985, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.121-b13
using thread-local object allocation.
Parallel GC with 8 thread(s)
Heap Configuration:
 MinHeapFreeRatio = 0
 MaxHeapFreeRatio = 100
 MaxHeapSize = 524288000 (500.0MB)
 NewSize = 174587904 (166.5MB)
 MaxNewSize = 174587904 (166.5MB)
 OldSize = 349700096 (333.5MB)
 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 = 131596288 (125.5MB)
 used = 127090976 (121.20339965820312MB)
 free = 4505312 (4.296600341796875MB)
 96.57641407028137% used
From Space:
 capacity = 21495808 (20.5MB)
 used = 21477712 (20.482742309570312MB)
 free = 18096 (0.0172576904296875MB)
 99.91581614424543% used
To Space:
 capacity = 21495808 (20.5MB)
 used = 0 (0.0MB)
 free = 21495808 (20.5MB)
 0.0% used
PS Old Generation
 capacity = 349700096 (333.5MB)
 used = 100703528 (96.03836822509766MB)
 free = 248996568 (237.46163177490234MB)
 28.79711191157351% used
2156 interned Strings occupying 152440 bytes.

示例⼆:显示堆中对象的统计信息

  • 命令:jmap -histo:live pid
  • 描述:显示堆中对象的统计信息
    其中包括每个Java类、对象数量、内存⼤⼩(单位:字节)、完全限定的类名。打印的虚拟机内部的类名称将会带有⼀个’*’前缀。如果指定了live⼦选项,则只计算活动的对象。
~ jmap -histo:live 8985
 num #instances #bytes class name
----------------------------------------------
 1: 3682 339156840 [B
 2: 3806 408160 [C
 3: 3794 91056 java.lang.String
 4: 593 67480 java.lang.Class
 5: 587 54568 [Ljava.lang.Object;
 6: 3273 52368 com.kkb.example.HeapInstanceTest

示例三:打印类加载器信息

  • 命令:jmap -clstats pid
  • 描述:打印类加载器信息
    -clstats是-permstat的替代⽅案,在JDK8之前,-permstat⽤来打印类加载器的数据
    打印Java堆内存的永久保存区域的类加载器的智能统计信息。对于每个类加载器⽽⾔,它的名称、活跃度、地址、⽗类加载器、它所加载的类的数量和⼤⼩都会被打印。此外,包含的字符串数量和⼤⼩也会被打印
~ jmap -clstats 8985
Attaching to process ID 8985, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.121-b13
finding class loader instances ..done.
computing per loader stat ..done.
please wait.. computing
liveness......................................................................
.............................done.
class_loader classes bytes parent_loader alive? type
<bootstrap> 517 969116 null live <internal>
0x00000007af095a08 0 0 0x00000007ae86f288 live 
java/util/ResourceBundle$RBClassLoader@0x00000007c00555e8
0x00000007ae86f288 9 29861 0x00000007ae8770f8 live 
sun/misc/Launcher$AppClassLoader@0x00000007c000f6a0
0x00000007ae8770f8 0 0 null live 
sun/misc/Launcher$ExtClassLoader@0x00000007c000fa48
total = 4 526 998977 N/A alive=4, dead=0 N/A

示例四:打印正等候回收的对象的信息

  • 命令:jmap -finalizerinfo pid
  • 描述:打印正等候回收的对象的信息
~ jmap -finalizerinfo 10067
Attaching to process ID 10067, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.121-b13
Number of objects pending for finalization: 0

Number of objects pending for finalization: 0 说明当前F-QUEUE队列中并没有等待Fializer线程执⾏final
示例五:⽣成堆转储快照dump⽂件

  • 命令:jmap -dump:format=b,file=heapdump.dump pid
  • 描述:⽣成堆转储快照dump⽂件。

以hprof⼆进制格式转储Java堆到指定filename的⽂件中。live⼦选项是可选的。如果指定了live⼦选项,堆中只有活动的对象会被转储。想要浏览heap dump,你可以使⽤jhat(Java堆分析⼯具)读取⽣成的⽂件

这个命令执⾏,JVM会将整个heap的信息dump写⼊到⼀个⽂件,heap如果⽐较⼤的话,就会导致这个过程⽐较耗时,并且执⾏的过程中为了保证dump的信息是可靠的,所以会暂停应⽤, 线上系统慎⽤

2.1.5: jhat

jhat:Java Heap Analysis Tool,jhat 命令解析Java堆转储⽂件,并启动⼀个 web server. 然后⽤浏览器来查看/浏览 dump 出来的 heap. jhat 命令⽀持预先设计的查询, ⽐如显示某个类的所有实例. 还⽀持对象查询语⾔(OQL, Object Query Language)。 OQL有点类似SQL,专⻔⽤来查询堆转储。 OQL相关的帮助信息可以在 jhat 命令所提供的服务器⻚⾯最底部. 如果使⽤默认端⼝, 则OQL帮助信息⻚⾯为: http://localhost:7000/oqlhelp/

Java⽣成堆转储的⽅式有多种:

  • 使⽤ jmap -dump 选项可以在JVM运⾏时获取 heap dump.
  • 使⽤ jconsole 选项通过 HotSpotDiagnosticMXBean 从运⾏时获得堆转储。
  • 在虚拟机启动时如果指定了 -XX:+HeapDumpOnOutOfMemoryError 选项, 则抛出OutOfMemoryError 时, 会⾃动执⾏堆转储。
  • 使⽤ hprof 命令。jhat [ options ] heap-dump-file
  • 参数:
    • options 可选命令⾏参数,请参考下⾯的 Options
    • heap-dump-file 要查看的⼆进制Java堆转储⽂件(Java binary heap dump file)。 如果某个转储⽂件中包含了多份 heap dumps, 可在⽂件名之后加上 # 的⽅式指定解析哪⼀个 dump, 如:myfile.hprof#3

Options

  • -stack false|true:关闭对象分配调⽤栈跟踪(tracking object allocation call stack)。 如果分配位置信息在堆转储中不可⽤.则必须将此标志设置为 false. 默认值为 true .
  • -refs false|true:关闭对象引⽤跟踪(tracking of references to objects)。 默认值为 true . 默认情况下, 返回的指针是指向其他特定对象的对象,如反向链接或输⼊引⽤(referrers or incoming references), 会统计/计算堆中的所有对象。
  • -port port-number:设置 jhat HTTP server 的端⼝号. 默认值 7000 .
  • -exclude exclude-file :指定对象查询时需要排除的数据成员列表⽂件(a file that lists data members that should be excludedfrom the reachable objects query)。 例如, 如果⽂件列列出了 java.lang.String.value , 那么当从某个特定对象 Object o 计算可达的对象列表时, 引⽤路径涉及 java.lang.String.value 的都会被排除。
  • -baseline exclude-file:指定⼀个基准堆转储(baseline heap dump)。 在两个 heap dumps 中有相同object ID 的对象会被标记为不是新的(marked as not being new). 其他对象被标记为新的(new). 在⽐较两个不同的堆转储时很有⽤.
  • -debug int:设置 debug 级别. 0 表示不输出调试信息。 值越⼤则表示输出更详细的 debug 信息.
  • -version:启动后只显示版本信息就退出
  • -h :显示帮助信息并退出. 同 -help
  • -help : 显示帮助信息并退出. 同 -h
  • -J< flag > :因为 jhat 命令实际上会启动⼀个JVM来执⾏, 通过 -J 可以在启动JVM时传⼊⼀些启动参数. 例如,:-J-Xmx512m 则指定运⾏ jhat 的Java虚拟机使⽤的最⼤堆内存为 512 MB. 如果需要使⽤多个JVM启动参数,则传⼊多个 -Jxxxxxx.

示例⼀:利⽤jhat分析刚刚jmap输出的堆⽂件
在这里插入图片描述
这样就启动起来了⼀个简易的HTTP服务,端⼝号是7000,尝试⼀下⽤浏览器访问⼀下它,本地的可以通过http://localhost:7000就可以得到这样的⻚⾯:

jhat 启动后显示的 html ⻚⾯中包含有:

  • All classes including platform:显示出堆中所包含的所有的类
  • Show all members of the rootset :从根集能引⽤到的对象
  • Show instance counts for all classes (including platform/excluding platform):显示平台包括的所有类的实例数量
  • Show heap histogram:堆实例的分布表
  • Show finalizer summary:Finalizer 摘要
  • Execute Object Query Language (OQL) query:执⾏对象查询语句(OQL)
select a from [I a where a.length > 256 //查询⻓度⼤于256的数组

2.1.6: jstack

jstack:Java Stack Trace,jstack是java虚拟机⾃带的⼀种堆栈跟踪⼯具。

jstack⽤于⽣成java虚拟机当前时刻的线程快照。线程快照是当前java虚拟机内每⼀条线程正在执⾏的⽅法堆栈的集合,⽣成线程快照的主要⽬的是定位线程出现⻓时间停顿的原因,如线程间死锁、死循环、请求外部资源导致的⻓时间等待等。 线程出现停顿的时候通过jstack来查看各个线程的调⽤堆栈,就可以知道没有响应的线程到底在后台做什么事情,或者等待什么资源。

如果java程序崩溃⽣成core⽂件,jstack⼯具可以⽤来获得core⽂件的java stack和native stack的信息,从⽽可以轻松地知道java程序是如何崩溃和在程序何处发⽣问题。另外,jstack⼯具还可以附属到正在运⾏的java程序中,看到当时运⾏的java程序的java stack和native stack的信息, 如果现在运⾏的java程序呈现hung的状态,jstack是⾮常有⽤的。

在thread dump中,要留意下⾯⼏种状态

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

使⽤⽅式

  • jstack [ option ] pid 查看当前时间点,指定进程的dump堆栈信息。
  • jstack [ option ] pid > ⽂件 将当前时间点的指定进程的dump堆栈信息,写⼊到指定⽂件中。注:若该⽂件不存在,则会⾃动⽣成;若该⽂件存在,则会覆盖源⽂件。
  • jstack [ option ] executable core 查看当前时间点,core⽂件的dump堆栈信息。
  • jstack [ option ] [server_id@] 查看当前时间点,远程机器的dump堆栈信息。
    可选参数说明
  • -F 当进程挂起了,此时’jstack [-l] pid’是没有相应的,这时候可使⽤此参数来强制打印堆栈信息,强制jstack),⼀般情况不需要使⽤。
  • -m 打印java和native c/c++框架的所有栈信息。可以打印JVM的堆栈,以及Native的栈帧,⼀般应⽤
    排查不需要使⽤。
  • -l ⻓列表. 打印关于锁的附加信息。例如属于java.util.concurrent的ownable synchronizers列表,会使得JVM停顿得⻓久得多(可能会差很多倍,⽐如普通的jstack可能⼏毫秒和⼀次GC没区别,加了-l 就是近⼀秒的时间),-l 建议不要⽤。⼀般情况不需要使⽤。
  • -h or -hel 打印帮助信息
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
public class JStackCase {
	 public static Executor executor = Executors.newFixedThreadPool(3);
	 public static Object lock = new Object();
	 public static void main(String[] args) {
		 StackTask task1 = new StackTask();
		 StackTask task2 = new StackTask();
		 executor.execute(task1);
		 executor.execute(task2);
	 }
	 static class StackTask implements Runnable{
		 public void run(){
			synchronized (lock){
				cal();
			}
		 }
		 public void cal(){
			 int i=0;
			 while(true){
				i++;
			 }
		 }
	 }
}

示例⼀:
在这里插入图片描述
示例⼆:将指定进程的当前堆栈情况记录到某个⽂件中:

 ~ jstack -l 13257 > jstack_info.txt

示例三:统计线程数

jstack -l 28367 | grep 'java.lang.Thread.State' | wc -l

示例四:检测死锁

public class DeathLock {
	 private static Lock lock1 = new ReentrantLock();
	 private static Lock lock2 = new ReentrantLock();
	 public static void deathLock() {
		 Thread t1 = new Thread() {
			 @Override
			 public void run() {
				 try {
					 lock1.lock();
					 TimeUnit.SECONDS.sleep(1);
					 lock2.lock();
				 } catch (InterruptedException e) {
					e.printStackTrace();
				 }
			 }
		 };
		 Thread t2 = new Thread() {
			 @Override
			 public void run() {
				 try {
					 lock2.lock();
					 TimeUnit.SECONDS.sleep(1);
					 lock1.lock();
				 } catch (InterruptedException e) {
					e.printStackTrace();
				 }
			 }
		 };
		 t1.setName("mythread1");
		 t2.setName("mythread2");
		 t1.start();
		 t2.start();
	 }
	 public static void main(String[] args) {
		deathLock();
	 }
}

在这里插入图片描述

2.1.7: jconsole

Jconsole:Java Monitoring and Management Console,Java 5引⼊,⼀个内置 Java 性能分析器,可以从命令⾏或在 GUI shell 中运⾏。您可以轻松地使⽤ JConsole来监控 Java 应⽤程序性能和跟踪Java 中的代码。

如何启动JConsole

如果是从命令⾏启动,使 JDK 在 PATH 上,运⾏ jconsole 即可。

如果从 GUI shell 启动,找到 JDK 安装路径,打开 bin ⽂件夹,双击 jconsole 。

当分析⼯具弹出时(取决于正在运⾏的 Java 版本以及正在运⾏的 Java 程序数量),可能会出现⼀个对话框,要求输⼊⼀个进程的 URL 来连接,也可能列出许多不同的本地 Java 进程(有时包含JConsole 进程本身)来连接。如下图所示:想分析那个程序就双击那个进程。在这里插入图片描述

如何设置JAVA程序运⾏时可以被JConsolse连接分析

本地程序(相对于开启JConsole的计算机),⽆需设置任何参数就可以被本地开启的JConsole连接(Java SE 6开始⽆需设置,之前还是需要设置运⾏时参数 -Dcom.sun.management.jmxremote )

JConsole如何连接远程机器的JAVA程序

jconsole 192.168.0.1:8999

也可以在已经打开的JConsole界⾯操作 连接->新建连接->选择远程进程->输⼊远程主机IP和端⼝号- >点击“连接”

在这里插入图片描述
进⼊视图后包括这六个标签:

  • Overview: Displays overview information about the Java VM and monitored values.
  • Memory: 显示内存使⽤信息
  • Threads: 显示线程使⽤信息
  • Classes: 显示类装载信息
  • VM Summary:显示java VM信息
  • MBeans: 显示 MBeans.
    上图描述有我们需要的信息,同时点击右键可以保存数据到CSV⽂件。

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

2.1.8 hprof

hprof:Heap/CPU Profiling Tool 能够展现CPU使⽤率,统计堆内存使⽤情况。
J2SE中提供了⼀个简单的命令⾏⼯具来对java程序的cpu和heap进⾏ profiling,叫做HPROF。HPROF实际上是JVM中的⼀个native的库,它会在JVM启动的时候通过命令⾏参数来动态加载,并成为JVM进程的⼀部分。若要在java进程启动的时候使⽤HPROF,⽤户可以通过各种命令⾏参数类型来使⽤HPROF对java进程的heap或者 (和)cpu进⾏profiling的功能。HPROF产⽣的profiling数据可以是⼆进制的,也可以是⽂本格式的。这些⽇志可以⽤来跟踪和分析 java进程的性能问题和瓶颈,解决内存使⽤上不优的地⽅或者程序实现上的不优之处。⼆进制格式的⽇志还可以被JVM中的HAT⼯具来进⾏浏和分析,⽤ 以观察java进程的heap中各种类型和数据的情况。在J2SE 5.0以后的版本中,HPROF已经被并⼊到⼀个叫做Java Virtual Machine Tool Interface(JVM TI)中。
语法格式

java -agentlib:hprof[=options] ToBeProfiledClass
java -Xrunprof[:options] ToBeProfiledClass
javac -J-agentlib:hprof[=options] ToBeProfiledClass

完整的命令选项

Option Name and Value Description Default
--------------------- ----------- -------
heap=dump|sites|all heap profiling all
cpu=samples|times|old CPU usage off
monitor=y|n monitor contention n
format=a|b text(txt) or binary output a
file=<file> write data to file java.hprof[.txt]
net=<host>:<port> send data over a socket off
depth=<size> stack trace depth 4
interval=<ms> sample interval in ms 10
cutoff=<value> output cutoff point 0.0001
lineno=y|n line number in traces? y
thread=y|n thread in traces? n
doe=y|n dump on exit? y
msa=y|n Solaris micro state accounting n
force=y|n force output to <file> y
verbose=y|n print messages about dumps y

官⽅指南上的实例。
CPU Usage Sampling Profiling(cpu=samples)的例⼦:

java -agentlib:hprof=cpu=samples,interval=20,depth=3 Hello

上⾯每隔20毫秒采样CPU消耗信息,堆栈深度为3,⽣成的profile⽂件名称是java.hprof.txt,在当前⽬录。
CPU Usage Times Profiling(cpu=times)的例⼦,它相对于CPU Usage Sampling Profile能够获得更加细粒度的CPU消耗信息,能够细到每个⽅法调⽤的开始和结束,它的实现使⽤了字节码注⼊技术(BCI):

javac -J-agentlib:hprof=cpu=times Hello.java

Heap Allocation Profiling(heap=sites)的例⼦:

javac -J-agentlib:hprof=heap=sites Hello.java

Heap Dump(heap=dump)的例⼦,它⽐上⾯的Heap Allocation Profiling能⽣成更详细的HeapDump信息:

javac -J-agentlib:hprof=heap=dump Hello.java

虽然在JVM启动参数中加⼊-Xrunprof:heap=sites参数可以⽣成CPU/Heap Profile⽂件,但对JVM性能影响⾮常⼤,不建议在线上服务器环境使⽤。
在这里插入图片描述
⽣成跟踪点类所占内存百分⽐

classes java -agentlib:hprof=heap=sites com.kkb.example.HprofTest
Dumping allocation sites ... done.

⽣成 java.hprof.txt⽂件
在这里插入图片描述

2.2 :Linux

2.2.1 :top

Linux中的top命令显示系统上正在运⾏的进程。它是系统管理员最重要的⼯具之⼀。被⼴泛⽤于监视服务器的负载。在本篇中,我们会探索top命令的细节。top命令是⼀个交互命令。在运⾏top的时候还可以运⾏很多命令。

top的使⽤⽅式 top [-d number] | top [-bnp]
使⽤⽅式

  • 显示进程信息 top
  • 显示完整命令 top -c
  • 以批处理模式显示程序信息 top -b
  • 以累积模式显示程序信息 top -S
  • 设置信息更新次数 top -n 2
  • 设置信息更新时间 top -d 3
  • 显示指定的进程信息 top -p 139
  • 显示更新⼗次后退出 top -n 10
  • 使⽤者将不能利⽤交谈式指令来对⾏程下命令 top -s

示例⼀:
Top命令输出,默认运⾏时,top命令会显示如下输出:
在这里插入图片描述

前⼏⾏⽔平显示了不同系统参数的概括,接下来是进程和它们在列中的属性。
1:系统运⾏时间和平均负载:
在这里插入图片描述
top命令的顶部显示与uptime命令相似的输出。
这些字段显示:

  • 当前时间
  • 系统已运⾏的时间
  • 当前登录⽤户的数量
  • 相应最近5、10和15分钟内的平均负载
    可以使⽤’l’命令切换uptime的显示
    2:任务:
    在这里插入图片描述
    第⼆⾏显示的是任务或者进程的总结。进程可以处于不同的状态。这⾥显示了全部进程的数量。除此之外,还有正在运⾏、睡眠、停⽌、僵⼫进程的数量(僵⼫是⼀种进程的状态)。这些进程概括信息可以⽤’t’切换显示。
    3:CPU 状态:
    在这里插入图片描述
    下⼀⾏显示的是CPU状态。 这⾥显示了不同模式下的所占CPU时间的百分⽐。这些不同的CPU时间表示:
  • us, user: 运⾏(未调整优先级的) ⽤户进程的CPU时间
  • sy,system: 运⾏内核进程的CPU时间
  • ni,niced:运⾏已调整优先级的⽤户进程的CPU时间
  • wa,IO wait: ⽤于等待IO完成的CPU时间
  • hi:处理硬件中断的CPU时间
  • si: 处理软件中断的CPU时间
  • st:这个虚拟机被hypervisor偷去的CPU时间(译注:如果当前处于⼀个hypervisor下的vm,实际上hypervisor也是要消耗⼀部分CPU处理时间的)。
    可以使⽤’t’命令切换显示。
    4:内存使⽤:
    在这里插入图片描述
    接下来两⾏显示内存使⽤率,有点像’free’命令。第⼀⾏是物理内存使⽤,第⼆⾏是虚拟内存使⽤(交换空间)。
    物理内存显示如下:全部可⽤内存、已使⽤内存、空闲内存、缓冲内存。相似地:交换部分显示的是:全部、已使⽤、空闲和缓冲交换空间。
    内存显示可以⽤’m’命令切换。

5字段/列:
在这里插入图片描述
在横向列出的系统属性和状态下⾯,是以列显示的进程。不同的列代表下⾯要解释的不同属性。
默认上,top显示这些关于进程的属性:

  • PID 进程ID,进程的唯⼀标识符
  • USER 进程所有者的实际⽤户名。
  • PR 进程的调度优先级。这个字段的⼀些值是’rt’。这意味这这些进程运⾏在实时态。
  • NI 进程的nice值(优先级)。越⼩的值意味着越⾼的优先级。
  • VIRT 进程使⽤的虚拟内存。
  • RES 驻留内存⼤⼩。驻留内存是任务使⽤的⾮交换物理内存⼤⼩。
  • SHR 是进程使⽤的共享内存。
  • S 这个是进程的状态。它有以下不同的值
  • D – 不可中断的睡眠态。
  • R – 运⾏态
  • S – 睡眠态
  • T – 被跟踪或已停⽌
  • Z – 僵⼫态
  • %CPU ⾃从上⼀次更新时到现在任务所使⽤的CPU时间百分⽐。
  • %MEM 进程使⽤的可⽤物理内存百分⽐。
  • TIME+ 任务启动后到现在所使⽤的全部CPU时间,精确到百分之⼀秒。
  • COMMAND 运⾏进程所使⽤的命令。
    还有许多在默认情况下不会显示的输出,它们可以显示进程的⻚错误、有效组和组ID和其他更多的信息。

2.2.2 :vmstat

2.2.3:iostat

2.2.3:pidstat

2.3 :第三方

2.3.1 VisualVM

2.3.2 MAT

2.3.3 GCViewer

2.3.4 Arthas

2.3.5 GChisto

2.3.6 IBM HeapAnalyzer

3:JVM参数

在JVM调整过程中,主要是对JVM参数做的调整,以下我们介绍主要参数。JVM参数有很多,其实我们直接使⽤默认的JVM参数,不去修改都可以满⾜⼤多数情况。但是如果你想在有限的硬件资源下,部署的系统达到最⼤的运⾏效率,那么进⾏相关的JVM参数设置是必不可少的。下⾯我们就来对这些JVM参数进⾏详细的介绍。

关于这些命令的详细解释,可以参考官⽹:
https://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html

3.1 标准参数

标准参数,顾名思义,标准参数中包括功能以及输出的结果都是很稳定的,基本上不会随着JVM版本的变化⽽变化。标准参数以-开头,如:java -version、java -jar等,通过java -help可以查询所有的标准参数,
我们可以通过 -help 命令来检索出所有标准参数。
在这里插入图片描述
-help 也是⼀个标准参数,再⽐如使⽤⽐较多的 -version也是。
在这里插入图片描述

3.2 ⾮标准参数

⾮标准参数以-X开头,是标准参数的扩展。对应前⾯讲的标准化参数,这是⾮标准化参数。表示在将来的JVM版本中可能会发⽣改变,但是这类以-X开始的参数变化的⽐较⼩。
我们可以通过 Java -X 命令来检索所有-X 参数。
在这里插入图片描述
我们可以通过设置⾮标准参数来配置堆的内存分配,常⽤的⾮标准参数有:

-Xmn新⽣代内存的最⼤值,包括Eden区和两个Survivor区的总和,
写法如:-Xmn1024,-Xmn1024k,-Xmn1024m,-Xmn1g 。

-Xms堆内存的最⼩值,默认值是总内存/64(且⼩于1G),默认情况下,当堆中可⽤内存⼩于40%(这个值可以⽤-XX: MinHeapFreeRatio 调整,如-X:MinHeapFreeRatio=30)时,堆内存会开始增加,⼀直增加到-Xmx的⼤⼩。

-Xmx堆内存的最⼤值,默认值是总内存/64(且⼩于1G),如果Xms和Xmx都不设置,则两者⼤⼩会相同,默认情况下,当堆中可⽤内存⼤于70%时,堆内存会开始减少,⼀直减⼩到-Xms的⼤⼩;

-Xss每个线程的栈内存,默认1M,⼀般来说是不需要改的

-Xrs减少JVM对操作系统信号的使⽤。

-Xprof跟踪正运⾏的程序,并将跟踪数据在标准输出输出;适合于开发环境调试。

-Xnoclassgc关闭针对class的gc功能;因为其阻⽌内存回收,所以可能会导致OutOfMemoryError错误慎⽤;

-Xincgc开启增量gc(默认为关闭);这有助于减少⻓时间GC时应⽤程序出现的停顿;但由于可能和应⽤程序并发执⾏,所以会降低CPU对应⽤的处理能⼒。

-Xloggc:file与-verbose:gc功能类似,只是将每次GC事件的相关情况记录到⼀个⽂件中,⽂件的位置最好在本地,以避免⽹络的潜在问题。

3.3 不稳定参数

这是我们⽇常开发中接触到最多的参数类型。这也是⾮标准化参数,相对来说不稳定,随着JVM版本的变化可能会发⽣变化,主要⽤于JVM调优和debug。
不稳定参数以-XX 开头,此类参数的设置很容易引起JVM 性能上的差异,使JVM存在极⼤的不稳定性。如果此类参数设置合理将⼤⼤提⾼JVM的性能及稳定性。
不稳定参数分为三类:

  • 性能参数:⽤于JVM的性能调优和内存分配控制,如内存⼤⼩的设置;
  • ⾏为参数:⽤于改变JVM的基础⾏为,如GC的⽅式和算法的选择;
  • 调试参数:⽤于监控、打印、输出jvm的信息;

不稳定参数语法规则:
布尔类型参数值:

  • -XX:+
  • -XX:-
  • 示例:-XX:+UseG1GC:表示启⽤G1垃圾收集器

数字类型参数值:

  • -XX:
  • 示例:-XX:MaxGCPauseMillis=500 :表示设置GC的最⼤停顿时间是500ms

字符串类型参数值:

  • -XX:
  • 示例:-XX:HeapDumpPath=./dump.core

3.4 常⽤参数

如以下参数示例

 –Xms4g -Xmx4g –Xmn1200m –Xss512k 
-XX:NewRatio=4 -XX:SurvivorRatio=8 -XX:PermSize=100m -XX:MaxPermSize=256m 
-XX:MaxTenuringThreshold=15 -XX:MaxDirectMemorySize=1G -XX:+DisableExplicitGC

上⾯为Java7及以前版本的示例,在Java8中永久代的参数-XX:PermSize和-XX:MaxPermSize已经失效。
参数解析:

  • -Xms4g:初始化堆内存⼤⼩为4GB,ms是memory start的简称,等价于-XX:InitialHeapSize。
  • -Xmx4g:堆内存最⼤值为4GB,mx是memory max的简称,等价于-XX:MaxHeapSize。
  • Xmn1200m:设置年轻代⼤⼩为1200MB。增⼤年轻代后,将会减⼩年⽼代⼤⼩。此值对系统性能影响较⼤,Sun官⽅推荐配置为整个堆的3/8。
  • -Xss512k:设置每个线程的堆栈⼤⼩。JDK5.0以后每个线程堆栈⼤⼩为1MB,以前每个线程堆栈⼤⼩为256K。应根据应⽤线程所需内存⼤⼩进⾏调整。在相同物理内存下,减⼩这个值能⽣成更多的线程。但是操作系统对⼀个进程内的线程数还是有限制的,不能⽆限⽣成,经验值在3000~5000左右。
  • -XX:NewRatio=4:设置年轻代(包括Eden和两个Survivor区)与年⽼代的⽐值(除去持久代)。设置为4,则年轻代与年⽼代所占⽐值为1:4,年轻代占整个堆栈的1/5
  • -XX:SurvivorRatio=8:设置年轻代中Eden区与Survivor区的⼤⼩⽐值。设置为8,则两个Survivor区与⼀个Eden区的⽐值为2:8,⼀个Survivor区占整个年轻代的1/10
  • -XX:PermSize=100m:初始化永久代⼤⼩为100MB。
  • -XX:MaxPermSize=256m:设置持久代⼤⼩为256MB。
  • -XX:MaxTenuringThreshold=15:设置垃圾最⼤年龄。如果设置为0的话,则年轻代对象不经过Survivor区,直接进⼊年⽼代。对于年⽼代⽐较多的应⽤,可以提⾼效率。如果将此值设置为⼀个较⼤值,则年轻代对象会在Survivor区进⾏多次复制,这样可以增加对象再年轻代的存活时间,增加在年轻代即被回收的概论。
  • -XX:MaxDirectMemorySize=1G:直接内存。报java.lang.OutOfMemoryError: Direct buffer memory异常可以上调这个值。
  • -XX:+DisableExplicitGC:禁⽌运⾏期显式地调⽤System.gc()来触发fulll GC。注意: Java RMI的定时GC触发机制可通过配置-Dsun.rmi.dgc.server.gcInterval=86400来控制触发的时间
  • -XX:CMSInitiatingOccupancyFraction=60:⽼年代内存回收阈值,默认值为68。
  • -XX:ConcGCThreads=4:CMS垃圾回收器并⾏线程线,推荐值为CPU核⼼数。
  • -XX:ParallelGCThreads=8:新⽣代并⾏收集器的线程数。
  • -XX:MaxTenuringThreshold=10:设置垃圾最⼤年龄。如果设置为0的话,则年轻代对象不经过Survivor区,直接进⼊年⽼代。对于年⽼代⽐较多的应⽤,可以提⾼效率。如果将此值设置为⼀个较⼤值,则年轻代对象会在Survivor区进⾏多次复制,这样可以增加对象再年轻代的存活时间,增加在年轻代即被回收的概论。
  • -XX:CMSMaxAbortablePrecleanTime=500:当abortable-preclean预清理阶段执⾏达到这个时间时就会结束。
  • 更多详细参数说明请参考官⽅⽂档:https://www.oracle.com/technetwork/java/javase/tech/vmoptions-jsp-140102.html

新⽣代、⽼⽣代、永久代的参数,如果不进⾏指定,虚拟机会⾃动选择合适的值,同时也会基于系统的开销⾃动调整。

3.4.1 -XX:+PrintFlagsFinal

Java 6(update 21oder 21之后)版本, HotSpot JVM 提供给了两个新的参数,在JVM启动后,在命令⾏中可以输出所有XX参数和值。

  • -XX:+PrintFlagsInitial:查看初始值
  • -XX:+PrintFlagsFinal:查看最终值(初始值可能被修改掉)
  • 让我们现在就了解⼀下新参数的输出。以 -client 作为参数的 -XX:+ PrintFlagsFinal 的结果是⼀个按字⺟排序的590个参数表格(注意,每个release版本参数的数量会不⼀样)

在这里插入图片描述
表格的每⼀⾏包括五列,来表示⼀个XX参数。第⼀列表示参数的数据类型,第⼆列是名称,第四列为值,第五列是参数的类别。第三列”=”表示第四列是参数的默认值,⽽”:=” 表明了参数被⽤户或者JVM赋值了。

如果我们只想看下所有XX参数的默认值,能够⽤⼀个相关的参数,-XX:+PrintFlagsInitial 。 ⽤ -XX:+PrintFlagsInitial , 只是展示了第三列为“=”的数据(也包括那些被设置其他值的参数)。

然⽽,注意当与-XX:+PrintFlagsFinal 对⽐的时候,⼀些参数会丢失,⼤概因为这些参数是动态创建的。

3.4.2 -XX:+PrintCommandLineFlags

让我们看下这个参数,事实上这个参数⾮常有⽤: -XX:+PrintCommandLineFlags 。这个参数让JVM打印出那些已经被⽤户或者JVM设置过的详细的XX参数的名称和值。

换句话说,它列举出 -XX:+PrintFlagsFinal的结果中第三列有":="的参数。以这种⽅式, 我们可以⽤-XX:+PrintCommandLineFlags作为快捷⽅式来查看修改过的参数。看下⾯的例⼦
在这里插入图片描述
现在如果我们每次启动java 程序的时候设置 -XX:+PrintCommandLineFlags 并且输出到⽇志⽂件上,这样会记录下我们设置的JVM 参数对应⽤程序性能的影响。

3.4.3 GC⽇志相关

设置JVM GC格式⽇志的主要参数包括如下8个:

  • 1: -XX:+PrintGC 输出简要GC⽇志
  • 2:-XX:+PrintGCDetails 输出详细GC⽇志
  • 3:-Xloggc:gc.log 输出GC⽇志到⽂件
  • 4:XX:+PrintGCTimeStamps 输出GC的时间戳(以JVM启动到当期的总时⻓的时间戳形式)
  • 5:-XX:+PrintGCDateStamps 输出GC的时间戳(以⽇期的形式,如 2020-04-26T21:53:59.234+0800)
  • 6:-XX:+PrintHeapAtGC 在进⾏GC的前后打印出堆的信息
  • 7: -verbose:gc : 在JDK 8中,-verbose:gc是-XX:+PrintGC⼀个别称,⽇志格式等价与:-XX:+PrintGC。不过在JDK 9中 -XX:+PrintGC被标记为deprecated。
    -verbose:gc是⼀个标准的选项,-XX:+PrintGC是⼀个实验的选项,建议使⽤-verbose:gc 替代-
    XX:+PrintGC
  • 8:-XX:+PrintReferenceGC 打印年轻代各个引⽤的数量以及时⻓

开启GC⽇志
多种⽅法都能开启GC的⽇志功能,其中包括:使⽤-verbose:gc或-XX:+PrintGC这两个标志中的任意⼀个能创建基本的GC⽇志(这两个⽇志标志实际上互为别名,默认情况下的GC⽇志功能是关闭的)使⽤-XX:+PrintGCDetails标志会创建更详细的GC⽇志。

推荐使⽤-XX:+PrintGCDetails标志(这个标志默认情况下也是关闭的);通常情况下使⽤基本的GC⽇志很难诊断垃圾回收时发⽣的问题。

开启GC时间提示
除了使⽤详细的GC⽇志,我们还推荐使⽤-XX:+PrintGCTimeStamps或者-XX:+PrintGCDateStamps,便于我们更精确地判断⼏次GC操作之间的时间。这两个参数之间的差别在于时间戳是相对于0(依据JVM启动的时间)的值,⽽⽇期戳(date stamp)是实际的⽇期字符串。由于⽇期戳需要进⾏格式化,所以它的效率可能会受轻微的影响,不过这种操作并不频繁,它造成的影响也很难被我们感知。

指定GC⽇志路径
默认情况下GC⽇志直接输出到标准输出,不过使⽤-Xloggc:filename标志也能修改输出到某个⽂件。除⾮显式地使⽤-PrintGCDetails标志,否则使⽤-Xloggc会⾃动地开启基本⽇志模式。使⽤⽇志循环(Log rotation)标志可以限制保存在GC⽇志中的数据量;
对于需要⻓时间运⾏的服务器⽽⾔,这是⼀个⾮常有⽤的标志,否则累积⼏个⽉的数据很可能会耗尽服务器的磁盘。

开启⽇志滚动输出

通过-XX:+UseGCLogfileRotation -XX:NumberOfGCLogfiles=N -XX:GCLogfileSize=N标志可以控制⽇志⽂件的循环。

默认情况下,UseGCLogfileRotation标志是关闭的。它负责打开或关闭GC⽇志滚动记录功能的。要求必须设置 -Xloggc参数。

开启UseGCLogfileRotation标志后,默认的⽂件数⽬是0(意味着不作任何限制),默认的⽇志⽂件⼤⼩是0(同样也是不作任何限制)。

因此,为了让⽇志循环功能真正⽣效,我们必须为所有这些标志设定值。需要注意的是:

  • The size of the log file at which point the log will be rotated, must be >= 8K. 设置滚动⽇志⽂件的⼤⼩,必须⼤于8k。
    当前写⽇志⽂件⼤⼩超过该参数值时,⽇志将写⼊下⼀个⽂件
  • 设置滚动⽇志⽂件的个数,必须⼤于等于1
  • 必须设置 -Xloggc 参数

开启语句

-XX:+PrintGCDetails -XX:+PrintGCDateStamps 
-Xloggc:/home/hadoop/gc.log 
-XX:+UseGCLogFileRotation 
-XX:NumberOfGCLogFiles=10 -XX:GCLogFileSize=512k

其他有⽤参数

-XX:+PrintGCApplicationStoppedTime :打印GC造成应⽤暂停的时间
-XX:+PrintTenuringDistribution :在每次新⽣代 young GC时,输出幸存区中对象的年龄分布

3.4.4 -XX:CMSFullGCsBeforeCompaction

CMSFullGCsBeforeCompaction 说的是,在上⼀次CMS并发GC执⾏过后,到底还要再执⾏多少次full GC才会做压缩。默认是0,也就是在默认配置下每次CMS GC顶不住了⽽要转⼊full GC的时候都会做压缩。 如果把CMSFullGCsBeforeCompaction配置为10,就会让上⾯说的第⼀个条件变成每隔10次真正的full GC才做⼀次压缩(⽽不是每10次CMS并发GC就做⼀次压缩,⽬前VM⾥没有这样的参数)。这会使full GC更少做压缩,也就更容易使CMS的old gen受碎⽚化问题的困扰。 本来这个参数就是⽤来配置降低full GC压缩的频率,以期减少某些full GC的暂停时间。CMS回退到full GC时⽤的算法是mark-sweepcompact,但compaction是可选的,不做的话碎⽚化会严重些但这次full GC的暂停时间会短些;这是个取舍。

  • -XX:+UseCMSCompactAtFullCollection
  • -XX:CMSFullGCsBeforeCompaction=10
  • 两个参数必须同时使⽤才能⽣效。

3.4.5 -XX:HeapDumpPath

堆内存出现OOM的概率是所有内存耗尽异常中最⾼的,出错时的堆内信息对解决问题⾮常有帮助,所以给JVM设置这个参数(-XX:+HeapDumpOnOutOfMemoryError),让JVM遇到OOM异常时能输出堆内信息,并通过(-XX:+HeapDumpPath)参数设置堆内存溢出快照输出的⽂件地址,这对于特别是对相隔数⽉才出现的OOM异常尤为重要。

这两个参数通常配套使⽤:
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=./

3.4.6 -XX:OnOutOfMemoryError

-
XX:OnOutOfMemoryError="/Library/Java/JavaVirtualMachines/jdk1.8.0_121.jdk/Cont
ents/Home/binjconsole"

表示发⽣OOM后,运⾏jconsole程序。这⾥可以不⽤加“”,因为jconsole.exe路径Program Files含有空格。

利⽤这个参数,我们可以在系统OOM后,⾃定义⼀个脚本,可以⽤来发送邮件告警信息,可以⽤来重启系统等等。

3.4.7 XX:InitialCodeCacheSize

JVM⼀个有趣的,但往往被忽视的内存区域是“代码缓存”,它是⽤来存储已编译⽅法⽣成的本地代码。代码缓存确实很少引起性能问题,但是⼀旦发⽣其影响可能是毁灭性的。如果代码缓存被占满,JVM会打印出⼀条警告消息,并切换到interpreted-only 模式:JIT编译器被停⽤,字节码将不再会被编译成机器码。因此,应⽤程序将继续运⾏,但运⾏速度会降低⼀个数量级,直到有⼈注意到这个问题。就像其他内存区域⼀样,我们可以⾃定义代码缓存的⼤⼩。相关的参数是-XX:InitialCodeCacheSize 和-XX:ReservedCodeCacheSize,它们的参数和上⾯介绍的参数⼀样,都是字节值。

3.4.8 -XX:+UseCodeCacheFlushing

如果代码缓存不断增⻓,例如,因为热部署引起的内存泄漏,那么提⾼代码的缓存⼤⼩只会延缓其发⽣溢出。为了避免这种情况的发⽣,我们可以尝试⼀个有趣的新参数:当代码缓存被填满时让JVM放弃⼀些编译代码。通过使⽤-XX:+UseCodeCacheFlushing 这个参数,我们⾄少可以避免当代码缓存被填满的时候JVM切换到interpreted-only 模式。不过,我仍建议尽快解决代码缓存问题发⽣的根本原因,如找出内存泄漏并修复它。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值