Java内存垃圾回收
这里记录一下Java内存中的垃圾回收的相关内容。垃圾回收就是找到垃圾,在合适的时间用合理的方法将不用的内存回收。
如何识别垃圾
垃圾回收讨论的是堆内存里面的对象。
什么是垃圾
以后永远不再被使用的对象就是垃圾。
识别算法
引用计数法
每个对象搞一个引用计数器,有别的地方引用,计数器加1;原来引用现在不再引用,计数器减1。当计数器为0时,没有任何地方引用到这个对象,这个对象就无法再被访问,就识别为垃圾。
优势
算法简单。
问题
循环引用或者引用孤岛。形成孤岛以后,对象没有办法被访问,但是引用计数不是0,无法被回收。
扩展知识
引用类型
引用类型 | 对象是否可引用 | 回收时间 | 使用场景 |
---|---|---|---|
强引用 Strongly Reference | 可以 | 不回收 | 一般对象 |
软引用 Soft Reference | 可以 | 内存不足时 | 内存敏感的高速缓存 |
弱引用 Weak Reference | 可以 | 下一次GC | 对象缓存 |
虚引用 Phantom Reference | 不可以 | 下一次GC,不影响对象生命周期 | 必须和引用队列(ReferenceQueue)一起使用,一般用于追踪垃圾收集器的回收动作。相比对象的finalize方法,虚引用的方式更加灵活和安全。 |
根可达性分析
GC Roots对象到该对象之间有引用,是可达的,就认为该对象不是垃圾,如果不可达,该对象是不可用的(不一定是垃圾)。
不可用的对象不一定被判定为可以回收的对象:判定对象为”死亡”至少需要经历两次标记的过程。
- 第一次标记:对象可达性分析,如果发现对象没有与GC Roots相连接的引用链,且对象需要执行finalize方法,将会被加入F-Queue队列中。
- 第二次标记:由一个优先级低的Finalizer线程去取F-Queue队列的对象,“尝试执行”对象的finalize方法。
JVM会保证触发满足条件的对象的finalize方法,但是并不承诺会等待方法执行结束。finalize方法是对象逃脱死亡命运的最后一次机会。
哪些对象可以作为GC Roots
- 虚拟机栈里面引用的对象
- 本地方法栈引用的对象
- 方法区中静态属性引用的对象
- 方法区中常量引用的对象
何时回收垃圾
主动回收
主动调用System.gc(),不推荐。
系统自动调用
系统觉得内存不够的时候。下面讨论一些虚拟机常用的收集方法,过程中可以看到什么情况下算内存不够。
如何回收垃圾
分代收集理论
- 弱分代假说:绝大多数对象朝生夕死
- 强分代假说:熬过越多次回收的对象越难以消亡
- 跨代引用假说:跨代引用占极少数
标记-清除算法
找到需求清除的内存对象,标记,然后统一清除。是一个基础算法
缺陷
- 执行效率不稳定
- 内存碎片
标记-复制算法
每次只使用一半空间,另外一半留着,如果标记完成,就把不需要清除的统一复制到另外一半内存,然后把原来一半清空作为待复制区。循环即可。
缺陷
- 内存利用率低
采用优化算法可以提高利用率,但是总是有些内存是不能被使用的。
新生代使用这种算法。
默认Eden取与survivor to区比值是8:1,两个survivor区一样大。
新对象构造出来先进Eden区,Eden区快满了,就把Eden区和from区的标记,然后拷贝到to区。如果太多,由老年代担保,放入老年代。拷贝完成之后,to和from的名字对调。如果直接来了一个特别大的对象,直接进入老年代。Eden去加上from区还有to区都是新生代,新生代GC叫young GC 或者 minor GC。对象在新生代GC超过15次还没有被回收就会放到老年代。
标记-整理算法
标记好了,移动存活的对象指针,空出剩余区域。
老年代使用这种算法,效率低,stop the world。
折中办法:平时标记清除,碎片太多分配不了了,就统一整理一把。