JVM调优
Posted 我是PP闻啊
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JVM调优相关的知识,希望对你有一定的参考价值。
“(什么是调优?)*(调优第一步-熟悉常用命令行参数)*(常见垃圾回收器组合参数设定)*(PS和PO垃圾回收器日志详解)”
好像从业以来,JVM调优在面试中只被问过一次,问的是“线程死锁你怎么查,jstack会用么?”Emmm,当时萌萌的我再一次被秒了,后来好在大佬收留了我,在工作中还教会了我dubbo invoke的用法,不过现在想想,大佬当时拿这种问题问一个初中级程序员真的好嘛~~~~
01
—
什么是调优?
首先给出调优要了解的两个基本量:
1.吞吐量(throughput):用户代码执行时间 /(用户代码执行时间 + 垃圾回收时间)。
2.响应时间:STW越短,响应时间越好。
一般说的JVM调优指的哪些呢?
1.JVM规划和预调优
调优的前提有两点:1.业务场景。2.可监控(可压测)。对于规划这块我们一般采用以下步骤:
i.熟悉业务场景,然后选择垃圾回收器的组合(没有最好的垃圾回收器,只有最合适的垃圾回收器)。我们首先要确定需求是吞吐量优先还是响应时间优先?还是在满足一定响应时间的情况下达到多大的吞吐量。以寻常的服务为例,如果是负责计算,数据挖掘的服务,是吞吐量优先,我们可以选择(PS+PO)的垃圾回收组合;如果是网站,对客户端,提供API的服务,一般要求的是响应优先,我们推荐用[G1,PN+CMS,ZGC]的垃圾回收器组合。
ii.计算内存需求,内存需求是弹性很大的一个问题,没有一定之规,很多系统内存由小变大反而变慢了,原因是程序设计有问题,增大内存只会延迟FullGC到来的时间,但由于内存变大,GC的时间也会变长,但是一般情况下,内存还是按上篇中结尾分享的那几个档位进行选择。
iii.CPU选定,越高越好,这个不用说了。
iv.年代大小、升级年龄:这块一般不调,用默认值,除非是特殊服务,上来就YGC怎么调都不行的时候才尝试这个。
v.设定日志参数,这个直接背吧。
-Xloggc:/opt/xxx/logs/xxx-xxx-gc-%t.log //指定日志位置
-XX:+UseGCLogFileRotation //文件循环存储,意思是指定空间满会覆盖
-XX:NumberOfGCLogFiles=5 //总共循环存储的文件数
-XX:GCLogFileSize=20M //每个文件大小
-XX:+PrintGCDetails //打印GC详情
-XX:+PrintGCDateStamps //打印GC时间戳
-XX:+PrintGCCause //打印GC原因
vi.观察日志
列出几个应用场景:
场景1:业余面试官问:垂直电商,最高每日百万订单,处理订单系统需要什么样的服务器配置?
说业余是因为我们分析就知道,比如内存,一定是要看实现业务过程中一笔订单产生多少对象消耗多少内存,才可以确定撑住高峰期的配置,直接谈的话只能靠经验大概估,到时候不够了还得扩容。
如果非要计算 :一天百万,1小时40000单(大概100单/s),而一般电商下单有集中时间段,一般在下午5点到7单,假设1000订单/秒,那么这个高峰值
就是我们配置要承受的,一个订单产生需要多少内存(一般512K-2M每单),512K * 1000=500M内存。
最好的问法是,要求响应时间的情况下使用什么样的配置?最后还是需要一边压测一边调整配置,选市面上好的机器,升CPU+内存然后上云才是最优解,严格要求配置只是给自己找麻烦。
场景2:12306遭遇春节大规模抢票应该如何支撑?
大流量的处理方法:分而治之。12306应该是中国并发量最大的秒杀网站:号称并发量100W最高,常规CDN -> LVS -> nginx -> 业务系统 -> 每台机器1W并发(10K问题)->100台机器(100个Redis)。12306的一种可能的模型:下单 -> 减库存 和 订单(redis kafka) 同时异步进行 ->等付款->减库存最后还会把压力压到一台服务器->可以做分布式本地库存 + 单独服务器做库存均衡。
2.优化运行JVM运行环境(结局"慢","卡顿"等问题)
3.解决JVM运行过程中出现的各种问题(OOM等造成服务器崩溃的问题)。
2和3都需要用实际问题+工具来分析处理,我们后面聊。
02
—
调优第一步-熟悉常用命令行参数
我们一般用的是HotSpot的JVM,所以拿这个作说明。HotSpot参数分三种:
1.标准参数:- 开头,所有的HotSpot都支持
2.非标准参数:-X 开头,特定版本HotSpot支持特定命令
3.不稳定参数:-XX 开头,下个版本可能取消
指令行java看到所有-开头的标准参数。
指令行java -X,看到所有当前JVM的非标准参数。
-XX,没办法直接看,需要查看所有参数然后从参数里筛选,看下面的例子。
看JVM所有的参数,我是JDK1.8的hotSpot总共有736个参数。
接着我们用一段程序了解一些基本的GC参数。
//-XX:+PrintGCDetails -XX:+UseConcMarkSweepGC -XX:+PrintFlagsFinal -XX:+PrintVMOptions -
public class T01_HelloGC {
public static void main(String[] args) {
for(int i=0; i<10000; i++) {
byte[] b = new byte[1024 * 1024];
}
System.out.println("Hello GC");
}
}
先运行java -XX:+PrintCommandLineFlags T01_HelloGC,结果如下
PrintCommandLineFlags:这个参数让JVM打印出那些已经被用户或者JVM设置过的详细的XX参数的名称和值。
因为我们没有设置过参数,所以打印出来的都是JVM默认设置过得,我们看看这些参数。
-XX:InitialHeapSize=29975808,初始化的堆大小。
-XX:MaxHeapSize=479612928,最大堆大小。
-XX:+PrintCommandLineFlags 我们设置的开启打印参数的选项
-XX:+UseCompressedClassPointers 开启类指针对象压缩,我们在classLoader里讲过,这个类指针是在对象布局的第二项,作用是把8字节的指针压缩成4字节。
-XX:+UseCompressedOops 开启普通对象指针压缩,我们在classLoader里讲过,这个普通对象指针是在对象布局的第三项,实例数据中如果存在属性就会进行压缩,作用是把8字节的指针压缩成4字节。
我们再运行
java -Xmn10M -Xms40M -Xmx60M -XX:+PrintCommandLineFlags -XX:+PrintGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCCause T01_HelloGC 结果如下(GC信息太多我们单独拿一条看):
-Xmn10M 表示设置最小新生代的大小,对应结果里的-XX:NewSize=10485760,注意这个时候还打印出来了最大新生代的大小。
-Xms40M 表示设置最小堆的大小,对应结果里的-XX:InitialHeapSize=41943040
-Xmx60M 表示设置最大堆的大小,对应结果里的
-XX:MaxHeapSize=62914560
-XX:+PrintGC 打印GC回收的信息
-XX:+PrintGCDetails 打印GC详细信息(这个后面说)
-XX:+PrintGCTimeStamps 打印GC时间戳(这个后面说)
-XX:+PrintGCCause 打印GC原因(这个后面说)
前面这个是默认垃圾回收器是PS+PO的日志,我们切换一下垃圾回收器,看下,PN+CMS的垃圾回收器日志。
我们需要换一个可以撑爆堆的程序。
import java.util.LinkedList;
import java.util.List;
public class Hello_Old {
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:+UseConcMarkSweepGC -XX:+PrintCommandLineFlags -XX:+PrintGC Hello_Old
可以看到
-XX:MaxTenuringThreshold=6 CMS默认回收年龄变成了6(原来是15)
-XX:OldPLABSize=16 老年代空间PLAB大小(年轻代晋升至老年代的缓冲区)
-XX:+UseParNewGC 我们使用了PN+CMS的垃圾回收组合
这是CMSGC日志和PS+PO最大的区别,这也是CMS标记的几个阶段,上次我们介绍GC的时候说过。
然后我们再补充一个常用的
java -XX:+PrintFlagsInitial 默认参数值,用法同PrintFlagsFinal。
03
—
常见垃圾回收组合参数设定
这里就是启动java程序的时候用指定参数的形式咋切换我们想要的程序。
-XX:+UseSerialGC = Serial New (DefNew) + Serial Old
* 小型程序。默认情况下不会是这种选项,HotSpot会根据计算及配置和JDK版本自动选择收集器 。
-XX:+UseParNewGC = ParNew + SerialOld
* 这个组合已经很少用(在某些版本中已经废弃)
https://stackoverflow.com/questions/34962257/why-remove-support-for-parnewserialold-anddefnewcms-in-the-future
-XX:+UseConcMarkSweepGC = ParNew + CMS + Serial Old
-XX:+UseParallelGC = Parallel Scavenge + Parallel Old (1.8默认,表示并行分代式垃圾回收器,其老年代和新生代都是多线程并行操作)
-XX:+UseParallelOldGC = Parallel Scavenge + Parallel Old(老年代是否使用并行的一个选项(默认开启),如果关闭则老年代退化为串行操作(Serial Old))
-XX:+UseG1GC = G1
Linux中没找到默认GC的查看方法,只能通过GC的日志来分辨。
Q:Linux下1.8版本默认的垃圾回收器到底是什么?
R:1.8.0_181 默认(看不出来)Copy MarkCompact
1.8.0_222 默认 PS + PO
04
—
PS+PO垃圾回收日志详解
GC那里:GC代表YGC,FULL GC代表FULL GC,其余的看下面。
还是前面那个程序,我们运行命令
java -Xmn10M -Xms40M -Xmx60M -XX:+PrintCommandLineFlags -XX:+PrintGCDetails Hello_Old
解释:GC(默认是YGC),GC原因:分配内存失败(年轻代满了分配不进去),年轻代大小:原来被使用了7328K,回收后现在使用了0K(年轻代整体大小9216K),耗时0.0000913秒,整个堆原来使用了7584K,回收后现在使用了256K,堆整体大小39936K,耗时0.0000996。linux系统时间(这里因为取了小数点不精确了),用户态操作0S,内核态操作0S,总共0秒。
后面还有一段,是详细信息。
堆:
Eden区:空间大小 8192K,使用了94%【同年轻代分析】
s0区:同Eden区
s1区:同Eden区
老年代:同年轻代
老年代使用空间:里面使用有4个值,只知道开头和结尾是年轻代那样,中间两个代表啥不太清除。(?)
元空间信息(方法区):已经使用了2531K,总共4486K,虚拟内存占用4864K,虚拟内存保留105678K
类使用空间:同元空间信息
OK,今天就分享到这了,下次开始分享监控JVM的工具,喜欢的给个关注吧~
项目源码:
https://github.com/pengwenqq/studyDemo
以上是关于JVM调优的主要内容,如果未能解决你的问题,请参考以下文章