如何判断对象是否"已死":

首先,我们要对对象进行垃圾回收之前,就必须要判断对象是否“已死”,也就是是否可回收。这里有两种判断逻辑:

引用计数法:

  1. 在对象内部维护一个引用计数器,每当有一处地方引用该对象时,该计数器就加一,每当有一个引用失效时,该计数器就减一,当引用计数器为0时,表示该对象不可能再被使用,属于可回收对象。

优点: 实现简单,判断效率高。
缺点: 很难解决对象之间循环引用的问题。比如下面代码:

public class Obj1 {private Obj2 obj2;public Obj2 getObj2() {return obj2;}public void setObj2(Obj2 obj2) {this.obj2 = obj2;}
}public class Obj2 {private Obj1 obj1;public Obj1 getObj1() {return obj1;}public void setObj1(Obj1 obj1) {this.obj1 = obj1;}
}public static void main(String[] args) {Obj1 obj11 = new Obj1();Obj2 obj22 = new Obj2();obj11.setObj2(obj22);obj22.setObj1(obj11);}

解析:如上面代码:

  1. 对象obj1持有对象obj2的引用,所以对象obj2的引用计数器为1。
  2. 对象obj2持有对象obj1的引用,所以对象obj1的引用计数器为1。
  3. 在main方法中,引用变量obj11引用着对象obj1,所以对象obj1的引用计数器为2。
  4. 在main方法中,引用变量obj22引用着对象obj2,所以对象obj2的引用计数器为2。
  5. 当main方法执行完之后,引用变量obj11和obj22会失效,所以引用计数器会都减一,变成了1,但是由于这两个对象相互引用,所以引用计数器不会变为0,所以不会被判断为可回收对象,垃圾收集器也不会回收这俩对象,但是这两个对象实际上将不再使用。

因为引用计数法会存在上述循环引用问题,所以现在一般主流虚拟机都不会采用引用计数法来管理内存。

可达性分析算法:

现在java的主流虚拟机都是采用可达性分析算法来判断对象是否存活的。
大致思路:通过一系列成为“GC Root” 的对象作为起始点,从这些节点向下搜索,搜索过的路径称为引用链,当一个对象没有任何的引用链相连,也就是说当一个对象没有直接或者间接地与“GC Root” 相连时,则证明此对象不可用。

在Java语言中,可作为GC Root 对象的有:

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

finalize()方法:

finalize()方法会在对象被回收时被虚拟机执行,但是也不一定被执行,只有该对象重写了finalize()方法并且该对象的finalize()方法没有被虚拟机调用过的情况下,才会被执行。

垃圾回收算法:

标记清除算法:

这个算法分为“标记”和“清除两个阶段”,标记阶段负责把可回收对象标记出来,一般使用可达性分析算法进行标记。然后清除阶段主要负责把标记的可回收对象回收。


该算法缺点:

  1. 效率:标记和清除的操作效率都不高。
  2. 空间问题:该算法回收对象后会导致大量的空间不连续碎片(上图可看出),空间碎片太多可能会导致后面分配比较大的对象时找不到足够的内存空间而提早再次出发gc。

复制算法:

该算法将内存分为大小相等的两个区域(为了保证全对象存活的极端情况,所以要相等保证保留区域足够大以便分配对象),每次只使用其中的一块,当这一个内存用完了,则将这块内存中的存活对象复制到另外一块上,然后将这块内存清空。



优点:实现简单,运行高效,不用考虑空间碎片的复杂情况。
缺点:因为使用了一半的内存作为复制的保留区域,所以内存的使用率只有50%,这个代价未免太高了。因为移动了对象,所以要重新引用。

改进:

  1. 现在商业虚拟机都是采用这种复制算法来回收新生代(young 区)的对象,新生代对象中绝大多数的对象都是“朝生夕死”的,所以并不需要按照1:1的比例来划分空间,而是将新生代划分为一个Eden区,一个s0区,一个s1区三个区域,默认Eden:s0:s1 = 8:1:1。
  2. 每次进行垃圾回收时,都会将Eden区和其中一块使用中的s区的存活对象复制到另外一块未使用s区中,然后把eden区和已使用的s区清空。
  3. 这样只有10%的内存空间会被浪费。
    问题:
    因为不能保证每次垃圾回收时存活的对象都会低于10%,所以s区的10%内存空间可能会出现不足以保存所有存活对象的情况。此时需要老年代的担保机制(具体细节后面说),把剩余的存活对象通过担保机制进入老年代中。

标记整理算法:

