Java 虚拟机垃圾搜集

垃圾搜集(GC)需要完成的三件事情。What:哪些内容需要回收?When:什么时后回收?How:如何回收?


What:哪些内容需要回收?

垃圾搜集主要关注的是java堆和方法区的内存回收。一个接口中的多个实现类需要的内存可能不一样,一个方法中的多个分支需要的内存也可能不一样,我们只有在程序处于运行期间才知道创建了哪些对象,这部分内存的和回收都是动态的。


When:什么时后回收?

当然是对象没有存在的意义了才会回收,不然回收了正在使用的对象,程序很容易就崩了。那如何确认对象时存活还是死去呢?常用用的方法是引用计数法和根搜索算法。


(1)引用计数算法
给对象中添加一个引用计数器,每当有一个地方引用它时,计数器的值就加1;当引用失效时,计数器的值就减1;任何时刻计数器的都为0的对象就是不可能再被使用的。
最由于它很难解决对象之间相互循环引用的问题,所以java语言中并没有选用引用计数算法来管理内存。


(2)根搜索算法
java实际上采用的是根搜索算法来判定对象是都存活的。基本思路就是通过一系列名称为“GC Roots”的对象作为起点,从这些起点开始向下搜索,搜索所走过的路径成为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连时,则证明此对象时不可用的。
可作为GC Roots的对象包括虚拟机栈引用对象,方法区中的静态属性和常量引用的对象,本地方法栈中JNI的引用对象。


为什么他们可以作为GC Roots的对象呢?

因为GC管理的主要区域是Java堆,一般情况下只针对堆进行垃圾回收。方法区、栈和本地方法区不被GC所管理,因而选择这些区域内的对象作为GC roots,被GC roots引用的对象不被GC回收。

宣告一个对象死亡至少需要两次标记过程。如果对象在进行根搜索后发现没有与GC Roots向连接的引用链,它将被第一次标记并且执行一次筛选。如果对象覆盖了finalize()方法且未被虚拟机调用过,这个对象会被放入一个F-Queue的队列,虚拟机会触发它的finalize()方法。finalize()方法是对象逃脱死亡的最后一次机会,稍后GC会对F-Queue中的对象进行第二次标记。如果对象想在finalize()中成功拯救自己,只要重新与引用链上的任何一个对象建立关联即可。任何一个对象的finalize()只会被系统调用一次,也就是只有一次拯救自己的机会。

java中引用分为如下四种类型:
强引用:类似 Object obj = new Object();只要强引用还存在,GC永远不会回收掉被引用的对象。
软引用:SoftReference用来描述还有用,但是非必须的对象。对于软引用关联的对象,在系统将要发生内存溢出异常之前,将会把这些对象列进回收范围并进行二次回收。如果这次回收还是没有足够的内存,才会抛出内存溢出异常。
弱引用:WeakReference也是描述非必须的对象。当GC发生时,无论当前内存是否足够,都会被回收。
虚引用:PhantomReference最弱的引用关系。为一个对象设置虚引用的唯一目的就是在这个对象被GC时收到一个系统通知。


How:如何回收?


垃圾搜集的方法论-垃圾搜集算法
(1)标记-清除算法:首先标记出所有需要回收的对象,在标记完成后统一回收掉所有被标记的对象。缺点:效率低,标记和清楚效率都不高;空间问题,标记清楚后会产生的大量的不连续内存碎片。碎片太多,在分配机较大对象时,可能因为无法找到足够的连续内存不得不提前触发一次GC.
(2)复制算法:为了解决效率问题出现的算法。将可用内存划分为容量大小相等的两块,每次只使用其中一,一块用完之后将还存活的对象复制到另外一块上面,然后清理掉已使用的一块呢内存空间。代价时内存缩小为原来的一半。
(3)标记-整理算法:标记过程和“标记-清除”算法一样,后续步骤是让所有存活对象都向一端移动,然后直接清理掉短边界以外的内存。
(4)分代搜集算法:当前商业虚拟机的垃圾搜集都采用这个算法。一般是把Java堆分为新生代和老年代。在新生代中,每次垃圾搜集时都发现有大批对象死去,只有少量存活,那就选用复制算法,只有少量的复制成本。老年代对象存活率高,没有额外的空间对他进行分配担保,所以必须使用“标记-清除”或者“标记-整理”算法来进行回收。

