Java的垃圾回收机制:Java的自动内存管理,开发人员无需手动参与内存的分配与回收,这样降低内存泄漏和内存溢出的风险,开发人员可以更专心地专注于业务开发。Java堆是垃圾收集器的工作重点。频繁收集年轻区、较少收集老年区、基本不动方法区。
如何确定一个对象是否为垃圾?
引用计数算法:在 Java 中,引用和对象是有关联的。如果要操作对象则必须用引用进行。因此,很显然一个简单 的办法是通过引用计数来判断一个对象是否可以回收。简单说,即一个对象如果没有任何与之关联的引用,即他们的引用计数都不为0,则说明对象不太可能再被用到,那么这个对象就是可回收对象。
可达性分析算法:为了解决引用计数法的循环引用问题,Java 使用了可达性分析的方法。通过一系列的“GC roots” 对象作为起点搜索。如果在“GC roots”和一个对象之间没有可达路径,则称该对象是不可达的。要注意的是,不可达对象不等价于可回收对象,不可达对象变为可回收对象至少要经过两次标记 过程。两次标记后仍然是可回收对象,则将面临回收。
1 标记清除算法
当堆中的有效内存空间被耗尽的时候,就会停止整个程序(STW,stop the world),然后进行两项工作,第一项是标记,第二项是清除。
- 标记:Collector从引用根节点开始遍历,标记所有被引用的对象。一般是在对象的Header中记录为可达对象。(标记非垃圾对象)
- 清除:Collector对堆内存从头到尾进行线性遍历,如果发现某个对象在其Header中没有标记为可达对象,则将该对象进行回收。
清除不是将垃圾对象的内存空间进行置空,而是把需要清除的对象地址保存在空闲的地址列表里。下次有新对象需要加载时,判断垃圾的位置空间是否足够。如果足够,就存放。
标记清除算法缺点:标记清除算法执行效率不算高,因为在标记阶段需要从引用根节点开始遍历,标记所有被引用的对象;而且在清除阶段,需要对所有对象进行线性遍历。 标记清除算法在进行GC的时候,需要停止整个程序,导致用户体验差。标记清除算法清理出来的空闲内存是不连续的,产生内存碎片,需要维护一个空闲列表。
2 复制算法
复制算法的核心思想:将内存空间分为两块,每次只能使用其中的一块,在垃圾回收时将正在使用的内存中的存活的对象复制到未被使用的内存块中,之后清除正在使用的内存块中的所有对象,交换两个内存的角色,最后完成垃圾回收。
复制算法虽然实现简单,内存效率高,不易产生碎片,但是最大的问题是可用内存被压缩到了原本的一半。且存活对象增多的话,复制算法的效率会大大降低。
3 标记压缩算法
结合了标记清除和复制算法,为了避免缺陷而提出。标记阶段和标记清除算法相同,标记后不是清 理对象,而是将存活对象移向内存的一端。然后清除端边界外的对象。
标记压缩算法消除了标记清除算法当中,内存区域分散的缺点,不会出现内存碎片问题。当需要申请内存时,JVM只需要持有一个内存的起始地址即可。同时消除了复制算法中内存减半的高额代价。
但是标记压缩算法从效率上来说,标记压缩算法要低于复制算法,移动对象的同时,如果对象被其它对象引用,则还需要调整引用的地址。移动对象的过程中,需要停止整个应用程序。(STW)
4 标记清除算法、复制算法、标记压缩算法的对比
标记清除算法 | 标记压缩算法 | 复制算法 | |
---|---|---|---|
速度 | 中等 | 最慢 | 最快 |
空间开销 | 少 | 少 | 多(需要两倍) |
产生内存碎片 | 会 | 不会 | 不会 |
移动对象 | 否 | 是 | 是 |
使用场景 | 老年代 | 老年代 | 新生代 |
年轻代回收特点:区域较小,对象声明周期短、存活率低,回收频繁。
老年代回收特点:区域较大,对象声明周期长、存活率高,回收相对不频繁。
标记清除算法和标记压缩算法的联系
- 标记压缩算法的最终效果等同于标记清除算法执行完成之后,再进行一次内存碎片整理。标记清除算法是一种非移动式的回收算法,标记清除算法标记的存活对象不会被整理,存活对象的地址不会更改,存在内存碎片问题,需要维护一个空闲列表。标记压缩是移动式的,标记的存活对象将会被整理,按照内存地址依次排列,而未被标记的内存会被清理掉,JVM只需要持有一个内存的起始地址即可,无需维护空闲列表。
5 增量收集和分区算法
增量收集
增量收集算法的基本思想:如果一次性将所有的垃圾进行处理,需要造成系统长时间的停顿,那么就可以让垃圾收集线程和应用程序线程交替执行。每次垃圾收集线程只收集一小片区域的内存空间,接着切换到应用程序线程。依次反复,直到垃圾收集完成。增量收集算法的基础仍然是传统的标记清除和复制算法,只不过是允许垃圾收集线程以分阶段的方式完成标记、清理或复制工作。这就解决了应用程序因为长时间处于Stop The World状态导致用户体验不好的情况。
增量收集算法的缺点:使用这种方式,由于垃圾收集过程中,间断性地还执行了应用程序代码,所以能减少系统的停顿时间。但是,因为线程切换和上下文转换的消耗,会使得垃圾回收的总体成本上升,造成了系统吞吐量的下降。
分区算法
因为堆空间特别大,所以一次GC需要的时间就会很长,GC产生的停顿就会很长。为了更好地控制GC产生的停顿时间,将一块大的内存区域分割成多个小块,根据目标的停顿时间,每次合理地回收若干个小区间,而不是整个堆空间,从而减少一次GC所产生的停顿。
分区算法的基本思想:分代算法将按照对象的生命周期长短划分成两个部分,分区算法将整个堆空间划分成连续的不同小区间。每一个小区间都独立使用,独立回收。这种算法的好处是可以控制一次回收多少个小区间。
参考:尚硅谷宋红康JVM全套教程(详解java虚拟机)_哔哩哔哩_bilibili