JVM(四)内存回收(二)

在上一节中“JVM(三)内存回收(一)”我讲到了垃圾回收的几种算法,算是解决了之前提到的3个问题中的最后一个。

关于内存回收,还应该了解常用的内存回收器(GC Collector),即执行上述回收算法的引擎。

2. 垃圾回收器(接上章中“怎么回收”)

  2.1 Serial / Serial old 回收器

  从名字的字面意思就能明白,这两个是串行垃圾回收器。请看下图:

  

Serial/Serial Old收集器运行示意图

    从图中可以看出,当到达“安全点”的时候,所有的用户线程都被挂起,即“Stop the world”发生了,然后再由单个GC线程来回收所有不需要使用的对象的内存。这里需要注意的是:

    • 在新生代(serial)中,采用的复制算法进行垃圾回收
    • 在老年代(serial old)中,则是采用的标记-整理算法进行垃圾回收

这两个收集器一般不建议在多处理器的服务器端使用,如果要使用,一般用在内存很小的桌面应用程序或者单核(或少核)的CPU服务器上。

  2.2 ParaNew/ Parallel old/ Parallel Scavenge 回收器

  这三个回收器均为并行回收器,其中,除了Parallel old用于老年代回收外,另外两个都用在新生代。其并行主要体现在:Serial回收器在GC阶段只有单个线程进行GC,而这些回收器,会存在多个GC线程,如下两图:

ParNew/Serial Old收集器运行示意图

Parallel Scavenge/Parallel Old收集器运行示意图

新生代两个并行收集器的主要区别如下:

  • ParaNew 除Serial收集器之外,只有它能和CMS搭配使用;另外,它更注重的是缩短停顿时间,达到更好的用户体验,比较适应用作与用户交互的应用程序上。使用 -XX:+UseConcMarkSweepGC选项默认会被指定为新生代的收集器,而使用-XX:+UseParaNewGC选项来强制指定它为新生代收集器;
  • Parallel Scavenge 则是更注重吞吐量的一个收集器,所谓的吞吐量就是“运行用户代码的时间/(运行用户代码时间+垃圾收集时间)”,主要适合在后台需要大量运算而不需要交互太多的任务。另外一个最大的不同是,该收集器在设定了虚拟机的优化目标之后,如指定了-Xmx,并且指定了MaxGCPauseMillis(更关注最大停顿时间)或GCTimeRatio(更关注吞吐量),再选择参数-XX:+UseAdaptiveSizePolicy,使用自适应功能之后,虚拟机会自动根据收集到的系统性能监控信息,动态调整新生代的大小(-Xmn)、Eden与Survivor区的比例(-XX:SurvivorRatio)、晋升老年代对象年龄(-XX:PretenureSizeThreshold)等细节参数了,从而提供最合适的停顿时间或者最大的吞吐量。

老年代的并行收集器(Parallel old),是Parallel Scavenge的老年代版本,使用多线程+“标记-整理”算法实现。二者搭配之后,主要用于比较关注高吞吐量、CPU敏感的应用系统。-XX:+UseParallelOldGC来指定。

2.3 CMS 回收器(老年代)

CMS GC (-XX:+UseConcMarkSweepGC),其工作过程比之前的任何一个GC Collector都要复杂,但是对低时延的应用非常有用。下面我们通过和Serial GC对比一下来说明是怎么样做到更低时延的。

从上图中可以看出,CMS一共包含如下4个步骤:

Unmarked objects == Dead Objects

    • Initial Mark(初始标记)这个阶段很快,仅仅是通过GC roots进行简单的可达性分析,会出现和serial GC同样的STW,不过时间非常短,标记出可达对象
    • Concurrent Mark(并行标记)并行标记耗时比较长,但是,从图中可以看出,应用线程是与GC线程同时(并行)执行的,故对用户线程可以认为没有影响;
    • Remark(重新标记)这个阶段会出现短暂的STW暂停,暂停时间比Initial Mark稍微长点,同时其执行时间远比Concurrent Mark要短,主要是修正之前的标记结果,并标记出在Concurrent Mark阶段new出来的一些可达对象
    • Concurrent Sweep(并行清理)将前面3个标记阶段标记出来的需要被清理的对象清除,和(Concurrent Mark)一样,都是和用户线程并行执行的。

由此可见,其低时延的实现原理是,使用户线程能与耗时较长的标记和清除动作并行。

总结一下优点和缺点:

优点:Stop-the-World 时间很短,这很适合做“Responsiveness”应用的垃圾收集器

缺点:

    1. 对CPU和内存的消耗比较大
    2. 对浮动垃圾无法回收(即在并行清理的时候,由用户线程运行所产生的可以被回收的对象)
    3. 容易产生垃圾碎片(从上述过程中,缺省情况下,CMS是没有进行内存整理的,就会造成,看起来还有较多的内存,但是在分配较大对象时,往往可能由于连续内存空间不够,而出发full GC,从而降低JVM的性能)

