Java垃圾回收机制

一、问题
1、哪些内存需要回收
2、什么时候回收、
3、如何回收

二、那些内存需要回收
1、不需要考虑回收的部分:
Java运行时区域的各个部分,其中程序计数器,虚拟机栈,本地方法栈这三个区域随线程而生,随线程而灭;栈中的栈帧随着方法的进入和退出有条不紊的出栈和进栈。因为每一个栈帧分配多少内存基本是在类结构确定下来的时候就已知了,所以这几个区域的内存分配以及回收都具备确定性,所以在这几个区域就不考虑回收的问题了,因为方法或者线程结束时,内存自然就跟着回收了。
2、需要回收的内存(Java堆和方法区)
因为在这两个地方,一个接口的多个实现类所需要的内存可能不一样,一个方法的多个分支需要的内存也不一样,我们只有在程序运行期间时候才会知道创建了哪些对象,这部分内存的分配和回收都是动态的。垃圾收集器所关注的就是这部分内存。

三、如何判断哪些内存需要回收了
对类的判断
1、引用计数算法
(1)执行原理:给对象中添加一个引用计数器,每当有一个地方引用他的时候,计数器值就加1;当以用失效的时候,计数器值就减1;任何时刻计数器为0的对象就是不可再被使用的。
(2)缺点:很难解决对象时间相互循环引用的问题。
比如两个对象已经不可能再被访问了,但是他们之间相互引用着对方,导致他们的引用计数都不为0,于是无法回收。
2、可达性分析算法
(1)执行原理:通过一系列称为“GC Roots”的对象作为起始点,从这些节点向下搜索,搜索所走过的路径称为引用链,当一个对象到“GC Roots”没有任何引用链相连的时候(通过图论也就是说“GC Roots”到这个对象不可达),则证明这个对象是不可用的,但是不可用只能说这个对像 时可以回收的,但是不能说一定会被回收,还要进一步判断(下面会说)
(2)可作为“GC Roots”的对象
虚拟机栈(栈帧中的本地变量表)中引用的对像
方法区中类静态属性引用的对象
方法区中常量引用的对象
本地方法中JNI(即一般说的Native方法)引用的对象。
(3)对可达性分析算法中不可达对象的进一步判断(通过finalize()方法)
对于在可达性算法中不可达的对象,这时候会处一个“缓刑”的状态,要宣告一个对象是真的死亡,那么至少还要经过两次标记的过程。对于可达性算法中不可达的对象首先会被第一次标记并且进行一次筛选,筛选的条件是此对象是否有必要执行finalize()方法
如果这个对象被判定有必要执行finalize()方法,那么这个对象就会放置到一个叫F-Qoeue的队列中,并且稍后会被一个Finalizer去执行它。所谓执行就是虚拟机会触发这个方法,但是并不是会等待它执行结束,因为一旦再执行过程中出现了执行缓慢或者死循环,那么可能会导致F-Queue队列中其他对象一直处于等待,或者可能导致整个垃圾回收机制崩溃。
稍后,GC会对F-Queue队列中的对象及进行第二次小规模的标记,如果对象要在finalize()方法中拯救自己,那么只要重新与引用链上的任何一个对象建立关系即可,那么第二次标记它就会被移除“即将回收”的集合,如果这个时候对象还没有逃脱,那么他就真的被回收了。
所以在进行第一次筛选的时候,如果这个对象被判定没有必要去执行finalize()方法,那么他就要被回收了。
注意:
任何一个对象的finalize()方法都只会被系统自动调用一次,如果对象面临下一次的回收,它的finalize()方法不会被执行,自救失败,被回收

对方法区的回收判断(常量和类)
堆里存放了几乎所有的对象实例,所以回收类就是在堆中。在方法区中(Java虚拟机中的永久代)要进行的回收就是废弃的常量和无用的类。
1、对“废弃常量”的判断:没有任何的对象引用来指向常量池中的常量。
2、对“无用的类”的判断
(1)该类的所有实例都已经被回收,也就是Java堆中不存在该类的任何实例
(2)加载该类的ClassLoader已经被回收
(3)该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。
但是这些也只能说是是可以被回收,但是必然会回收,是否对类进行回收,HotSpot虚拟机提供了-Xnoclassgc参数进行控制。还可以用-verbose:class以及-XX:+TraceClassLoading,-XX:+TraceClassUnLoading查看类加载和卸载的消息,其中-verbose:class和-XX:+TraceClassloading可以在Product版的虚拟机中使用,-XX:+TraceClassUnLoading参数需要FastDebug版的虚拟机支持。
(4)大部分环境虾都需要虚拟机具备类卸载的功能,以保证永久代不会溢出。