复制算法在对象存活率高的老年代中,要进行较多的复制操作,效率会变低,并且如果不想浪费50%的空间,就必须有额外的空间进行担保,以应对极端情况,所以这种算法在老年代不适用。

所以提出了在老年代适用的标记-整理算法,标记过程和标记-清除算法一样,整理过程会把存活的对象都往一端移动,然后直接清理边界以外的内存。



优点:解决了空间碎片问题,又解决了浪费内存和内存担保问题。
缺点:标记过程效率较低,因为移动了对象,所以要重新引用。

分代收集算法:

  1. 当前商业虚拟机都是采用分代收集算法进行垃圾回收,所谓分代收集只是把堆内存分为老年代和新生代,不同的代使用不同的垃圾回收算法。老年代的对象比较稳定,存活率较高,新生代绝大多数对象朝生夕死,存活率低。
  2. 新生代使用复制算法,只需要付出少量存活对象的复制成本,便可完成收集。老年代对象存活率较高,没有额外空间进行担保,所以不能使用复制算法,只能使用标记-清除或者标记-整理算法。

垃圾收集器:

如果说垃圾回收算法是垃圾回收的方法论,那么垃圾收集器则是垃圾回收算法的落地实现。

  1. 上图是常用的垃圾回收器图,serial、parNew、paralled scavenger是新生代的垃圾回收器。CMS、Serial Old Paralled Old是老年代的垃圾回收器。G1既是新生代的也是老年代的。
  2. 如果两个收集器之间存在连线,说明他们可以搭配使用。
  3. 没有最好的垃圾回收器,只有最合适的垃圾回收器。

Serial收集器:

  1. Serial是最基本,历史最悠久的垃圾收集器。基于复制算法实现,是一个新生代收集器。
  2. 是一个单线程收集器,只会使用一条垃圾回收线程去回收垃圾。
  3. 在进行垃圾回收过程中,必须停止所有用户工作线程。直到垃圾回收结束。具有Stop The World特性。
  4. 是虚拟机运行在client模式下的默认垃圾回收器。
  5. 没有线程切换,专心做垃圾回收,可获得最高的线程效率。
  6. 适用于运行内存小(几十兆到一两百兆)的应用,适用于服务器单cpu的场景。

以下是运行原理图:

parNew收集器:

  1. parNew收集器其实就是serial收集器的多线程版本,是一个新生代收集器,除了使用多个线程进行垃圾回收之外,其他包括Serial收集器可用控制参数、收集算法、Stop The World、对象分配规则、回收策略等都是与Serial收集器完全一样。两种收集器在实现上也共用了很多代码。
  2. 是许多运行在server模式下的虚拟机的首选的新生代的收集器,原因是除了Serial收集器外,他是唯一能与老年代收集器CMS收集器搭配使用的收集器。
  3. 如果老年代收集器使用参数-XX:+UseConcMarkSweepGC(CMS)指定CMS收集器,那么,新生代的收集器会默认改为parNew收集器,也可以使用参数-XX:+UseParNewGC强制指定新生代收集器为ParNew收集器。
  4. ParNew在单CPU的环境下性能绝对不会比Serial收集器强,但是在多核环境下,对GC系统资源有效利用还是很有好处,他会默认开启与cpu数相同的线程来进行垃圾回收,也可以使用参数-XX:ParallelGCThreads=xx参数来指定线程数。

Parallel Scavenge收集器:

  1. Parallel Scavenge收集器是一个新生代的多线程并行收集器,基于复制算法实现的。
  2. Parallel Scavenge收集器与ParNew收集器相比的最大特点是它更关注吞吐量。目标是达到一个可控制的吞吐量。所谓的吞吐量=用户代码运行时间/(用户代码运行时间+垃圾回收时间),比如虚拟机运行了100分钟,其中用户代码运行99分钟,垃圾回收1分钟,那么吞吐量=99/(99+1)=99%。
  3. 高吞吐量的垃圾收集器可以更高效率地利用cpu,尽快完成运算任务,主要适合在后台运行而不需要太多交互的应用程序。
  4. -XX:MaxGCPauseMillis=xx ,这个参数是设置垃圾回收的最大停顿时间,参数值是一个大于0的毫秒数,收集器会尽可能地保证垃圾回收时间不超过这个毫秒数,但不是设定的小就一定可以把垃圾回收的时间加快,GC停顿时间的缩短是以牺牲吞吐量和新生代的空间大小换来的。系统会把新生代调小,所以变快了,但是垃圾回收的频率也会变高,假如本来10秒一次,一次100毫秒,一分钟的吞吐量大概=59.4/60=99%。后来5秒一次,一次70毫秒,停顿时间是变低了,但是一分钟吞吐量大概=59.16/60=98.6%,降低了。
  5. -XX:GCTimeRatio=xx ,这个参数是设置收集器的吞吐量。参数值是一个大于0小于100的整数,默认值为99。假设设置参数值为n,可以使用公式1/(n+1)来计算允许的最大GC时间占总时间的比率,假设设置的是19,则允许最大GC时间占总时间的比率=1/(19+1)=5%,如果是默认值99.则为1/100=1%。
  6. -XX:+UseAdaptiveSizePolicy 参数,这是一个开关参数,当这个参数打开后就无需手工指定新生代大小,Eden区与s区的比例,晋升老年代的年龄等参数了。只要MaxGCPauseMillis参数或者设置GCTimeRatio参数给虚拟机设立一个优化目标,然后由虚拟机自动调节已获得最合适的停顿时间和最大的吞吐量,这种方式称为自适应调节策略,是Parallel Scavenge收集器与ParNew收集器的一个重要区别,如果对收集器不太了解,手工优化困难的,使用此策略也是一个不错的选择。

