JVM命令行参数参考: https://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html
1. 基本概念
- 吞吐量:
- 响应时间快:
用户线程停顿时间短
所谓调优,首先确定,追求啥?吞吐量优先,还是响应时间优先?还是在满足一定的响应时间的情况下,要求达到多大的吞吐量?
问题:
科学计算,吞吐量优先。数据挖掘,吞吐量优先优先。吞吐量优先的一般使用PS + PO。
网站或者图形界面程序,肯定是响应时间优先, 一般是使用G1。
1. HotSpot参数分类:
参数类型 | 表示 | 说明 |
---|---|---|
标准参数 | **-** 开头 |
所有HotSpot版本都支持 |
非标准参数 | **-X** 开头 |
特定版本HotSpot支持特定命令 |
不稳定参数 | **-XX** 开头 |
下个版本可能取消 |
2. 常用JVM参数
java -X
:打印非标类参数
实验小程序:
import java.util.LinkedList;
import java.util.List;
public class TestGC {
public static void main(String[] args) {
System.out.println("HelloGC!");
List list = new LinkedList();
for(;;) {
byte[] b = new byte[1024*1024];
list.add(b);
}
}
}
java -XX:+PrintCommandLineFlags
:打印命令行参数选项
-XX:InitialHeapSize=267117376 |
指定初始堆大小 |
---|---|
-XX:MaxHeapSize=4273878016 |
指定最大堆大小 |
XX:+UseCompressedClassPointers |
使用压缩指针 |
-XX:+UseCompressedOops |
使用压缩Oops |
-XX:+UseParallelGC |
使用ParallelGC |
-Xmn10M |
新生代的大小 |
-Xms40M |
指定最小堆大小为40M |
-Xmx60M |
指定最小堆大小为60M 一般情况下要指定最小堆大小和最大堆大小要一样的, 因为如果不一样的话JAVA会自动弹性扩容和收缩堆的空间, 浪费系统性能。 |
-XX:+PrintGC |
打印GC基本信息 |
-XX:+PrintGCDetails |
打印GC详细信息 |
-XX:+PrintGCTimeStamps |
打印GC的时间信息 |
-XX:+PrintGCCause |
打印GC产生的原因 |
在windows下面打印GC日志,并解析GC日志
指定-classpath D:\code\IDEA\idea_workspace\learn\JVM\out\production\JVM
是因为java搜索class文件是在classpath里面搜索的。
java -XX:+PrintCommandLineFlags TestGC
3. GC常用参数
- -Xmn -Xms -Xmx -Xss: 年轻代 最小堆 最大堆 栈空间
- -XX:+UseTLAB: 使用TLAB,默认打开
- -XX:+PrintTLAB: 打印TLAB的使用情况
- -XX:TLABSize: 设置TLAB大小
- -XX:+DisableExplictGC:System.gc()不管用 ,FGC
- -XX:+PrintGC
- -XX:+PrintGCDetails
- -XX:+PrintHeapAtGC
- -XX:+PrintGCTimeStamps
- -XX:+PrintGCApplicationConcurrentTime (低)打印应用程序时间
- -XX:+PrintGCApplicationStoppedTime (低)打印暂停时长
- -XX:+PrintReferenceGC (重要性低) 记录回收了多少种不同引用类型的引用
- -verbose:class 类加载详细过程
- -XX:+PrintVMOptions
- -XX:+PrintFlagsFinal -XX:+PrintFlagsInitial 必须会用
- -Xloggc:opt/log/gc.log
- -XX:MaxTenuringThreshold 升代年龄,最大值15
锁自旋次数 -XX:PreBlockSpin 热点代码检测参数-XX:CompileThreshold 逃逸分析 标量替换 …
这些不建议设置4. Parallel常用参数
-XX:+UseConcMarkSweepGC
- -XX:ParallelCMSThreads
CMS线程数量 - -XX:CMSInitiatingOccupancyFraction
使用多少比例的老年代后开始CMS收集,默认是68%(近似值),如果频繁发生SerialOld卡顿,应该调小,(频繁CMS回收) - -XX:+UseCMSCompactAtFullCollection
在FGC时进行压缩 - -XX:CMSFullGCsBeforeCompaction
多少次FGC之后进行压缩 - -XX:+CMSClassUnloadingEnabled
- -XX:CMSInitiatingPermOccupancyFraction
达到什么比例时进行Perm回收 - GCTimeRatio
设置GC时间占用程序运行时间的百分比 - -XX:MaxGCPauseMillis
停顿时间,是一个建议时间,GC会尝试用各种手段达到这个时间,比如减小年轻代
5. G1常用参数
- -XX:+UseG1GC
- -XX:MaxGCPauseMillis
建议值,G1会尝试调整Young区的块数来达到这个值 - -XX:GCPauseIntervalMillis
?GC的间隔时间 - -XX:+G1HeapRegionSize
分区大小,建议逐渐增大该值,1 2 4 8 16 32。
随着size增加,垃圾的存活时间更长,GC间隔更长,但每次GC的时间也会更长
ZGC做了改进(动态区块大小) - G1NewSizePercent
新生代最小比例,默认为5% - G1MaxNewSizePercent
新生代最大比例,默认为60% - GCTimeRatio
GC时间建议比例,G1会根据这个值调整堆空间 - ConcGCThreads
线程数量 - InitiatingHeapOccupancyPercent
启动G1的堆空间占用比例
6. 预调优
- 调优,从业务场景开始,没有业务场景的调优都是耍流氓
- 无监控(压力测试,能看到结果),不调优
- 步骤:
- 熟悉业务场景(没有最好的垃圾回收器,只有最合适的垃圾回收器)
- 响应时间、停顿时间 [CMS G1 ZGC] (需要给用户作响应)
- 吞吐量 = 用户时间 /( 用户时间 + GC时间) [PS]
- 选择回收器组合
- 计算内存需求(经验值 1.5G 16G)
- 选定CPU(越高越好)
- 设定年代大小、升级年龄
- 设定日志参数
-Xloggc:/opt/xxx/logs/xxx-xxx-gc-%t.log -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=5 -XX:GCLogFileSize=20M -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCCause
- 熟悉业务场景(没有最好的垃圾回收器,只有最合适的垃圾回收器)
-Xloggc: 指定文件存放路径
-XX:+UseGCLogFileRotation:使用循环文件
-XX:NumberOfGCLogFiles=5:GC日志文件最多为5个和-XX:+UseGCLogFileRotation配合会滚动删除最早的日志文件
-XX:GCLogFileSize=20M: 每个文件大小最大20M
-XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCCause : 打印日志详细,时间,产生原因
1. 或者每天产生一个日志文件
- 观察日志情况
案例1:垂直电商,最高每日百万订单,处理订单系统需要什么样的服务器配置?
这个问题比较业余,因为很多不同的服务器配置都能支撑(1.5G 16G) 1小时360000集中时间段, 100个订单/秒,(找一小时内的高峰期,1000订单/秒) 经验值, 非要计算:一个订单产生需要多少内存?512K * 1000 500M内存 专业一点儿问法:要求响应时间100ms 压测!
案例2:12306遭遇春节大规模抢票应该如何支撑?
12306应该是中国并发量最大的秒杀网站: 号称并发量100W最高 CDN -> LVS -> NGINX -> 业务系统 -> 每台机器1W并发(10K问题) 100台机器 普通电商订单 -> 下单 ->订单系统(IO)减库存 ->等待用户付款 12306的一种可能的模型: 下单 -> 减库存 和 订单(redis kafka) 同时异步进行 ->等付款 减库存最后还会把压力压到一台服务器 可以做分布式本地库存 + 单独服务器做库存均衡 大流量的处理方法:分而治之
怎么得到一个事务会消耗多少内存?
- 弄台机器,看能承受多少TPS?是不是达到目标?扩容或调优,让它达到
- 用压测来确定
4. 调优实战
一个小程序,这个小程序最终会造成OOM。
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内存达到尽早OOM的目的。
java -Xms200M -Xmx200M -XX:+PrintGC com.mashibing.jvm.gc.T15_FullGC_Problem01
一般是运维团队首先受到报警信息
CPU居高不下、内存不断增长
top
命令观察到问题找出CPU居高不下或者内存不断增长的进程, 记住PID
top -Hp [``PID``]
观察进程中的线程观察哪个进程CPU居高不下或者内存不断增长。
jps + jstack 进一步分析
jps定位具体java进程
jstack 定位线程状况,重点关注:WAITING BLOCKED
eg.
waiting on <0x0000000088ca3310> (a java.lang.Object)找到阻塞线程
假如有一个进程中100个线程,很多线程都在waiting on ,一定要找到是哪个线程持有这把锁。
怎么找?搜索jstack dump的信息,看哪个线程持有这把锁RUNNABLE
PS:为什么阿里规范里规定,线程的名称(尤其是线程池)都要写有意义的名称? 怎么样自定义线程池里的线程名称?(自定义ThreadFactory)
- jinfo pid
- jstat -gc 动态观察gc情况 / 阅读GC日志发现频繁GC / arthas观察 / jconsole/jvisualVM/ Jprofiler(最好用)
jstat -gc 4655 500 : 每个500个毫秒打印GC的情况
如果面试官问你是怎么定位OOM问题的?如果你回答用图形界面(错误)
1:已经上线的系统不用图形界面用什么?(cmdline arthas)
2:图形界面到底用在什么地方?测试!测试的时候进行监控!(压测观察) - jmap - histo 4655 | head -20,查找有多少对象产生
- jmap -dump:format=b,file=xxx pid :
线上系统,内存特别大,jmap执行期间会对进程产生很大影响,甚至卡顿(电商不适合)
1:设定了参数HeapDump,OOM的时候会自动产生堆转储文件
2:很多服务器备份(高可用),停掉这台服务器对其他服务器不影响
3:在线定位(一般小点儿公司用不到) - java -Xms20M -Xmx20M -XX:+UseParallelGC -XX:+HeapDumpOnOutOfMemoryError com.mashibing.jvm.gc.T15_FullGC_Problem01
- 使用MAT / jhat /jvisualvm 进行dump文件分析
https://www.cnblogs.com/baihuitestsoftware/articles/6406271.html
jhat -J-mx512M xxx.dump
http://192.168.17.11:7000
拉到最后:找到对应链接
可以使用OQL查找特定问题对象 - 找到代码的问题
5. 在线排查工具
5.1 jconsole 远程连接
程序启动加入参数:
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
5.2 jvisualvm远程连接
https://www.cnblogs.com/liugh/p/7620336.html (简单做法)
5.3 jprofiler (收费)
5.4 arthas
- 为什么需要在线排查?
在生产上我们经常会碰到一些不好排查的问题,例如线程安全问题,用最简单的threaddump或者heapdump不好查到问题原因。为了排查这些问题,有时我们会临时加一些日志,比如在一些关键的函数里打印出入参,然后重新打包发布,如果打了日志还是没找到问题,继续加日志,重新打包发布。对于上线流程复杂而且审核比较严的公司,从改代码到上线需要层层的流转,会大大影响问题排查的进度。 - jvm观察jvm信息
- thread定位线程问题
- dashboard 观察系统情况
- heapdump + jhat分析
- jad反编译
动态代理生成类的问题定位
第三方的类(观察代码)
版本问题(确定自己最新提交的版本是不是被使用) - redefine 热替换
目前有些限制条件:只能改方法实现(方法已经运行完成),不能改方法名, 不能改属性
m() -> mm() - sc - search class
- watch - watch method
- 没有包含的功能:jmap