面试中经常被q,小结如下
- 如何确定一个对象是一个"垃圾对象"?
引用计数法:
首先,java是通过引用和对象进行关联的,因此如果一个对象没有任何引用与之关联,则说明该对象基本不太可能在其他地方被使用到,那么这个对象就成为可被回收的对象了。简言之就是“引用计数法”。
特点:这种方式的特点是实现简单,而且效率较高,但是它无法解决循环引用的问题,因此在Java中并没有采用这种方式(Python采用的是引用计数法)。
循环引用的例子
class A{
public B b;
}
class B{
public A a;
}
public class Main{
public static void main(String[] args){
A a = new A();
B b = new B();
a.b=b;
b.a=a; // 到此时,对象A和对象B的引用数都是2
}
}
在函数的结尾,a(这里指的是a引用的对象的计数)和b的计数均为2
先撤销a,然后a的计数为1,在等待b.a对a的引用的撤销,也就是在等待b的撤销
对于b来讲,也是同理,类似死锁。两个对象都在等待对方撤销,所有这两个资源均不能释放。这样内存便无法回收,会引起内存泄露的问题。
可达性分析
为了解决这个问题,在Java中采用可达性分析法。该方法的基本思想是通过一系列的“GC Roots”对象作为起点进行搜索,如果在“GC Roots”和一个对象之间没有可达路径,则称该对象是不可达的,不过要注意的是被判定为不可达的对象不一定就会成为可回收对象。被判定为不可达的对象要成为可回收对象必须至少经历两次标记过程,如果在这两次标记过程中仍然没有逃脱成为可回收对象的可能性,则基本上就真的成为可回收对象了。
- 垃圾回收算法
-
1 Mark-Sweep(标记-清除)算法:主要分为两个阶段,标记阶段和清除阶段。
标记清除算法
-
从图中可以很容易看出标记-清除算法实现起来比较容易,但是有一个比较严重的问题就是容易产生内存碎片,碎片太多可能会导致后续过程中需要为大对象分配空间时无法找到足够的空间而提前触发新的一次垃圾收集动作。
- Copying(复制)算法
Copying算法
Copying算法过程:一开始就将内存分为相等的两份,然后一开始只使用其中一半内存,等该半内存使用完成后。将该半内存中存活对象整体移动到另外未使用的一半中。然后整体清理之前的那半内存。这样就可以保证有连续的可用空间。
缺点:对内存空间的使用做出了高昂的代价,因为能够使用的内存缩减到原来的一半。很显然,Copying算法的效率跟存活对象的数目多少有很大的关系,如果存活对象很多,那么Copying算法的效率将会大大降低。
- Mark-Compact(标记-整理)算法(压缩法)
Mark-Compact算法过程:
该算法标记阶段和Mark-Sweep一样,但是在完成标记之后,它不是直接清理可回收对象,而是将存活对象都向一端移动,然后清理掉端边界以外的内存。其实主要思想就是将存活对象向一端移动,然后清理剩余空间内存。
缺点:这种方法可以解决内存碎片问题,但是会增加停顿时间。
- Generational Collection(分代收集)算法
分代收集算法是目前大部分JVM的垃圾收集器采用的算法。
算法详情:他的核心思想是根据对象存活的生命周期将内存划分为不同的区域。一般将堆区划分为老年代(Tenured Generation)和新生代(Young Generation)。老年代的特点是每次垃圾回收的时候只有少量的对象需要回收,新生代的特点是每次垃圾回收的时候有大量的对象回收。
目前大部分垃圾收集器对新生代采用复制算法。
因为新生代中每次垃圾回收都要回收大部分对象,也就是说需要复制的操作次数较少,但是实际中并不是按照1:1的比例来划分新生代的空间的,一般来说是将新生代划分为一块较大的Eden空间和两块较小的Survivor空间,每次使用Eden空间和其中的一块Survivor空间,当进行回收时,将Eden和Survivor中还存活的对象复制到另一块Survivor空间中,然后清理掉Eden和刚才使用过的Survivor空间。
由于老年代需要收集的对象少,对于老年代一般采用标记-整理算法。
- 典型的垃圾收集器
参考 1
网友评论