Serial Old收集器:

是Serial收集器的老年代版本,基于标记-整理算法实现的,主要也是给client模式下的虚拟机使用的。

Parallel Old收集器:

  1. 是Parallel Scavenge收集器的老年代版本,基于标记-整理算法的多线程垃圾回收器。JDK1.6之后提供的,解决了新生代用Parallel Scavenge之后,老年代只能用Serial Old的问题,因为Serial会拖累Parallel Scavenge获得吞吐量最大化的效果。
  2. Parallel Scavenge 和 Parallel Old组合可以很好地应用在注重吞吐量和CPU利用率的场合。

CMS收集器(Concurrent Mark Sweep):

  1. CMS收集器是一种以最短回收停顿时间为目标的收集器。非常符合Java web、B/S模式的多交互应用程序。基于标记-清除算法实现的的。
  2. 该收集器进行垃圾回收会分为四步:
    a)初始标记:Stop The World特性,会停止所有用户线程,但是仅仅是标记一下GC Root能直接关联到的对象。也就是不追踪引用链,仅仅标记出被GC Root直接引用的对象。速度很快,用单个线程完成。
    b)并发标记:进行引用链的追踪,使用多个线程进行并发标记,标记每一个被GC Root间接引用的对象。此过程不会停止用户线程,用户线程仍然可以运行,只是由于标记线程会占用cpu资源,所以此过程中用户线程的运行速度可能会降低。
    c)重新标记:为了修正在并发标记期间,用户线程运行而导致标记产生变动的那一部分对象标记记录。停顿时间会比初始标记长一点,但远比并发标记时间短。此阶段是采用多条线程去执行,并且有Stop The World特性,停止所有用户线程。
    d)并发清除:对可回收对象进行清除,多条线程并发执行,此过程不会停止用户线程,清除时间也比初始标记和重新标记长。

因为有STW性质初始标记阶段和重新标记阶段停顿时间都很短,然后停顿时间较长的并发标记和并发清除都不停止用户线程,所以从总体上看内存回收过程是与用户线程一起并发执行的。

缺点:

  1. CMS收集器对CPU资源非常敏感,在并发阶段虽然不会停止用户线程,但是会占用一部分CPU资源,导致系统吞吐量降低。CMS默认启动的回收线程=(CPU数量+3)/ 4。假如有5个cpu,就会默认启动2个回收线程。回收线程占总资源的40%,这个占用率会在cpu个数增加时而减少,但是不会低于25%。在CPU不够4个的情况下,会对用户程序的影响很大。比如2个时就会占用50%的CPU资源。
  2. CMS无法处理浮动垃圾,从而发生“Concurrent Mode Failure”错误,导致再一次full gc。因为CMS的并发清理阶段用户线程也在运行着,该期间产生的垃圾对象称为浮动垃圾。要等待下次垃圾清理清理掉。所以CMS要预留一部分老年代的内存空间给并发清理阶段用户线程运行所产生的对象分配。所以CMS收集器不是在老年代被填满时才进行垃圾回收。而是在老年代被使用空间达到一个阈值时就进行垃圾回收。jdk1.5默认68%,jdk1.6默认92%,可以通过参数:-XX:CMSInitiatingOccupancyFraction=xx来指定触发阈值,可根据程序实际情况来指定,如果老年代对象增长慢的话,就把阈值设置高点,反之设置低点。如果预存的内存无法满足需要就会发生“Concurrent Mode Failure”错误,然后启动备用方案,使用Serial Old收集器重新进行垃圾收集,这个停顿时间就长了。所以要根据实际情况不断调整阈值,达到最合适的阈值,阈值太高的好,预存的空间就会变小,就可能会频繁地导致Concurrent Mode Failure”错误,性能反而降低。如果设置的太低的话,预留空间变大,但是使用空间就会变小,可能会导致比较频繁的老年代GC。
  3. 因为CMS收集器是使用标记清除算法的,所以垃圾回收后会有大量的空间碎片问题。所以CMS收集器提供了一个参数-XX:+UseCMSCompactAtFullCollection开关参数,默认开启,该参数开启后会在进行Full GC之前进行一个碎片整理,碎片整理过程停止所有用户线程。还有另外一个参数:-XX:CMSFullGCsBeforeCompaction=xx,这个参数是设置在进行xx次不碎片整理的Full GC后,进行一个碎片整理的full gc。假设设置5,就是每5次Full GC前都不进行碎片整理,第六次FullGC前就进行碎片整理。默认值为0,表示每次都进行碎片整理。