相关优化的参数:

针对缺点2,在JDK 1.5的默认设置下,CMS收集器当老年代使用了68%的空间后就会被激活,这是一个偏保守的设置,如果在应用中老年代增长不是太快,可以适当调高参数-XX:CMSInitiatingOccupancyFraction的值来提高触发百分比,以便降低内存回收次数从而获取更好的性能,在JDK1.6中,CMS收集器的启动阈值已经提升至92%。要是CMS运行期间预留的内存无法满足程序需要,就会出现一次“Concurrent Mode Failure”失败,这时虚拟机将启动后备预案:临时启用Serial Old收集器来重新进行老年代的垃圾收集,这样停顿时间就很长了。所以说参数-XX:CM SInitiatingOccupancyFraction设置得太高很容易导致大量“Concurrent Mode Failure”失败,性能反而降低。

针对缺点3,CMS收集器提供了一个-XX:+UseCMSCompactAtFullCollection开关参数(默认就是开启的),用于在CMS收集器顶不住要进行FullGC时开启内存碎片的合并整理过程,内存整理的过程是无法并发的,空间碎片问题没有了,但停顿时间不得不变长。虚拟机设计者还提供了另外一个参数-XX:CMSFullGCsBeforeCompaction,这个参数是用于设置执行多少次不压缩的Full GC后,跟着来一次带压缩的(默认值为0,表示每次进入Full GC时都进行碎片整理)。

2.4 G1 回收器

Garbage-First(G1,垃圾优先)收集器是服务类型的收集器,目标是多处理器机器、大内存机器。它高度符合垃圾收集暂停时间的目标,同时实现高吞吐量。Oracle JDK 7 update 4 以及更新发布版完全支持G1垃圾收集器。G1垃圾回集器为以下应用设计:

    • 类似CMS收集器,可以和应用线程同时并发的执行
    • 压缩空闲空间时没有GC引起的暂停时间
    • 需要更可预言的GC暂停时间
    • 不想牺牲大量的吞吐量性能
    • 不需要特别大的Java堆

G1垃圾收集器计划长期替换并发标记清除收集器(CMS,Concurrent Mark-Sweep Collector)。G1和CMS比较,有一些不同点让G1成为一个更好的解决方案。

一个不同点是G1是一个压缩收集器。G1收集器充分地压缩空间以完全避免使用细粒度的空闲列表来分配空间,取而代之的是使用区域。这相当简化了收集器的部件,和尽量消除潜在的碎片问题。

同时,G1收集器相比CMS收集器而言,提供更可预言的垃圾收集暂停时间,允许用户指定想要暂停时间指标。

关于G1的参考:

原版:http://www.oracle.com/technetwork/tutorials/tutorials-1876574.html

翻译版:http://blog.csdn.net/zhanggang807/article/details/45956325

深入理解 Java G1 垃圾收集器:http://blog.jobbole.com/109170/

2.5 总结

一个好的垃圾回收器,对应用的性能起着至关重要的作用。但是,在具体的使用过程中,还是要具体情况具体分析,用好各种GC Collector,理解其运行原理,才是JVM性能优化的基本之道。最后,附上一张图,从宏观上区分各回收器的使用区域

HotSpot虚拟机的垃圾收集器

    如果两个收集器之间存在连线,就说明它们可以搭配使用。

参考:《深入理解Java虚拟机》

时间: 2024-08-29 15:12:11

JVM(四)内存回收(二)的相关文章

图解JVM垃圾内存回收算法

图解JVM垃圾内存回收算法 这篇文章主要介绍了图解JVM垃圾内存回收算法,由于年轻代堆空间的垃圾回收会很频繁,因此其垃圾回收算法会更加重视回收效率,下面博主和大家来一起学习一下吧 前言 首先,我们要讲的是JVM的垃圾回收机制,我默认准备阅读本篇的人都知道以下两点: JVM是做什么的 Java堆是什么 因为我们即将要讲的就是发生在JVM的Java堆上的垃圾回收,为了突出核心,其他的一些与本篇不太相关的东西我就一笔略过了 众所周知,Java堆上保存着对象的实例,而Java堆的大小是有限的,所以我们只

Java工作原理:JVM,内存回收及其他

JAVA虚拟机系列文章 http://developer.51cto.com/art/201001/176550.htm Java语言引入了Java虚拟机,具有跨平台运行的功能,能够很好地适应各种Web应用.同时,为了提高Java语言的性能和健壮性,还引入了如垃圾回收机制等新功能,通过这些改进让Java具有其独特的工作原理. 1.Java虚拟机 Java虚拟机(Java Virtual Machine,JVM)是软件模拟的计算机,它可以在任何处理器上(无论是在计算机中还是在其他电子设备中)安全兼

