文章目录
本系列文章:
JVM学习之路(一)Java运行时区域、对象的创建过程、对象的内存布局、垃圾回收器
JVM学习之路(二)类加载、Java内存模型、JVM调试命令、JVM调优案例
JVM学习之路(三)JVM调优实战
一般垃圾收集器跟内存大小的对应关系:
- Serial 几十兆
- PS 上百兆 - 几个G
- CMS - 20G
- G1 - 上百G
- ZGC - 4T - 16T(JDK13)
- JVM调优指什么?两个目标:
- 尽量减少FGC;
- 减少STW时间。
一、JVM参数
Java虚拟机参数类型 有三种,如图:
常见垃圾回收器组合参数设定
:
-XX:+UseSerialGC = Serial + Serial Old
适用于小型程序。默认情况下不会是这种选项,HotSpot会根据计算及配置和JDK版本自动选择收集器。-XX:+UseParNewGC = ParNew + SerialOld
在一般未明确指定Old区垃圾回收器时,就代表用的是SerialOld。这个组合已经很少用(在某些版本中已经废弃)。- -XX:+UseConc(urrent)MarkSweepGC = ParNew + CMS + Serial Old
红色的字体代表在某些JDK版本上要加上,示例的在JDK1.8上不需要加红色的。 -XX:+UseParallelGC = Parallel Scavenge + Parallel Old (1.8默认)
- -XX:+UseParallelOldGC = Parallel Scavenge + Parallel Old
-XX:+UseG1GC = G1
JVM的参数分为三种:
- 标准参数,-开头的参数, 所有版本JDK都支持,直接输入java 查看;
- 非标准参数, -X开头的参数,输入java -X 查看;
- 不稳定参数 -XX:(+/-), 每个版本可能不同, java -XX:+PrintFlagsFinal 查看,特别多,几百个 。
常见的调优用的是 -X命令。
如:java -XX:+PrintFlagsFinal
命令可以查看所有的XX参数,如果在Linux环境下,可以+ |grep CMS
来过滤指定命令。
一个测试小demo:
package Basic;
import java.util.List;
import java.util.LinkedList;
public class HelloGC {
public static void main(String[] args) {
System.out.println("HelloGC!");
List list = new LinkedList();
for(;;) {
byte[] b = new byte[1024*1024];
list.add(b);
}
}
}
在Eclipse上编写此程序会,会在项目的bin目录下生成对应的HelloGC.class文件,如在"E:\WorkSpace\HelloWorld\bin\Basic"目录:
在CMD窗口,切换到bin目录:
然后就可以调试各种参数。当然也可以在Eclipse中设置JVM参数,此处以CMD方式为例。执行java -XX:+PrintCommandLineFlags Basic.HelloGC
命令,可以看默认的虚拟机参数设置:
图中的现象是:内存溢出(要区分内存泄露与内存溢出)。图中的参数:
-XX:InitialHeapSize=65738496 起始堆大小
-XX:MaxHeapSize=1051815936 最大堆大小
-XX:+UseCompressedClassPointers 之前提过的类型指针压缩
-XX:+UseCompressedOops 普通指针压缩
假如要设置参数,可以使用类似命令:java -Xmn10M -Xms40M -Xmx60M -XX:+PrintCommandLineFlags -XX:+PrintGC Basic.HelloGC
。
-Xmn 新生代大小
-Xms 初始堆大小
-Xmx 最大堆大小
-XX:+PrintGC 显示GC回收信息扫描二维码关注公众号,回复: 13415605 查看本文章
在打印GC方面,除了PrintGC
,还有一些类似的命令:
PrintGCDetails 打印更详细的信息
PrintGCTimeStamps 打印GC时的时间
PrintGCCauses 打印GC产生的原因
上面命令的执行结果:
发现最大堆大小、最小堆大小、年轻代大小已经设置成功。
如果使用CMS的话,打印的信息会更详细一些,命令:java -XX:+UseConcMarkSweepGC -XX:+PrintCommandLineFlags -XX:+PrintGC Basic.HelloGC
,结果:
要查看GC日志的详细意义,可以参考:
一般未显示"Full GC",就代表是YGC;
4544K->259K,分别代表回收前后的年轻代大小;6144K代表总的年轻代大小;
后面的代表此次回收所用的时间;
接着的4544K->4356K代表整个堆回收前后的大小;
19840K代表整个堆的大小;
后面的时间分别表示占了用户态多少时间、内核态多少时间、总共多少时间。
发生OOM时的详细dump信息:
total = eden + 1个survivor。
eden space 5632K, 94% used [0x00000000ff980000,0x00000000ffeb3e28,0x00000000fff00000)
后面的内存地址指的是,起始地址,使用空间结束地址,整体空间结束地址。从第一个地址到第三个地址之间的长度是5632K。从第一个地址到第二个地址代表使用完的地址,占总地址的94%。
Mataspeace,元数据区。最后一个是全元数据区所预留的全部空间,倒数第二个是已经占用的空间,倒数第一个是目前的容量是多少,第一个是真正使用的空间。
1. 吞吐量
:用户代码执行时间 /(用户代码执行时间 + 垃圾回收时间)
2. 响应时间
:STW越短,响应时间越好
所谓调优,首先确定目的?吞吐量优先,还是响应时间优先?还是在满足一定的响应时间的情况下,要求达到多大的吞吐量?
比如科学计算/数据挖掘,追求
吞吐量优先。该种情况一般选用PS + PO
。
网站、带界面的程序、对外程序的API,一般是响应时间优先。看JDK版本,优先选G1
。
二、具体调优实践
调优可以简单分为三个方面:
根据需求进行JVM规划和预调优
优化JVM运行环境(慢,卡顿)
解决JVM运行过程中出现的各种问题(OOM)
2.1 调优,从规划开始
调优,从业务场景开始,没有业务场景的调优都没有意义。
无监控(也就是要进行压力测试,这样能看到结果),不调优。
参考调优步骤:
熟悉业务场景
(没有最好的垃圾回收器,只有最合适的垃圾回收器),根据业务场景来选择垃圾回收器。
响应时间、停顿时间 [CMS G1 ZGC] (需要给用户作响应)
吞吐量 = 用户时间 /( 用户时间 + GC时间) [PS]
选择回收器组合
计算内存需求
。该步骤比较难以计算,范围较大,内存较小的话,可以回收的频繁一些。选定CPU
(越高越好)设定年代大小、升级年龄
- 设定日志参数,给出两个例子:
1)滚动日志:
-Xloggc:/opt/xxx/logs/xxx-xxx-gc-%t.log -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=5 -XX:GCLogFileSize=20M -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCCause
上面的命令意思是:按GC时间来生成滚动日志,最多可以生成5个,每个最大20M。
2)或者每天产生一个日志文件
看一些案例:
- 案例1:垂直电商,最高每日百万订单,处理订单系统需要什么样的服务器配置?
这个问题不太专业,因为垂直电商不可能做到每日百万订单。
遇到这种问法时,还是需要去分析。比如考虑高峰访问量,假设一小时产生36w订单,即100订单/秒,高峰就再次假设1000订单/秒。
接下来较考虑一个订单产生多少内存。即new出来订单对象,需要多少内存
。假设一个订单对象为512k,1000订单总和是500M左右。
这样新生代设置500M就可以,当然250M也可以,多回收几次就行。所以此时一般有响应时间要求,即在多少响应时间(比如100ms)内进行设计,然后进行压测。
初次设定参数后,就可以进行压测,满足不了要求就扩大参数,再不行就加服务器数量
。 - 案例2:12306遭遇春节大规模抢票应该如何支撑?
12306应该是中国并发量最大的秒杀网站,号称并发量100W最高CDN -> LVS -> NGINX -> 业务系统 -> 每台机器1W并发(10K问题) 100台机器
一般先从CDN开始,在全国做不同的CDN缓存,接下来是一堆的LVS,接下来就是NGINX,接下来就是Tomcat等服务器。
Redis可以撑得住单机1w并发。
此外,架构设计也是和业务逻辑紧密相关的。
在商城付款流程中,普通电商订单 -> 下单 ->订单系统(IO)减库存,减库存和订单的生成应该是异步进行的,最后一步是用户付款。
在具体的功能模块,比如订单生成,最后还会把压力压到一台服务器,可以做分布式本地库存 + 单独服务器做库存均衡。
大流量的处理方法:分而治之
。
2.2 优化JVM运行环境
- 问题1、有一个50万PV(页面浏览量)的资料类网站(从磁盘提取文档到内存)原服务器32位,1.5G的堆,用户反馈网站比较缓慢,因此公司决定升级,新的服务器为64位,16G的堆内存,结果用户反馈卡顿十分严重,反而比以前效率更低了?
- 原1.5G为什么慢?
很多用户浏览数据,很多数据load到内存,内存不足,频繁GC
,STW长,响应时间变慢。 - 为什么会更卡顿?
内存变大,FGC时间变长
。 - 怎么解决?
可以将PS换成PN + CMS 或者 G1(即使用响应时间优先的垃圾回收器)。
2.3 解决JVM运行过程中出现的各种问题
- 问题1、系统CPU经常100%,如何调优?(面试高频)
CPU100%那么一定有线程在占用系统资源。
- 找出哪个进程cpu高(top)
- 该进程中的哪个线程cpu高(top -Hp)
- 导出该线程的堆栈 (jstack)
- 查找哪个方法(栈帧)消耗时间 (jstack)
CPU经常100%,需要考虑:工作线程占比高和垃圾回收线程占比高两种情况。
- 问题2、系统内存飙高,如何查找问题?(面试高频)
- 导出堆内存 (jmap)
- 分析 (jhat jvisualvm mat jprofiler … )
- 问题3、如何监控JVM
- jstat jvisualvm jprofiler arthas top…
2.3.1 用jstack定位锁相关问题
用一个例子来尝试分析问题:
package com.mashibing.jvm.gc;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* 从数据库中读取信用数据,套用模型,并把结果进行记录和传输
*/
public class T15_FullGC_Problem01 {
private static class CardInfo {
BigDecimal price = new BigDecimal(0.0);
String name = "张三";
int age = 5;
Date birthdate = new Date();
public void m() {
}
}
private static ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(50,
new ThreadPoolExecutor.DiscardOldestPolicy());
public static void main(String[] args) throws Exception {
executor.setMaximumPoolSize(50);
for (;;){
modelFit();
Thread.sleep(100);
}
}
private static void modelFit(){
List<CardInfo> taskList = getAllCardInfo();
taskList.forEach(info -> {
// do something
executor.scheduleWithFixedDelay(() -> {
//do sth with info
info.m();
}, 2, 3, TimeUnit.SECONDS);
});
}
private static List<CardInfo> getAllCardInfo(){
List<CardInfo> taskList = new ArrayList<>();
for (int i = 0; i < 100; i++) {
CardInfo ci = new CardInfo();
taskList.add(ci);
}
return taskList;
}
}
使用java -Xms200M -Xmx200M -XX:+PrintGC com.mashibing.jvm.gc.T15_FullGC_Problem01
让程序运行,输出GC信息。
一般公司都使用网管监控软件,检测服务器,可以进行告警等操作。一般是运维团队首先受到报警信息(CPU Memory)
接着开发才会进行下一步的定位分析。首先要先使用top
命令找到内存使用和CPU占比较高的进程。
然后用top -Hp + 进程ID
查看该进程内的线程内存使用和CPU占比情况,观察进程中的线程,找到哪个线程CPU和内存占比高。示例:
top命令查看的是所有的进程信息,jps可以查看Java进程信息。
图中第一列的PID就是该进程内的线程号。接下来就要用jstack
命令定位具体的线程情况。"jstack + 进程ID"会把该进程的线程情况都列出来:
上图中的NID是十六进制的线程号,用top -Hp + 进程ID
命令看到的线程号是十进制的。
此时就可以看到每个线程的状况,要重点关注的是线程的异常状态,如:WAITING、BLOCKED
jstack中的主要线程状态:
RUNNABLE 线程运行中或I/O等待
BLOCKED 线程在等待monitor锁(synchronized关键字)
TIMED_WAITING 线程在等待唤醒,但设置了时限
WAITING 线程在无限等待唤醒
看一段关键的日志信息:
图中的"t2"是示例的用户线程名称,状态是WAITING,有这么一段:
waiting on <0x0000000088ca3310> (a java.lang.Object)
即在等待着一把锁的释放。假如有一个进程中100个线程,很多线程都在waiting on <xx>
,一定要找到是哪个线程持有这把锁,这时候一般是这个线程长期持有这把锁不释放。怎么找?搜索jstack dump的信息,找<xx>
,看哪个线程持有这把锁,状态一般是RUNNABLE。
同时,此时也能看到出问题代码的具体位置:
此时就明白阿里Java开发规范中,线程的名称(尤其是线程池)都要写有意义的名称。在使用线程池时,自定义线程名称的方式是:自定义ThreadFactory。
2.3.2 OOM问题的定位方式
在上面的实验中,用到了jstack、top、top -Hp等命令,当然还有别的命令可以使用:
jps,查看Java进程信息
jinfo,查看一些配置信息,用法是jinfo+进程号,示例:
jstat,查看一些进程信息,但是内容较乱,不常用:
比如jstat -gc 动态观察gc情况;jstat -gc 4655 500 : 每过500个毫秒,动态打印GC的情况
远程的服务器一般是不安装图形化界面的,所以可以在本地和远程服务器建立连接,此时有个标准的协议JMX。也就是说如果要在本地和远程服务器建立连接,就需要在远程服务器上进行JMX的相关配置。一些配置的示例:
- 程序启动加入参数:
shell
java -Djava.rmi.server.hostname=192.168.17.11 -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=11111 -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false XXX
- 如果遭遇 Local host name unknown:XXX的错误,修改/etc/hosts文件,把XXX加入进去
192.168.17.11 basic localhost localhost.localdomain localhost4 localhost4.localdomain4
::1 localhost localhost.localdomain localhost6 localhost6.localdomain6
- 关闭linux防火墙(实战中应该打开对应端口)
service iptables stop
chkconfig iptables off #永久关闭
- windows上打开 jconsole远程连接 192.168.17.11:11111
JConsole是JDK自带的图形化CPU监测工具,如果要连接远程服务器,需要连接远程进程:
JConsole连接成功后的界面:
类似工具JVirtualVM界面:
该工具上添加远程连接,成功后界面:
JVirtualVM能看到CPU、类、堆、线程的一些信息。
下面的这张图是最直观的信息显示:有多少类,占多少个字节,有多少个实 例。了解这些信息,也大致能够进行问题定位了,因为有大量的对象未被回收,一定是相关代码出了问题。通过这种图形化界面工具,能够较简单地定位到OOM问题的原因
。
那怎么定位OOM问题的?不是通过图形化工具。因为如果通过图形化界面定位OOM问题的话,代表在远程服务器上一直有个服务在后台运行。那么此时就会有两个问题:
- 已经上线的系统不用图形界面用什么来定位OOM问题?
1)可以用arthas等命令行模拟图形化界面
2)线上系统一般会配置一个参数:java -Xms20M -Xmx20M -XX:+UseParallelGC -XX:+HeapDumpOnOutOfMemoryError
,这个参数代表OOM的时候会自动产生堆转储文件。
3)很多服务器备份(高可用),停掉这台服务器对其他服务器不影响
4)在线定位(一般小点儿公司用不到)
那要获取和JVirtualVM相似的查看对象数量、占用字节相似效果的话,需要用什么么?jmap。示例:jmap - histo 4655 | head -20
,查看数量排名前20的对象信息:
注意在线上系统中,尽量不使用jmap -dump:format=b,file=xxx pid
类似的命令在线转储日志,jmap执行期间会对进程产生很大影响,甚至卡顿(电商不适合)。
2. 图形界面到底用在什么地方?测试!测试的时候进行监控!(压测观察)
总结来说:先由运维团队报告问题,如CPU高、内存占用高等--->用top命令查出出问题的线程--->如果是锁相关的问题,就继续用stack进行定位到具体线程--->如果发现频繁GC,就用jmap定位到是什么对象一直占用内存,未被回收
。
如果是数据库连接未释放之类的问题,不容易通过上述调试JVM的方式看出,需要看数据库连接池日志。
2.4 arthas在线排查工具
为什么需要在线排查?在生产上我们经常会碰到一些不好排查的问题,例如线程安全问题,用最简单的threaddump或者heapdump不好查到问题原因。为了排查这些问题,有时我们会临时加一些日志,比如在一些关键的函数里打印出入参,然后重新打包发布,如果打了日志还是没找到问题,继续加日志,重新打包发布。对于上线流程复杂而且审核比较严的公司,从改代码到上线需要层层的流转,会大大影响问题排查的进度。
线上系统一般不用图形化工具来排查问题,因为远程服务器没有装,如果本地连接远程服务器的话,还需要管理员来权限之类的。
arthas不包含jmap功能
。
arthas是阿里的开源在线分析工具。该工具的下载安装可以在github上寻找:
该工具的文件目录:
可以通过命令的方式,启动:
此时输入"1",就可以将arthas绑定在该进程上,然后就可以使用arthas相关命令来观察该进程。绑定成功:
help
可以查看常用命令:
jvm
命令,可以查看JVM的相关信息:
thread
命令可以查看线程相关情况:
thread + 线程号
,可以查看某个线程的详细情况:
dashboard
命令,观察系统情况,类似于top
命令效果:
heapdump
命令可以导出dump文件:
2.4.1 用jhat分析dump文件
此时可以用jhat命令分析.hprof(dump)文件:
图中表示用最多512M内存来分析,分析4244718个对象。
jhat命令起了Server,7000端口,所以可以在浏览器进行访问jhat解析过的内容。示例:
该界面对底部,还可以查看其它问题
点击第三个、第四个可以查看对象的数量,类似于jmap:
最底部的"OQL Query",可以查询特定问题对象:
点某个,可以看某个对象所占用的字节数和相关的引用:
2.4.2 用JVirtualVM分析dump文件
生产dump文件后,当然也可以用本地图形化工具分析,比如JVirtualVM:
也可以使用OQL查询。
2.4.3 arthas的特有功能
- 1、反编译
jad
命令可以用来反编译:
该功能可以排查动态代理相关的问题;还可以排查版本问题,即提交的代码是否被使用。 - 2、热替换
目前有些限制条件:只能改方法实现(方法已经运行完成),不能改方法名, 不能改属性。
假设有这样两个文件:
然后用javac命令编译这两个java文件。然后目前的情况是运行T,再随便输入,就会输出1:
此时直接改TT.java,改成输出"2",然后编译TT.java,接着:
就已经完成了热替换:
三、调优案例(OOM产生的原因)
OOM产生的原因多种多样,有些程序未必产生OOM,不断FGC(CPU飙高,但内存回收特别少)。如上面的示例代码,使用new ScheduledThreadPoolExecutor创建线程池,是有隐藏风险的。
- 1、硬件升级系统反而卡顿的问题
【需掌握】
原因:堆内存变大,FGC的时间也会变长了。解决方法:使用合适的垃圾回收器。 - 2、线程池不当运用产生OOM问题
【需掌握】
原因:利用Executors创建线程池(四种方式都有隐藏风险)会造成OOM问题。解决方法:使用ThreadPoolExecutor方式创建线程池。 - 3、jira问题(排查不出真实原因的问题)
现象是系统卡顿,从log来看不断FGC,查不出原因。像这种问题的通常解决方法:加内存 + 更换垃圾回收器(如G1)。 - 4、tomcat http-header-size过大问题
【需掌握】
Tomcat配置中有个字数:max-http-header-size,该参数设置过大会产生问题,每来一个请求就会占用这么多内存(单位:字节),导致OOM。出问题的对象是Http11OutputBuffer,该对象产生过多:
解决方法:将该参数调小。 - 5、直接内存溢出问题(少见)
《深入理解Java虚拟机》P59,使用Unsafe分配直接内存,或者使用NIO的问题。 - 6、栈溢出问题(较简单)
java.lang.StackOverflowError 栈内存溢出。原因:-Xss设定太小。解决方法:将该参数设置大点。
一个栈溢出的小例子:
public class StackOverFlow {
public static void main(String[] args) {
m();
}
static void m() {
m(); }
}
- 7、比较一下这两段程序的异同,分析哪一个是更优的写法【非案例】
Object o = null;
for(int i=0; i<100; i++) {
o = new Object();
//业务处理
}
for(int i=0; i<100; i++) {
Object o = new Object();
}
第一种写法较好,因为当重新创建对象时,之前创建的对象就可以回收,而第二种不能回收。
- 8、重写finalize引发频繁GC
现象是卡顿,原因是C++程序员写Java代码,仿照C++写法,重写了finalize方法,并且在finalize方法中进行了耗时操作,到时频繁GC。 - 9、如果有一个系统,内存一直消耗不超过10%,但是观察GC日志,发现FGC总是频繁产生(较简单)
原因:有人显式调用了System.gc()。
四、CMS日志
假设执行以下命令进行测试:
java -Xms20M -Xmx20M -XX:+PrintGCDetails -XX:+UseConcMarkSweepGC com.mashibing.jvm.gc.T15_FullGC_Problem01
小例子:
[GC (Allocation Failure) [ParNew: 6144K->640K(6144K), 0.0265885 secs] 6585K->2770K(19840K), 0.0268035 secs] [Times: user=0.02 sys=0.00, real=0.02 secs]
ParNew:年轻代收集器
6144->640:收集前后的对比
(6144):整个年轻代容量
6585 -> 2770:整个堆的使用情况
(19840):整个堆大小
[GC (CMS Initial Mark) [1 CMS-initial-mark: 8511K(13696K)] 9866K(19840K), 0.0040321 secs] [Times: user=0.01 sys=0.00, real=0.00 secs]
//CMS Initial Mark : 初始标记
//8511 (13696) : 老年代使用(最大)
//9866 (19840) : 整个堆使用(最大)
[CMS-concurrent-mark-start]
[CMS-concurrent-mark: 0.018/0.018 secs] [Times: user=0.01 sys=0.00, real=0.02 secs]
//CMS-concurrent-mark : 并发标记
//这里的时间意义不大,因为是并发执行
[CMS-concurrent-preclean-start]
[CMS-concurrent-preclean: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
//标记Card为Dirty,也称为Card Marking
[GC (CMS Final Remark) [YG occupancy: 1597 K (6144 K)][Rescan (parallel) , 0.0008396 secs][weak refs processing, 0.0000138 secs][class unloading, 0.0005404 secs][scrub symbol table, 0.0006169 secs][scrub string table, 0.0004903 secs][1 CMS-remark: 8511K(13696K)] 10108K(19840K), 0.0039567 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
// CMS Final Remark : 重新标记
//STW阶段,YG occupancy:年轻代占用及容量
//[Rescan (parallel):STW下的存活对象标记
//weak refs processing: 弱引用处理
//class unloading: 卸载用不到的class
//scrub symbol(string) table:
//cleaning up symbol and string tables which hold class-level metadata and
//internalized string respectively
//CMS-remark: 8511K(13696K): 阶段过后的老年代占用及容量
//10108K(19840K): 阶段过后的堆占用及容量
[CMS-concurrent-sweep-start]
[CMS-concurrent-sweep: 0.005/0.005 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
//标记已经完成,进行并发清理
[CMS-concurrent-reset-start]
[CMS-concurrent-reset: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
//重置内部结构,为下次GC做准备
对于CMS而言,在日志中更多的是,关注GC是否频繁,和耗费的时间在不在允许范围之内。
五、G1日志
G1可以设置参数,表明每次回收建议的暂停时间,虚拟机参考这个时间,动态调整年轻代大小(以尽量拟合到设置的时间)。
G1的调优目标:尽量不要FGC
。
在G1的日志中,YGC和Mixed GC常常是混在一起的。
[GC pause (G1 Evacuation Pause) (young) (initial-mark), 0.0015790 secs]
//young -> 年轻代 Evacuation-> 复制存活对象 (即表示是YGC)
//initial-mark 混合回收的阶段,这里是YGC混合老年代回收
[Parallel Time: 1.5 ms, GC Workers: 1] //一个GC线程
[GC Worker Start (ms): 92635.7]
[Ext Root Scanning (ms): 1.1]
[Update RS (ms): 0.0]
[Processed Buffers: 1]
[Scan RS (ms): 0.0]
[Code Root Scanning (ms): 0.0]
[Object Copy (ms): 0.1]
[Termination (ms): 0.0]
[Termination Attempts: 1]
[GC Worker Other (ms): 0.0]
[GC Worker Total (ms): 1.2]
[GC Worker End (ms): 92636.9]
[Code Root Fixup: 0.0 ms]
[Code Root Purge: 0.0 ms]
[Clear CT: 0.0 ms]
[Other: 0.1 ms]
[Choose CSet: 0.0 ms]
[Ref Proc: 0.0 ms]
[Ref Enq: 0.0 ms]
[Redirty Cards: 0.0 ms]
[Humongous Register: 0.0 ms]
[Humongous Reclaim: 0.0 ms]
[Free CSet: 0.0 ms]
[Eden: 0.0B(1024.0K)->0.0B(1024.0K) Survivors: 0.0B->0.0B Heap: 18.8M(20.0M)->18.8M(20.0M)]
//这是此次回收的一个总结:18.8M->18.8M,代表没有回收,有内存泄露。
[Times: user=0.00 sys=0.00, real=0.00 secs]
//以下是混合回收其他阶段
[GC concurrent-root-region-scan-start]
[GC concurrent-root-region-scan-end, 0.0000078 secs]
[GC concurrent-mark-start]
//无法evacuation(复制),进行FGC
[Full GC (Allocation Failure) 18M->18M(20M), 0.0719656 secs]
[Eden: 0.0B(1024.0K)->0.0B(1024.0K) Survivors: 0.0B->0.0B Heap: 18.8M(20.0M)->18.8M(20.0M)], [Metaspace: 38
76K->3876K(1056768K)] [Times: user=0.07 sys=0.00, real=0.07 secs]
六、常用参数
-Xmn -Xms -Xmx -Xss
年轻代 最小堆 最大堆 栈空间
,示例:
java -Xmx3550m -Xms3550m -Xmn2g -Xss128k
-XX:+UseTLAB
使用TLAB,默认打开
【一般不需要调整】- -XX:+PrintTLAB
打印TLAB的使用情况【一般不需要调整】 -XX:TLABSize
设置TLAB大小
【一般不需要调整】-XX:+DisableExplicitGC
使显示调用System.gc()时不起作用 (System.gc()会产生FGC)。
System.gc()默认会触发一次Full GC,如果在代码中不小心调用了System.gc()会导致JVM间歇性的暂停。
-XX:+PrintGC
打印GC信息
-XX:+PrintGCDetails
打印GC详细信息
- -XX:+PrintHeapAtGC
GC的时候打印堆栈情况 -XX:+PrintGCTimeStamps
打印GC发生时的时间戳- -XX:+PrintGCApplicationConcurrentTime (低)
打印应用程序时间 - -XX:+PrintGCApplicationStoppedTime (低)
打印应用程序暂停时长 - -XX:+PrintReferenceGC (重要性低)
记录回收了多少种不同引用类型的引用 -verbose:class
打印类加载详细过程- -XX:+PrintVMOptions
可以在程序运行时,打印虚拟机接受的命令行显式参数 -XX:+PrintFlagsFinal / -XX:+PrintFlagsInitial
打印出最终的参数 / 初始的参数
【必须会用】-Xloggc:opt/log/gc.log
生成GC日志
-XX:MaxTenuringThreshold
升代年龄
(该参数主要是控制新生代需要经历多少次GC晋升到老年代中的最大阈值),最大值15- 锁自旋次数 -XX:PreBlockSpin 热点代码检测参数-XX:CompileThreshold
这些不建议更改
6.1 Parallel常用参数
-XX:SurvivorRatio
Eden和S区的比例
【一般不需要调整】
该值默认为8,即Eden占新生代的8/10,From幸存区和To幸存区各占新生代的1/10。
- -XX:PreTenureSizeThreshold
大对象阈值,即大于这个值的参数直接在老年代分配。 - -XX:MaxTenuringThreshold
-XX:+ParallelGCThreads
并行收集器的线程数
,同样适用于CMS,一般设为和CPU核数相同- -XX:+UseAdaptiveSizePolicy
自动选择各区大小比例。
开启:-XX:+UseAdaptiveSizePolicy;
关闭:-XX:-UseAdaptiveSizePolicy
1)在 JDK 1.8 中,如果使用 CMS,无论 UseAdaptiveSizePolicy 如何设置,都会将 UseAdaptiveSizePolicy 设置为 false;不过不同版本的JDK存在差异;
2)UseAdaptiveSizePolicy不要和SurvivorRatio参数显示设置搭配使用,一起使用会导致参数失效;
3)由于AdaptiveSizePolicy会动态调整 Eden、Survivor 的大小,有些情况存在Survivor 被自动调为很小,比如十几MB甚至几MB的可能,这个时候YGC回收掉 Eden区后,还存活的对象进入Survivor 装不下,就会直接晋升到老年代,导致老年代占用空间逐渐增加,从而触发FULL GC,如果一次FULL GC的耗时很长(比如到达几百毫秒),那么在要求高响应的系统就是不可取的。
6.2 CMS常用参数
-XX:+UseConcMarkSweepGC
使用CMS垃圾回收器
-XX:ParallelCMSThreads
CMS线程数量
- -XX:CMSInitiatingOccupancyFraction
使用多少比例的老年代后开始CMS收集,默认是68%(近似值),如果频繁发生SerialOld卡顿,应该调小,(频繁CMS回收) - -XX:+UseCMSCompactAtFullCollection
在FGC时进行压缩 - -XX:CMSFullGCsBeforeCompaction
多少次FGC之后进行压缩 - -XX:+CMSClassUnloadingEnabled
回收方法区不用的class - -XX:CMSInitiatingPermOccupancyFraction
达到什么比例时进行Perm回收 -XX:GCTimeRatio
设置GC时间占用程序运行时间的百分比
-XX:MaxGCPauseMillis
停顿时间,是一个建议时间
,GC会尝试用各种手段达到这个时间,比如减小年轻代
6.3 G1常用参数
-XX:+UseG1GC
使用GC垃圾回收器
-XX:MaxGCPauseMillis
每次年轻代垃圾回收的最长时间
,如果无法满足此时间,JVM会自动调整年轻代大小,以满足此值。- -XX:GCPauseIntervalMillis
GC的间隔时间 - -XX:+G1HeapRegionSize
一个Region的大小可以通过参数-XX:G1HeapRegionSize设定,取值范围从1M到32M,且是2的指数(1 2 4 8 16 32)。如果不设定,那么G1会根据Heap大小自动决定。。
随着size增加,垃圾的存活时间更长,GC间隔更长,但每次GC的时间也会更长
(ZGC对此做了改进,是动态区块大小)。 -XX:G1NewSizePercent
新生代最小比例
,默认为5%-XX:G1MaxNewSizePercent
新生代最大比例
,默认为60%-XX:GCTimeRatio
GC时间建议比例
,G1会根据这个值调整堆空间,值为0-100的整数- ConcGCThreads
线程数量 - InitiatingHeapOccupancyPercent
启动G1的堆空间占用比例
七、纤程/协程
线程和纤程的区别,一个通过内核空间,一个不通过内核空间。目前在Java中纤程可以通过第三方库 Quasar来实现。
八、常见问题
1.生产环境中,倾向于将最大堆内存和最小堆内存设置为
:(为什么?)
A: 相同 B:不同
A,好处是:
1) 避免JVM在运行过程中向操作系统申请内存
2)延后启动后首次GC的发生时机
3)减少启动初期的GC次数
-
什么是响应时间优先?
注重的是垃圾回收时STW的时间最短
。 -
什么是吞吐量优先?
吞吐量是指应用程序线程用时占程序总用时的比例
,也就是说尽量多让用户程序去执行。 -
ParNew和PS的区别是什么?
都是年轻代多线程收集器。
ParNew 回收器是通过控制 垃圾回收 的 线程数 来进行参数调整,而 Parallel Scavenge 回收器更关心的是程序运行的吞吐量
。即一段时间内,用户代码 运行时间占 总运行时间 的百分比。 -
ParNew和ParallelOld的区别是什么?(年代不同,算法不同)
前者是年轻代收集器,后者是老年代收集器,然后解释两者。 -
长时间计算的场景应该选择:吞吐量优先的收集器和策略。
-
大规模电商网站应该选择:停顿时间少(即响应时间快)的收集器和策略。
-
JDK1.7 1.8 1.9的默认垃圾回收器是什么?如何查看?
jdk1.7 默认垃圾收集器Parallel Scavenge(新生代)+Parallel Old(老年代);
jdk1.8 默认垃圾收集器Parallel Scavenge(新生代)+Parallel Old(老年代);
jdk1.9 默认垃圾收集器G1。
java -XX:+PrintCommandLineFlags -version
命令可以查看使用的垃圾回收器。 -
所谓调优,到底是在调什么?
本人认为,是根据业务需要,在吞吐量和响应时间之间做出选择。 -
如果采用PS + ParrallelOld组合,怎么做才能让系统基本不产生FGC
应该和下个问题的答案是有相通之处的。 -
如果采用ParNew + CMS组合,怎样做才能够让系统基本不产生FGC
1)加大JVM内存
2)加大Young(年轻代)的比例
3)提高Y-O(最大值是15)的年龄
4)提高S(survivor)区比例
5)避免代码内存泄漏
-
如果G1产生FGC,你应该做什么?
1)扩内存
2)提高CPU性能(回收的快,业务逻辑产生对象的速度固定,垃圾回收越快,内存空间越大)
3)降低MixedGC触发的阈值,让MixedGC提早发生(默认是45%)
。具体的参数是:
-XX:InitiatingHeapOccupancyPercent=45
- 问:生产环境中能够随随便便的dump吗?
小堆影响不大,大堆会有服务暂停或卡顿(加live可以缓解),dump前会有FGC - 问:常见的OOM问题有哪些?
栈 堆 方法区