JVM - 垃圾回收器与内存分配策略

确定哪些哪些内存需要回收。

什么时候回收。

如何回收。

判断对象是否可以回收

引用计数算法

给对象添加一个引用计数器,每当一个地方引用它时,计数器+1;当引用失效时,计数器-1。任何时刻计数器为0 的对象就是不可能再使用的对象。实现简单,判断效率高。但是它很难解决对象之间的互相循环引用的问题。

可达性分析算法

基本思想是通过“GC Roots” 的对象作为起始点,从这些对开始向下搜索,搜索所走过的路径称为引用链,当一个对象到GC roots 没有任何引用链相连时,则说明此对象不可用。

110344_wb83_3171491.png

可作为GC Roots的对象包括:

  1. 虚拟机栈中(本地变量表)引用的对象。
  2. 方法区中静态属性引用的对象。
  3. 方法区中常量引用的对象。
  4. 本地方法栈中JNI(Native方法)引用的对象 。

其他引用

强引用:代码中普遍存在的,类似 Object o = new Object();这类的引用,只要强引用存在垃圾回收器永远不会回收掉被引用的数据。

软引用:描述一些还有用但并非必须的对象。在发生内存溢出异常之前,将这些对象列进可回收范围范围之中进行第二次回收。如果这次回收还是没有足够的内存,才抛出内存溢出异常。SoftReference。

弱引用:描述非必须的对象。被若引用的对象只能存活到下一次垃圾回收之前,无论当前内存是否可用,都会回收掉被弱引用的对象。WeakReference。

虚引用:一个对象是否有虚引用完全不会对其生存时间构成影响,也无法通过虚引用取得一个对象实例。作为虚引用的唯一目的是能在对象被回收时收到一个系统通知。PhantomReference。

finalize()

如果对象在进行可达性分析后发现没有与GC Roots相连,那它会被第一次标记并进行筛选,筛选的条件是此对象是否有必要执行finalize()方法。当对象没有重写finalize()或者finalize()已经被jvm调用过,视为没有必要。

如果有必要,则将对象放入F-Queue队列中,并在稍候由jvm自动建立的、低优先级的Finalizer线程去执行它。所谓的执行并不会等到方法的结束,有可能finalize()执行缓慢、发生死循环,将可能导致F-Queue中的其他对象永久等待。如果过在finalize()重新如引用链关联(this 赋给某个类的变量等),则将其从“即将回收”集合移除。如果还没有逃脱,则基本上是真正可以被回收的了。  

应避免使用finalize(),运行代价昂贵,不确定性大,无法保证各个对象的调用顺序。对于通过finalize()进行一些资源的关闭之类的工作,可以通过try-finally或者其他方式可以做的更好、更及时。

回收方法区

回收效率低。主要回收废弃的常量和无用的类。

判断无用的类:

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

是否对类进行回收,通过-Xnoclassgc参数进行控制,还可以使用-verbose:class以及-XX:TraceClassLoading、-XX:+TraceClassUnloading查看类的加载和卸载信息。-verbose:class和-XX:TraceclassLoading可以在product版的虚拟机使用,-XX:TraceClassUnLoading参数需要FastDebug版的虚拟机支持。

在大量使用反射、动态代理、CGLib等ByteCode框架、动态生成JSP以及OSGi这类频繁定义ClassLoader的场景需要虚拟机具备类卸载的功能,以保证永久代不会溢出。

 

 

垃圾回收算法

标记-清除

最基本的算法(Mark-Sweep),后续的算法都是基于该算法不足的改进而得到的。 它主要不足有两个:一个是效率问题,标记和清除两个过程的效率都不高;另外一个是空间问题,清除后会产生大量连续不断的内存碎片。

复制算法

为了解决效率问题(Coping)。它将可用内存按容量划分为两个大小相等的块,每次只使用其中的一块,然后把已使用过的内存空间一次清理掉。实现简单,运行高效。缺点浪费内存空间。

商业虚拟机都是采用这种方法回收算法来回收新生代。大部分的对象都是“朝生夕灭”的,所以没有必要按照1:1的大小划分,而是将内存划分为一块较大的Eden空间和两块较小且大小相等的Survivor空间,每次使用Eden和其中一块Survivor。当回收时,将Eden和Survivor中还存活的对象一次性复制到另外一块Survivor中,最后清理掉Eden和刚才用过的Survivor。HotSpot虚拟机默认Eden:Survivor 为 8:1,只有10%的空间会被浪费掉。但我们没法保证每次回收都不多于10%的对象存活,当Survivor空间不够时,需要依赖其他内存(老年代)进行分配担保

缺点:对象存活率较高时要进行较多的复制,效率将会变低。老年代不能选择该算法

标记-整理

(Mark-Compact)让存活的对象都移到一端,然后直接清理掉端边界以外的内存。

130926_yhFB_3171491.png

分代回收算法

将对分为新生代和老年代,根据各代的特点选择合适的算法。

新生代中,每次都需要回收大部分对象,只有少数的对象存活,那就选用复制算法。老年代中,对象的存活率较高,没有额外空间对它进行分配担保,就必须使用 标记-清理或者标记-整理算法。

 

 

HotSopt的算法实现

枚举根节点:OopMap?

安全点:SafePoint?

安全区域?

 

 

垃圾回收器

132216_B7FD_3171491.png

Serial

141423_AO7B_3171491.png

最基本、历史悠久的回收器。单线程的回收器,但它的单线程的意义不仅说明使用一个CPU或一个线程,更重要的是,必须暂停其他所有的工作线程,直到它回收结束(Stop The World)。

