前面介绍了垃圾回收器几个方面的内容:
- 如何标记垃圾
- 如何处理垃圾
那么还有什么问题要解决呢?
既然是自动垃圾回收,那么自动是什么情况呢?
在我看来,自动主要来自两个方面:
- 当我们年轻代、老年代内存不足时,触发某种条件,进行垃圾收集
- 还有一个就是我们接下来说的安全点、安全区域
安全点
为什么要有安全点呢?
在一个程序执行过程中,我们不一定要等到内存不足的时候,再进行垃圾回收整理。
如果只是等待到内存不足的时候再进行整理,那么这个时候,就会有大量的垃圾,那么处理起来,要耗费的时间就会增长。
所以,为了减少这种行为,平时的时候就可以进行收集。可以看下下面这个程序。
package GC;
import java.util.ArrayList;
public class SafePoint {
public static void main(String[] args) {
while(true){
ArrayList list = new ArrayList<>();
for (int i = 0; i < 1000; i++) {
list.add(i);
}
}
}
}
这个程序,每次一个新的循环的时候,之前的list引用就指向了新的引用。我们设置 JVM 运行参数为:
-XX:+PrintGCDetails -Xms10m -Xmx10m
设置打印 GC 收集的过程,以及堆的大小为 10M。
可以看到,垃圾回收器并没有等到内存不足的时候再去清理。而是在 2M 左右的时候,就去进行了清理。再清理之后,内存占用只有 1M 不到。
像这种,在程序的某个点进行垃圾回收的,这个点就是安全点。
在垃圾回收的过程中,如果程序仍在运行,有可能使得对象的引用发生改变,那么也许我们上一秒标记的存活对象,这一秒就是死亡对象了。
所以,我们相对而言,要寻找那种,让程序运行比较耗时的指令出,进行安全点的设置。进而允许垃圾回收,比如方法调用,循环跳转,异常跳转等。
安全点与垃圾回收
注意:程序运行到安全点,不是一定要进行垃圾回收。而是在这些点上进行垃圾回收,较为安全。所以叫做安全点。
如何在垃圾收集发生时,让所有线程到达最近的安全点?
- 抢先式中断:发生垃圾回收时,所有的线程都中断,如果有的线程不在安全点,那么恢复一会,再中断,直到到达安全点
- 主动式中断:如果有线程到达安全点,会去主动询问一个标识位,这个标志位说明,要不要发生中断。如果需要发生中断,那么这个线程就会中断自己,等到垃圾回收。
抢先式中断,会造成大量的线程切换,浪费大量的时间,所以几乎不使用。更多的是使用 主动式中断。
安全区域
我们上面说了,线程运行时的状态,寻找安全点进行询问是否垃圾回收。
那么如果线程处于 睡眠 或者 阻塞 状态,那么怎么办?这个时候,他就无法进行询问标志位了。
如果在线程进入这些状态前,标示自己可以随时被 垃圾回收器 进行相关对象的标记即可。
其实,这就是安全区域,因为一旦线程进入这些状态,那么其内对象不会发生改变,所以也就是随时可以被垃圾回收器进行处理。
如果当线程重新启动后,发生垃圾回收器还没处理完,那么就继续等待。如果垃圾回收器没有处理或者已经处理完了,那么就继续执行。
安全区域,无非就是另一个告诉垃圾回收器,你可以处理我的方式。
package GC;
public class SafeRegion {
public static void main(String[] args) throws InterruptedException {
byte[] bigSize = new byte[10 * 1024 * 1024];
bigSize = null;
new Thread(()->{
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.gc();
}).start();
Thread.sleep(10000);
}
}
可以看到,主线程已经进入睡眠状态,这个时候,再另一个线程中,调用垃圾回收,仍然可以对垃圾进行清理。