四、再谈引用
强引用(Strong Reference),软引用(Soft Reference),弱引用(Weak Reference),虚引用(Phantom Reference)
1、强引用:就是普遍存在的,类似于“Object obj=new Object()”,只要强引用还在,垃圾收集器永远不会回收掉被引用的对象
2、软引用:用来描述一些还有用但并非需要对象,对于他们,在系统将要发生内存溢出异常之前,将会把这些对象列入回收范围之内进行第二次回收。如果这次回收还没有足够的内存,才会抛出内存溢出异常。
3、弱引用:用来描述非必须对象,被弱引用关联的对象只能生存到下一次垃圾收集发生之前。当垃圾收集器工作的时候,无论发当前内存是否足够,都会回收掉只被弱引用关联的对象。
4、虚引用:最弱的一种引用关系。为一个对象设置虚引用关联的唯一目的就是能在这个对象被收集器回收时收到一个系统通知。一个对象是否有虚引用,完全不会对其生存时间构成影响,也无法通过虚引用来获取一个对象实例。
五、垃圾收集算法
说了那么多如何判断是否要回收,也该讲解一下如何让及逆行垃圾收集算法了
1、标记-清除算法
(1)执行原理:分两步,第一步是标记,标记过程就是前文说的,第二步就是在标记完成后,统一回收所有标记的对象。
(2)缺点:也是两个。第一个就是无论是标记还是清除 的效率都太低了,第二个就是空间效率问题,因为清除会产生大量的内存碎片,这就导致下次要执行大的内存分配,找不到足够大的内存,从而要进行第一次垃圾收集。
2、复制算法
(1)一般的复制算法:
将可用内存按照容量分为等大的两块,每次都是都一块进行处理,当一次垃圾回收完事后,就将没有回收的复制到另一块中。再把已经用过内存空间的全部清除掉,如此反复,每次都是操作半个内存空间,所以不会有内存碎片的问题,但是代价很高毕竟每次都要将内存减半
(2)改进的复制算法:
将内存按照Eden:Survivor=8:1的比例将内存分配为一个大的Ende和两个小的Survivor,这样的话每次使用一个Ende和一个Survivor。然后将这两个里面剩余的复制到另一个Survivor中,要是Survivor空间不足。需要依赖其他内存(这里指老年代)进行分配担保
(3)复制算法一般应用于新生代,老年代一般不使用这个。因为要是存活率很高的话就要进行很多复制操作,效率很低,而且要进行额外的空间来做分配担保以便遇到存活率100%的极端情况。
3、标记-整理算法
(1)执行原理:首先也是标记,然后不是直接对可回收对象进行清除,而是将所有存活的对象移动到一边,然后将另一边的清除掉。
4、总结(现在最常用的垃圾回收算法)
根据前面所说的三种算法,采用“分代采集”的算法,就是根据对象的存活周期来进行划分为几块,一般是把java堆划分为老年代和新生代。其中在新生代中,垃圾回收机制会发现大批对象死去,所以需要复制少,利用“复制算法”。而老年代存活下来的很多,所以使用“标记-清除”或者“标记-整理”的方法。

