JVM-垃圾回收

1.垃圾回收如何判定

  1.1引用计数法

    引用计数法是给对象添加一个引用计数器,当有对该对象的引用时,计数器加1,引用失效时,计数减1,计数器为0时不能再使用.该对象可以被垃圾回收器回收,但是存在一个问题,就是当两个对象相互进行引用时,它们的计数器最终都不会为0,导致垃圾回收器无法回收它们。

1.2可达性分析算法

    算法的基本思路就是通过一系列的称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连(用图论的话来说,就是从GC Roots到这个对象不可达)时,则证明此对象是不可用的。

    Java语言中,可作为GC Roots的对象包括下面几种:
    虚拟机栈(栈帧中的本地变量表)中引用的对象。
    方法区中类静态属性引用的对象。
    方法区中常量引用的对象。
    本地方法栈中JNI(即一般说的Native方法)引用的对象

  1.3判断当前引用类型

    Java对引用的概念进行了扩充,将引用分为强引用(Strong Reference)、 软引用(Soft Reference)、 弱引用(Weak Reference)、 虚引用(Phantom Reference)4种,这4种引用强度依次逐渐减弱

    强引用就是指在程序代码之中普遍存在的,类似“Object obj=new Object()”这类的引用,只要强引用还存在,垃圾收集器永远不会回收掉被引用的对象。

     软引用是用来描述一些还有用但并非必需的对象。 对于软引用关联着的对象,在系统将要发生内存溢出异常之前,将会把这些对象列进回收范围之中进行第二次回收。 如果这次回收还没有足够的内存,才会抛出内存溢出异常。 在JDK 1.2之后,提供了SoftReference类来实现软引用。

      弱引用也是用来描述非必需对象的,但是它的强度比软引用更弱一些,被弱引用关联的对象只能生存到下一次垃圾收集发生之前。 当垃圾收集器工作时,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。 在JDK 1.2之后,提供了WeakReference类来实现弱引用。

      虚引用也称为幽灵引用或者幻影引用,它是最弱的一种引用关系。 一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。 为一个对象设置虚引用关联的唯一目的就是能在这个对象被收集器回收时收到一个系统通知。 在JDK 1.2之后,提供了PhantomReference类来实现虚引用

  1.4判断对象是否可以回收

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

  1.5针对永久代的垃圾回收

    永久代的垃圾收集主要回收两部分内容:废弃常量和无用的类。

   废弃常量:没有任何对象是当前String类型的常量,比如说当前String s = "adc",当没有对象引用"adc"时,该常量就可以被垃圾回收器回收

    废弃的无用类:Java堆中不存在该类的任何实例

           加载该类的ClassLoader已经被回收;

           java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。

2.垃圾回收算法

  2.1标记-清除算法

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

   优点:实现原理简单

   缺点:标记和清理时效率不高;清楚过程中会产生内存碎片。

  2.2复制算法

   它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。 当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉

   优点:实现简单,运行高效

   缺点:浪费了一半的内存,需要将内存缩小为原来的一半来复制存活的对象区域。

  2.3标记-整理算法

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

  2.4分代收集算法   

  根据对象存活周期的不同将内存划分为几块。 一般是把Java堆分为新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法。 在新生代中,每次垃圾收集时都发现有大批对象死去,只有少量存活,那就选用复制算法,只需要付出少量存活对象的复制成本就可以完成收集。 而老年代中因为对象存活率高、 没有额外空间对它进行分配担保,就必须使用“标记—清理”或者“标记—整理”算法来进行回收

   新的对象分配在Eden 和 Survivor1 这两个空间中如果空间不足, 发起GC, 将 Eden , Survivor1中的存活对象移动到Survivor2中然后将Eden, Survivor1中对象清理掉。 -- 如果Survivor2的空间也不够,则依赖其他内存(老年代)接下来新对象会被分配在Eden 和Survivor2中, 即 Survivor1 变成了完全空闲空间,它和Survivor2的角色进行了转换 , 如此循环下去。

