zoukankan      html  css  js  c++  java
  • GC算法基础

    寻找垃圾对象的算法:1. 引用计数(无法处理循环引用) 2. 根寻法(被广泛引用在gc算法中)

    清理垃圾的算法: 1. 标记复制  2. 标记清理  3. 标记整理

    分代算法的好处:

       1. 分代处理,可以减少一次处理的内存大小,减少停顿时间。  

       2. 不同的代有不同的特点,再加上有针对性的gc算法和代码优化,整体的性能更好:

            年轻代特点是分配快,对象存活时间短,所以采用标记复制时间消耗不会太高,效率高。优化手段就是减少分配需求的速度,减少对象的存活时间,这样使得youngGc频率和停顿都降低,也减少了老年代的压力,是很有效的优化手段。

            老年代的特点是内存较大,对象存活时间久,所以相对来说比较年轻代要复杂。所以有各种各样不同的gc算法来对老年代进行处理,考量点主要是吞吐量和停顿时间。

           老年代担任的一个很重要的角色是给youngGC做担保,保证对象的晋升可以成功。所以这里的一个重要的指标是,FullGC发生的频率。FullGC的发生,一般是由于老年代的内存清理速度更不上晋升的速度,或者是老年代的内存碎片,所以保证对象的存活时间尽可能的短是有很多好处的。

    GC算法: 

    1. 串行GC

    2. ThroughPutGC(并行GC)—目的:提高吞吐量

    3. ConcurrentGC— 目的:降低停顿

    垃圾收集器:

    Serial收集器:

       年轻代的收集器+串行GC+标记复制

    ParNew收集器:

       年轻代的收集器+并行GC+标记复制

    Parallel Scavenge收集器:

       年轻代的收集器+并行GC+标记复制。 和ParNew的区别可能是引进了用户设定目标,gc自己调整大小的机制

    Serial Old收集器:

       老年代的收集器+串行GC+标记整理

    Parallel Old收集器:

       老年代的收集器+并行GC+标记整理

    CMS收集器:

       老年代的收集器+并发GC+标记清理

    G1收集器:

       年轻代+老年代的收集器

       年轻代=并行GC+标记复制

       老年代=并发GC+标记整理

    GC算法选择:

      Gc算法的选择考虑的方向主要是停顿时间和吞吐量俩个方面。需要根据程序的特征,CPU资源来进行权衡。

      1. 先从吞吐量来说,其实考虑的主要是可用的cpu资源有多少(即有多少空闲的cpu资源)。如果空闲的cpu资源较少,使用throughput类的gc会更好,因为它本身的收集效率就比并发的gc好。而并发的gc算法会和应用程序抢夺cpu资源。就会造成吞吐量下降。而如果空闲的cpu资源很多,那么并发的gc可以利用这些空闲的cpu来并发的完成gc,而且对应用程序的影响是很小的。反正那些cpu空着也是空着。如果这时使用throughput收集器,这些空闲的cpu会被白白浪费,而且还会stw来独占cpu进行gc,如果gc本身无法完全利用这些cpu资源,而且还会影响应用程序,就得不偿失了。

      2. 在衡量停顿时间上,一般来说,throughput的收集器的90%的响应时间都要比并发gc的快,因为90%的时间是没有gc的,只有在发生full-gc时才会导致响应时间过长。 但是,当full-gc发生的更频繁的时候,这个百分比也就会下降。当然,当发生这种情况是,并发gc也许也会难以避免full-gc。

    CMS和G1的选择:

       一般认为,当堆比较大时(大于4G),使用G1比较好,否则CMS的性能会更好一点。原因在于,gc消耗的时间和堆的大小是有很大的关系的,比如标记需要扫描整个堆。但是G1对堆进行了进一步的划分,在清理周期中,可以只清理垃圾最多的一部分分区,所以在大堆的情况下,性能是比CMS要好一些。另外,由于G1是标记复制的算法,所以不容易造成内存碎片。 

       反过来,由于G1的算法和数据结构要更加复杂,所以它的内存和cpu的消耗也就更大。如果堆本身就比较小的话,使用G1会造成应用程序可以使用的堆大小会更小。

        

    GC调优基础:

    1. 调整堆的大小:

         A.堆过小,导致gc频率变高。 堆太大,导致单次gc的时间变长。所以,要通过调整对的大小来找到一个我们认为相对合适的目标。目标主要从停顿时间和吞吐量俩个方面来考虑。

         B.堆的大小,不要超过机器的物理内存,因为jvm对底层的swap并无感知,swap带来的gc停顿变长,可能会导致并发失效,从而导致full-gc

         C.-Xms4096m -Xmx4096m 这种最大和初始一样时,堆的大小就不会因为自适应调整而改变。当我们知道最合适的堆的大小时,这种设置是有好处的,因为jvm不需要再进行堆的大小计算和调整。但是当我们不知道合适的堆的大小应该是多少时,我觉得还是不要这么搞吧。

    2. 调整代的大小:

        新生代太小,会导致youngGc频率高,对象晋升快,对老年代的压力增大。 新生代太大,新生代的gc时间可能会变长,对象晋升变慢,老年代的大小受限,可能会导致full-gc。

        老年代太小,可能会导致full-gc变多。老年代太大,可能会导致老年代的gc太慢。

        -XX:NewRatio=

        -XX:NewSize=

         -XmnN
        -XX:MaxNewSize=N

    3. 调整永久代和元空间的大小:

         永久代和元空间也会触发fullgc,所以也需要关注一下大小是否合适。

    4. 控制并发:

        gc的线程任务是计算密集型

        -XX:ParallelGCThreads=N 来控制并并GC的线程数,比如:

        a.使用-XX:+UseParallelGC收集新生代空间
        b.使用-XX:+UseParallelOldGC收集老年代空间

        c. CMS的“STW”阶段(不包括Full-GC)

        d. G1的“STW”阶段(不包括Full-GC)

        e.使用-XX:+UseParNewGC收集的新生代空间  

        f.使用-XX:+UseG1GC收集的新生代空间

    5. 自适应调整:

        用户设定目标:响应时间和gc消耗时间占比 -XX:MaxGCPauseMillis=N 和 -XX:GCTimeRatio=N ,这俩个配置会同时影响年轻代和老年代

        自适应调整会调整堆的大小,新生代和老年代的大小来尽量适用目标值。如果堆的初始值和最大值一样,则自适应调整不会对堆的整体大小进行调整。如果堆,年轻代的初始值和最大值一样,则自适应就完全失效了,不过survivor还是会进行自适应调整。

       -XX:-UseAdaptiveSizePolicy  关闭自适应(默认是开)。 -XX:+PrintAdaptiveSizePolicy 在gc时会打印调整的信息。

       对精细化计算过的jvm内存比例,可以把各个区域的大小进行限定,这样可以减少堆自适应的消耗。  

    Throughput收集器:

       Gc分为youngGc和fullGC。正常的FullGc不会对永久区进行回收,当回收区满时,会触发fullGc,这时会对永久区进行回收。

       调优:

         1.自适应调整:首先会调整堆的大小来达到目标停顿时间,然后慢慢增大堆的大小来尽可能达到目标吞吐量。然后又开始降低堆的大小来减少jvm的内存占用。目标值的设定一定要合理,太极端会导致极端的问题。

         2.调整并行线程数,因为是stw,而且gc的任务是计算密集型的,所以线程数根据cpu和计算密集型的公式就好。

    CMS:

        CMS有三个gc动作:1. yooung区的gc(STW)  2.老年代的gc(并发gc+标记清除)  3.full-gc(串行full-gc)

        老年代的GC过程:  

      1. 初始标记(STW): 标记那些直接被Root对象引用的对象

                     2.并发标记: 并发的进行Root Tracing的过程,时间较长。无法处理浮动垃圾。

                     3.重新标记(STW): 主要是对步骤2中可能出现的遗漏进行补充,时间比1长,但是远比2短

                     4.并发清理:并发的进行垃圾的清理,不会进行内存压缩,所以可能会造成内存碎片

        一 YGC   89.853: [GC 89.853: [ParNew: 629120K->69888K(629120K), 0.1218970 secs]

    •                             1303940K->772142K(2027264K), 0.1220090 secs]
    •                         [Times: user=0.42 sys=0.02, real=0.12 secs]
    •                       user的时间表示cpu时间,real表示时钟的时间。对于并发gc,real时间一般比user时间少。
    •   二初始标记  89.976: [GC [1 CMS-initial-mark: 702254K(1398144K)]
    •                             772530K(2027264K), 0.0830120 secs]
    •                             [Times: user=0.08 sys=0.00, real=0.08 secs]

       三并发标记   90.059: [CMS-concurrent-mark-start]

                                90.887: [CMS-concurrent-mark: 0.823/0.828 secs]

                                 [Times: user=1.11 sys=0.00, real=0.83 secs]

       四预清理       90.887: [CMS-concurrent-preclean-start]

                                 90.892: [CMS-concurrent-preclean: 0.005/0.005 secs]

                                 [Times: user=0.01 sys=0.00, real=0.01 secs]

       五重新标记    90.892: [CMS-concurrent-abortable-preclean-start]

                                 92.392: [GC 92.393: [ParNew: 629120K->69888K(629120K), 0.1289040 secs]

                                              1331374K->803967K(2027264K), 0.1290200 secs]

                                              [Times: user=0.44 sys=0.01, real=0.12 secs]

                                 94.473: [CMS-concurrent-abortable-preclean: 3.451/3.581 secs]

                                               [Times: user=5.03 sys=0.03, real=3.58 secs]

                                94.474: [GC[YG occupancy: 466937 K (629120 K)]

                                              94.474: [Rescan (parallel) , 0.1850000 secs]

                                              94.659: [weak refs processing, 0.0000370 secs]

                                              94.659: [scrub string table, 0.0011530 secs]

                                                           [1 CMS-remark: 734079K(1398144K)]

                                                           1201017K(2027264K), 0.1863430 secs]

                                             [Times: user=0.60 sys=0.01, real=0.18 secs]

                               这个过程执行了多个步骤,首先是可中断的预清理。由于jvm为了避免俩次连续的停顿(刚发生一次YGC后,立马发生重新标记),所以在发生YGC后,jvm预测下一个YGC发生的时间周期,在这个周期中间停止了可停顿预清理,然后开始了重新标记。

                  这也意味了,老年代的并发周期和YGC是并发进行的。

         六并发清理阶段   94.661: [CMS-concurrent-sweep-start]

                                        95.223: [GC 95.223: [ParNew: 629120K->69888K(629120K), 0.1322530 secs]

                                                                           999428K->472094K(2027264K), 0.1323690 secs]

                                                                            [Times: user=0.43 sys=0.00, real=0.13 secs]

                                        95.474: [CMS-concurrent-sweep: 0.680/0.813 secs]

                                                      [Times: user=1.45 sys=0.00, real=0.82 secs]

                                  这里日志表示,在并发清理阶段,发送了YGC,也表明老年代的并发周期和YGC是并发进行的。而且在并发周期中至少会发生一次YGC,就是可中断预清理的过程中。

          七并发重置阶段   95.474: [CMS-concurrent-reset-start]

                                         95.479: [CMS-concurrent-reset: 0.005/0.005 secs]

                                                       [Times: user=0.00 sys=0.00, real=0.00 secs]

                                   这是并发周期的最后一个阶段,但是我们没法中日志中得知这个阶段回收了多少内存,只能从上下的GC日志中进行推测。

          八并发模式失败(concurrent mode failure)

                                         267.006: [GC 267.006: [ParNew: 629120K->629120K(629120K), 0.0000200 secs]

                                                        267.006: [CMS267.350: [CMS-concurrent-mark: 2.683/2.804 secs]

                                                        [Times: user=4.81 sys=0.02, real=2.80 secs]

                                                       (concurrent mode failure):

                                                      1378132K->1366755K(1398144K), 5.6213320 secs]

                                                        2007252K->1366755K(2027264K),

                                                     [CMS Perm : 57231K->57222K(95548K)], 5.6215150 secs]

                                                      [Times: user=5.63 sys=0.00, real=5.62 secs]

                                    这是在发生YGC时,老年代没有足够的空间来容纳晋升的对象,就退化成了FULL-GC。这个过程是串行的,所以很慢。

             九晋升失败       6043.903: [GC 6043.903:

                 [ParNew (promotion failed): 614254K->629120K(629120K), 0.1619839 secs]

                 6044.217: [CMS: 1342523K->1336533K(2027264K), 30.7884210 secs]

                 2004251K->1336533K(1398144K),

                 [CMS Perm : 57231K->57231K(95548K)], 28.1361340 secs]

                 [Times: user=28.13 sys=0.38, real=28.13 secs]

                                        这是在发生YGC后,而且JVM判断old区有足够的空间容纳晋升对象,但是在晋升的过程中,由于old区的内存碎片,导致的晋升失败,然后进行了FULL-GC. 由于这个过程是发送YGC失败导致的,所以耗时比并发模式失败还要多。

            十永久代或者元空间用尽导致的full-gc

                                                279.803: [Full GC 279.803:

                         [CMS: 88569K->68870K(1398144K), 0.6714090 secs]

                         558070K->68870K(2027264K),

                         [CMS Perm : 81919K->77654K(81920K)],

                         0.6716570 secs]

    G1:  G1对堆内存进全部行了分区(region),一个代的空间里的分区并不是连续的。有些分区属于老年代,有些分区属于年轻代。年轻代的gc和其他回收器是一样的,只所以也进行分区是因为可以更方便的进行分区的大小调整。老年代的gc是对垃圾最多的分区进行清理,这样可以使用更少的时间来达到最好的清理效果。所以对于比较大的堆,G1的停顿时间是要比CMS好的。 G1的老年代清理是把一个region

          G1的4个操作:

          1.新生代垃圾的收集 :并行收集

          2.后台收集,并发周期:对垃圾最多的分区进行标记,期间至少会发生一次YGC

          3.混合式垃圾收集

          4.必要的full-gc 

          并发周期:

                a. 50.541: [GC pause (young) (initial-mark), 0.27767100 secs]

                 [Eden: 1220M(1220M)->0B(1220M)

                 Survivors: 144M->144M Heap: 3242M(4096M)->2093M(4096M)]

             [Times: user=1.02 sys=0.04, real=0.28 secs]

            并发周期的开始—初始化标记。这个动作是STW的,这里使用了YGC来进行STW,

        b. 50.819: [GC concurrent-root-region-scan-start] 

                         51.408: [GC concurrent-root-region-scan-end, 0.5890230]

    扫描根分区,这个过程是并发进行的。但是,这个阶段中,不能进行YGC,如果这个时候触发了YGC,YGC必须等待这个动作的结束,然后再进行。这导致了YGC时间变长,意味着需要进行调优了,所以就会有下面这种日志。

        350.994: [GC pause (young)

                  351.093: [GC concurrent-root-region-scan-end, 0.6100090]

                  351.093: [GC concurrent-mark-start],

                  0.37559600 secs]

        c.  350.994: [GC pause (young)

                  351.093: [GC concurrent-root-region-scan-end, 0.6100090]

                  351.093: [GC concurrent-mark-start],

                  0.37559600 secs]

    并发标记,这个过程是可以中断的,中间是可以有YGC并发进行的。

        d.   120.910: [GC remark 120.959:

                  [GC ref-PRC, 0.0000890 secs], 0.0718990 secs]

                  [Times: user=0.23 sys=0.01, real=0.08 secs]

          120.985: [GC cleanup 3510M->3434M(4096M), 0.0111040 secs]

                  [Times: user=0.04 sys=0.00, real=0.01 secs]

    重新标记和清理阶段。这俩个阶段是STW的,而且这个清理阶段只会清理很少的垃圾,主要还是对垃圾的标记。

      e.   120.996: [GC concurrent-cleanup-start]

                   120.996: [GC concurrent-cleanup-end, 0.0004520]

    一个并发清理,同样回收的垃圾很少。

           

            混合式垃圾收集周期:

               这个周期会进行YGC和老年代的并发清理。这是stw的

               a.  79.826: [GC pause (mixed), 0.26161600 secs]

         ....

            [Eden: 1222M(1222M)->0B(1220M)

               Survivors: 142M->144M Heap: 3200M(4096M)->1964M(4096M)]

            [Times: user=1.01 sys=0.00, real=0.26 secs]

              混合式垃圾回收周期会进行多轮,直到满足我们设定的指标。然后整个老年代的回收周期就结束了。

            FULL-GC的发生:

    1. 并发模式失败:老年代开始了标记周期,但是在标记周期完成之前就被填满了。这种情况下,G1日志里会有放弃标记周期的日志:

                      51.408: [GC concurrent-mark-start]

             65.473: [Full GC 4095M->1395M(4096M), 6.1963770 secs]

              [Times: user=7.87 sys=0.00, real=6.20 secs]

             71.669: [GC concurrent-mark-abort]

    2. 晋升失败:老年代已经开始了mix-gc周期,但是老年代在垃圾回收释放出足够的内存之前就被耗尽了。这种情况是mix-gc之后马上就有一次Full-Gc

          2226.224: [GC pause (mixed)

                   2226.440: [SoftReference, 0 refs, 0.0000060 secs]

                   2226.441: [WeakReference, 0 refs, 0.0000020 secs]

                   2226.441: [FinalReference, 0 refs, 0.0000010 secs]

                   2226.441: [PhantomReference, 0 refs, 0.0000010 secs]

                   2226.441: [JNI Weak Reference, 0.0000030 secs]

                           (to-space exhausted), 0.2390040 secs]

           ....

               [Eden: 0.0B(400.0M)->0.0B(400.0M)

                   Survivors: 0.0B->0.0B Heap: 2006.4M(2048.0M)->2006.4M(2048.0M)]

               [Times: user=1.70 sys=0.04, real=0.26 secs]

           2226.510: [Full GC (Allocation Failure)

                   2227.519: [SoftReference, 4329 refs, 0.0005520 secs]

                   2227.520: [WeakReference, 12646 refs, 0.0010510 secs]

                   2227.521: [FinalReference, 7538 refs, 0.0005660 secs]

                   2227.521: [PhantomReference, 168 refs, 0.0000120 secs]

                   2227.521: [JNI Weak Reference, 0.0000020 secs]

                           2006M->907M(2048M), 4.1615450 secs]

               [Times: user=6.76 sys=0.01, real=4.16 secs]

    3.疏散失败:在新生代的回收中,survivor区和老年代没有足够的内存来容纳晋升和存活的对象,这时会有一个比较特别的日志:

    60.238: [GC pause (young) (to-space overflow), 0.41546900 secs]

    4.巨型对象分配失败:没有什么好的办法来排查这种问题,如果出现了比较奇怪的full-gc,可以考虑这种情况。

  • 相关阅读:
    LightOJ 1313
    LightOJ 1285
    [坑] treap
    HDU 4511 小明系列故事——女友的考验 (AC自动机+DP)
    ZOJ 3765 Lights (伸展树splay)
    HDU 4818 RP problem (高斯消元, 2013年长春区域赛F题)
    SGU 104. Little shop of flowers (DP)
    HDU 3472 HS BDC (混合图的欧拉路径判断)
    POJ 1386 Play on Words (有向图欧拉路径判定)
    微信小程序上架需要增值电信业务经营许可证ICP?
  • 原文地址:https://www.cnblogs.com/zhaoxinshanwei/p/10342051.html
Copyright © 2011-2022 走看看