垃圾搜集的具体实现-GC搜集器
包括Serial搜集器,ParNew搜集器,Parallel Scavenge搜集器,serial old搜集器,parallel old搜集器,cms搜集器,g1搜集器

(1)Serial搜集器
单线程的新生代搜集器,采用复制算法。会使用一个CPU或者一个线程区完成垃圾搜集,它在搜集时必须暂停所有其他所有的工作线程。
优点是使用单线程搜集,没有线程交互开销,简单高效,专心做垃圾搜集可以获得最高的单线程搜集率。
Serial搜集器运行在Client模式下的虚拟机的不错选择。

(2)ParNew搜集器
是Serial搜集器的多线程版本,也是新生代搜集器,采用复制算法。是许多运行在server模式下的虚拟机首选的新生代搜集器。

(3)Parallel Scavenge搜集器
并行的多线程搜集器,是一个新生代搜集器,采用复制算法。它关心的是CPU的吞吐量,是一个吞吐量优先的搜集器。
吞吐量 = 用户执行代码的时间/(运行代码时间+垃圾搜集时间)
-XX:+UseAdaptiveSizePplicy 这个参数打开后,就不需要手工指定新生代的大小(-Xmn),Eden与Survivor区的比例、晋升老年代对象的年龄(-XX:PretenureSizeThreshold)等参数细节了,虚拟机会根据当前系统的运行情况搜集性能监控信息,动态调整这些参数来提供最合适的停顿时间或者最大吞吐量,这种调节方式成为GC自适应调节策略。
如果对搜集器的工作原理不太了解,可以只设置基本的内存数据(如-Xmx设置最大堆),然后使用MaxGCPauseMillis参数(更关注最大停顿时间)或者GCTimeRatio(更关注吞吐量)参数给虚拟机设立一个优化目标,具体细节参数的调解就由虚拟机自己完成。

(4)serial old搜集器
是serial搜集器的老年代版本,也是一个单线程搜集器,使用标记-整理算法。主要给Client模式下的虚拟机使用。

(5)parallel old搜集器
parallel old是parallel Scavenge搜集器的老年代版本,使用多线程和标记整理算法。在注重吞吐量以及cpu资源敏感的场合,可以优先考虑parallel Scavenge 和parallel old搜集器的组合。

(6)cms 搜集器(Concurrent Mark Sweep)
是一种与获取最短停顿时间为目标的收集器,使用标记-整理算法,它的优点是并发收集,低停顿。目前很大一部分的java应用集中在互联网站或者bs系统的服务端上这类应用尤其重视服务的响应速度,希望系统停顿的时间最短给用户带来较好的体验。Cms收集器就非常符合这类应用的需求。

Cms收集器的过程分为四步。初始标记、并发标记、重新标记和并发清除。初始标记和重新标记两个步骤,仍然需要stop the world.整个过程中耗时最长的并发标记和并发清除过程收集器,线程都是可以与用户线程一起工作。所以总地来说,cms收集器的内存回收过程是与用户线程一起并发执行的。

(7)g1搜集器
面向服务端应用的垃圾搜集器,是当今搜集器发展的最前沿成果之一。它能充分利用多cpu,多核环境下的硬件优势。其他收集器原本需要停顿iava线程执行的gc操作,G1搜集器仍然可以通过并发的方式让java程序继续执行。从整体来看g1搜集器是基于标记整理实现搜集,从局部来看是基于复制算法实现的。整使用这两种算法意味着G1运作期间不会产生内存空间碎片,搜集后能提供规整的可用内存。这种特性有利于程序长时间运行,分配大对象时不会因为无法找到连续的内存空间提前出发下一次GC.使用者可以明确指定在一个长度为M毫秒的时间片内,消耗在垃圾搜集上的时间不得超过N毫秒。

G1搜集器运作步骤:
1、初始标记。标记一下GC Roots能直接关联到的对象。
2、并发标记。从GC Roots开始对堆中的对象进行可达性分析,找出存活对象,耗时较长,但是可以与用户程序并发执行。
3、最终标记。修正在并发标记期间因为用户程序继续运行导致标记产生变动的那一部分标记记录。
4、筛选回收。对各个Region的回收价值和成本进行排序,根据用户所期望的GC停顿时间来执行回收计划。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值