跳至主要內容

垃圾回收

blacklad大约 4 分钟JvmJavaJvm

Jvm 垃圾回收

栈中的数据随着方法的进入和退出执行入栈和出栈操作,在类结构确定的时候已经确定,同时和线程有相同的生命周期,内存的分配和回收具有确定性。

而在堆和方法区中,只有在运行期间才能知道程序会创建多少个对象,导致内存的分配和回收都是动态的。

1 判断对象是否存活

1.1 引用计数法

在对象中添加一个引用计数器,当引用数为0时进行回收。

无法解决循环引用的问题,即A中有B的实例,B中有A的实例,导致引用计数都是1,不能被回收。

1.2 可达性分析法

从所有的GC Roots的根对象作为起点进行搜索,搜索的路径为引用连,如果不在引用链上则对象不可达可以被回收。

可以作为GCRoots的对象:

  1. 虚拟机栈中引用的对象, 方法参数,局部变量,临时变量for(int i)
  2. 方法区中静态属性引用的对象
  3. 常量引用的对象
  4. 本地方法栈中引用的对象
  5. 虚拟机内部引用Class对象, 异常对象, 类加载器等
  6. 同步锁持有的 对象

GC Roots就是正在使用的引用。

2 引用类型

为了更好的进行内存回收,将引用扩充为4种:

  1. 强引用 Object obj = new Object(),只要有引用存在垃圾回收器就不会回收

  2. 软引用 SoftReference 在系统发生内存溢出异常前,会把这些对象加到回收范围中进行回收,如果内存仍然不够,则会抛出异常

    缓存

  3. 弱引用 WeakReference 生存到下一次垃圾收集发生为止,无论内存是否够用

  4. 虚引用 PhantomReference 对对象的生存时间没有影响,主要用来当对象被回收时收到一个通知

    1. DirectByteBuffer就是通过虚引用来实现堆外内存的释放的。

    虚引用使用场景主要有两个

    1、它允许你知道具体何时其引用的对象从内存中移除。而实际上这是Java中唯一的方式。这一点尤其表现在处理类似图片的大文件的情况。当你确定一个图片数据对象应该被回收,你可以利用虚引用来判断这个对象回收之后在继续加载下一张图片。这样可以尽可能地避免可怕的内存溢出错误。

    2、虚引用可以避免很多析构时的问题。finalize 方法可以通过创建强引用指向快被销毁的对象来让这些对象重新复活。然而,一个重写了 finalize 方法的对象如果想要被回收掉,需要经历两个单独的垃圾收集周期。在第一个周期中,某个对象被标记为可回收,进而才能进行析构。

    但是因为在析构过程中仍有微弱的可能这个对象会重新复活。这种情况下,在这个对象真实销毁之前,垃圾回收器需要再次运行。因为析构可能并不是很及时,所以在调用对象的析构之前,需要经历数量不确定的垃圾收集周期。这就意味着在真正清理掉这个对象的时候可能发生很大的延迟。这就是为什么当大部分堆被标记成垃圾时还是会出现烦人的内存溢出错误。

3 如何回收

当对象不可达时,会被第一次标记。

当对象覆盖了finalize()方法时,且没有被虚拟机调用时,会将改对象放在一个优先级比较低的队列中,执行finalize()方法,如果对象在finalize中对象重新与GCRoots中建立了关联,则对象逃过出即将回收的集合,其余的轻快会被二次标记回收。

由于队列优先级较低,为了防止队列阻塞等问题,finalize()可能还没有被执行就被二次标记回收了。

不建议使用,优先使用 try catch。

4 方法区回收

jvm虚拟机规范中不要求回收方法区,对方法区的回收效果比较低。

主要回收 废弃的常量不再使用的类型

  1. 废弃的常量: 没有任何字符串对象引用常量池中的“java”常量,且虚拟机中也没有其他地方引用这个字面量
  2. 不再使用的类型:
    1. 该类所有的实例都已经被回收。
    2. 加载该类的类加载器已经被回收.
    3. Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。
上次编辑于:
贡献者: blacklad