Roots没有任何引用链相连时必威,当垃圾收集成为

垃圾收集器回收哪些虚拟机内存区域

java堆和方法区

3.1概述

主要问题:

  • 哪些内存需要回收?
  • 什么时候回收?
  • 如何回收?

其中程序计数器,虚拟机栈,本地方法栈随线程而生,随线程而灭,而堆内存和方法区是线程共享,需要动态回收内存

GC机制的基本算法是:分代收集。

1.概述
垃圾收集(Garbage Collection,GC).
当需要排查各种内存溢出、内存泄露问题时,当垃圾收集成为系统达到更高并发量的瓶颈时,我们就需要对这些“自动化”的技术实施必要的监控和调节。
Java内存运行时,程序计数器、虚拟机栈、本地方法栈三个区域随线程生,随线程灭;栈中的栈帧随方法的进入和退出,有条不紊的执行着出栈和入栈操作。每个栈帧分配的内存基本在类结构确定下来时就已知(尽管在运行期会由JIT编译器进行一些优化)。这几个区域不需过多考虑回收问题。
2.对象存活状态
堆中几乎存放着所有的对象实例,垃圾收集器在对堆进行回收前,首先要确定哪些对象还“存活”着,哪些对象已经“死去”(不可能再被任何途径使用)。
2.1引用计数算法
引用计数算法实现简单,判定效率高,如微软COM技术Python语言等都使用引用计数算法进行内存管理。
算法原理: 给对象添加一个引用计数器,每有一个地方引用他时,计数器值加1;当引用失效时,计数器值减1;任何时刻计数器值都为0的对象就是不可能再被使用的。
!!!!Java语言中没有选用引用计数算法来管理内存,主要原因是它很难解决对象之间的相互循环引用问题。!!!!
2.2根搜索算法
Java,C#及Lisp都是采用此算法判定对象是否存活。
基本思路: 通过一系列的名为”GC Roots“的对象作为起始点,从这些节点向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连(图论的话来说就是从GC Roots到这个对象不可达)时,则证明此对象是不可用的。
必威 1
Java中,可作为GC Roots的对象包括下面几种:
1)虚拟机栈(栈帧中的本地变量表)中的引用的对象;
2)方法区中的类静态属性引用的对象;
3)方法区中的常量引用的对象;
4)本地方法栈中JNI(Native方法)的引用的对象。
2.3再谈引用
Java有四种引用:
1)强引用: 在程序代码中普遍存在的,类似”Object obj = new Object( )”这类的引用。只要强引用还存在,则垃圾收集器永远不会回收掉被引用的对象。
2)软引用: 一些还有用,但并非必须的对象。对软引用关联着的对象,在系统将要发生内存溢出前,会把这些对象列进回收范围中并进行第二次回收。若这次回收还是没有足够的内存,才会抛出内存溢出异常。SoftReference类实现软引用。
3)弱引用: 非必须对象,强度比软引用更弱一些,被若引用关联的对象只能生存到下一次垃圾回收之前。WeakReference类实现弱引用。
4)虚引用: 最弱的一种引用关系。无法通过虚引用来取得一个对象实例。为对象设置虚引用的唯一目的是希望在对象被垃圾收集器回收时收到一个系统通知。
PhantomReference类实现虚引用。
2.4生还是死?
根搜索算法中不可达的对象,并非是“非死不可”的。
真正宣告一个对象死亡,至少要经历两次标记过程:对象在跟搜索后没有与GC Roots的引用链,将会被第一次标记并进行一次筛选,筛选条件是此对象是否有必要执行finalize( )方法。当对象没有覆盖finalize()方法,或finalize()方法已经被虚拟机调用过,虚拟机将此两种情况都视为“没有必要执行”。
若对象被判定为有必要执行finalize( )方法,那么对象将会被放置在一个名为F-Queue的队列中,并在稍后由一条由虚拟机自动建立的、低优先级的Finalizer线程去执行。(对象的finalize()被执行,但仍可能存活。只要重新与引用链上的任何一个对象建立关联即可,那么第二次标记时将被移除出“即将回收”的集合。!!不推荐使用
2.5回收方法区
方法区(HotSpot虚拟机中的永久代)的垃圾收集主要回收两部分内容:废弃常量无用的类。回收效率低!
判定常量是否是“废弃常量”比较简单,
判断一个类是否是“无用的类”需同时满足下面3个条件:
1)类所有的实例都已经被回收,也就是Java堆中不存在该类的任何实例;
2)加载该类的ClassLoader已经被回收;
3)该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。
满足以上3个条件的无用类“可以”(不是一定会)被回收,不是和对象一样,不使用就必然会回收。
是否对类进行回收,HotSpot虚拟机提供了 -Xnoclassgc参数进行控制,还可使用-verbose:class及-XX:+TraceClassLoading、-XX:+TraceClassUnLoading查看类的加载和卸载信息。
在大量使用反射、动态代理、CGLib等bytecode框架的场景,及动态生成JSP和OSGi这类频繁自定义ClassLoader的场景都需要虚拟机具备类卸载功能,保证永久代不会溢出。
3.垃圾收集算法
3.1标记 — 清除算法
首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象。
它是最基本的收集算法,其他算法都是基于此思路并对其缺点进行改进得到。
缺点:1)效率低;2)会产生大量不连续内存碎片。
必威 2
3.2复制算法
为了解决效率问题。
将内存按容量划分为大小相等的两块,每次只使用其中一块。当此块内存用完之后,将还存活着的对象复制到另一块,然后把已使用过的内存空间一次清理掉。使得 每次只对其中一块内存进行回收,内存分配时不用考虑内存碎片等情况,只要移动堆顶指针,按顺序分配内存即可,实现简单,运行高效。
代价:将内存缩小为原来的一半。
必威 3
现在商业虚拟机都采用这种算法回收新生代
HotSpot将内存分为一块较大的Eden空间两块较小的Survivor空间,每次使用Eden和其中一块Survivor。回收时,将Eden和Survivor中还存活的对象一次性拷贝到另外一块Survivor空间,最后清理掉Eden和Survivor中空间。
HotSpot虚拟机默认Eden和Survivor比例为8:1,只有10%空间被“浪费”。当Survivor空间不够用时,需依赖其他内存(此处指老年代)进行分配担保
复制算法在对象存活率较高时要执行较多的复制操作,效率会变低。
3.3标记 — 整理算法
标记出需要回收的对象之后,不是直接清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。
必威 4
3.4分代收集算法
当前商业虚拟机的垃圾收集都采用“分代收集”算法。
原理: 根据对象存活周期不同,将内存划分为几块。一般把Java堆分为新生代老年代,根据各年代的特点采用最适当的收集算法。
新生代—>复制算法;
老年代—>标记清除/标记整理
4.垃圾收集器
收集算法视为内存回收方法论;垃圾收集器视为内存回收具体实现。
讨论基于Sun HotSpot虚拟机1.6版Update22的收集器:
必威 5
上图展示了7种作用于不同分代的收集器(包括JDK 1.6_Update14后引入的Early Access版G1收集器),若两收集器之间连线,说明可搭配使用。
4.1Serial收集器 —>新生代收集
它是一个单线程收集器,不仅是只会使用一个CPU一条收集线程去完成垃圾收集工作,更重要的是在它工作时必须暂停其他所有的工作线程(Stop the world),直到他收集结束。此工作是由虚拟机在后台自动发起和自动完成的。
它是现在为止,虚拟机运行在Client模式下的默认新生代收集器。它与其它收集器的单线程比,没有线程交互的开销,简单而高效。
必威 6
适用性Serial收集器对于运行在Client模式下的虚拟机来说是很好的选择
4.2ParNew收集器 —>新生代收集
其实是Serial收集器的多线程版本。
Serial收集器可用的所有控制参数(-XX:SurvivorRatio、-XX:PrenureSizeThreshold、- XX:HandlePromotionFailure等)、收集算法、Stop The World、对象分配规则、回收策略等都与Serial收集器完全一样。
必威 7
适用性许多运行在Server模式下虚拟机中首选的新生代收集器
除Serial收集器外,目前只有它能与CMS收集器(并发收集器、老年代收集器)配合工作。
它默认开启的收集线程数与CPU数量相同。可以使用-XX:ParallelGCThreads参数来限制垃圾收集线程数。
并行(Parallel):多条垃圾收集线程并行工作,但此时用户线程处于等待状态;
并发(Concurrent):用户线程与垃圾收集线程同时执行(不一定并行,可能交互执行),用户程序继续运行,而垃圾收集程序运行于另一个CPU上。)
4.3Parallel Scavenge收集器 —>新生代收集
也是使用复制算法的收集器,又是并行的多线程收集器。
独有的特点是,收集器目标是达到一个可控制的吞吐量
吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间)
高吞吐量主要适合在后台运算而不需要太多交互的任务。
提供的用于精确控制吞吐量的两个参数:1)控制最大垃圾收集停顿时间的-XX:MaxGCPaulseMillis参数;2)直接设置吞吐量大小的-XX:GCTimeRatio参数。
MaxGCPaulseMillis参数允许值是一个大于0的毫秒数;GC停顿时间缩短是以牺牲吞吐量和新生代空间来换取的。
??? GCTimeRatio参数值是一个大于0小于100的整数(垃圾收集时间占总时间的比率)。若设置为19,则允许的最大GC时间占总时间的1/(1+19).
GC自适应调节策略:打开-XX:+UseAdaptiveSizePolicy开关参数,虚拟机会根据当前系统运行 情况收集性能监控信息,动态调整参数以提供最合适的停顿时间或最大吞吐量。(不再需要手工指定新生代大小(-Xmn)、Eden与Survivor区比例 (-XX:SurvivorRatio)、晋升老年代对象年龄(-XX:PretenureSizeThreshold)等细节参数)
只需把基本内存数据设置好(如-Xmx设置最大堆),然后使用-XX:MaxGCPaulseMillis或-XX:GCTimeRatio参数给虚拟机设立一个优化目标,具体细节参数的调节工作交由虚拟机完成。
4.4Serial Old收集器 —>老年代
Serial收集器的老年代版本,同是一个单线程收集器,使用“标记-整理”算法。
主要意义也是被Client模式下虚拟机使用。
必威 8
4.5Parallel Old收集器(JDK1.6中开始提供) —>老年代
Parallel Scavenge收集器的老年代版本,使用多线程和“标记-整理算法”。
适用性:在注重吞吐量及CPU资源敏感的场合,都可以优先考虑Parallel Scavenge加Parallel Old收集器。
必威 9
4.6CMS收集器 —>老年代
CMS(Concurent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器,基于“标记-清除”算法实现。
目前很大一部分Java的应用都集中在互联网站或B/S系统的服务端上,这类应用尤其重视服务响应速度,希望系统停顿时间最短,有较好使用体验。CMS收集器非常符合这类应用需求。
运作过程可分为4步:初始标记—>并发标记—>重新标记—>并发清除
初始标记和重新标记两个步骤仍需”Stop The World”.初始标记仅标记一下GC Roots能直接关联到的对象,速度很快;并发标记就是进行GC Roots Tracing的过程;重新标记则是为了修正并发标记期间,因用户程序继续运行而导致标记产出变动的那一部分对象的标记记录,这阶段停顿时间一般比初始标 记阶段长,但远比并发标记时间短。
整个过程耗时最长的并发标记和并发清除过程中,收集器线程都可以与用户线程一起工作,总体来说,CMS收集器的内存回收过程是与用户线程一起并发执行的。
必威 10
4.7G1收集器
与CMS收集器相比的两个显著改进:1)G1收集器基于“标记-整理”算法,不会产生空间碎片;2)可非常精确的控制停顿,能让使用者明确指定在一个长度为M毫秒的时间片内,消耗在垃圾收集上的时间不超过N毫秒,几乎是实时Java(RTSJ)的垃圾收集器特征。
它可实现基本不牺牲吞吐量前提下完成低停顿的内存回收(由于能极力避免全区域垃圾收集,之前收集器都是针对整个新生代或老年代。G1将Java堆划分为多 个大小固定的独立区域,并跟踪这些区域里面的垃圾堆积程度,在后台维护一个优先列表,每次根据允许的收集时间优先回收垃圾最多的区域)。
区域划分以及有优先级的区域回收,保证了G1收集器在有限时间内可获得最高收集效率。
4.8垃圾收集器参数总结
必威 11
5.内存分配与回收策略
Java技术体系中自动内存管理自动化的解决了两个问题:1)给对象分配内存;2)回收分配给对象的内存。
对象内存分配,大方向讲,就是在上分配(也可能经JIT编译后被拆散为标量类型并间接在栈上分配),对象主要分配在新生代Eden区上,如果启动本地线程分配缓冲,将按线程优先在TLAB上分配。少数情况会直接分配在老年代,细节取决于使用哪种垃圾收集器组合,还有虚拟机中与内存相关参数配置。
5.1对象优先在Eden分配
当Eden区没有足够空间进行分配时,虚拟机将发起一次Minor GC
虚拟机提供-XX:+PrintGCDetails收集器日志参数,让虚拟机在发生垃圾收集行为时打印内存回收日志,并在进程退出时输出当前内存各区域分配情况。
新生代GC(Minor GC):发生在新生代的垃圾收集动作,因Java对象多具有朝生夕灭特性,Minor GC非常频繁,一般回收速度也较快。
老年代GC(Major GC/Full GC):出现Major GC经常会伴随至少一次的Minor GC(并非绝对,在ParallelScavenge收集器的收集策略里,就有直接进行Major GC的策略选择过程)。Major GC速度一般比Minor GC慢10倍以上。
5.2大对象直接进入老年代
大对象指,需大量连续内存空间的Java对象。
经常出现大对象易导致内存还有不少空间时就提前触发垃圾收集以获取足够连续空间来“安置”它们。
虚拟机提供-XX:PretenureSizeThreshold参数(只对Serial和ParNew两款收集器有效),令大于这个值的对象直接在老年代分配(避免在Eden区及两个Survivor区之间发生大量内存拷贝)。
5.3长期存活的对象将进入老年代
虚拟机给每个对象定义了一个对象年龄(Age)计数器。若对象在Eden出生并经第一次Minor GC后仍存活,并能被Survivor容纳,将被移动到Survivor空间,并将对象年龄设为1.对象在Survivor区中每熬过一次Minor GC,年龄增加1岁当年龄增加到一定程度(默认15)时,就会被晋升到老年代中。对象晋升老年代阈值可通过参数-XX:MaxTenuringThreshold来设置。
5.4动态对象年龄判定
为能更好适应不同程序内存状况,虚拟机并不总要求对象年龄必须达到MaxTenuringThreshold才晋升老年代。若Survivor空间中相同年龄所有对象大小的总和**大于Survivor空间的一半,则大于或等于该年龄的对象就可以直接进入老年代,无需等到MaxTenuringThreshold要求的年龄。
5.5空间分配担保
发生Minor GC时,虚拟机会检测之前每次晋升到老年代的平均大小是否大于老年代的剩余空间大小,若大于则直接进行一次Full GC。若小于,则查看HandlePromotionFailure设置是否允许担保失败,若允许则只会进行Minor GC;若不允许,则改为进行一次Full GC。
当出现大量对象在Minor GC后仍存活的情况时(最极端就是内存回收后新生代中所有对象都存活),就需要老年代进行分配担保,让Survivor无法容纳的对象直接进入老年代(前提是老年代本身还有容纳这些对象的剩余空间)。
如果担保失败(HandlePromotionFailure),就之后在失败后重新进行一次Full GC。

  • 哪些内存需要回收
  • 什么时候回收
  • 如何回收