G1收集器(Garbage First):

G1收集器是当今收集器中最前沿的成果之一,在jdk7u4之后达到足够商用的成熟度。是一款面向服务端应用的垃圾收集器。未来可能会替换的CMS收集器。
特点:

  1. 并行与并发:G1能够充分利用多CPU、多核环境下的硬件优势。使用多CPU来减少STW的停顿时间。部分其它垃圾收集器需要停顿用户线程来进行GC。G1收集器却可以通过并发的方式让用户程序继续执行。
  2. 分代收集:与其他收集器一样,分代概念在G1中仍然保留。虽然G1收集器可以不需要其他收集器配合而独立管理整个GC堆内存。但他能够采用不同的方式处理新创建的对象和已经旧对象以得到更好的收集效果。
    3.空间整合: 与CMS的标记-清除算法不同,CMS从整体上看使用标记-整理算法,实际上更复杂,只需要知道G1收集器在进行垃圾回收时不会产生空间碎片即可。
  3. 可预测的停顿:G1收集器在追求低停顿的前提下,还能建立可预测的停顿时间模型,让使用者在指定一个长度为M毫秒的时间片段内,消耗在垃圾收集上的时间不超过N毫秒。

G1收集器细节:

  1. G1收集器不再区分新生代和老年代了,而是把整个java堆划分为多个大小相等的独立区域。
  2. G1能够预测停顿时间模型,是他避免了进行在整个java堆中进行全区域的垃圾回收。G1会追踪堆中背个独立区域的垃圾堆积的价值大小(由回收所获得的空间大小以及回收所需的时间决定)。在后台维护一张优先级列表。每次根据允许的收集时间优先回收价值最大的独立区域。这种使用region划分内存空间以及有优先级的回收方式,保证了G1收集器在优先回收时间内可以尽可能地得到高效率收集。

G1收集器回收大致可分为以下四步:

  1. 初始标记:与CMS相似。
  2. 并发标记:与CMS相似。
  3. 最终标记:与CMS相似。
  4. 筛选回收:首先对各个region的回收价值和成本进行排序,根据用户设置的GC 停顿值来进行回收计划,而不是回收所有region。该阶段是停止用户线程的。

总结:该收集器总的来说就是把堆内存划分为多个region区域,每次根据优先级进行回收而获得高效率地回收。但是因为划分了多个region,所以每次回收的空间都不大,所以可能会使得垃圾回收的频率会较为地多,牺牲了一些吞吐量,但是回收地快,停顿时间少,所以称该收集器是关注停顿时间的收集器。

最后附上一张垃圾收集器相关的参数:

出自周志明老师的《深入理解Java虚拟机》:


这里都没有对垃圾收集器的性能进行验证收集。但是会在后面的博客中展出。