JVM(四) 垃圾回收

1. 堆内存结构 Java堆从GC的角度可以细分为:新生代(Eden区.From Survivor区和To Survivor区)和老年代. 1.1 新生代 新生代是用来存放新生的对象.一般占据堆的1/3空间.由于频繁创建对象,所以新生代会频繁触发MinorGC进行垃圾回收.新生代又分为Eden区.ServivorFrom.ServivorTo三个区. 1.1.1.Eden区Java新对象的出生地(如果新创建的对象占用内存很大,则直接分配到老年代).当Eden区内存不够的时候就会触发MinorGC

JVM(3) --内存回收

那些内存需要回收 内存回收是对运行时内存区域的内存回收,其中程序计数器.虚拟机栈.本地方法栈3个区域随线程而生,随线程而灭:栈中的栈帧随着方法的进入和退出而有条不紊的执行着出栈和入栈操作.每一个栈帧中分配多少内存基本上是在类结构确定下来就已知的,因此这几个区域的内存分配和回收都具备确定性,在这几个区域就不需要过多考虑回收的问题,因为方法结束或者线程结束时,内存自然就跟随着回收了. 而Java堆和方法区则不一样,一个接口中的多个实现类需要的内存可能不一样,一个方法的多个分支需要的内存可能也不一样,

JVM的内存回收机制

垃圾回收机制,简称gc.对堆与方法区的对象进行回收,因为java不像c需要编程人员手动clear,虚拟机通过垃圾回收算法,对堆与方法区的对象进行自动回收处理. 1.引用计数法(jvm没有采用,因为当两个对象相互引用的时候,它们的引用数量永远为1,这样就不会被自动回收,会造成内存泄漏.) 意思就是,对对象的引用数量进行计数,引用一次+1,减少一个引用则-1,当一段时间引用数量为0时,则认为该对象可被回收. 2.可达性分析法(jvm采用的就是该算法) 通过一系列的称为 “GC Roots” 的对象作

JVM的内存区域划分以及垃圾回收机制详解

在我们写Java代码时,大部分情况下是不用关心你New的对象是否被释放掉,或者什么时候被释放掉.因为JVM中有垃圾自动回收机制.在之前的博客中我们聊过Objective-C中的MRC(手动引用计数)以及ARC(自动引用计数)的内存管理方式,下方会对其进行回顾.而目前的JVM的内存回收机制则不是使用的引用计数,而是主要使用的"复制式回收"和"自适应回收". 当然除了上面是这两种算法外,还有其他是算法,下方也将会对其进行介绍.本篇博客,我们先简单聊一下JVM的区域划分,

2 - JVM随笔分类(JVM堆的内存回收)

JVM常用的回收算法是: 标记/清除算法 标记/复制算法 标记/整理算法 其中上诉三种算法都先具备,标记阶段,通过标记阶段,得到当前存活的对象,然后再将非标记的对象进行清除,而对象内存中对象的标记过程,则是使用的  “根搜索算法”,通过遍历整个堆中的GC ROOTS,将所有可到达的对象标记为存活的对象的一种方式,则是 “根搜索算法”,其中根是指的“GC ROOTS”,在JAVA中,充当GC ROOTS的对象分别有:“虚拟机栈中的引用对象”,“方法区中的类静态属性引用的对象”,“方法区中的常量引用

一张图让你看懂JVM之垃圾回收算法详解

前言 从上面这个图我们总体上对JVM的结构特别是内存结构有了比较清晰的认识,虽然在JDK1.8+的版本中,JVM内存管理结构有了一定的优化调整.主要是方法区(持久代)取消变成了直接使用元数据区(直接内存)的方式,但是整体上JVM的结构并没有大改,特别是我们最为关心的堆内存管理方式并没有在JDK1.8+的版本中有什么变化,所以图中的结构整体上是没有什么不准确的,之所以将方法区以及持久代标注出来,主要还是为了起到对比认识的作用,大家知道就可以了. 关于持久代元数据区的使用问题,目前可以理解就是使用的

JVM的内存结构,JVM的回收机制

内存作为系统中重要的资源,对于系统稳定运行和高效运行起到了关键的作用,Java和C之类的语言不同,不需要开发人员来分配内存和回收内存,而是由JVM来管理对象内存的分配以及对象内存的回收(又称为垃圾回收.GC),这对于开发人员来说确实大大降低了编写程序的难度,但带来的一个副作用就是,当系统运行过程中出现JVM抛出的内存异常(例如OutOfMemoryError)的时候,很难知道原因是什么,另外一方面,要编写高性能的程序,通常需要借助内存来提升性能,因此如 何才能合理的使用内存以及让JVM合理的进行