回收什么样的内存区域

回收“已死”的对象占用的内存

3.2对象已死吗?

  • 引用计数算法:
    给对象添加一个引用计数器,每当有一个地方引用它时,计数器+1,当引用失效时,计数器-1,任何时刻计数器为0的对象就是不可再使用的,微软的com、使用ActionScript 3的FlashPlayer、Python,但是存在对象互相引用无法回收的问题.

  • 可达性分析算法:
    当一个对象到GC Roots没有任何引用时,则证明对象时不可用的

  • GC Roots包括:
    虚拟机栈(栈中本地变量表)引用的对象
    方法区类静态属性引用的对象
    方法区中常量引用的对象
    JNI引用的对象

  • 引用类型:
    强引用
    弱引用
    软引用
    虚引用

  • 回收方法区(永久代),主要包括两部分内容:
    1)废弃常量:与java堆对象回收类似,可回收常量池中的字面量、他类(接口)、方法、字段的符号引用
    2)无用的类: 同时满足条件 该类所有的实例都被回收、加载该类的ClassLoader已经被回收、该类的对应的java.lang.Class对象没有任何地方被引用,无法再任何地方通过反射访问该类的方法。

年轻代:

       在新生代中,使用"停止-复制"算法进行清理,将新生代内存分为2部分,1部分为Eden区较大,1部分Survivor比较小,并被划分为两个等量的部分。每次进行清理时,将Eden区和一个Survivor中仍然存活的对象拷贝到另一个Survivor中,然后清理掉Eden和刚才的Survivor。

        这里也可以发现,停止复制算法中,用来复制的两部分并不总是相等的(传统的停止复制算法两部分内存相等,但新生代中使用1个大的Eden区和2个小的Survivor区来避免这个问题)。

        由于绝大部分的对象都是短命的,甚至存活不到Survivor中,所以,Eden区与Survivor的比例较大,HotSpot默认是8:1,即分别占新生代的80%,10%,10%。如果一次回收中,Surivor+Eden中存活下来的内存超过了10%,则需要将一部分对象分配到老年代。用-XX:SurvivorRatio参数来配置Eden区域Survivor区的容量比值,默认是8,代表Eden:Survivor0:Survivor1=8:1:1。

