美文网首页
java gc 内存回收机制

java gc 内存回收机制

作者: felix_feng | 来源:发表于2016-11-03 10:12 被阅读255次

    参考网址:http://www.blogjava.net/ldwblog/archive/2013/07/24/401919.html

                      http://blog.csdn.net/a1134075691/article/details/51706275

                      http://blog.csdn.net/a1134075691/article/details/51706275

    GC的优化配置

    配置描述

    -Xms初始化堆内存大小

    -Xmx堆内存最大值

    -Xmn新生代大小

    -XX:PermSize初始化永久代大小

    -XX:MaxPermSize永久代最大容量

    JVM的堆是Java对象的活动空间,程序中的类的对象从中分配空间,其存储着正在运行着的应用程序用到的所有对象。这些对象的建立方式就是那些new一类的操作,当对象无用后,是GC来负责这个无用的对象(地球人都知道)。

    被回收的目标是找到所有的GC的根结点(GC Root), 将他们放到队列里,然后依次递归地遍历所有的根结点以及引用的所有子节点和子子节点,将所有被遍历到的结点标记成live。弱引用不会被考虑在内。

    1、虚拟机(JVM)栈中的引用的对象,引用是在栈帧中的本地变量表中的,真正的对象在堆中

    2、方法区中的类静态属性引用的对象

    3、方法区中的常量引用的对象(主要指声明为final的常量值)

    4、本地方法栈中JNI的引用的对象

    JVM堆

    (1) 新域:存储所有新成生的对象

    (2) 旧域:新域中的对象,经过了一定次数的GC循环后,被移入旧域

    (3)永久域:存储类和方法对象,从配置的角度看,这个域是独立的,不包括在JVM堆内。默认为4M。

    新域会被分为3个部分:

    1.第一个部分叫Eden。

    2.另两个部分称为辅助生存空间,我这里一个称为From 幸存区(survivor)”和”To 幸存区(survivor)”。

    按照规定,新对象会首先分配在 Eden 中(如果新对象过大,会直接分配在老年代中)。在GC中,Eden 中的对象会被移动到survivor中,直至对象满足一定的年纪(定义为熬过GC的次数),会被移动到老年代。

    虚拟机给每个对象定义了一个对象年龄(Age)计数器。如果对象在 Eden 出生并经过第一次 Minor GC 后仍然存活,并且能被 Survivor 容纳的话,将被移动到 Survivor 空间中,并将对象年龄设为 1。对象在 Survivor 区中每熬过一次 Minor GC,年龄就增加 1 岁,当它的年龄增加到一定程度(默认为 15 岁)时,就会被晋升到老年代中。对象晋升老年代的年龄阈值,可以通过参数 -XX:MaxTenuringThreshold 来设置。

    小于时被HandlePromotionFailure参数强制full gc;gc与非gc时间耗时超过了GCTimeRatio的限制引发OOM,调优诸如通过NewRatio控制新生代老年代比例,通过MaxTenuringThreshold控制进入老年前生存次数等……

    新生代通常存活时间较短,因此基于Copying算法来进行回收,所谓Copying算法就是扫描出存活的对象,并复制到一块新的完全未使用的空间中,对应于新生代,就是在Eden和FromSpace或ToSpace之间copy。新生代采用空闲指针的方式来控制GC触发,指针保持最后一个分配的对象在新生代区间的位置,当有新的对象要分配内存时,用于检查空间是否足够,不够就触发GC。当连续分配对象时,对象会逐渐从eden到survivor,最后到旧生代,

    旧生代与新生代不同,对象存活的时间比较长,比较稳定,因此采用标记(Mark)算法来进行回收,所谓标记就是扫描出存活的对象,然后再进行回收未被标记的对象,回收后对用空出的空间要么进行合并,要么标记出来便于下次进行分配,总之就是要减少内存碎片带来的效率损耗。在执行机制上JVM提供了串行GC(SerialMSC)、并行GC(parallelMSC)和并发GC(CMS),具体算法细节还有待进一步深入研究。

    HandlePromotionFailure  关闭新生代收集担保。

    什么是新生代收集担保?

    在一次理想化的minor gc中,Eden和First Survivor中的活跃对象会被复制到Second Survivor。

    然而,Second Survivor不一定能容纳下所有从E和F区copy过来的活跃对象。

    为了确保minor gc能够顺利完成,GC需要在年老代中额外保留一块足以容纳所有活跃对象的内存空间。

    这个预留操作,就被称之为新生代收集担保(New Generation Guarantee)。如果预留操作无法完成时,仍会触发major gc(full gc)。

    为什么要关闭新生代收集担保?

    因为在年老代中预留的空间大小,是无法精确计算的。

    为了确保极端情况的发生,GC参考了最坏情况下的新生代内存占用,即Eden+First Survivor。

    这种策略无疑是在浪费年老代内存,从时序角度看,还会提前触发Full GC。

    为了避免如上情况的发生,JVM允许开发者手动关闭新生代收集担保。

    在开启本选项后,minor gc将不再提供新生代收集担保,而是在出现survior或年老代不够用时,抛出promotion failed异常。 

    垃圾收集器的种类有:Serial收集器,ParNew收集器,Parallel Scavenge收集器,Serial Old收集器,Parallel Old收集器,CMS收集器,G1收集器

    1)Seria串行l收集器 ---client级别默认

    单线程,工作时需要暂停用户所有线程,可以和CMS配合工作

    2)ParNew并发收集器

    多线程,工作时需要用户暂停所有线程,可以和CMS配合工作

    3)Parallel Scavenge并行回收收集器 ---server级别默认采用

    Parallel Scavenge收集器的主要目标是达到可控制吞吐量。吞吐量就是CPU运行用户代码的时间占CPU消耗时间的比值,即吞吐量=运行用户代码的时间/(运行用户代码的时间+垃圾收集的时间)。

    -XX:MaxGCPauseMillis=M设置垃圾收集时间尽量在M毫秒完成,减低M值不一定可以提高吞吐量

    -XX:GCTimeRatio=N用于设置允许最大的垃圾收集时间占总时间的1/(1+N),默认值为99

    -XX:+UseParallelGC来强制指定,用-XX:ParallelGCThreads=4来指定线程数

    4)Serial Old收集器

    Serial Old是Seria老年代版本,可以与Parallel Scavenge收集器配合使用,或者作为CMS的后备预案

    5)Parallel Old收集器

    Parallel Old收集器是Parallel Scavenge收集器的老年代版本,Parallel Scavenge收集器不可以与CMS收集器配合使用,如果使用Serial Old收集器,性能会被拖累,所以可以选择Parallel Old收集器,ParallelOld 垃圾收集只对整个堆执行压缩。

    6)Concurrent Mark & Sweep   CMS并发收集器

    通过JVM参数 -XX:+UseConcMarkSweepGC 使用

    -XX:ParallelCMSThreads= 并发标记扫描垃圾回收器 =为使用的线程数量

    第一步初始化标记(initial mark) 比较简单。这一步骤只是查找那些距离类加载器最近的幸存对象。因此,停顿的时间非常短暂。在之后的并行标记(concurrent mark)步骤,所有被幸存对象引用的对象会被确认是否已经被追踪和校验。这一步的不同之处在于,在标记的过程中,其他的线程依然在执行。在重新标记(remark)步骤,会再次检查那些在并行标记步骤中增加或者删除的与幸存对象引用的对象。最后,在并行交换(concurrent sweep)步骤,转交垃圾回收过程处理。垃圾回收工作会在其他线程的执行过程中展开。一旦采取了这种GC类型,由GC导致的暂停时间会极其短暂。CMS GC也被称为低延迟GC。它经常被用在那些对于响应时间要求十分苛刻的应用之上。

    CMS并发收集器的优点:并发收集,低停顿

    CMS收集器的缺点:CMS对CPU比较敏感,默认线程数为(CPU数量+3)/4,无法处理浮动垃圾(垃圾回收过程中用户线程没有停止导致),标记清楚算法会产生空间碎片,它会比其他GC类型占用更多的内存和CPU.

    在使用这个GC类型之前你需要慎重考虑。如果因为内存碎片过多而导致压缩任务不得不执行,那么stop-the-world的时间要比其他任何GC类型都长,你需要考虑压缩任务的发生频率以及执行时间。 

    7)G1收集器

    JDK7时使用。G1收集器采用标记整理算法,没有碎片;可以精确控制停顿,指定M毫秒的时间片段内,消耗在垃圾搜集上的时间不能超过N毫秒;可以在不牺牲吞吐量的情况下完成低停顿的垃圾回收,原理:把堆分成多个大小固定的独立区域,并跟踪垃圾的堆积程度,在后台维护一个优先级列表。 

    通过JVM参数–XX:+UseG1GC使用G1垃圾回收器

    在使用G1垃圾回收器的时候,通过 JVM参数-XX:+UseStringDeduplication。 我们可以通过删除重复的字符串,只保留一个char[]来优化堆内存。这个选择在Java 8 u 20被引入。

    阶段说明

    (1) 初始标记 (Initial Mark)(Stop the World Event,所有应用线程暂停) 在老年代(old generation)中的对象, 如果从年轻代(young generation)中能访问到, 则被 “标记,marked” 为可达的(reachable).对象在旧一代“标志”可以包括这些对象可能可以从年轻一代。暂停时间一般持续时间较短,相对小的收集暂停时间.

    (2) 并发标记 (Concurrent Marking)在Java应用程序线程运行的同时遍历老年代(tenured generation)的可达对象图。扫描从被标记的对象开始,直到遍历完从root可达的所有对象. 调整器(mutators)在并发阶段的2、3、5阶段执行,在这些阶段中新分配的所有对象(包括被提升的对象)都立刻标记为存活状态.

    (3) 再次标记(Remark)(Stop the World Event, 所有应用线程暂停) 查找在并发标记阶段漏过的对象,这些对象是在并发收集器完成对象跟踪之后由应用线程更新的.

    (4) 并发清理(Concurrent Sweep)回收在标记阶段(marking phases)确定为不可及的对象. 死对象的回收将此对象占用的空间增加到一个空闲列表(free list),供以后的分配使用。死对象的合并可能在此时发生. 请注意,存活的对象并没有被移动.

    (5) 重置(Resetting)清理数据结构,为下一个并发收集做准备.

    垃圾收集器采用的算法:

    1)标记清除算法

    原理:对于“活”的对象,一定可以追溯到其存活在堆栈、静态存储区之中的引用。这个引用链条可能会穿过数个对象层次,算法基于有向图,采用深度优先搜索。

    第一阶段:从GC roots开始遍历所有的引用,对有活的对象进行标记。

    第二阶段:对堆进行遍历,把未标记的对象进行清除。

    优点:解决了循环引用的问题。

    缺点:

    (1)暂停整个应用;

    (2)会产生内存碎片。

    (3)不管你这个对象是不是可达的,即是不是垃圾,都要在清楚阶段被检查一遍,非常耗时.

    2)复制算法

    原理:把内存空间划分为2个相等的区域,每次只使用一个区域。垃圾回收时,遍历当前使用区域,把正在使用的对象复制到另外一个区域。

    优点:不会出现碎片问题。

    缺点:

    (1)暂停整个应用。

    (2)需要2倍的内存空间。

    3)标记整理算法

    4)分代搜集

    把堆分成新生代以及老年代,新生代使用复制算法,老年代使用标记整理算法

    java虚拟机没有使用引用计数算法,而是使用根搜索算法来查找不可用对象。

    引用计数算法:当一个对象被引用,引用计数器加1,当引用失效的时候,引用计数器减1,当引用计数器为0的时候,被认为是可收回对象。

    根搜索算法:通过一系列的”GC ROOTS“对象作为起始点,从这些点向下搜索,不能喝GC ROOTS联通的对象便是可回收对象

    java中可以作为“GC ROOTS”的对象包括:虚拟机栈(栈帧中的本地变量表)中的引用对象,方法区中的类静态属性引用的对象,方法区中的常量引用对象,本地方法栈中JNI(Native方法)的引用对象。

    引用的类型:

    强引用:只要强引用还存在,GC永远不会回收内存

    软引用:对于软引用关联着的对象,当要发生内存溢出异常之前,把这些对象列入回收范围并进行第二次回收

    弱引用:对于弱引用关联着的对象,能下一次GC工作的时候,不管内存是否足够,都会被回收

    虚引用:虚引用不会影响关联着的对象的生存时间,也无法通过虚引用来获取对象的实例,虚引用的唯一目的就是这个对象被GC回收时收到一个系统通知

    垃圾收集器的执行逻辑:

    1)用根搜索算法进行第一次标记

    2)对标记的对象进行一次筛选,如果对象覆盖了finalize方法,并且没有执行过,那么有可能在这个finalize方法中再次使用这个对象,这样的话就把该对象从待删除的对象队列中删除。finalize方法只能执行一次,保证对象循环引用的时候不可能永远存在。

    3)java虚拟机创建一个低优先级的Finalize线程去执行对象覆盖的finalize方法,垃圾收集器对待删除队列再次进行标记

    4)垃圾收集器把两次标记的对象回收了

    ===========================================================================

    并行和并发的区别

    这两个名词都是并发编程中的概念,在谈论垃圾收集器的上下文语境中,可以这么理解这两个名词:

    1、并行Parallel

    多条垃圾收集线程并行工作,但此时用户线程仍然处于等待状态

    2、并发Concurrent

    指用户线程与垃圾收集线程同时执行(但并不一定是并行的,可能会交替执行),用户程序在继续运行,而垃圾收集程序运行于另一个CPU上

    Minor GC和Full GC的区别

    1、新生代GC(Minor GC)

    指发生在新生代的垃圾收集动作,因为大多数Java对象存活率都不高,所以Minor GC非常频繁,一般回收速度也比较快

    2、老年代GC(Major GC/Full GC)

    指发生在老年代的垃圾收集动作,出现了Major GC,经常会伴随至少一次的Minor GC(但并不是绝对的)。Major GC的速度一般要比Minor GC慢上10倍以上

    相关文章

      网友评论

          本文标题:java gc 内存回收机制

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