这一节的主要内容讲的就是垃圾回收相关的内容,主要的有回收哪些内容?怎么回收?有哪些算法?
1.jvm都会收集哪些区域呢?
a)主要关注的是堆和方法区,因为在栈的内存随着函数的调用栈帧的开辟会被自动回收,所以不需要垃圾回收器来特别的关注。
2.这么回收呢?
a)首先判定哪些对象是存活的,对于非存活对象进行一系列的回收算法。下面都是详细为绕这个点讲的。
3.如何判定对象是存活的呢?
答:
这里使用的是可达性的分析算法,就是通过一系列的称为GC ROOT的对象作为节点,然后从这个跟节点开始向下搜索,搜索过的路径称为引用链,当一个对象到GC ROOT没有引用链的时候,即到不可达的时候,则说明这个对象是不可用的即不是存活的,这种对象就要被回收。
但是上述的引用链来判断对象是否被引用从而决定回收不回收的话显的太过于死板,比如有些对象在系统中可有可无的时候,但是他是被引用的对象,那么通过上面的可达性分析算法就没法回收这类对象。这样的话这种对象又占用着内存就很难搞,所以在JDK1.2之后,对于引用的定义,进行了扩充,增加了强引用,软引用,弱引用,虚引用。那么他们具体指什么呢?
什么是 强引用,软引用,弱引用,虚引用?
强引用就是Object obj=new ObJect(),new出来的对象就是强引用。只要这种引用关系还在的话,是绝对不会被垃圾回收器回收的。
软引用的描述一些还有用但是不是必须的对象,软引用关联的对象,在系统将要发生内存溢出的时候,会进行一次垃圾回收,但是这次垃圾回收的过程不会回收软引用,而是将其放入回收的范围,进行第二次回收,如果第二次回收还没有内存,则抛出异常。SoftReference类来实现软引用
弱引用描述的也是可有可无的对象,但是他比软引用还要可有可无。被弱引用的对象,只能活到下一次垃圾回收发生之前。当本次垃圾回收器开始回收的时候,无论内存够不够都回收弱引用的对象。WeaReference类来实现弱引用。
虚引用他的引用粒度弱到你可以当他没有存在感,一个对象是否有虚引用,不会对其的生存周期造成任何影响,当然也无法通过虚引用取得一个对象的实例。设置虚引用的目的就是一个对象设置了虚引用关联则这个对象在被垃圾回收器回收的时候会受到系统通知。PhantomReference类实现引用。
好了既然知道了如何判断对象的存活,那么描述一下一个简单的垃圾回收过程?
答:即在可达性分析算法中一个对象没有引用链了也不是说必死无疑,他还有机会自救。怎么自救。就需要说一下垃圾回收的简单过程了。
首先垃圾回收器开始工作了,进行可达性分析后发现一批对象没有引用链了,那么这些对象会被进行第一次标记,并且会对他们进行一次筛选。(筛选条件就是如果当前对象没有覆盖finalize方法或者当前的这个方法已经被执行了),这是一类,其次覆盖了没执行时一类。前面的这一类直接就被回收掉了,那么剩余后面这次覆盖了而且没执行,那么会将其放在FQuenue队列里面,然后由一个优先级比较低的线程Finalier去执行第二类这些对象的finalize方法(这里就是一个对象的自救的机会),稍后垃圾回收器会对F-Quenue队列中的对象进行第二次标记(即检查一下有没有自救成功的),如果没有则全部回收掉,如果有自救的则移除回收集合。他就自救成功了。
方法区的回收,主要是无用的常量和类的卸载。。。这里因为之前写的博客CSDN没有保存成功,所以下次在补充,详细信息看书吧。。。
既然已经知道了垃圾回收的一个相对来说即简单又不简单的过程,那么下面就详细说一下垃圾回收算法吧
4.垃圾回收算法都有哪些?各个的有缺点是什么,适用什么场景?
简单来说垃圾回收的基础算法有三个
a)标记-清除法
实现过程:简单来说就是标记出来需要回收的对象,然后进行回收。
缺点:一个是效率不高。即标记和清楚的两个过程效率都不高(标记上面刚说过)。另外一个是空间的问题,标记清除以后容易产生大量的内存碎片,空间碎片太多的话。这样导致后面需要给大对象分配内存的时候,就会因为无法找到连续的足够的内存而不得不再一次触发垃圾回收。
b)复制算法
实现过程:粗略的将就是将内存分为大小相等的两块,每次只使用其中的一块,当这块的内存用完了,就将其中还存活的对象复制到另外一块内存上面去,然后把已经使用过的内存空间一次性清理掉。这样的话,每次都是对整个完整的半区进行回收,也就不用在考虑内存碎片的问题了。
缺点:将内存缩小到了原来的一半。
详细点:现在的商业虚拟机都采用复制算法来回收新生代区域。因为新生代的对象98%是朝生夕死,所以并不需要1:1划分区域,而是将内存分为一块较大的Eden空间和两块较小的Survivor空间,每次使用Eden和其中一块Survivor空间。当回收的时候将Eden和Survivor中还存活的对象一次性复制都另外一块Survivor空间上,最后清理掉Eden和Survivor。HotSpot虚拟机默认Eden和Survivor的比例是8:1,也就是每次新生代的可用空间为整个新生代的90%,只有10%的内存会被浪费。
当然并不是说每次回收都保证98%的对象被回收,万一有超过10%的对象存活的话,那么Survivor的内存不够的时候,就需要依赖其他的内存进行分配担保,这里其他内存一般指的是老年代
c)标记-整理算法
因为复制收集算法在对象存活率比较低的时候时候使用,而对象存活率比较高的时候,就不适用了。所以对于老年代有人提出了标记整理算法。
实现过程:和标记清楚算法一样,但是这个标记以后并不是立马清除,而是让存活的对象都向一端移动,然后直接清除掉边界意外的内存。
除了上面三种,还有一种分代收集算法,其实就是将堆分为了新生代和老年代,根据不同的区域使用上面的三种不同的垃圾回收算法