1.GC的回收算法

1.引用计数法(没有被java使用)

对于一个对象A,只要有任何一个对象引用了A,则A的引用计数器就加1,当引用失效时,引用计数器就减1。只要对象A的引用计数器的值为0,则对象A就不可能再被使用。
引用计数法存在如下问题:
1 引用和去引用伴随加法和减法,影响性能
2 很难处理循环引用

2、标记-清除

标记-清除算法是现代垃圾回收算法的思想基础。标记-清除算法将垃圾回收分为两个阶段:标记阶段和清除阶段。一种可行的实现是,在标记阶段,首先通过根节点,标记所有从根节点开始的可达对象。因此,未被标记的对象就是未被引用的垃圾对象。然后,在清除阶段,清除所有未被标记的对象。
标记-清除算法适合与老年代垃圾回收,标记清除会产生内存碎片。

3、标记-压缩

标记-压缩算法适合用于存活对象较多的场合,如老年代。它在标记-清除算法的基础上做了一些优化。和标记-清除算法一样,标记-压缩算法也首先需要从根节点开始,对所有可达对象做一次标记。但之后,它并不简单的清理未标记的对象,而是将所有的存活对象压缩到内存的一端。之后,清理边界外所有的空间。
标记-压缩相比较于标记清理算法可以避免垃圾碎片的产生,标记-压缩算法也适合与老年代的垃圾回收。

4、复制算法

与标记-清除算法相比,复制算法是一种相对高效的回收方法
不适用于存活对象较多的场合 如老年代
将原有的内存空间分为两块,每次只使用其中一块,在垃圾回收时,将正在使用的内存中的存活对象复制到未使用的内存块中,之后,清除正在使用的内存块中的所有对象,交换两个内存的角色,完成垃圾回收
复制算法是一种高效的垃圾回收算法,但是会造成内存浪费。新生代适合采用复制算法来进行垃圾回收。对象可在s0和s1区之间进行复制。

5、垃圾对象识别(可触及性)

可触及的
从根节点可以触及到这个对象
可复活的
一旦所有引用被释放,就是可复活状态
因为在finalize()中可能复活该对象
不可触及的
在finalize()后,可能会进入不可触及状态
不可触及的对象不可能复活
可以回收
可触及性:
经验:避免使用finalize(),操作不慎可能导致错误。(不要手动覆盖finalize方法)
优先级低,何时被调用, 不确定
何时发生GC不确定
可以使用try-catch-finally来替代它

2.java的四种引用

一、强引用

开发中用的最多的就是强引用了。
强引用声明格式:
String str=”abc”;
只要某个对象与强引用关联,那么JVM在内存不足的情况下,宁愿抛出outOfMemoryError错误,也不会回收此类对象。
如果我们想要JVM回收此类被强引用关联的对象,我们可以显示地将str置为null,那么JVM就会在合适的时间回收此对象

二、软引用

java中使用SoftRefence来表示软引用,如果某个对象与软引用关联,那么JVM只会在内存不足的情况下回收该对象。
那么利用这个特性,软引用可以用来做缓存。
软引用声明格式:

  1. import java.lang.ref.SoftReference;
  2. public class TestRef {
  3. public static void main(String args[]) {
  4. SoftReference<String> str = new SoftReference<String>(new String("abc"));
  5. System.out.println(str.get());
  6. //通知JVM进行内存回收
  7. System.gc();
  8. System.out.println(str.get());
  9. }
  10. }

可以看得出来,此时JVM内存充足,暂时还没有回收被软引用关联的对象。
软引用适合做缓存,在内存足够时,直接通过软引用取值,无需从真实来源中查询数据,可以显著地提升网站性能。当内存不足时,能让JVM进行内存回收,从而删除缓存,这时候只能从真实来源查询数据。

三、弱引用

java中使用WeakReference来表示弱引用。如果某个对象与弱引用关联,那么当JVM在进行垃圾回收时,无论内存是否充足,都会回收此类对象。
弱引用声明格式:

  1. import java.lang.ref.WeakReference;
  2. public class TestRef {
  3. public static void main(String args[]) {
  4. WeakReference<String> str = new WeakReference<String>(new String("abc"));
  5. System.out.println(str.get());
  6. //通知JVM进行内存回收
  7. System.gc();
  8. System.out.println(str.get());
  9. }
  10. }