3.垃圾收集器

  3.1 Serial/Serial Old收集器

  Serial收集器是单线程收集器,在进行垃圾回收时,必须暂停其他所有线程

  Serial Old收集器是Serial收集器的老年代版本

  Serial收集器和Serial Old收集器对新生代都采用了复制算法,对老年代采用了标记-整理算法,都可以在Client模式下的虚拟机使用,在Server模式下,Serial Old还可以搭配Parallel Scavenge ,在并发收集发生Concurrent Mode Failure时作为CMS收集器的备用方案使用

  3.2 ParNew收集器

  是Serial收集器的多线程版本,也需要暂停用户线程,对象分配与回收策略与Serial收集器基本一致,除了Serial收集器,只有它能与CMS搭配使用。

  3.3 Parallel Scavenge/Parallel Old收集器       

    Parallel Scavenge:针对新生代, 多线程, 采用复制算法    

    Parallel Old: 针对老年代, 多线程,标记-整理算法    

           吞吐量优先:用户代码运行时间/(垃圾收集时间+用户代码运行时间),虚拟机总共运行了100分钟,其中垃圾收集花掉1分钟,那吞吐量就是99%

  3.5 CMS收集器   

  CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器 ,

  应用场合互联网,B/S服务器端,重视服务的响应速度,希望系统停顿时间最短,以给用户带来较好的体验

  采用标记-清除算法,易产生碎片。

  处理过程:初始标记,并发标记,重新标记,并发清理

  初始标记和重新标记是需要暂停用户线程 

  3.6 G1收集器

  面向服务器端应用的垃圾回收器,针对老年代和年轻代,采用标记整理算法

  具有以下四个特征:

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

  分代收集:与其他收集器一样,分代概念在G1中依然得以保留。 虽然G1可以不需要其他收集器配合就能独立管理整个GC堆,但它能够采用不同的方式去处理新创建的对象和已经存活了一段时间、 熬过多次GC的旧对象以获取更好的收集效果。

  空间整合:与CMS的“标记—清理”算法不同,G1从整体来看是基于“标记—整理”算法实现的收集器,从局部(两个Region之间)上来看是基于“复制”算法实现的,但无论如何,这两种算法都意味着G1运作期间不会产生内存空间碎片,收集后能提供规整的可用内存。 这种特性有利于程序长时间运行,分配大对象时不会因为无法找到连续内存空间而提前触发下一次GC。  

  可预测的停顿:这是G1相对于CMS的另一大优势,降低停顿时间是G1和CMS共同的关注点,但G1除了追求低停顿外,还能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为M毫秒的时间片段内,消耗在垃圾收集上的时间不得超过N毫秒,这几乎已经是实时Java(RTSJ)的垃圾收集器的特征了。

  处理过程:初始标记,并发标记,最终标记,筛选标记