依然是Client模式下默认的新生代回收器:简单而高效(与其他回收器的单线程比)。

ParNew

141443_Vkxa_3171491.png

 

Serial 多线程版本,除了多线程,其余行为包括Serial可用的控制参数(-XX:SurvivorRatio、-XX:PretenureSizeThreshold、-XX:HandlePromotionFailure等)、回收算法、Stop The World、对象分配规则、回收策略等都和Serival相等。

Server模式下首选的新生代回收器。一个与性能无关但很重要的原因,除了Servial以外,目前只有它能与CMS配合工作。ParNew是使用-XX:UseConcMarkSweepGC 选项后的默认新生代回收器。也可以使用-XX:UseParNewGC 来强制指定。

效果不一定比Serial好。默认开启与CPU数量相同的线程数。在CPU非常多的情况,可以通过-XX:ParallelGCThreads参数来限制垃圾回收的线程数。

Parallel Scavenge

复制算法。它的关注点与其他回收器不同,它关注的是达到一个可控制的吞吐量。吞吐量=运行用户代码时间/(运行用户代码时间+GC时间)。

提供了两个参数来控制吞吐量:

  1. 最大GC停顿时间-XX:MaxGCPauseMillis参数,JVM尽可能保证不超过该值,调小该值并不会使GC速度更快,GC停顿时间的缩小是以牺牲吞吐量和新生代空间来换取的:回收300M总比回收500M快,但是GC更加频繁,原来10秒回收一次,停顿100毫秒,现在5秒一次,停顿70毫秒,吞吐量下降。
  2. 直接设置吞吐量大小-XX:GCTimeRadio。大于0小于100的整数,垃圾回收时间占总时间的比例

还有一个参数:-XX:UseAdaptivePolicy,打开后就不需要指定新生代的大小、Eden与Survivor的比例、晋升老年代年龄等细节参数。JVM会根据运行情况收集性能监控信息,动态调整这些参数以提供合适的停顿时间或者最大的吞吐量,这中调节方式称为GC自适应的调节策略。

Serial Old

141502_AWXv_3171491.png

Serial的老年代版本。单线程算法,标记-整理算法。在Client模式下使用。如果在Server下使用:一种用途是与Parallel Scavenge回收器搭配。另外一种是作为CMS的后备预案,在CMS发生Concurrent Mode Failure时使用。

Parallel Old

141531_N94D_3171491.png

Parallel Scavenge的老年代版本。使用多线程的标记-整理

CMS

标记-清除。以获取最短回收停顿时间为目标的回收器。

  1. 初始阶段。
  2. 并发标记阶段。
  3. 重新标记。
  4. 并发清除。

其中,初始阶段和重新标记阶段依然需要Stop The World。初始阶段仅仅是标记下GC Roots能直接关联到的对象,速度很快。并发阶段是进行GC Roots Tracing的过程,而重新标记阶段则是为了修正并发阶段时期引用变动的一部分对象标记,比初始阶段长,但是比并发标记阶段短。

由于整个过程耗时最长的并发标记和并发清除阶段可以和用户线程一起工作,所以总体上来说,CMS的GC过程和用户线程是并行执行的。

142653_HaAA_3171491.png

3个明显的缺点:

  1. 对CPU资源敏感。并发阶段,占用一部分线程二导致应用变慢,总体吞吐量降低。CMS默认启动(CPU数量+3)/4,也就是当CPU为4时,GC线程占用不少于25%的CPU资源 ,并随着CPU数量增加而降低。但是CPU不足4时,CMS对用户程序的影响就可能变的很大。
  2. CMS无法处理浮动垃圾。可能出现”Concurrent Mode Failure“失败而导致一次Full GC。在并发清除阶段,随着程序的运行,会不断产生新的垃圾,只好待下次GC时再回收。这部分垃圾称为浮动垃圾。CMS不能像其他回收器一样,等老年代几乎满了再回收,需要预留一部分空间提供并发回收时的程序运行使用。JDK1.5默认68%,JDK1.6默认92%。如果CMS运行期间预留的内存无法满足程序需要,就会发生一次”Concurrent Mode Failure“失败,这是JVM启动后备预案:临时启用 Serial Old。所以-XX:CMSInitiatingOccupancyFraction 设置太高很容易导致大量”Concurrent Mode Failure“,性能反而降低。
  3. CMS基于标记-清除的算法实现,会产生大量的内存碎片。为了解决这个问题,CMS提供了-XX:+UseCMSCompactAtFullCollection(默认开启),在CMS要进行Full GC时开启内存整理,停顿时间不得不边长。-XX:CMSFullGCsBeforeCompaction 设置执行多少次不压缩的Full GC 后,跟着来一次带压缩的(默认值为0,表示每次Full GC都进行碎片整理)。

G1

145654_6SmH_3171491.png

带完善。。。

 

 

内存回收策略

  • 对象现在Eden区分配,当Eden区没有足够的空间,JVM发起一次minor GC。-XX:PrintGCDetails打印回收信息。
  • 大对象直接进入老年代。-XX:PretenureSizeThreshold参数指定大于该值的对象直接进入老年代。
  • 长期存活的对象将进入老年代。通过对象年龄计数器,默认15。通过-XX:MaxTenuringThreshold设置阀值。
  • 动态年龄判断。当Survivor空间相同年龄所有对象大小的总和大于Survivor的一半,年龄大于该年龄的对象直接进入老年代,无需等待MaxTenuringThreshold要求的年龄。
  • 空间分配担保。 151514_kEU1_3171491.png

 

 

转载于:https://my.oschina.net/u/3171491/blog/1518040

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值