java虚拟机-垃圾回收机制

垃圾回收机制

  • 如何确定对象已死
    • 引用计数法
    • 根搜索法
  • 垃圾回收算法
    • Mark-Sweep(标记-清除)算法
    • Copying(复制)算法
    • Mark-Compact(标记-整理)算法
    • Generational Collection(分代收集)算法
  • 垃圾收集器
    • Serial
    • ParNew
    • Parallel Scavenge
    • CMS
    • G1

如何确定对象已死

“死”说的就是没用的对象,那么如何确定已经死亡的对象呢!有下面两个方法

引用计数法

实现原理就是在对象中增加一个引用计数器,当计数器为零时,说明对象已死可以被回收了。

但是由于解决不了循环引用的问题,就是A引用B、B引用C、C在引用A
这样他们永远不会被回收,所以java并没有使用此方法,java中用到的方法是下面的

根搜索法

根搜索算法的基本思路是通过一系列的“GC Roots”的对象作为起始点,从这些节点开始往下搜索,搜索的走过的路径称为引用链。

当一个对象到“GC Roots”没有引用链可达时(也就是用图论的话说就是从GC Roots到这个对象不可达),则证明此对象是不可用的,这样的对象被判定为是可回收的。

java中可以作为GC Roots对象包括以下几种:

1.虚拟机栈(栈帧中的本地变量表)中的引用对象。

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

3.方法区中的常量引用的对象。

4.本地方法栈中JNI(也即一般说的Native方法)的引用的对象。

根搜索算法判断对象是否存活与引用有关。java将引用分为四类:强引用、软引用、弱引用、虚引用,这四种引用强度依次逐步减弱。

强引用: 类似new出来的对象,只要引用还在永远不会被回收
软引用: 用来描述还有点用但非必须的对象,在内存溢出时将会把这些对象进行回收。
弱引用: 被弱引用的对象只能活到下一次GC发生之前,典型的例子就是触发finalize()过后的对象,如有需要对象都可以重写finalize()方法躲过一次GC
虚引用: 被虚引用肯定会被回收的。

垃圾回收算法

Mark-Sweep(标记-清除)算法

java虚拟机-垃圾回收机制_第1张图片
标记-清除算法分为两个阶段:标记阶段和清除阶段。标记阶段的任务是标记出所有需要被回收的对象,清除阶段就是回收被标记的对象所占用的空间。

但是这种算法很容易产生碎片,碎片太多可能会导致后续过程中需要为大对象分配空间时无法找到足够的空间而提前触发新的一次垃圾收集动作。而且效率也不高

Copying(复制)算法

java虚拟机-垃圾回收机制_第2张图片
它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用的内存空间一次清理掉,这样一来就不容易出现内存碎片的问题。

这种算法虽然实现简单,运行高效且不容易产生内存碎片,但是却对内存空间的使用做出了高昂的代价,因为能够使用的内存缩减到原来的一半。

很显然,Copying算法的效率跟存活对象的数目多少有很大的关系,如果存活对象很多,那么Copying算法的效率将会大大降低。是新生代常用的算法

Mark-Compact(标记-整理)算法

java虚拟机-垃圾回收机制_第3张图片
算法标记阶段和Mark-Sweep一样,但是在完成标记之后,它不是直接清理可回收对象,而是将存活对象都向一端移动,然后清理掉端边界以外的内存。是老年代常用的算法

Generational Collection(分代收集)算法

这是现在java采用的算法,将堆分为新生代和老年代,根据各个年代的特点分别采用不同的算法。

老年代的特点是每次垃圾收集时只有少量对象需要被回收,所以使用标记-整理算法,而新生代的特点是每次垃圾回收时都有大量的对象需要被回收所以使用复制算法,前面的博文也讲过新生代是分为三个区的。

垃圾收集器

Serial

在jdk1.3.1之前是唯一的选择,是一个单线程的收集器。

Serial收集器是针对新生代的收集器,采用的是Copying算法
Serial Old收集器是针对老年代的收集器,采用的是Mark-Compact算法。

它的优点是实现简单高效,但是缺点是会给用户带来停顿,相当于看电视高潮的时候卡屏了,想想那种感觉把!

使用方式:-XX:+UseSerialGC,打开该开关后,使用Serial(年轻代)+Serial Old(老年代) 组合进行GC。

ParNew

并行收集器,Serial的多线程版本,使用多条线程进行垃圾回收,其他特性与Serial一致。需要注意的是,ParNew在单核甚至双核环境下绝对不会有比Serial收集器更好的效果,但是随着CPU数量的增加ParNew相较于Serial的优势会越来越明显,但并不是成倍增长的,原因还是那个,多线程切换开销。

另外ParNew用于垃圾回收的线程可用参数-XX:ParallelGCThreads=n进行配置。建议n与主机逻辑cpu数一致。

使用方式:-XX:+UseParNewGC,打开该开关后,使用ParNew(年轻代)+Serial Old(老年代)组合进行GC。另外,ParNew是CMS收集器的默认年轻代收集器。

Parallel Scavenge

Parallel Scavenge新生代多线程收集器,使用Copying算法
Parallel Old 老年代多线程收集器,使用Mark-Compact算法

Parallel Scavenge收集器的关注点与其他收集器不同, ParallelScavenge收集器的目标则是达到一个可控制的吞吐量(Throughput)。所谓吞吐量就是CPU用于运行用户代码的时间与CPU总消耗时间的比值,即吞吐量 = 运行用户代码时间 /(运行用户代码时间 + 垃圾收集时间),虚拟机总共运行了100分钟,其中垃圾收集花掉1分钟,那吞吐量就是99%。

