深入理解java虚拟机笔记(3)垃圾收集器与内存分配

垃圾收集器与内存分配策略

虚拟机栈、本地方法栈和程序计数器随线程而生,也随线程而灭。这几个区域的内存分配与回收非常确定。

java堆和方法区则充满了不确定性。

对java对象进行回收前,需要考虑的一个问题时,哪些java对象可以被回收?

一、引用计数算法

在对象中添加一个引用计数器, 每当有一个地方引用它时, 计数器值就加一; 当引用失效时, 计数器值就减一; 任何时刻计数器为零的对象就是不可能再被使用的

客观地说, 引用计数算法 虽然占用了一些额外的内存空间来进行计数, 但它的原理简单, 判定效率也很高, 在大多数情况下它都是一个不错的算法。 也有一些比较著名的应用案例, 例如微软COM,FlashPlayer、 Python语 言都使用了引用计数算法进行内存管理。

但是, 在Java领域, 至少主流的Java虚拟机里面都没有选用引用计数算法来管理内存, 主要原因是单纯的引用计数就很难解决对象之间相互循环引用的问题。

二、可达性分析算法

当前主流的商用程序语言(Java、 C#) 的内存管理子系统, 都是通过可达性分析(Reachability Analysis) 算法来判定对象是否存活的。 这个算法的基本思路就是通过一系列称为“GC Roots”的根对象作为起始节点集, 从这些节点开始, 根据引用关系向下搜索, 搜索过程所走过的路径称为“引用链”(Reference Chain) , 如果某个对象到GC Roots间没有任何引用链相连,或者用图论的话来说就是从GC Roots到这个对象不可达时, 则证明此对象是不可能再被使用的

在Java技术体系里面, 固定可作为GC Roots的对象包括以下几种:

1.在虚拟机栈(栈帧中的本地变量表) 中引用的对象, 譬如各个线程被调用的方法堆栈中使用到的参数、 局部变量、 临时变量等。

2.在方法区中类静态属性引用的对象, 譬如Java类的引用类型静态变量。

3.在方法区中常量引用的对象, 譬如字符串常量池(String Table) 里的引用

4.在本地方法栈中JNI(即通常所说的Native方法) 引用的对象。

5.Java虚拟机内部的引用, 如基本数据类型对应的Class对象, 一些常驻的异常对象(比如NullPointExcepiton、 OutOfMemoryError) 等, 还有系统类加载器。

6.所有被同步锁(synchronized关键字) 持有的对象。

7.反映Java虚拟机内部情况的JMXBean、 JVMTI中注册的回调、 本地代码缓存等。

除了这些固定的GC Roots集合以外, 根据用户所选用的垃圾收集器以及当前回收的内存区域不同, 还可以有其他对象“临时性”地加入, 共同构成完整GC Roots集合。 譬如后文将会提到的分代收集和局部回收(Partial GC) , 如果只针对Java堆中某一块区域发起垃圾收集时 , 必须考虑到内存区域是虚拟机自己的实现细节 , 更不是孤立封闭的, 所以某个区域里的对象完全有可能被位于堆中其他区域的对象所引用, 这时候就需要将这些关联区域的对象也一并加入GC Roots集合中去, 才能保证可达性分析的正确性。

三、方法区的垃圾收集

方法区的垃圾收集主要回收两部分内容: 废弃的常量和不再使用的类型。 回收废弃常量与回收Java堆中的对象非常类似。 举个常量池中字面量回收的例子, 假如一个字符串“java”曾经进入常量池中, 但是当前系统又没有任何一个字符串对象的值是“java”, 换句话说, 已经没有任何字符串对象引用常量池中的“java”常量, 且虚拟机中也没有其他地方引用这个字面量。 如果在这时发生内存回收, 而且垃圾收集器判断确有必要的话, 这个“java”常量就将会被系统清理出常量池。 常量池中其他类(接口) 、 方法、 字段的符号引用也与此类似。

判定一个常量是否“废弃”还是相对简单, 而要判定一个类型是否属于“不再被使用的类”的条件就比较苛刻了。 需要同时满足下面三个条件:

1.该类所有的实例都已经被回收, 也就是Java堆中不存在该类及其任何派生子类的实例。

2.加载该类的类加载器已经被回收, 这个条件除非是经过精心设计的可替换类加载器的场景, 如OSGi、 JSP的重加载等, 否则通常是很难达成的。

3.该类对应的java.lang.Class对象没有在任何地方被引用, 无法在任何地方通过反射访问该类的方法。

Java虚拟机被允许对满足上述三个条件的无用类进行回收, 这里说的仅仅是“被允许”, 而并不是和对象一样, 没有引用了就必然会回收。 在大量使用反射、 动态代理、 CGLib等字节码框架, 动态生成JSP以及OSGi这类频繁自定义类加载器的场景中, 通常都需要Java虚拟机具备类型卸载的能力, 以保证不会对方法区造成过大的内存压力

 

posted @ 2022-03-16 16:24  Mars.wang  阅读(28)  评论(0编辑  收藏  举报