4.内存分配与回收策略

  4.1对象优先在Eden分配

  大多数情况下,对象在新生代Eden区中分配。 当Eden区没有足够空间进行分配时,虚拟机将发起一次Minor GC 。

  新生代GC(Minor GC):指发生在新生代的垃圾收集动作,因为Java对象大多都具备朝生夕灭的特性,所以Minor GC非常频繁,一般回收速度也比较快。

  老年代GC(Major GC/Full GC):指发生在老年代的GC,出现了Major GC,经常会伴随至少一次的Minor GC(但非绝对的,在Parallel Scavenge收集器的收集策略里就有直接进行Major GC的策略选择过程)。 Major GC的速度一般会比Minor GC慢10倍以上。  

  4.2大对象直接进入老年代

  大对象是指,需要大量连续内存空间的Java对象,最典型的大对象就是那种很长的字符串以及数组(笔者列出的例子中的byte[]数组就是典型的大对象)

  4.3长期存活的对象将进入老年代

  虚拟机给每个对象定义了一个对象年龄(Age)计数器。 如果对象在Eden出生并经过第一次Minor GC后仍然存活,并且能被Survivor容纳的话,将被移动到Survivor空间中,并且对象年龄设为1。 对象在Survivor区中每“熬过”一次Minor GC,年龄就增加1岁,当它的年龄增加到一定程度(默认为15岁),就将会被晋升到老年代中

  4.4动态对象年龄判定

  并不总是要求对象的年龄到达一定程度才进入老年代。如果在Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代

  4.5空间分配担保

  Minor GC之前,虚拟机会先检查老年代最大可用的连续空间是否大于新生代所有对象总空间,如果这个条件成立,那么Minor GC可以确保是安全的。 如果不成立,则虚拟机会查看HandlePromotionFailure设置值是否允许担保失败。 如果允许,那么会继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小,如果大于,将尝试着进行一次Minor GC,尽管这次Minor GC是有风险的;如果小于,或者HandlePromotionFailure设置不允许冒险,那这时也要改为进行一次Full GC。

  此处冒险是指:新生代使用复制收集算法,但为了内存利用率,只使用其中一个Survivor空间来作为轮换备份,因此当出现大量对象在MinorGC后仍然存活的情况(最极端的情况就是内存回收后新生代中所有对象都存活),就需要老年代进行分配担保,把Survivor无法容纳的对象直接进入老年代。 与生活中的贷款担保类似,老年代要进行这样的担保,前提是老年代本身还有容纳这些对象的剩余空间,一共有多少对象会活下来在实际完成内存回收之前是无法明确知道的,所以只好取之前每一次回收晋升到老年代对象容量的平均大小值作为经验值,与老年代的剩余空间进行比较,决定是否进行Full GC来让老年代腾出更多空间

该文章大部分参考自:《深入理解Java虚拟机》周志明 著

时间: 2024-10-19 05:16:56

JVM-垃圾回收的相关文章

JVM垃圾回收算法 总结及汇总

先看一眼JVM虚拟机运行时的内存模型: 1.方法区 Perm(永久代.非堆) 2.虚拟机栈 3.本地方法栈 (Native方法) 4.堆 5.程序计数器 1 首先的问题是:jvm如何知道那些对象需要回收 ? 目前两种标识算法.三种回收算法.两种清除算法.三种收集器 引用计数法 每个对象上都有一个引用计数,对象每被引用一次,引用计数器就+1,对象引用被释放,引用计数器-1,直到对象的引用计数为0,对象就标识可以回收 这个可以用数据算法中的图形表示,对象A-对象B-对象C 都有引用,所以不会被回收,

JVM垃圾回收算法(最全)

JVM垃圾回收算法(最全) 下面是JVM虚拟机运行时的内存模型: 1.方法区 Perm(永久代.非堆) 2.虚拟机栈 3.本地方法栈 (Native方法) 4.堆 5.程序计数器 1 首先的问题是:jvm如何知道那些对象需要回收 ? 目前两种标识算法.三种回收算法.两种清除算法.三种收集器 引用计数法 每个对象上都有一个引用计数,对象每被引用一次,引用计数器就+1,对象引用被释放,引用计数器-1,直到对象的引用计数为0,对象就标识可以回收 这个可以用数据算法中的图形表示,对象A-对象B-对象C

JVM垃圾回收机制

JVM采用分代的垃圾回收策略:不同的对象的生命周期是不一样的.因此,不同生命周期的对象可以采取不同的收集方式,以便提高回收效率. 从垃圾回收角度看内存分配 从JVM垃圾回收的角度来看,Java内存分为三个区:新生代(Young Generation).老年代(Old Generation)和持久代(Permanent Generation),如下图. 新生代: 所有新生成的对象首先都是放在年轻代的.年轻代分三个区.一个Eden区,两个Survivor区(一般而言).大部分对象在Eden区中生成.

JDK分析工具&JVM垃圾回收(转)

转自:http://blog.163.com/[email protected]/blog/static/10510751320144201519454/ 官方手册:http://docs.oracle.com/javase/7/docs/     ---->http://docs.oracle.com/javase/7/docs/technotes/tools/solaris/java.html   java命令的各种选项的说明 参考书籍: <深入理解Java虚拟机:JVM高级特性与最佳实践

浅析JVM垃圾回收机制

