垃圾回收入门(下)
上篇文章中提到了jvm为一个新对象进行内存分配的过程和实现,接下来我们看看如何将进行垃圾回收。在jvm中,垃圾指的是已经死亡的对象所占据的堆空间。这里便涉及如何确定一个对象已经死亡?
引用计数法及可达性分析
引用计数法
每个对象被引用一次后,计数+1;如果引用者销毁了,则引用计数-1;
优点:引用计数收集器可以很快的执行,交织在程序运行中。对程序需要不被长时间打断的实时环境比较有利。
弱点:无法解决两个类中的对象互相引用,计数无法为0的问题:
例如现在有两个类:
Class A {
B aMember;
}
class B {
A bMember;
}
即使类A 和 类B 的对象都没有被引用了,此时他们的引用计数无法为0。因为类中彼此使用了对方的对象作为成员变量;
可达性分析法
把所有的引用关系看作一张图,从一个节点GC ROOT开始,寻找对应的引用节点,找到这个节点以后,继续寻找这个节点的引用节点,当所有的引用节点寻找完毕之后,剩余的节点则被认为是没有被引用到的节点,即无用的节点,就是可以被回收的。
java中可作为GC Root的对象有
1.虚拟机栈中引用的对象(本地变量表)
2.方法区中静态属性引用的对象
3. 方法区中常量引用的对象
4.本地方法栈中引用的对象(Native对象)
当标记完所有存活的对象,我们就可以真的进行回收了
回收方式
- 清除
将未被标记存活的对象空间标记为空闲,挂在free list上,等下次要分配空间时,从free list中取出。这种的做法比较简单,但很容易产生内存碎片,同时也容易出现总空间足够,但没有合适的内存可分配的情况。
2.标记整理算法
对标记-清除算法进行改进,在回收不存活的对象占用的空间后,会将所有的存活对象往左端空闲空间移动,并更新对应的指针。它解决了内存碎片的问题,但代价是压缩算法消耗了更多的性能。
- 标记复制算法
为了克服句柄的开销和解决堆碎片的垃圾回收。它开始时把堆分成 一个对象 面和多个空闲面, 程序从对象面为对象分配空间,当对象满了,基于copying算法的垃圾 收集就从根集中扫描活动对象,并将每个 活动对象复制到空闲面,这样空闲面变成了对象面,原来的对象面变成了空闲面,程序会在新的对象面中分配内存。
这里KEN FOX对上述几种算法实现了一个可视化演示,效果非常直观:https://spin.atomicobject.com/2014/09/03/visualizing-garbage-collection-algorithms/
总结
在文章开始,我们总结了
附录
上述回收算法是根据我们所选用的垃圾回收器(Garbage Collector)来选用的,一般来说不同的代会采用不同的collector,如下图:
image.png
针对新生代,它的垃圾回收器是Serial、ParNew和Parallel Scavenge,使用的是标记复制算法。
Serial:适用于单线程运行的环境,例如命令行程序。它会停止当前应用运行,用单个线程去回收、压缩内存,在服务端程序上不适用。 可以在程序运行时设置-XX:+UseSerialGC JVM 参数来使用 serial 的回收器.
Parallel: 现代jvm的默认选项。当CPU数大于2,就可用多线程进行新生代的垃圾回收。进行FullGC的时候还是会停止应用的运行。适用于需要完成大量任务并且能接受时间较长的应用暂停。
针对老年代,它的垃圾回收器是CMS 或 G1。
CMS (Concurrent Mark Sweep): 回收策略采用的是压缩整理算法。适用于回收耗时要较短,例如桌面应用程序,web服务器的请求、数据库的查询;用到的垃圾回收算法与 Parallel一样. FullGC用多线程进行处理,不过是在应用运行的时候,同时并发地运行回收,最小化“停止程序运行”的时间,但不进行压缩。
G1 (Garbage First): 对标是CMS的替代品,可以进行老年代的压缩。但G1其实能在新生代和老年代中混用。
链接: https://www.oracle.com/webfolder/technetwork/tutorials/obe/java/gc01/index.html