如何判断对象是否存活

可达性分析算法(Reachability Analysis): 通过一系列"GC Roots"对象作为起始点,从这些节点开始往下搜索,搜索过的路径称为引用链(Reference Chain),当任意一个对象到GC Roots没有任何引用链相连时,则证明此对象不可用。

GC Roots对象包括以下几种:

  • 虚拟机栈(栈帧中的本地变量表)中引用的对象
  • 方法区中类静态变量属性引用的对象
  • 方法区中常量引用的对象
  • 本地方法栈中JNI(本地Native方法)引用的对象

引用包括:

  • 强引用:Java代码中类似Object obj = new Object()这类的引用。只要有强引用在,垃圾收集器永远不会回收掉被引用的对象
  • 软引用:用来描述一些还有用但并非必要的对象。对于软引用关联着的对象,在系统发生内存溢出之前,将会把这些对象列进回收范围之中进行二次回收。如果这次回收还没有足够的内存,才会抛出内存溢出异常。
  • 弱引用:比软引用更弱一些,只能存活到垃圾收集活动发生之前。
  • 虚引用:它是最弱的一种引用关系,它存在的目的就是能在这个对象被收集器回收时收到系统通知。

怎么判断对象“已死”

3.3垃圾收集算法

老年代:

        老年代存储的对象比年轻带多得多,而且不乏大对象,对老年代进行内存清理时,如果使用"停止-复制"算法,则相当低效。一般,老年代用的算法是"标记-整理"算法,即:标记处仍然存活的对象(存在引用的),将所有存活的对象向一端移动,以保证内存的连续。

        在发生Minor GC时,虚拟机会检查每次晋升进入老年代的大小是否大于老年代的剩余空间大小,如果大于,则直接触发一次Full GC,否则,就查看是否设置了-XX:+HandlerPromotionFailure(允许担保失败),如果允许,则只会进行MinorGC,此时可以容忍内存分配失败;如果不允许,则仍然进行Full GC(这个代表着如果设置-XX:+Handler PromotionFailure,则触发MinorGC就会同时出发Full GC,哪怕老年代还有很多内存,所以,最好不要这样做)。

