1 前言
Java与C/C++相比最大的特点就是Java不需要开发人员进行内存的相关管理。内存的创建与销毁都是由JVM完成的,创建一个对象后会涉及内存空间的分配,对象使用完成后会涉及到内存的回收。当我们需要排查各种内存溢出,内存泄漏问题时,当垃圾收集成为系统达到更高并发量的瓶颈时,我们就需要对这些“自动化”的技术实施必要的监控和调节。这过程必不可少的需要学习JVM对于内存空间的创建分配与回收、JVM是如何判断对象是否存活、不存活的对象(垃圾)如何进行回收等知识。
2 对象存活判断
在JVM中,如果在程序中还存有对一个对象的引用,则表示该对象正在使用,不是垃圾。如果一个对象没有任何指针指向该对象引用地址,则表示该对象是垃圾,需要被JVM回收。对象是否存活的标准是在程序中是否持有该对象的引用。
2.1 引用计数算法
给对象中添加一个引用计数器,每当一个地方引用它,计数器加1,当引用失效时,计数器值就减1。该算法比较简单,执行效率也高,但他有一个缺点,他无法解决两个对象之间相互引用的问题。主流的Java虚拟机里面没有选用引用计数算法来管理内存,就是这个原因。
2.2 可达性分析算法
通过一系列的名为“GC Root”的对象作为起点,从这些节点向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Root没有任何引用链相连时,则该对象不可达,该对象是不可使用的,垃圾收集器将回收其所占的内存。
在java语言中,可作为GCRoot的对象包括以下几种:
- java虚拟机栈(栈帧中的本地变量表)中的引用的对象。
- 方法区中的类静态属性引用的对象。
- 方法区中的常量引用的对象。
- 本地方法栈中JNI本地方法的引用对象。
2.2.1 可达性分析图解![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/e899d488bdb6fe006a041825abbe138f.png)
3 垃圾收集算法
由于整个JVM运行过程中,所有对象实例都放在堆内存中,所以垃圾回收算法也是基于堆内存进行讨论。
3.1 标记-清除(Mark-Sweep)
3.1.1 概念
找出内存中需要回收的对象,并且把它们标记出来。在标记完成后清除掉被标记需要回收的对象,释放出对应的内存空间。
3.1.2 过程图解
3.1.3 优缺点
- 优点:算法实现简单。
- 缺点:1、执行效率低,标记与清除过程效率都不高;2、标记清除后会产生大量不连线的内存空间碎片。
3.2 标记-整理(Mark-Compact)
3.2.1 概念
标记过程仍然与"标记-清除"算法一样,但是后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。
3.2.2 过程图解
3.2.3 优缺点
- 优点:不产生碎片。
- 缺点:执行效率低,在标记-清除的基础上还要进行对象的移动,时间成本更高。
3.3 复制(Copying)
3.3.1 概念
将可用内存按容量分为大小相等的两块,每次只使用其中的一块,当这块内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉
3.3.2 过程图解
3.3.3 优缺点
- 优点:实现简单、执行效率高。
- 缺点:1、需要将空间分成相等的两块,浪费了一半的空间;2、对象存活率高时使用复制算法,执行效率会变低。
4 分代收集算法(Generationial Collection)
当前商业虚拟机的垃圾收集都采用“分代收集”(Generational Collection)算法,这种算法并没有什么新的思想,只是根据对象存活周期的不同将内存划分为几块。一般是把Java堆分为新生代和老年代,这样就可以根据各个年代的特点采用最适合的收集算法。在新生代中,每次垃圾收集时都发现有大批对象死去,只有少量存活,那就选用复制算法,只需要付出少量存活对象的复制成本就可以完成。而老年代中因为对象存活率高、没有额外空间对他进行分配担保,就必须使用“标记-清除”或者“标记-整理”算法来进行回收。
分代收集算法图解如下:
5 引用
- 《深入理解Java虚拟机 JVM高级特性与最佳实践》-周志明著