JVM初学之JVM的垃圾回收机制与垃圾回收器相关推荐

  1. jvm垃圾回收机制_JVM 垃圾回收机制之堆的分代回收

    JVM垃圾回收机制之堆的分代回收 前言 前文我们了解了Java的GC机制,对于堆中的对象,JVM采用引用计数和可达性分析两种算法来标记对象是否可以清除,本文中我们还会了解到JVM将对分成了不同的区域, ...

  2. JVM架构、JVM垃圾回收机制、垃圾回收算法、垃圾回收器、JMM(内存模型)

    0 JVM和Java的关系 JDK = JRE + Java开发工具(java,javac,javadoc,javap-) JRE = JVM + Java核心类库 即: JDK = JVM + Ja ...

  3. JVM (二) 垃圾回收机制概念+垃圾回收器种类

    前言 做一个有趣的程序员.哈哈哈哈 本次铁村的小蓝猫主要给大家详细分享JVM中垃圾回收机制 学习JVM 肯定是要了解垃圾回收机制的. 分享前,我们先了解下本次分享内容的框架. 一.垃圾回收机制定义 1 ...

  4. java对于垃圾回收机制[GC垃圾回收机制] 为什么有GC还会有内存溢出呢?

    java垃圾回收机制 来源于书本和工作中的总结. 内存泄露 如果分配出去的内存得不到释放,及时回收,就会引起系统运行速度下降,甚至导致程序瘫痪,这就是内存泄露 GC机制 java内存分配和回收 都是j ...

  5. php7垃圾回收机制l_PHP7 垃圾回收机制(GC)解析

    垃圾回收机制 垃圾回收机制是一种动态存储分配方案.它会自动释放程序不再需要的已分配的内存块. 自动回收内存的过程叫垃圾收集.垃圾回收机制可以让程序员不必过分关心程序内存分配,从而将更多的精力投入到业务 ...

  6. python中垃圾回收机制_python 垃圾回收机制

    首先我们要说是 以引用计数为主 标记清楚和分代回收为辅 接下来分以下几个方面解释 一 引用计数 每个对象内部都维护了一个值,该值记录这此对象被引用的次数,如果次数为0,则Python垃圾回收机制会自动 ...

  7. 垃圾回收机制?垃圾回收的流程?

    垃圾回收机制? Java中,一个显著地特点是引入了垃圾回收机制,编写程序时,不再需要考虑内存管理,有效的防止内存泄露,提高内存的内存率. 垃圾回收器通常作为一个单独的低级线程运行,不可预知的情况下,对 ...

  8. JS 垃圾回收机制以及垃圾回收策略

    垃圾回收机制 什么是垃圾回收机制: 解释:执行环境负责管理代码执行过程中使用的内存.JS的垃圾回收机制是为了以防内存泄漏,简单来说就是:间歇的不定期的寻找到不再使用的变量,并释放掉它们所指向的内存. ...

  9. JVM垃圾回收机制(超级无敌认真好用,万字收藏篇!!!!)

    文章目录 JVM垃圾回收机制 1 判断对象是否存活的算法 1.1 引用计数器算法 1.2 可达性分析算法 2 对象的四种引用方式 2.1 强引用 2.2 软引用 2.3 弱引用 2.4 虚引用 3 垃 ...

最新文章

  1. DropFileName = “svchost.exe“ 问题解决方案
  2. 拦截导弹问题(信息学奥赛一本通-T1322)
  3. 【guava】大数据量下的集合过滤—Bloom Filter
  4. javascript 创建ajax函数 四部曲
  5. java io读取文件_java io读取文件操作代码实例
  6. LeetCode每日一题——剑指 Offer 10- I. 斐波那契数列
  7. 「HNOI 2015」实验比较
  8. VS 2010 快捷键
  9. 分享240道有意思的逻辑思维题
  10. Windows批处理文件bat学习(一)
  11. CSS font-size单位
  12. 2023年全国最新二级建造师精选真题及答案60
  13. 基于springboot的资产管理系统
  14. centos7搭建开源ERP-PSI
  15. $inject的用法
  16. ubuntu 手机连接不到电脑,配置
  17. Apriori 算法原理以及python实现详解
  18. ssm基于Java和MySql的产业信息管理系统的设计与实现毕业设计源码260839
  19. 充电宝转换率排行,转化率最好的充电宝推荐
  20. msm8953 android7.1 配置笔记

热门文章

  1. Storm案例:统计单词个数
  2. 专业英语笔记:Install and Use Python
  3. 【BZOJ1040】【codevs1423】骑士,第一次的基环外向树DP
  4. 【Tyvj3500】【BZOJ1031】字符加密,后缀数组
  5. 游戏教玩家学java,技术|帮你学习Java语言的游戏
  6. 2017.9.9 股票交易 思考记录
  7. 【英语学习】【Level 08】U05 Better option L4 Being social
  8. linux为mysql用户授权,Linux环境 Mysql新建用户和数据库并授权
  9. hosts多个ip对应一个主机名_Ubuntu16.04修改主机名和查看主机名的方法
  10. jquery获得当前元素父级元素_灵活运用各种时尚元素,轻松获得街头法式浪漫风格...