对象的死亡

在可达性分析算法中不可达的对象,至少要经历两次标记的过程,才确定是否被回收:

1、如果对象在可达性分析后发现没有与GC Roots相连的引用链,那它将会被第一次标记并进行一次筛选,筛选的条件是此对象是否有必要执行finalize()方法。当对象没有覆盖finalize()方法时,或者finalize()方法已经被虚拟机调用过,虚拟机将这两种情况都视为“没有必要执行”。

2、如果对象有必要执行finalize()方法,那么这个对象将会被放在一个F—Queue队列之中,虚拟机的finalizer线程会执行它

3、finalize()方法是对象逃脱死亡的最后一次机会,稍后GC将对F-Queue队列中的对象进行第二次小规模的标记,如果对象成功在finalize()中拯救自己(建立与引用链的联系),那在第二次标记时它将被移除出“即将回收”的集合;否则它就要被回收。

引用计数法

做法:给对象中添加一个引用计数器,每当被引用时,计数器就加1;每当引用失效时,计数器就减1;任何时刻计数器为0的对象就是不可能再被使用的。

使用:客观上,引用计数法实现简单,判断效率高,在很多情况下都是一个不错的算法,但是,在java虚拟机里面并没有选择引用计数算法来管理内存,其中最主要的原因就是它很难解决对象之间相互循环引用的问题。

