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的非标准参数。

JVM调优(一)

     -XX,没办法直接看,需要查看所有参数然后从参数里筛选,看下面的例子。

    看JVM所有的参数,我是JDK1.8的hotSpot总共有736个参数。

JVM调优(一)

    这么多一个个找,看默认值是很费劲的,这里推荐一个命令搜索。比如我们要找新生代和老年代的大小比,参数名是NewRatio。我们可以使用以下搜索方式。
     

JVM调优(一)

    grep后面支持模糊搜索,搜索结果是设定的值。
接着我们用一段程序了解一些基本的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,结果如下

JVM调优(一)

 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信息太多我们单独拿一条看):

JVM调优(一)

        -Xmn10M 表示设置最小新生代的大小,对应结果里的-XX:NewSize=10485760,注意这个时候还打印出来了最大新生代的大小。

        -Xms40M 表示设置最小堆的大小,对应结果里的-XX:InitialHeapSize=41943040

        -Xmx60M 表示设置最大堆的大小,对应结果里的

-XX:MaxHeapSize=62914560

        -XX:+PrintGC 打印GC回收的信息

JVM调优(一)

        -XX:+PrintGCDetails 打印GC详细信息(这个后面说)


JVM调优(一)

        -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

JVM调优(一)

可以看到

-XX:MaxTenuringThreshold=6 CMS默认回收年龄变成了6(原来是15)

-XX:OldPLABSize=16 老年代空间PLAB大小(年轻代晋升至老年代的缓冲区)

-XX:+UseParNewGC 我们使用了PN+CMS的垃圾回收组合

JVM调优(一)

这是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垃圾回收日志详解


JVM调优(一)

GC那里:GC代表YGC,FULL GC代表FULL GC,其余的看下面。

还是前面那个程序,我们运行命令

java -Xmn10M -Xms40M -Xmx60M -XX:+PrintCommandLineFlags -XX:+PrintGCDetails Hello_Old

JVM调优(一)

解释: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调优的主要内容,如果未能解决你的问题,请参考以下文章

JVM性能调优

JVM性能调优

JVM常用调优参数 ——JVM篇

JVM调优经验分享

Java开发经典实战!java代码编译过程

JVM调优经验分享