一、为什么需要垃圾回收
如果不进行垃圾回收,内存迟早都会被消耗空,因为我们在不断的分配内存空间而不进行回收。除非内存无限大,我们可以任性的分配而不回收,但是事实并非如此。所以,垃圾回收是必须的。
二、哪些内存需要回收?
哪些内存需要回收是垃圾回收机制第一个要考虑的问题,所谓“要回收的垃圾”无非就是那些不可能再被任何途径使用的对象。那么如何找到这些对象?
1、引用计数法
这个算法的实现是,给对象中添加一个引用计数器,每当一个地方引用这个对象时,计数器值+1;当引用失效时,计数器值-1。任何时刻计数值为0的对象就是不可能再被使用的。
2、可达性分析法
这个算法的基本思想是通过一系列称为“GC Roots”的对象作为起始点,从这些节点向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链(即GC Roots到对象不可达)时,则证明此对象是不可用的。
那么问题又来了,如何选取GCRoots对象呢?在Java语言中,可以作为GCRoots的对象包括下面几种:
- 虚拟机栈(栈帧中的局部变量区,也叫做局部变量表)中引用的对象。
- 方法区中的类静态属性引用的对象。
- 方法区中常量引用的对象。
- 本地方法栈中JNI(Native方法)引用的对象。
三、四种引用状态
1、强引用
代码中普遍存在的类似"Object obj = new Object()"这类的引用,只要强引用还存在,垃圾收集器永远不会回收掉被引用的对象。
2、软引用
描述有些还有用但并非必需的对象。在系统将要发生内存溢出异常之前,将会把这些对象列进回收范围进行二次回收。如果这次回收还没有足够的内存,才会抛出内存溢出异常。Java中的类SoftReference表示软引用。
3、弱引用
描述非必需对象。被弱引用关联的对象只能生存到下一次垃圾回收之前,垃圾收集器工作之后,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。Java中的类WeakReference表示弱引用。
4、虚引用
这个引用存在的唯一目的就是在这个对象被收集器回收时收到一个系统通知,被虚引用关联的对象,和其生存时间完全没关系。Java中的类PhantomReference表示虚引用。
四、方法区的垃圾回收
1、方法区的垃圾回收主要回收两部分内容
- 废弃常量。
- 无用的类。
2、如何判断无用的类呢?
- 该类的所有实例都已经被回收,即Java堆中不存在该类的任何实例。
- 加载该类的ClassLoader已经被回收。
- 该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。
五、垃圾收集算法
- 标记-清除(Mark-Sweep)算法
过程:标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象。
缺点:1.标记和清除的效率不高 2.会产生大量的不连续的内存碎片,会导致gc提前触发 - 复制(Copying)算法(针对新生代)
过程:将内存空间化为大小相等的两块,每次只使用其中一块,当这一块内存用完了,就将还存活的对象复制到另外一块上面,然后把已使用过的内存空间一次清理掉。
缺点:1:1划分使得可使用内存大幅度缩小,代价比较高
优化: HotSpot默认将内存分为一块较大的Eden空间和两块较小的Survivor空间,大小比例8:1,每次使用Eden和其中一块Survivor,当回收时,将Eden和Survivor中还存活的对象一次性地复制到另外一块Survivor空间上,最后清理掉Eden和刚才使用过的Survivor空间,这样可以使新生代可用内存空间为90%,但是我们并不能保证每次gc的时候只有不多于10%的对象存活,这时需要依赖其他内存(老年代)进行分配担保。 - 标记-整理(Mark-Compact)算法(针对老年代)
过程:与标记-清除一致,但后续步骤不是直接对可回收对象进行整理,而是让所有存活对象都向一端移动,从而清理掉端边界以外的内存。 - 分代收集算法
新生代每次回收都有大批对象死去,只有少量存活,所以采用复制算法
老年代因为对象存活率高,没有额外空间对它进行分配担保,所以必须采用标记-整理、标记-清除算法