3.3.1 标记-清除算法(基础)

不足:

  • 效率问题,标记和清除两个过程效率都不高
  • 空间问题,清除完成后空间碎片较多,如果需要分配大内存,不得不提前触发垃圾回收

方法区(永久代):

永久代的回收有两种:常量池中的常量,无用的类信息,常量的回收很简单,没有引用了就可以被回收。对于无用的类进行回收,必须保证3点:

1.类的所有实例都已经被回收;

2.加载类的ClassLoader已经被回收;

3.类对象的Class对象没有被应用(既没有通过反射引用该类的地方)。

        永久代的回收并不是必须的,可以通过参数来设置是否对类进行回收。HotSpot提供-Xnoclassgc进行控制使用-verbose,-XX:+TranceClassLoading、-XX:+TraceClassUnLoading可以查看累加载和卸载信息-verbose、-XX:+TraceClassLoading可以在Product版HotSpot中使用;-XX:+TraceClassUnLoading需要fastdebug版HotSpot支持。

方法区的回收(永久代)

方法区垃圾收集的效率是比较低的,收集的内容主要有两部分:废弃的常量和无用的类

无用的类需要符合三个条件:

  • 该类的所有实例都已经被回收
  • 加载该类的ClassLoader已经被回收
  • 该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法

可达性分析算法