六、垃圾回收器
新生代:Serial,ParNew,Parallel Scavenge
老年代:CMS, SerialOld(MSC),Parallel Old
并行(Parallel):指多条垃圾收集线程并行工作,但此时用于的线程仍然处于一种等待状态
并发(Concurrent):指用户线程与垃圾回收线程同时执行(但不一定是并行的,可能会交互执行),用户程序在继续与逆行,而垃圾收集程序运行在另一个CPU上。
1、Serial收集器
(1)虚拟机运行在Client模式下的默认新生代收集器
(2)它进行垃圾回收时,必须暂停其他所有的工作线程,直到它收集结束。
(3)优点:简单高效
(4)单线程收集器:但并不是只会使用一个CPU或者一条收集线程去完成垃圾收集工作
2、ParNew收集器(并行多线程收集器)
(1)Serial收集器的多线程版本
(2)虚拟机运行在Server模式下的默认新生代收集器
(3)除了Serial之外,只有他可以与老年代中的CMS一起配合使用
3、Parallel Scavenge收集器(和ParNew差不多,以下说明不同点)(吞吐量优先地收集器)(并行)
(1)更加注重达到一个可控制的吞吐量。吞吐量=运行用户代码时间/(运行用户代码时间+垃圾回收时间)
(2)停顿时间越短越适合需要与用户交互的程序,而高吞吐量则可以高效率地利用CPU时间
(3)拥有自适应调节策略(因为有一个参数-XX:+UseAdaptiveSizePolicy),当这个参数打开的时候,就不需手工指定一些细节参数了。虚拟机会根据当前系统的运行情况收集性能监控信息,动态调整这些参数以提供最合适地停顿时间或者最大吞吐量
4、Serial Old(Serial收集器的老年代版本)
(1)在JDK1.5以及以前的版本中与Parallel Scavenge收集器搭配使用
(2)作为CMS的后备方案,在并发收集发生Concurrent Mode Failure时使用
5、Parallel Old收集器(Parallel Scavenge收集器的老年代版本)(并行)
(1)在注重吞吐量以及Cpu资源敏感的场合,都可以用Parallel Scavenge和Parallel Old收集器的组合。
6、CMS收集器
(1)以一种获取最短回收停顿时间为目标的收集器
(2)基于标记-清除(之前的的老年代收集器都是清除-整理)
(3)分四步{
初始标记
并发标记
重新标记
并发清除
}
(4)优点:并发收集,低停顿
(5)缺点:对CPU资源非常敏感(因为并发会占用一些CPU资源)
无法处理浮动垃圾,出现Concurrent Mode Failure(临时启用Serial Old收集器来重新进行老年代 的垃圾收集)
因为用到清除,所以会产生内存碎片(提供一个参数用于碎片整合)
7、G1收集器
(1)G1是一款面向服务端应用的垃圾收集器
(2)并行与并发
(3)分代收集
(4)空间整合(因为从局部看是复制,从整体看是标记整理)
(它将整个Java堆化分为多各大小相等的独立区域(Region),虽然还保留有新生代会和老年代的概念,但是新老之间已经不是物理隔离的了,他们都是一部分Region(不需要连续)的集合)
(5)可预测的停顿(因为会对Region进行价值评估,优先回收价值最大的Region)
(6)执行过程{
初始标记
并发标记
最终标记
筛选回收
}

七、内存分配策略
之前一直在说垃圾回收机制,那么内存是怎么分配的呢?接下来看一下
1、对象优先在Ende分配:
大多数情况下,对象在新生代Ende区中分配,但是当Ende中没有足够的空间来进行分配的时候,虚拟机将发起一次Minor GC(指发生在新生代的垃圾回收机制,与之对应在老年代中进行垃圾回收成为Full GC)这次的Minor GC是进行分配担保机制。将之前一些内存转移到老年代中,从而Ende中倒出空间给大内存存放。
2、大对象直接进入老年代
虚拟机提供了一个参数,这个参数可依规定大小大于某个值的内存将直接被存放到老年代中
3、长期存活的对象将进入老年代
虚拟机会给每一个对象定义一个对象年龄计数器,当对象出生在Ende中的时候,并且经历了一次Minor GC依然存活,并且能在Survivor区容纳的话,将被移动到Survivor中,对象年龄就会是1,,独像每次在Survivor中熬过一次就会年龄加1.当达到15岁时就会进入到老年代。(默认15岁),可以通过参数进行更改
4、动态对象年龄判定
有时候不需要必须达到15岁(或者参数值)才会进入到老年代。如果在Survior中同龄对象的内存总和大于Survivor总和的一半,那么大于这个年龄或者时等于这个年龄就会进入到老年代。
5、空间分配担保
在进行Minor GC之前,首先会判断老年代的最大连续空间是否大于新生代所有对象空间或者历次晋升的平均大小,如果是,那么Minor GC是安全的。否则要进行Full GC(为了给老年代倒出空间,以为了可能需要的分配担保)。

八、浅谈安全点
1、安全点就是进行回收机制时候需要程序停下来的点,从而开始GC(与之对应的有安全区域)。
2、如何让GC发生时候让所有线程都跑到安全点呢?
(1)抢断式中断
(2)主动式中断

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值