问题:垃圾回收对哪些区域进行回收
方法区和堆区,当空间不够用时执行垃圾回收动作,若回收不了则OutOfMemoryError
加载类:空间不够(加载类信息)
堆区:空间不够(分配实例)
当一个对象没有引用时,则对象没有存在的意义,则该对象会被垃圾回收
可达性分析

GC Roots:是对对象的引用集合
GC Roots对象主要有
- 虚拟机栈(栈帧中的本地变量表)中引用的对象
- 方法区中类静态属性引用的对象
- 方法区中常量引用的对象
- 本地方法栈中JNI(Native方法)引用的对象
可达性分析主要是分析栈区中的引用是否有在引用堆中的实例
JVM中对内存进行回收时,需要判断对象是否仍在使用中,可以通过GC Roots Tracing辨别。
通过一系列名为”GCRoots”的对象作为起始点,从这个节点向下搜索,搜索走过的路径称为ReferenceChain,当一个对象到GCRoots没有任何ReferenceChain相连时,则证明这个对象不可用。
四种引用
在JDK 1.2之后,Java对引用的概念进行了扩充,将引用分为
- 强引用(Strong Reference)
- 软引用(Soft Reference)
- 弱引用(Weak Reference)
- 虚引用(Phantom Reference)
这4种引用强度依次逐渐减弱。
强引用
我们平常写的一些new出来的对象都是强引用如:
Object obj = new Object();
这类引用只要引用还存在,垃圾收集器永远不会回收掉被引用的对象。
强引用有引用变量指向时永远不会回收,JVM宁愿抛出OutOfMemoryError错误也不会回收这类对象。
如果想终端强引用和某个对象之间的关联,可以直接将引用赋值为null
软引用
Object obj = new Object();
SoftReference ref = new SoftReference(obj);
// 当结束obj这个实例的强引用,ref对象成为了软引用对象
obj = null;
// 重新获得对该实例的强引用,若不回收,则get到的对象不为null,若执行了垃圾回收则get到的对象为null。
Object o = ref.get();
System.out.println(o == null);
用来描述一些还有用但非必须的对象。
对于软引用关联的对象,在系统将要发生内存溢出异常之前,将会把这些对象列进回收范围之中进行回收。
弱引用
WeakReference<String> reference = new WeakReference<>(new String());
System.out.println(reference.get() == null);
// 通知JVM回收资源
System.gc();
System.out.println(reference.get() == null);
与软引用对象一样使用来描述非必须的对象,但是它的强度比软引用更弱,被弱引用关联的对象只能生存到下一次垃圾回收发生之前。
当垃圾收集器工作时,无论内存是否足够,都会回收掉弱引用关联的对象。
虚引用
ReferenceQueue<String> queue = new ReferenceQueue<>();
PhantomReference<String> reference = new PhantomReference<>(new String(),queue);
System.out.println(reference.get());
最弱的一种引用关系。
一个对象是否有徐引用的存在,完全不会对其生存时间造成影响,也无法通过虚引用来取得一个对象实例。
为一个对象设置虚引用关联的唯一目的就是能在这个对象被收集器回收时收到一个系统通知。
垃圾回收算法
- 标记清除
- 标记整理
- 复制
- 分代收集算法
标记清除
标记-清除(Mark-Sweep)算法,该算法分为“标记”和“清除”两个阶段:首先标记处所有需要回收的对象,在标记完成后统一回收所有被标记的对象。

该算法缺陷:
- 效率问题,标记和清除的效率都不高
- 空间问题,标记清除后会产生大量不连续的内存碎片
标记整理
标记-整理(Mark-Compact)算法,标记过程与标记清除算法一样,但是后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后清理掉边界意外的内存。

优点: 解决了标记清除算法造成的内存碎片问题。
缺点: 整理势必会造成效率问题。
复制
将可用内存按容量划分为大小相等的两块,每次只是用其中一块。
当这块的内存用完了,就将还存活的对象复制到另外一块上面,然后再把已经使用过的内存空间一次性清理掉。
这样使得每次都是对整个半区进行回收,内存分配时也就不用考虑内存碎片等复杂情况,只要移动指针,按顺序分配内存即可,实现简单,运行高效。

缺点: 内存缩小为原来的一半,内存利用率低。
分代收集算法
目前商业虚拟机都是采用分代收集算法,这种算法根据对象存活周期的不同将内存划分为几块。
一般把Java堆分为新生代和老年代,这样就可以根据各个年代的特点采用最适合的收集算法。
在新生代中,每次垃圾收集时发现大批的对象都是朝生夕死,只有少量存活,那就选用复制算法,只需要付出少量存活对象的复制成本就可以完成收集。
而老年代中因为对象存活率高,没有额外空间对它进行分配,就必须使用标记清除或标记整理算法来进行回收。

网友评论