首先我们需要知道Java的内存分配与回收全部由JVM垃圾回收机制自动完成.每种JVM实现可能采用不同的方法实现垃圾回收机制.在收购SUN之前,Oracle使用的是JRockit JVM,收购之后使用HotSpot JVM.目前Oracle拥有两种JVM实现并且一段时间后两个JVM实现会合二为一.HotSpot JVM是目前Oracle SE平台标准核心组件的一部分.市面上探讨垃圾回收机制,默认都是基于HotSpot JVM的.Ok,我们切入正题,先来看下JVM的内存区域模型, 这张图非常直观的展

JVM内存管理和JVM垃圾回收机制

JVM内存管理和JVM垃圾回收机制(1) 这里向大家描述一下JVM学习笔记之JVM内存管理和JVM垃圾回收的概念,JVM内存结构由堆.栈.本地方法栈.方法区等部分组成,另外JVM分别对新生代和旧生代采用不同的垃圾回收机制. AD: 你对JVM内存组成结构和JVM垃圾回收机制是否熟悉,这里和大家简单分享一下,希望对你的学习有所帮助,首先来看一下JVM内存结构,它是由堆.栈.本地方法栈.方法区等部分组成,结构图如下所示. JVM学习笔记 JVM内存管理和JVM垃圾回收 JVM内存组成结构 JVM内存

JVM垃圾回收机制与算法

JVM内存由几个部分组成:堆.方法区.栈.程序计数器.本地方法栈 JVM垃圾回收仅针对公共内存区域,即:堆和方法区进行,因为只有这两个区域在运行时才能知道需要创建些对象,其内存分配和回收都是动态的. 一.垃圾回收策略 1.1分代管理 将堆和方法区按照对象不同年龄进行分代: (Young Generation and Old Generation)堆中会频繁创建对象,基于一种分代的思想,按照对象存活时间将堆划分为新生代和旧生代两部分,并不是一次垃圾回收新生代存活的对象就放入旧生代, 而是要经过几次

JVM垃圾回收算法总结

整理了一下JVM垃圾回收的分代回收算法,旨在能够以后能够快速熟悉这些算法,而不用去查找大量资料(可以认为是偷懒),也是为了分纤箱一下自己的一些理解,有不足或错误之处,希望大家指正,共同进步!1.分代回收算法分代回收算法是标记-复制算法和标记-整理算法(标记-清楚)的集合,朱亚平是对新生代和老年代分别进行处理:在介绍分代回收算法之前首先介绍一下标记-复制算法.标记-清除和标记-整理算法:1)标记-复制算法:将可用内存分为两部分,只用其中的一部分,当这部分内存用完时,会将存活的对象复制到另一部分内存

JVM垃圾回收 GC

一.判断对象是否存活 1.引用计数算法 给对象添加一个引用计数器,每当一个地方引用了该对象,计数器加1,:当引用失效,计数器减1.当计数器为0表示该对象已死,可回收.但是无法解决两个对象互相引用的情况 2.可达性分析算法 通过一系列称为的GC Roots对象为起点,从这些节点往下搜索,搜索走过的路径为引用链,当一个对象到GC Roots没有任何引用链相连是,即对象到GC Roots不可达,则证明对象已死,可回收 可作为GC Roots的对象包括:虚拟机栈中引用的对象,本地方法栈中Native方法

必知必会JVM垃圾回收——对象搜索算法与回收算法

垃圾回收(GC)是JVM的一大杀器,它使程序员可以更高效地专注于程序的开发设计,而不用过多地考虑对象的创建销毁等操作.但是这并不是说程序员不需要了解GC.GC只是Java编程中一项自动化工具,任何一个工具都有它适用的范围,当超出它的范围的时候,可能它将不是那么自动,而是需要人工去了解与适应地适用. 拥有一定工作年限的程序员,在工作期间肯定会经常碰到像内存溢出.内存泄露.高并发的场景.这时候在应对这些问题或场景时,如果对GC不了解,很可能会成为个人的发展瓶颈. 接下来的两文将详细学习下JVM中垃圾