哪些内存需要回收?
垃圾收集(Garbage Collection,GC),即常说的GC。
尽管目前“自动化”时代,内存由JVM自动回收,但需要了解其回收机制。当出现内存溢出、泄漏问题时方便排查,当垃圾收集成为系统达到更高并发量的瓶颈时,方便监控调节。
由于程序计数器、虚拟机栈、本地方法栈随线程生灭,且每个栈帧分配多少内存都是在类结构确定下来就已知,所以这几个区域的内存分配和回收都有确定性。
而堆和方法区的内存分配是 动态 的,这里所讨论的GC即指 堆和方法区 的内存。
什么时候回收?
内存的回收时机主要在于确定 对象是否还通过任何途径被使用。
引用计数算法(已经淘汰的算法)
引用计数算法是给每个对象添加一个引用计数器,每当一个地方引用它,计数器就加一;引用失效时,计数器就减一。
其特点是:算法很简单,判定效率高。但它不能解决相互引用的问题。
看如下示例代码:
public class ReferenceCountingGC { public Object instance = null; //这个成员属性单纯用于内存占用,方便查看是否回收 private byte[] bigSize = new byte[2*1024*1024]; public static void testGC(){ ReferenceCountingGC objA = new ReferenceCountingGC(); ReferenceCountingGC objB = new ReferenceCountingGC(); objA.instance = objB; objB.instance = objA; //清除引用 objA = null; objB = null; //GC 观察objA与objB能否被回收 System.gc(); } public static void main(String[] args) { testGC(); } }
对象ObjA与ObjB互相引用,但再无其他引用。如果采用引用计数算法,GC就无法回收他们。
主流的JVM中,没有采用引用计数法,就是因为它无法解决对象间循环引用的问题。
运行上段代码,我们可以看到内存 4328k->644k 的变化,说明对ObjA和ObjB的内存进行了回收,也说明Java虚拟机中没有采用引用计数算法。
运行结果
可达性分析算法
主流商用程序语言(Java,C#等)中,都是通过 可达性分析(Rechability Analysis) 判断对象存活。
可达性分析
可达性分析是通过一系列的"GC Roots"作为起始点,向下开始搜索,所经过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何链相连,则对象不可用。
Java中,可作为GC Roots的对象包括以下几种:
虚拟机栈中引用的对象 (栈帧中的局部变量表)
本地方法栈中JNI引用的对象 (JNI即Native方法)
方法区中静态属性引用的对象 (static)
方法区中常量引用的对象 (final)
对象引用
判断对象的存活,无论是引用计数法计算数量,还是可达性分析法判断引用的可达性,都与 引用 有关。
引用可分为 4 类:
强引用(Strong Reference):形如 Object obj = new Object(),只要引用在,GC不回收对象。
软引用(SoftReference):内存溢出前,会进行二次回收,适合做缓存
弱引用(WeakReference):下一次GC前,会直接进行回收
虚引用(PhantomRefence):仅用于对象回收时发送系统通知
强引用
强引用的对象,不会被GC回收;删除强引用后,GC才会回收。
示例代码如下:
/** * 强引用:GC不会回收对象 */public class StrongRef { public static void main(String[] args) throws InterruptedException { Referred strong = new Referred(); System.gc(); Thread.sleep(2000); //删去引用 strong = null; System.out.println("删除强引用后"); System.gc(); Thread.sleep(2000); } static class Referred { @Override protected void finalize() throws Throwable { System.out.println("GC时引用对象被收集"); } } }
finalize()函数:如果一个对象覆盖了finalize()函数,则在对象被回收时,finalize()方法会被GC收集器触发。每个对象的finalize()函数只会被系统调用一次。
运行结果如下:
删除强引用后
GC时引用对象被收集
当强引用存在时,GC时对象没被回收;当强引用被删除,GC时对象被回收。
软引用
软引用的对象,在内存溢出前,会进行二次回收,这种特性非常适合做缓存。
示例代码如下:
/** * VM args: -Xmx100m -Xms100m * */public class SoftRef { public static void main(String[] args) throws InterruptedException { SoftReference<Referred> soft = new SoftReference<Referred>(new Referred()); System.gc(); Thread.sleep(2000); System.out.println("开始堆占用"); try { List<SoftRef> heap = new ArrayList<>(); while (true) { heap.add(new SoftRef()); } } catch (OutOfMemoryError e) { // 软引用对象应该在这个之前被收集 System.out.println("内存溢出"); } } static class Referred { @Override protected void finalize() throws Throwable { System.out.println("GC时引用对象被收集"); } } }
作者:weberweber
链接:https://www.jianshu.com/p/cbe1b7412fe2