学习内容:第3章 - 垃圾收集器与内存分配策略
在前面对 Java 内存区域和对应的内存溢出异常的学习中,多次提及了 JVM 的垃圾收集器和内存动态分配,在这一章,我将跟随作者的叙述,去学习 Java 虚拟机的垃圾收集器与内存分配策略的相关知识
章节概述
垃圾收集(Garbage Collection,GC)被很多人当成是 Java 语言的伴生产物,但事实上,垃圾收集的概念和应用很早就被提出了,1960 年的 Lisp 语言,是第一个使用垃圾收集技术与内存动态分配的语言,其作者思考过垃圾收集需要完成的三件事:
- 哪些内存需要回收?
- 什么时候回收?
- 如何回收?
在第 2 章的学习中,我们知道,程序计数器、虚拟机栈、本地方法栈这 3 个区域随线程而生,随线程而灭,这几个区域的内存分配和回收都具备确定性,不需要过多考虑如何进行回收的问题,因为当方法结束或者线程结束时,内存自然就跟随着回收了。
而 Java 堆和方法区则有显著的不确定性,只有在运行期间我们才能知道程序究竟会创建哪些对象,创建多少对象,这部分的内存分配和回收是动态的。垃圾收集器关注的正是这部分内存该如何管理。
对象的存活与死去
如前面提到, 垃圾收集器需要先判断哪些内存需要回收,对此设计了一些相关的算法。
引用计数算法(Reference Counting)
最容易想到的是引用计数算法,为每个对象维护一个引用计数器,每当有一个地方进行引用的时候,计数器值加一,引用失效时,计数器值减一,计数器值为零的对象不可用。引用计数算法原理简单、判定高效,所以在一些领域,如 Flash Player、微软 COM 技术、Python 语言等都使用了引用计数算法进行内存管理。但是在 Java 中,并没有选用它来管理内存。因为这个看似简单的算法,必须要配合大量的额外处理才能保证正确的工作,比如单纯使用引用计数算法无法解决对象之间循环引用的问题。
代码均在我的 Git 仓库中:https://github.com/nx-xn2002/JVM-Learn.git
/**
* reference count gc
* VM Options:-Xlog:gc*
* 代码均在我的 Git 仓库中:[https://github.com/nx-xn2002/JVM-Learn.git]
*
* @author Ni Xiang
*/
public class ReferenceCountGC {
public Object instance = null;
private static final int _1MB = 1024 * 1024;
/**
* 占用一点内存,以便在 GC 日志中看清楚是否有进行回收
*/
private byte[] bigSize = new byte[2 * _1MB];
public static void main(String[] args) {
ReferenceCountGC objA = new ReferenceCountGC();
ReferenceCountGC objB = new ReferenceCountGC();
objA.instance = objB;
objB.instance = objA;
objA = null;
objB = null;
//假设此处发生GC,objA 和 objB 是否能被回收?
System.gc();
}
}
此处作者没有明确说明如何查看 GC 日志,我找到的是使用 -XX:+PrintGCDetails
参数,运行时虚拟机提示说这个已经过时了,所以改为使用控制台中提示的 -Xlog:gc*
参数
结果如下:
本来按引用计数法,不该发生 GC 的 objA
和 objB
却被回收了,因此说明 JVM 使用的不是引用计数法来判断对象是否存活。