一、System.gc()的理解
在默认情况下,通过System.gc()或者Runtime.getRuntime().gc()的调用,会显式触发Full GC,同时对老年代和新生代进行回收,尝试释放被丢弃对象占用的内存。
注意:System.gc() 不一定能够确保马上执行gc。通过重写 finalize()方法 来显示查看是否gc。
而 System.runFinalization()强制调用使用引用的对象的finalize()方法。
JVM实现者可以用过System.gc() 调用来决定JVM的GC行为。而一般情况下,垃圾回收应该是自动进行的,无需手动触发,否则太过麻烦。在一些特殊情况下,如我们正在编写一个性能基准,我们可以在运行之间调用System.gc()。
二、内存溢出和内存泄漏
1.内存溢出(OutOfMemory)
没有空闲空间,并且垃圾收集器也无法提供更多内存
2.内存泄漏(Memory Leak)
严格来说,只有对象不会再被程序用到了,但是GC又不能回收它们的情况,才叫内存泄漏。
三、Stop The World
指的是GC事件发生过程中,会产生应用程序的停顿。
停顿产生时整个应用程序线程都会被暂停,没有任何响应,有点卡死的感觉,这个停顿称为STW。
STW事件和采用哪款GC无关,所有的GC都有这个事件。
STW是JVM在后台自动发起和自动完成的。在用户不可见的情况下,把用户正常的工作线程全部停掉。
开发中不要用System.gc();会导致Stop-the-World的的发生。
public class STWTest {
public static class WorkThread extends Thread {
List<byte[]> list = new ArrayList<>();
@Override
public void run() {
super.run();
while (true){
for (int i = 0; i < 1000; i++) {
byte[] buffer = new byte[1024];
list.add(buffer);
}
if (list.size()>10000){
list.clear();
System.gc();//触发full gc,进而出现 STW事件,导致用户线程 或子线程 卡顿。
}
}
}
}
public static class PrintThread extends Thread {
public final long startTime = System.currentTimeMillis();
@Override
public void run() {
super.run();
while (true){
try {
// 每秒打印时间
long t = System.currentTimeMillis()-startTime;
System.out.println(t/1000+"."+t%1000);
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) {
PrintThread printThread = new PrintThread();
WorkThread workThread = new WorkThread();
printThread.start();
workThread.start();
}
}
四、垃圾回收的并行与并发
并行:指多条垃圾收集线程并行工作,但此时用户线程仍处于等待状态。
串行:相较于并行的概念,单线程执行。如果内存不够,则程序暂停,启动JVM垃圾回收器进行回收。回收完,再启动 序的线程。
并发和并行,在谈论垃圾收集器的上下文语境中,它们可以解释如下:
并发:指用户线程与垃圾收集线程同时执行(但不一定是并行的,可能会交替执行),垃圾回收线程在执行时不会停顿用 户程序的运行。用户程序在继续运行,而垃圾收集程序运行在另一个CPU上。如:CMS、G1
五、安全点与安全区域
1.安全点
程序执行时并非在所有地方都能停顿下来开始GC,只有在特定的位置才能停顿下来开始GC,这些位置称为“安全点(Safepoint)”。
Safe Point 的选择很重要,如果太少可能导致GC等待的时候太长,如果太频繁可能导致运行时的性能问题。
大部分指令的执行时间都非常短暂,通常会根据“是否具有让程序长时间执行的特征”为标准。比如:选择一些执行时间较长的指令作为Safe Point,如方法调用、循环跳转和异常跳转等。
如何在GC发生时,检查所有线程都跑到最近的安全点停顿下来呢?
抢先式中断:(目前没有虚拟机采用了)首先中断所有线程。如果还有线程不在安全点,就恢复线程,让线程跑到安全点。
主动式中断:设置一个中断标志,各个线程运行到Safe Point的时候主动轮询这个标志,如果中断标志为真,则将自己进行中断挂起。
2.安全区域(Safe Region)
Safe Region机制保证了程序执行时,在不太长的时间内就会遇到可进入 GC 的Safepoint。但是,程序“不执行”的时候呢?例如线程处于sleep状态或blocked状态,这时候线程无法响应 JVM 的中断请求,“走”到安全点中去中断挂起,JVM也不太可能等待线程被唤醒。对于这种情况,就需要安全区域来解决。
安全区域是指在一段代码片段中,对象的引用关系不会发生变化,在这个区域中的任何位置开始GC都是安全的。也可以把安全区域看做是被扩展了的Safe point。
实际执行时:
1、当线程运行到Safe Region的代码时,首先标识已经进入了 Safe Region,如果这段时间内发生GC,JVM会忽略标识了Safe Region状态的线程。
2、当线程即将离开Safe Region 时,会检查JVM是否完成GC,如果完成了,则继续运行,否则线程必须等待直到收到可以安全离开Safe Region 的信号为止;
六、再谈引用
强引用(Strong Reference):无论任何情况下,只要强引用关系还存在,垃圾收集器就永远不会回收掉被引用的对象。
软引用(Soft Reference):在系统将要发生内存溢出之前,将会把这些对象列入回收范围之中进行第二次回收。如果这次回收后还没有足够的内存,才会抛出内存溢出异常。
弱引用(Weak Reference):被引用关联的对象只能生存到下一次垃圾收集之前。当垃圾收集器工作时,无论内存空间是否足够,都会回收掉被弱引用关联的对象。
虚引用(Phantom Reference):一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用获得一个对象的实例。为一个对象设置虚引用关联的唯一目的就是能在这个对象被收集器回收时收到一个系统通知。
import java.lang.ref.PhantomReference;
import java.lang.ref.ReferenceQueue;
/**
* 虚引用的测试
*/
public class ReferenceDemo {
public static ReferenceDemo obj;//当前类对象的声明
static ReferenceQueue<ReferenceDemo> phantomQueue = null;//引用队列
public static class CheckRefQueue extends Thread {
@Override
public void run() {
while (true) {
if (phantomQueue != null) {
PhantomReference<ReferenceDemo> objt = null;
try {
objt = (PhantomReference<ReferenceDemo>) phantomQueue.remove();
} catch (InterruptedException e) {
e.printStackTrace();
}
if (objt!=null){
System.out.println("追踪垃圾回收过程:ReferenceDemo实例被GC了");
}
}
}
}
}
@Override
protected void finalize() throws Throwable {
super.finalize();
// finalize 方法只能被调用一次
System.out.println("调用当前类的finalize()方法");
obj = this;
}
public static void main(String[] args) {
CheckRefQueue checkRefQueue = new CheckRefQueue();
checkRefQueue.setDaemon(true);//设置为守护线程 :当程序中没有非守护线程时,守护线程也就执行结束。不然CheckRefQueue线程会一直执行,while true
checkRefQueue.start();
phantomQueue = new ReferenceQueue<>();
obj = new ReferenceDemo();
//构造了 ReferenceDemo 对象的虚引用,并指定了引用对列
PhantomReference<ReferenceDemo> phantomReference = new PhantomReference<>(obj, phantomQueue);
try {
// 不可获取虚引用中的对象
System.out.println(phantomReference.get());
// 将强引用去除
obj = null;
//第一次进行GC,由于对象可复活,GC无法回收该对象
System.gc();
Thread.sleep(1000);
if (obj == null) {
System.out.println("obj 是 null");
} else {
System.out.println("obj 可用");
}
System.out.println("第 2 次 gc");
obj = null;
System.gc();// 一旦将obj对象回收,就会将此虚引用放入引用对列
Thread.sleep(1000);
if (obj == null) {
System.out.println("obj 是 null");
} else {
System.out.println("obj 可用");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
null
调用当前类的finalize()方法
obj 可用
第 2 次 gc
追踪垃圾回收过程:ReferenceDemo实例被GC了
obj 是 null