做法:通过一系列的称为"GC Roots“的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为”引用链“,当一个对象到GC Roots没有任何引用链相连(用图论的话来说,就是从GC Roots到这个对象不可达)时,则证明此对象是不可用的,如下图所示,object5、object6、object7虽然相互有关联,但是它们到GC Roots是不可达的,所以它们将会被判定为是可回收对象。

必威 12

3.3.2 复制算法

它将可用内存等量的两块,每次只使用其中一块,当这一块内存用完了,把还可用的对象复制到另外一块内存,然后把已使用过的内存直接清理掉
优点:

  • 实现简单,运行高效,内存连续
    不足:
  • 内存少了一半

目前都采用这种算法来收集 新生代内存 ,一般是将内存分为一块较大的Eden空间和两块较小的Survivor空间,每次都使用Eden空间和一块Survivor空间,当回收时将存活的对象复制到一块Survivor空间,清理使用的Eden和Survivor空间

垃圾收集器

在GC机制中,起重要作用的是垃圾收集器,垃圾收集器是GC的具体实现,Java虚拟机规范中对于垃圾收集器没有人任何规定,所以不同厂商实现的垃圾收集器各不相同,HotSpot 1.6版使用的垃圾收集器如下图(图中两个收集器之间有连线,说明他们可以配合使用)

必威 13

        在介绍垃圾收集器之前,需要明确一点,就是在新生代采用的"停止-复制"算法中,"停止(Stop-the-world)"的意义是在回收内存时,需要暂停其他所有线程的执行。这个是很低效的,现在的各种新生代收集器越来越优化这一点,但仍然之事将暂停的时间变短,并未彻底取消停止。

Serial收集器:新生代收集器,使用"停止-复制"算法,使用一个线程进行GC,其他工作线程暂停。使用-XX:+UseSerialGC可以使用Serial+Serial Old模式运行进行内存回收(这也是虚拟机在Client模式下运行的默认值)