可以看得出来,被弱引用关联的对象,总是会在垃圾回收时被回收掉。
注意:(1)System.out.println(str.get());有可能取不到str的值。这是因为我们在声明了弱引用之后,立即对其输出,而gc线程是一个优先级很低的守护线程,还来不及扫描该该对象所在的区域,即来不及对该对象的回收。如果我们在声明之后的很长时间后,再次get,是有可能get不到值的。
弱引用可以在回调函数在防止内存泄露。因为回调函数往往是匿名内部类,一个非静态的内部类会隐式地持有外部类的一个强引用,当JVM在回收外部类的时候,此时回调函数在某个线程里面被回调的时候,JVM就无法回收外部类,造成内存泄漏。在安卓activity内声明一个非静态的内部类时,如果考虑防止内存泄露的话,应当显示地声明此内部类持有外部类的一个弱引用。

四、虚引用

java中使用PhantomReference来表示虚引用。虚引用,虚引用,引用就像形同虚设一样,就像某个对象没有引用与之关联一样。若某个对象与虚引用关联,那么在任何时候都可能被JVM回收掉。虚引用不能单独使用,必须配合引用队列一起使用。
虚引用声明格式:

  1. import java.lang.ref.PhantomReference;
  2. import java.lang.ref.ReferenceQueue;
  3. public class TestRef {
  4. public static void main(String args[]) {
  5. ReferenceQueue<String> queue = new ReferenceQueue<>();
  6. PhantomReference<String> str = new PhantomReference<String>("abc", queue);
  7. System.out.println(str.get());
  8. }
  9. }

使用get方法无法获取该对象的值
当垃圾回收器准备回收一个对象时,如果发现它与虚引用关联,就会在回收它之前,将这个虚引用加入到引用队列中。程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被回收,如果确实要被回收,就可以做一些回收之前的收尾工作。

3.GC的堆的回收过程

Java 堆= 老年代+ 新生代新生代= Eden + S0 + S1
当Eden 区的空间满了,Java 虚拟机会触发一次Minor GC,以收集新生代的垃圾,存活下来的对象,则会转移到Survivor 区。
大对象(需要大量连续内存空间的Java 对象,如那种很长的字符串)直接进入老年态;如果对象在Eden 出生,并经过第一次Minor GC 后仍然存活,并且被Survivor 容纳的话,年龄设为1,每熬过一次Minor GC,年龄+1,若年龄超过一定限制(15),则被晋升到老年态。即长期存活的对象进入老年态。老年代满了而无法容纳更多的对象,Minor GC 之后通常就会进行Full GC,Full GC 清理整个内存堆– 包括年轻代和年老代。Major GC 发生在老年代的GC,清理老年区,经常会伴随至少一次Minor GC,比Minor GC慢10 倍以上。

4.GC如何判断对象是否回收

1) 在Java 中采取了 可达性分析法
通过一系列的“GC Roots”对象作为起点进行搜索,如果在“GC Roots”和一个对象之间没有可达路径,则称该对象是不可达的,不过要注意的是被判定为不可达的对象不一定就会成 为可回收对象。被判定为不可达的对象要成为可回收对象必须至少经历两次标记过程,如果 在这两次标记过程中仍然没有逃脱成为可回收对象的可能性,则基本上就真的成为可回收对 象了。
2) 虚拟机栈中引用的对象、方法区类静态属性引用的对象、方法区常量池引用的对象、本地 方法栈JNI 引用的对象

5.GC的基本原理

对于GC 来说,当程序员创建对象时,GC 就开始监控这个对象的地址、大小以及使用情况。通常,GC 采用有向图的方式记录和管理堆(heap)中的所有对象。通过这种方式确定哪些对象
是”可达的”,哪些对象是”不可达的”。当GC 确定一些对象为”不可达”时,GC 就有责任回收这些内存空间。可以。程序员可以手动执行System.gc(),通知GC 运行,但是Java 语言规范并不保证GC 一定会执行。