由于与吞吐量关系密切,Parallel Scavenge收集器也经常被称为“吞吐量优先”收集器。

该垃圾收集器,是JAVA虚拟机在Server模式下的默认值,使用Server模式后,java虚拟机使用Parallel Scavenge收集器(新生代)+ Serial Old收集器(老年代)的收集器组合进行内存回收。

重要的参数有三个,其中两个参数用于精确控制吞吐量,分别是控制最大垃圾收集停顿时间的-XX:MaxGCPauseMillis参数及直接设置吞吐量大小的 -XX:GCTimeRatio参数。另外一个是UseAdaptiveSizePolicy开关参数。

当这个参数打开之后,就不需要手工指定新生代的大小(-Xmn)、Eden与Survivor区的比例(-XX:SurvivorRatio)、晋升老年代对象年龄(-XX:PretenureSizeThreshold)等细节参数了,虚拟机会根据当前系统的运行情况收集性能监控信息,动态调整这些参数以提供最合适的停顿时间或最大的吞吐量,这种调节方式称为GC自适应的调节策略(GC Ergonomics)。

CMS

Concurrent Mark-Sweep 很明显是采用标记-清除算法的并发收集器,用来收集老年代的垃圾减少full gc发生。

在启动JVM参数加上-XX:+UseConcMarkSweepGC表示使用CMS

CMS运行过程:

  • 初始标记(STW initial mark) ***暂停应用
  • 并发标记(Concurrent marking)
  • 并发预清理(Concurrent precleaning)
  • 重新标记(STW remark) *** 暂停 应用
  • 并发清理(Concurrent sweeping)
  • 并发重置(Concurrent reset)

初始标记 : 在这个阶段,需要虚拟机停顿正在执行的任务,官方的叫法STW(Stop The Word)。这个过程从垃圾回收的"根对象"开始,只扫描到能够和"根对象"直接关联的对象,并作标记。所以这个过程虽然暂停了整个JVM,但是很快就完成了。

并发标记 : 这个阶段紧随初始标记阶段,在初始标记的基础上继续向下追溯标记。并发标记阶段,应用程序的线程和并发标记的线程并发执行,所以用户不会感受到停顿。

并发预清理 : 并发预清理阶段仍然是并发的。在这个阶段,虚拟机查找在执行并发标记阶段新进入老年代的对象(可能会有一些对象从新生代晋升到老年代, 或者有一些对象被分配到老年代)。通过重新扫描,减少下一个阶段"重新标记"的工作,因为下一个阶段会Stop The World。

重新标记 : 这个阶段会暂停虚拟机,收集器线程扫描在CMS堆中剩余的对象。扫描从"跟对象"开始向下追溯,并处理对象关联。

并发清理 : 清理垃圾对象,这个阶段收集器线程和应用程序线程并发执行。

并发重置 : 这个阶段,重置CMS收集器的数据结构,等待下一次垃圾回收。

java虚拟机-垃圾回收机制_第4张图片

缺点:
1.因为CMS标记阶段应用程序的线程还是在执行的,那么就会有堆空间继续分配的情况。为了保证在CMS回 收完堆之前还有空间分配给正在运行的应用程序,必须预留一部分空间。
而采用标记清除算法会产生碎片,所以老年代明明还有很多空间却因为碎片无法进行进行分配,不得不提前触发full gc,所以不能把– XX:CMSInitiatingOccupancyFraction设置的太高

2.很占CPU资源。为了让应用程序不停顿,CMS线程和应用程序线程并发执行,这样就需要有更多的CPU,单纯靠线程切 换是不靠谱的。并且,重新标记阶段,为空保证STW快速完成,也要用到更多的甚至所有的CPU资源。

G1

G1把堆重新划分没有整块的新生代和老年代了,而且多了一个Humongous区域,属于一个全新的垃圾收集器,简单看下划分
java虚拟机-垃圾回收机制_第5张图片
从整体来看算是标记整理的算法,从局部来看算是复制算法,所以它不会产生碎片的缺点。

而且G1能充分利用CPU、多核环境下的硬件优势,使用多个CPU(CPU或者CPU核心)来缩短stop-The-World停顿时间。部分其他收集器原本需要停顿Java线程执行的GC动作,G1收集器仍然可以通过并发的方式让java程序继续执行。
G1运行过程:

1、初始标记;
2、并发标记;
3、最终标记;
4、筛选回收;

上面几个步骤的运作过程和CMS有很多相似之处。

初始标记阶段仅仅只是标记一下GC Roots能直接关联到的对象,并且修改TAMS的值,让下一个阶段用户程序并发运行时,能在正确可用的Region中创建新对象,这一阶段需要停顿线程,但是耗时很短。

并发标记阶段是从GC Root开始对堆中对象进行可达性分析,找出存活的对象,这阶段时耗时较长,但可与用户程序并发执行。

而最终标记阶段则是为了修正在并发标记期间因用户程序继续运作而导致标记产生变动的那一部分标记记录,虚拟机将这段时间对象变化记录在线程Remenbered Set Logs里面,最终标记阶段需要把Remembered Set Logs的数据合并到Remembered Set Logs里面,最终标记阶段需要把Remembered Set Logs的数据合并到Remembered Set中,这一阶段需要停顿线程,但是可并行执行。

最后在筛选回收阶段首先对各个Region的回收价值和成本进行排序,根据用户所期望的GC停顿时间来制定回收计划。

你可能感兴趣的:(CMS,G1,GC,垃圾回收机制,垃圾收集,java虚拟机)