垃圾回收
怎么确定垃圾?
引用计数法
多用于python
操作对象必须引用进行,可将对象和引用结合,当对象被引用,计数+1,引用失效时计数+1,当计数为0时,对象没有被引用,进行GC
当对象相互引用且不被其他变量引用时,计数永远为1,不能被GC,不然会造成内存泄露(循环引用)
可达性分析
为了解决引用计数法中循环引用的问题,java使用可达性分析。先确定一系列GC roots根对象作为起点
–》从上到下扫描堆中的对象,如果被根对象直接/间接引用,则不能GC
当根对象和对象之间无可达路径时,对象被标记为不可达对象,但是不可达不代表是可回收,至少需要经过两次标记后仍然是可回收才可被GC
需要在一个一致性的快照中进行,不然结果的准确性难以保证,也是GC时需要STW的原因之一
而什么才是根对象?
参考了其他文档,得出大概的解释
静态变量,GC时当前正在执行的方法中的本地变量,native栈中操作系统引用的对象,系统类(由启动类加载器加载的),活动线程,被加锁的对象
垃圾收集算法
标记清除MarkSweep
两个阶段:标记和清除
在内存空间被耗尽时,先STW,进行标记操作和清除操作,从引用根节点开始遍历,标记被引用的对象,清除未被标记为可达对象的,但是清除并不是置空,而是把要清除的对象放在空闲的地址列表中,当下一次插入新对象时先判断空间是否足够
容易产生内存碎片,当对象内存较大,清除后的内存区不连续,导致无法插入新对象,会导致内存溢出
复制Copy
主要解决了标记清除内存碎片化的问题,分为两个区域,From和To,如下图所示,将存活的对象复制到To区,复制的过程中顺便进行碎片化整理,再将From区的垃圾清空,最后From和To区交换(新生代的minor GC操作就是用的复制算法)
虽然复制算法解决了内存碎片化问题,但是因为需要将内存分为两个部分,当存活对象较多时,执行效率会降低
标记整理MarkCompact
结合之前的两种算法,解决了内存碎片化和划分内存的缺陷
先标记要被回收的对象,将存活的对象往内存一端进行移动,再清理掉边界外的内存。
但是整理时牵扯到对象的移动,速度较慢
Java会根据具体情况,多种算法结合进行GC操作
分代收集算法
根据jvm内存的不同生命周期划分为不同的区域,java运行时内存就是采用该方法,即分为新生代和老年代,每个区域采用合适的算法去实现GC操作
详细的新生代和老年代介绍可查看
JVM运行时内存