JVM调优

作者: Suny____ | 来源:发表于2020-02-14 22:30 被阅读0次
    1、GC优化

    内存被使用了之后,难免会有不够用或者达到设定值的时候,就需要对内存空间进行垃圾回收。

    • 垃圾收集发生的时机
      • 当Eden区或者S区不够用了
      • 老年代空间不够用了
      • 方法区空间不够用了
      • System.gc()
        • System.gc()只是通知要回收,什么时候回收由JVM决定。 但是不建议手动调用该方法,因为消耗的资源比较
          大。

    要想分析日志的信息,得先拿到GC日志文件才行,所以得先配置一下

    -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps - Xloggc:gc.log
    可以看到默认使用的是ParallelGC
    
    • Parallel GC日志【吞吐量优先】
    2019-06-10T23:21:53.305+0800: 1.303:
    [GC (Allocation Failure) [PSYoungGen: 65536K[Young区回收前]->10748K[Young区回收后]
    (76288K[Young区总大小])] 65536K[整个堆回收前]->15039K[整个堆回收后](251392K[整个堆总大小]),
    0.0113277 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
    注意如果回收的差值中间有出入,说明这部分空间是Old区释放出来的
    
    • CMS日志【停顿时间优先】
    参数设置:-XX:+UseConcMarkSweepGC -Xloggc:cms-gc.log
    
    CommandLine flags: -XX:-BytecodeVerificationLocal -XX:-BytecodeVerificationRemote -XX:InitialHeapSize=52428800 -XX:+ManagementServer -XX:MaxHeapSize=52428800 -XX:MaxNewSize=17477632 -XX:MaxTenuringThreshold=6 -XX:NewSize=17477632 -XX:OldPLABSize=16 -XX:OldSize=34951168 -XX:+PrintGC -XX:+PrintGCDateStamps -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:TieredStopAtLevel=1 -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseConcMarkSweepGC -XX:-UseLargePagesIndividualAllocation -XX:+UseParNewGC 
    # 由于CMS垃圾回收器是针对老年代的, 所以JVM又自动帮我们加上了新生代的收集器UseParNewGC
    2020-02-14T19:29:21.502+0800: 1.592: [GC (Allocation Failure) 2020-02-14T19:29:21.502+0800: 1.592: [ParNew: 15184K->1630K(15360K), 0.0025403 secs] 25518K->12754K(49536K), 0.0025893 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
    2020-02-14T19:29:21.504+0800: 1.595: [GC (CMS Initial Mark 初始标记) [1 CMS-initial-mark: 11124K(34176K)] 13010K(49536K), 0.0003460 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
    2020-02-14T19:29:21.505+0800: 1.595: [CMS-concurrent-mark-start 并发标记]
    2020-02-14T19:29:21.513+0800: 1.603: [CMS-concurrent-mark: 0.008/0.008 secs] [Times: user=0.05 sys=0.00, real=0.01 secs] 
    2020-02-14T19:29:21.513+0800: 1.603: [CMS-concurrent-preclean-start]
    2020-02-14T19:29:21.513+0800: 1.604: [CMS-concurrent-preclean: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
    2020-02-14T19:29:21.513+0800: 1.604: [GC (CMS Final Remark 最终标记) [YG occupancy: 3166 K (15360 K)]2020-02-14T19:29:21.513+0800: 1.604: [Rescan (parallel) , 0.0005004 secs]2020-02-14T19:29:21.514+0800: 1.604: [weak refs processing, 0.0000946 secs]2020-02-14T19:29:21.514+0800: 1.604: [class unloading, 0.0022654 secs]2020-02-14T19:29:21.516+0800: 1.607: [scrub symbol table, 0.0037533 secs]2020-02-14T19:29:21.520+0800: 1.610: [scrub string table, 0.0005109 secs][1 CMS-remark: 11124K(34176K)] 14291K(49536K), 0.0074995 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
    2020-02-14T19:29:21.521+0800: 1.611: [CMS-concurrent-sweep-start 并发清理]
    2020-02-14T19:29:21.524+0800: 1.614: [CMS-concurrent-sweep: 0.003/0.003 secs] [Times: user=0.05 sys=0.00, real=0.00 secs] 
    2020-02-14T19:29:21.524+0800: 1.614: [CMS-concurrent-reset-start 重新初始化CMS内部数据结构,以备下一轮 GC 使用]
    2020-02-14T19:29:21.524+0800: 1.614: [CMS-concurrent-reset: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
    2020-02-14T19:29:21.607+0800: 1.698: [GC (Allocation Failure) 2020-02-14T19:29:21.608+0800: 1.698: [ParNew: 15326K->1664K(15360K), 0.0022847 secs] 22953K->12516K(49536K), 0.0023406 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
    
    • G1日志【停顿时间优先】
    参数设置:-XX:+UseG1GC -Xloggc:g1-gc.log
    
    # young gc
    #什么时候发生的GC,相对的时间刻,GC发生的区域young,总共花费的时间,0.0019663s,
    2020-02-14T19:35:42.242+0800: 0.680: [GC pause (G1 Evacuation Pause) (young), 0.0019663 secs]
        # 多少个垃圾回收线程,并行的时间
       [Parallel Time: 1.5 ms, GC Workers: 8]
       # GC线程开始相对于上面的0.680的时间刻
          [GC Worker Start (ms): Min: 680.1, Avg: 680.1, Max: 680.2, Diff: 0.1]
          [Ext Root Scanning (ms): Min: 0.0, Avg: 0.3, Max: 0.7, Diff: 0.7, Sum: 2.1]
          [Update RS (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
             [Processed Buffers: Min: 0, Avg: 0.0, Max: 0, Diff: 0, Sum: 0]
          [Scan RS (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
          [Code Root Scanning (ms): Min: 0.0, Avg: 0.0, Max: 0.1, Diff: 0.1, Sum: 0.2]
          [Object Copy (ms): Min: 0.6, Avg: 1.0, Max: 1.1, Diff: 0.5, Sum: 8.0]
          [Termination (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.3]
             [Termination Attempts: Min: 1, Avg: 17.0, Max: 31, Diff: 30, Sum: 136]
          [GC Worker Other (ms): Min: 0.0, Avg: 0.1, Max: 0.2, Diff: 0.2, Sum: 0.5]
          [GC Worker Total (ms): Min: 1.3, Avg: 1.4, Max: 1.4, Diff: 0.1, Sum: 11.1]
          [GC Worker End (ms): Min: 681.5, Avg: 681.5, Max: 681.5, Diff: 0.0]
       [Code Root Fixup: 0.0 ms]
       [Code Root Purge: 0.0 ms]
       [Clear CT: 0.1 ms]
       [Other: 0.3 ms]
          [Choose CSet: 0.0 ms]
          [Ref Proc: 0.2 ms]
          [Ref Enq: 0.0 ms]
          [Redirty Cards: 0.1 ms]
          [Humongous Register: 0.0 ms]
          [Humongous Reclaim: 0.0 ms]
          [Free CSet: 0.0 ms]
       [Eden: 9216.0K(9216.0K)->0.0B(20.0M) Survivors: 0.0B->2048.0K Heap: 9216.0K(50.0M)->1804.4K(50.0M)]
     [Times: user=0.00 sys=0.00, real=0.00 secs] 
    
     # mixed gc
     2020-02-14T19:35:43.648+0800: 2.086: [GC pause (G1 Evacuation Pause) (mixed), 0.0034092 secs]
       [Parallel Time: 2.8 ms, GC Workers: 8]
          [GC Worker Start (ms): Min: 2086.4, Avg: 2086.4, Max: 2086.5, Diff: 0.1]
          [Ext Root Scanning (ms): Min: 0.1, Avg: 0.2, Max: 0.7, Diff: 0.6, Sum: 1.8]
          [Update RS (ms): Min: 0.2, Avg: 0.6, Max: 0.8, Diff: 0.6, Sum: 4.6]
             [Processed Buffers: Min: 1, Avg: 2.5, Max: 4, Diff: 3, Sum: 20]
          [Scan RS (ms): Min: 0.0, Avg: 0.2, Max: 0.5, Diff: 0.5, Sum: 1.3]
          [Code Root Scanning (ms): Min: 0.0, Avg: 0.0, Max: 0.1, Diff: 0.1, Sum: 0.2]
          [Object Copy (ms): Min: 1.3, Avg: 1.6, Max: 1.7, Diff: 0.4, Sum: 12.5]
          [Termination (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
             [Termination Attempts: Min: 1, Avg: 1.3, Max: 2, Diff: 1, Sum: 10]
          [GC Worker Other (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.1]
          [GC Worker Total (ms): Min: 2.5, Avg: 2.6, Max: 2.6, Diff: 0.1, Sum: 20.5]
          [GC Worker End (ms): Min: 2089.0, Avg: 2089.0, Max: 2089.0, Diff: 0.0]
       [Code Root Fixup: 0.0 ms]
       [Code Root Purge: 0.0 ms]
       [Clear CT: 0.2 ms]
       [Other: 0.4 ms]
          [Choose CSet: 0.0 ms]
          [Ref Proc: 0.2 ms]
          [Ref Enq: 0.0 ms]
          [Redirty Cards: 0.1 ms]
          [Humongous Register: 0.0 ms]
          [Humongous Reclaim: 0.0 ms]
          [Free CSet: 0.0 ms]
       [Eden: 1024.0K(1024.0K)->0.0B(24.0M) Survivors: 4096.0K->1024.0K Heap: 19.9M(50.0M)->18.0M(50.0M)]
     [Times: user=0.00 sys=0.00, real=0.00 secs] 
    
    • GC日志文件分析工具
      • gcView 利用它来比较不同的垃圾收集器的吞吐量和停顿时间
    # cms-log
    Throughput(吞吐量)     Mix Pause     Max Pause      Avg Pause      GC次数
    96.15%                0.00035s      0.0075s         0.00259s        36
    
    • 电脑配置

      • 系统: Windows 10
      • CPU:I7 4790K
      • 内存:16G
    • 以G1垃圾收集器为例调优

      • 是否选用G1垃圾收集器的判断依据
        • 50%以上的堆被存活对象占用
        • 对象分配和晋升的速度变化非常大
        • 垃圾回收时间比较长
      • 使用G1GC垃圾收集器: -XX:+UseG1GC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps
      # 修改配置参数,获取到gc日志,使用GCViewer分析吞吐量和响应时间 默认配置
      Throughput(吞吐量)     Mix Pause     Max Pause      Avg Pause        GC次数
      98.63%                  0.00052s      0.01052s        0.00505s        13
      
      • 调整内存大小再获取gc日志分析
        • -XX:MetaspaceSize=100M -Xms300M -Xmx300M
      # 设置堆内存的大小,获取到gc日志,使用GCViewer分析吞吐量和响应时间
      Throughput(吞吐量)     Mix Pause     Max Pause      Avg Pause        GC次数
      98.29%                  0.000391s     0.01504s        0.00793s        3
      
      • 调整最大GC停顿时间指标
        • -XX:MaxGCPauseMillis=100 我这台机器查看到的默认暂停时间是200ms
      # 调整最大停顿时间,获取到gc日志,使用GCViewer分析吞吐量和响应时间
      Throughput(吞吐量)     Mix Pause     Max Pause      Avg Pause        GC次数
      98.08%                  0.00294s      0.00971s        0.00553s        3
      
      • 启动并发GC时堆内存占用百分比
        • -XX:InitiatingHeapOccupancyPercent=55
      # G1用它来触发并发GC周期,基于整个堆的使用率,而不只是某一代内存的使用比例。值为 0 则表示“一直执行GC循环)'. 默认值为 45 (例如, 全部的 45% 或者使用了45%)
      Throughput(吞吐量)     Mix Pause     Max Pause      Avg Pause        GC次数
      98.48%                  0.00288s      0.00943s        0.00541s        3
      
      • 通过这样不断的尝试,找到适合当前机器配置的最佳参数配置
    • 最佳指南 【官方推荐】

      • 不要手动设置新生代和老年代的大小,只要设置整个堆的大小
      G1收集器在运行过程中,会自己调整新生代和老年代的大小其实是通过adapt(自适应)代的大小来调整对象晋升的速度和年龄,从而达到为收集器设置的暂停时间目标,如果手动设置了大小就意味着放弃了G1的自动调优
      
      • 不断调优暂停时间目标
      一般情况下这个值设置到100ms或者200ms都是可以的(不同情况下会不一样),但如果设置成50ms就不太合理。暂停时间设置的太短,就会导致出现G1跟不上垃圾产生的速度。最终退化成Full GC。所以对这个参数的调优是一个持续的过程,逐步调整到最佳状态。暂停时间只是一个目标,并不能总是得到满足。
      
      • 使用-XX:ConcGCThreads=n 来增加标记线程的数量
      IHOP(InitiatingHeapOccupancyPercent)如果阀值设置过高,可能会遇到转移失败的风险,比如对象进行转移时空间不足。如果阀值设置过低,就会使标记周期运行过于频繁,并且有可能混合收集期回收不到空间。IHOP值如果设置合理,但是在并发周期时间过长时,可以尝试增加并发线程数,调高ConcGCThreads
      
      • MixedGC调优
      -XX:InitiatingHeapOccupancyPercent
      -XX:G1MixedGCLiveThresholdPercent
      -XX:G1MixedGCCountTarger
      -XX:G1OldCSetRegionThresholdPercent
      
      • 适当增加堆内存大小
    2、高并发场景实战调优
    • 一个Java对象的大小计算: 64位系统

      • 对象头:8字节

      • ClassPointer:8字节

      • 数组长度:4字节(只有数组才会有)

      • 对象数据(自己计算每个字段所需的大小)

      • 对齐填充(为了保证对象的大小为8字节的整数倍)

    • 假设我们的Order对象一个为2KB

      • 那么每台服务器每秒产生的数据为:1000 * 2 = 2000KB

      • 在这期间还会有其他服务也会产生数据:2000KB * 20 = 40000KB ≈ 40M

    • 如果将堆内存设置为8G(8192M),按照新老年代1:4的比例,新生代的内存约为1638M,老年代的内存约为6553M

      • 新生代又划分为Eden、S0、S1,按照8:2的比例,

      • Eden区的内存约为1310M,S0和S1各163M

    • 每秒产生40M垃圾,也就是说32秒左右会将Eden区填满,下一秒产生对象存入Eden区会发现Eden区已经放不下了,此时就会触发Minor GC,会将大部分的垃圾回收掉,少部分对象进入幸存区。

    • 当对象的年龄达到16或者新生代放不下新的对象时会将对象移到老年代

    • 基于这点,我们就是要适当增加新生代的内存大小,能尽量多的存入对象,尽量少的让对象不要进入老年代,从而达到减少Full GC引起的STW

    高并发下堆内存设置.png
    3、性能优化指南
    性能优化.png
    4、JVM常见问题思考
    • 内存泄漏与内存溢出的区别
      • 内存泄漏:对象无法得到及时的回收,持续占用内存空间,从而造成内存空间的浪费。
      • 内存溢出:内存泄漏到一定的程度就会导致内存溢出,但是内存溢出也有可能是大对象导致的。
    • young gc会有stw吗?
      • 不管什么 GC,都会有 stop-the-world,只是发生时间的长短。
    • major gc 和 full gc 的区别
      • major gc 指的是老年代的gc,而Full gc 等于Young + old + MetaSpace 的 gc。
    • G1与CMS的区别是什么
      • CMS 用于老年代的回收,而 G1 用于新生代和老年代的回收。
      • G1 使用了 Region 方式对堆内存进行了划分,且基于标记整理算法实现,整体减少了垃圾碎片的产生。
    • 什么是直接内存
      • 直接内存是在java堆外的、直接向系统申请的内存空间。通常访问直接内存的速度会优于Java堆。因此出于性能的考虑,读写频繁的场合可能会考虑使用直接内存。
    • 不可达的对象一定要被回收吗?
      • 即使在可达性分析法中不可达的对象,也并非是“非死不可”的,这时候它们暂时处于“缓刑阶段”,要真正宣告一个对象死亡,至少要经历两次标记过程;
      • 可达性分析法中不可达的对象被第一次标记并且进行一次筛选,筛选的条件是此对象是否有必要执行 finalize 方法。当对象没有覆盖 finalize 方法,或 finalize 方法已经被虚拟机调用过时,虚拟机将这两种情况视为没有必要执行。被判定为需要执行的对象将会被放在一个队列中进行第二次标记,除非这个对象与引用链上的任何一个对象建立关联,否则就会被真的回收。
    • 方法区中的无用类回收
      • 方法区主要回收的是无用的类,那么如何判断一个类是无用的类的呢?
      • 判定一个常量是否是“废弃常量”比较简单,而要判定一个类是否是“无用的类”的条件则相对苛刻许多。类需要同时满足下面 3 个条件才能算是 “无用的类” :
        • 该类所有的实例都已经被回收,也就是 Java 堆中不存在该类的任何实例。
        • 加载该类的 ClassLoader 已经被回收。
        • 该类对应的 java.lang.Class 对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。
      • 虚拟机可以对满足上述 3 个条件的无用类进行回收,这里说的仅仅是“可以”,而并不是和对象一样不使用了就会必然被回收。

    相关文章

      网友评论

        本文标题:JVM调优

        本文链接:https://www.haomeiwen.com/subject/nkrsfhtx.html