ParNew收集器:新生代收集器,使用"停止-复制"算法,Serial收集器的多线程版,用多个线程进行GC,其他工作线程暂停,关注所短垃圾收集时间。使用-XX:+UserParNewGC开关来控制使用ParNew+Serial Old收集器组合手机内存;使用-XX:ParallelGCThreads来设置执行内存回收的线程数。

Parallel Scavenge收集器:新生代收集器,使用"停止-复制"算法,关注CPU吞吐量,即运行用户代码的时间/总时间,比如:JVM运行100分钟,其中运行用户代码99分钟,垃圾收集1分钟,则吞吐量是99%,这种收集器能最高效率的利用CPU,适合后台运算(关注缩短垃圾收集时间的收集器,如CMS,等待时间很少,所以适合用户交互,提高用户体验)。使用-XX:+UseParallelGC开关控制使用 Parallel Scavenge+Serial Old收集器组合回收垃圾(这也是在Server模式下的默认值);使用-XX:GCTimeRatio来设置用户执行时间占总时间的比例,默认99,即 1%的时间用来进行垃圾回收。使用-XX:MaxGCPauseMillis设置GC的最大停顿时间(这个参数只对Parallel Scavenge有效)。

Serial Old收集器:老年代收集器,单线程收集器,使用标记整理(整理的方法是Sweep(清理)和Compact(压缩),清理是将废弃的对象干掉,只留幸存 的对象,压缩是将移动对象,将空间填满保证内存分为2块,一块全是对象,一块空闲)算法,使用单线程进行GC,其它工作线程暂停(注意,在老年代中进行标 记整理算法清理,也需要暂停其它线程),在JDK1.5之前,Serial Old收集器与ParallelScavenge搭配使用。

Parallel Old收集器:老年代收集器,多线程,多线程机制与Parallel Scavenge差不错,使用标记整理(与Serial Old不同,这里的整理是Summary(汇总)和Compact(压缩),汇总的意思就是将幸存的对象复制到预先准备好的区域,而不是像Sweep(清 理)那样清理废弃的对象)算法,在Parallel Old执行时,仍然需要暂停其它线程。Parallel Old在多核计算中很有用。Parallel Old出现后(JDK 1.6),与Parallel Scavenge配合有很好的效果,充分体现Parallel Scavenge收集器吞吐量优先的效果。使用-XX:+UseParallelOldGC开关控制使用Parallel Scavenge +Parallel Old组合收集器进行收集。

CMS(Concurrent Mark Sweep)收集器:老年代收集器,致力于获取最短回收停顿时间,使用标记清除算法,多线程,优点是并发收集(用户线程可以和GC线程同时工作),停顿小。使用-XX:+UseConcMarkSweepGC进行ParNew+CMS+Serial Old进行内存回收,优先使用ParNew+CMS(原因见后面),当用户线程内存不足时,采用备用方案Serial Old收集。

CMS收集的方法是:先3次标记,再1次清除,3次标记中前两次是初始标记和重新标记(此时仍然需要停止(stop the world)), 初始标记(Initial Remark)是标记GC Roots能关联到的对象(即有引用的对象),停顿时间很短;并发标记(Concurrent remark)是执行GC Roots查找引用的过程,不需要用户线程停顿;重新标记(Remark)是在初始标记和并发标记期间,有标记变动的那部分仍需要标记,所以加上这一部分 标记的过程,停顿时间比并发标记小得多,但比初始标记稍长。在完成标记之后,就开始并发清除,不需要用户线程停顿。

所以在CMS清理过程中,只有初始标记和重新标记需要短暂停顿,并发标记和并发清除都不需要暂停用户线程,因此效率很高,很适合高交互的场合。

CMS也有缺点,它需要消耗额外的CPU和内存资源,在CPU和内存资源紧张,CPU较少时,会加重系统负担(CMS默认启动线程数为(CPU数量+3)/4)。

