目录
System.gc()的理解
调用System.gc()或Runtime.getRuntime.gc()会显示的触发一次Full GC,但是不保证一定会执行。对新生代、老年代、元空间进行垃圾回收。
底层调用的是Runtime.getRuntime.gc()。Runtime的gc方法是一个被native修饰的本地方法
System.runFinalization():调用这个方法意味着虚拟机要花费精力运行那些已经被丢弃但finalize方法还没有被执行的的对象的finalize方法。(调用垃圾对象里还没有调用过的finalize方法)
内存泄漏与内存溢出
内存泄漏:严格上定义的就是一些不再使用,但是又无法被回收的对象。宽泛上的定义就是一些不合理的代码导致对象的生命周期得到延长,甚至直到虚拟机关闭才回收的对象。可能是应该断开的引用链没有断开,导致与GC Root还保持住引用。
例子:比如1、单例Bean里定义的引用对象。2、没有及时关闭的外部资源引用,比如数据库连接、io等
内存溢出:没有多余的内存,并且垃圾收集器也无法提供更多的内存。没有多余的可用空间再为新的对象分配内存。
- 堆内存设置的不够导致
- 创建了大量对象,并且不能被垃圾收集器收集
Stop The World
简称STW,指的是GC事件发生过程中,会产生的应用程序停顿。停顿产生时整个应用线程将被暂停,没有任何响应。任何收集器都无法完全避免STW。只能尽可能的缩短暂停时间。
可达性分析算法中的根节点(GC Root)枚举就会暂停所有的用户线程
因为要确保分析在一个一致性的快照中进行。一致性指的是在此期间没有对象、对象引用的变化。如果在分析期间对象引用关系还在不断的变化,那么保证不了分析结果的准确性。
垃圾回收的并行和并发
并行:还是和用户线程交替执行,但是有多条垃圾收集线程
串行:还是和用户线程交替执行,只有一条垃圾收集线程
并发:垃圾收集线程和用户线程同时执行(但不一定是并行的,也可能交替执行)
安全点
垃圾回收不是在任何时候都可以停下来下了去执行,所以就有了安全点的设计。线程只有执行到了安全点才可以停下来。安全点的选定既不能太少以至于让收集器等待时间过长,也不能太过频繁以至于过分增大运行时的内存负荷。
安全点的选定基本上是以“是否具有让程序长时间执行的特征”为标准进行选定的。例如方法调用、循环跳转、异常跳转等都属于指令序列复用,所以只有具有这些功能的指令才会产生安全点
另一个问题就是,当在垃圾收集时如何让线程执行到安全点,有两种方法:
抢先式中断:不需要线程的执行代码主动去配合,在垃圾收集发生时,系统首先把所有用户线程全部中断,如果发现有用户线程中断的地方不在安全点上,就恢复这条线程执行,让它一会再重新中断,直到跑到安全点上。现在几乎没有虚拟机这么做
主动式中断:不直接对线程操作,仅仅简单地设置一个标志位,各个线程执行过程时会不停地主动去轮询这个标志,一旦发现中断标志为真时就自己在最近的安全点上主动中断挂起。轮询标志的地方和安全点是重合的,另外还要加上所有创建对象和其他需要在Java堆上分配内存的地方,这是为了检查是否即将要发生垃圾收集,避免没有足够内存分配新对象
安全区域
那如果在垃圾收集的时候恰好有一些线程刚好处于sleep状态或者Blocked状态,那它不可能跑到安全点。所以又有了安全区域作为补充。
安全区域是指能够确保在某一段代码片段之中,引用关系不会发生变化,因此,在这个区域中任意地方开始垃圾收集都是安全的。也可以把安全区域看作被扩展拉伸了的安全点。
当用户线程执行到安全区域里面的代码时,首先会标识自己已经进入了安全区域,那样当这段时间里虚拟机要发起垃圾收集时就不必去管这些已声明自己在安全区域内的线程了。当线程要离开安全区域时,它要检查虚拟机是否已经完成了根节点枚举(或者垃圾收集过程中其他需要暂停用户线程的阶段),如果完成了,那线程就当作没事发生过,继续执行;否则它就必须一直等待,直到收到可以离开安全区域的信号为止。
强引用
new 关键字创建的对象,情愿报OOM,也不回收。程序中99%都是这样的对象
软引用
在堆内存空间不足时回收
SoftReference<Cat> softReference = new SoftReference<>(new Cat(2, "小白"));
Cat cat = softReference.get();
// 或者结合队列
ReferenceQueue<Object> queue = new ReferenceQueue<>();
oftReference<Cat> softReference = new SoftReference<>(new Cat(2, "小白"), queue);
// 必须把强引用给断开,否则不会回收
Cat cat = new Cat();
SoftReference<Cat> softReference = new SoftReference<>(cat);
cat = null;
System.gc();
弱引用
在下一次垃圾回收时回收
WeakReference<Cat> weakReference = new WeakReference<>(new Cat(2, "小白"));
Cat cat = weakReference.get();
ReferenceQueue<Cat> queue = new ReferenceQueue<>();
WeakReference<Cat> weakReference = new WeakReference<Cat>(new Cat(2, "小白"), queue);
虚引用
和没有引用几乎一样,可以在任何时候回收,必须结合ReferenceQueue使用,并且无法通过get()方法获取对象。为对象设置虚引用的唯一目的在于跟踪垃圾回收过程。比如能在这个对象被收集器回收的时候收到一个系统通知。因此可以把一些资源释放操作放在虚引用中执行
public class ReferenceTest {
public static ReferenceQueue<Cat> queue = null;
public static void main(String[] args) {
// 虚引用
Thread thread = new Thread(() -> {
while (true){
if (queue != null){
PhantomReference<Cat> pr = null;
try {
pr = (PhantomReference<Cat>) queue.remove();
} catch (InterruptedException e) {
e.printStackTrace();
}
if (pr != null){
System.out.println("虚对象被回收了");
}
}
}
});
thread.setDaemon(true);
thread.start();
queue = new ReferenceQueue<>();
PhantomReference<Cat> phantomReference = new PhantomReference<>(new Cat(2, "小白"), queue);
System.out.println(phantomReference.get());
System.gc();// null
}
}
class Cat{
int age;
String name;
byte[] b;
public Cat(int age, String name, byte[] b) {
this.age = age;
this.name = name;
this.b = b;
}
public Cat(int age, String name) {
this.age = age;
this.name = name;
}
public Cat() {
}
@Override
public String toString() {
return "Cat{" +
"age=" + age +
", name=" + name +
'}';
}
}