另外,在并发收集过程中,用户线程仍然在运行,仍然产生内存垃圾,所以可能产生“浮动垃圾”,本次无法清理,只能下一次Full GC才清理,因此在GC期间,需要预留足够的内存给用户线程使用。所以使用CMS的收集器并不是老年代满了才触发Full GC,而是在使用了一大半(默认68%,即2/3,使用-XX:CMSInitiatingOccupancyFraction来设置)的时候就要进行Full GC,如果用户线程消耗内存不是特别大,可以适当调高-XX:CMSInitiatingOccupancyFraction以降低GC次数,提高性能,如果预留的用户线程内存不够,则会触发Concurrent Mode Failure,此时,将触发备用方案:使用Serial Old 收集器进行收集,但这样停顿时间就长了,因此-XX:CMSInitiatingOccupancyFraction不宜设的过大。

还有,CMS采用的是标记清除算法,会导致内存碎片的产生,可以使用-XX:+UseCMSCompactAtFullCollection来设置是否在Full GC之后进行碎片整理,用-XX:CMSFullGCsBeforeCompaction来设置在执行多少次不压缩的Full GC之后,来一次带压缩的Full GC。

并发是指用户线程与GC线程同时执行(不一定是并行,可能交替,但总体上是在同时执行的),不需要停顿用户线程(其实在CMS中用户线程还是需要停顿的,只是非常短,GC线程在另一个CPU上执行);

并行收集是指多个GC线程并行工作,但此时用户线程是暂停的;所以,Serial和Parallel收集器都是并行的,而CMS收集器是并发的。

参考链接:

垃圾收集算法

在java语言中,可作为GC Roots的对象

  1. 虚拟机栈中引用的对象;
  2. 方法区中类静态属性引用的对象;
  3. 方法区中常量引用的对象;
  4. 本地方法栈中JNI(即一般的Native方法)引用的对象;

3.3.3 标记-整理算法

标记过程仍然与 (标记-清除算法)一样,但是后续步骤不是直接对可回收对象进行整理,而是让所有存活对象都向一端移动,然后直接清理掉端边界以外的对象内存

一般老年代因对象存活率高,没有额外空间做分配担保,就必须使用 标记-清除算法、标记-整理算法

标记-清除算法

标记-清除算法分两阶段:首先标记所有需要回收的对象,在标记完成后统一回收所有被标记的对象

标记-清除算法的不足:第一两个过程的效率都不高;第二会产生内存碎片

怎么判定对象是否会被最终回收

即使可达性分析算法分析完毕后,也不代表不可达的对象会被立即回收,此时不可达对象将会进入一个“缓刑期”。一个对象要被最终回收,将会进行两次标记过程,如果在可达性分析后,对象不可达,则会进行一次标记并且判断对象是否有必要执行finalize()方法,如果对象没有执行finalize()方法权限,或者finalize()方法已经被虚拟机调用过,则表示对象没有必要执行finalize()方法。

如果对象被判定有必要执行finalize()方法,那么对象将会被放置在一个"F-QUEQUE"队列中,虚拟机会自动创建一个finalizer线程,操作队列中的对象,这是对象最后一次“逃生”的机会,如果在finalizer线程处理过程中,能够与任何一条引用链挂钩,从而可达,那么该对象将逃离被虚拟机回收的结局;否则,对象将会被进行第二次标记,最终被垃圾回收。

3.4HotSpot的算法实现

复制算法

复制算法是把内存分成相等的两块,每次只使用其中的一块。当这一块内存用完了,就将还存活的对象复制到另外一块内存上,然后将当前这块内存一次清理掉。

复制算法的好处就是实现简单,效率高,不足之处就是浪费内存空间。

如果对象的存活率较高,复制操作的效率会比较低,所以复制算法适合对象存活率较低的情况。

现在商业虚拟机及HotSpot虚拟机都采用这种算法来回收新生代。新生代的内存空间会分为Eden、From Survivor和To Survivor三块,每次使用Eden空间和From Survivor空间。当进行回收时,将Eden空间及From Survivor空间存活的对象复制到To Survivor,然后清理Eden和From Survivor空间。

本文由必威发布于必威-编程,转载请注明出处:Roots没有任何引用链相连时必威,当垃圾收集成为

TAG标签:
Ctrl+D 将本页面保存为书签,全面了解最新资讯,方便快捷。