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


举个简单的例子:对象objA和objB都有字段,赋值令objA.=objB及objB.=objA,除此之外,这两个对象再无任何引用,实际上这两个对象已经不可能再被访问,但是它们因为互相引用着对方,导致它们的引用计数都不为零,引用计数算法也就无法回收它们 。所以主流的Java虚拟机不选用该算法 。
(objA 引用 objB,objB 引用了objA) 互相引用,objA的计数器是 1,objB的计数器也是 1,如果使用引用计数算法,永远不会被回收,但是极大可能是无用的对象 。
可达性分析
当前主流的商用程序语言(Java、C#,上溯至前面提到的古老的Lisp)的内存管理子系统,都是通过可达性分析( )算法来判定对象是否存活的 。这个算法的基本思路就是通过一系列称为“GC Roots”的根对象作为起始节点集,从这些节点开始,根据引用关系向下搜索,搜索过程所走过的路径称为“引用链”( Chain),如果某个对象到GC Roots间没有任何引用链相连,或者用图论的话来说就是从GC Roots到这个对象不可达时,则证明此对象是不可能再被使用的 。
如图所示,对象 5、 6、 7虽然互有关联,但是它们到GC Roots是不可达的,因此它们将会被判定为可回收的对象 。
在Java技术体系里面,固定可作为GC Roots的对象包括以下几种:(4 + 1 个答案)
? ·在虚拟机栈(栈帧中的本地变量表)中引用的对象,譬如各个线程被调用的方法堆栈中使用到的参数、局部变量、临时变量等 。
? ·在方法区中类静态属性引用的对象,譬如Java类的引用类型静态变量 。(JvmGc jvmGc ;)
? ·在方法区中 常量引用 的对象,譬如字符串常量池( Table)里的引用 。
? ·在本地方法栈中JNI(即通常所说的方法)引用的对象 。
? ·Java虚拟机内部的引用,如基本数据类型对应的Class对象,一些常驻的异常对象(比如、)等,还有系统类加载器 。
? · 所有被同步锁(关键字)持有的对象。
? ·反映Java虚拟机内部情况的、JVMTI中注册的回调、本地代码缓存 等。
除了这些固定的GC Roots集合以外,根据用户所选用的垃圾收集器以及当前回收的内存区域不同,还可以有其他对象“临时性”地加入,共同构成完整GC Roots集合 。譬如后文将会提到的分代收集和局部回收( GC),如果只针对Java堆中某一块区域发起垃圾收集时(如最典型的只针对新生代的垃圾收集),必须考虑到内存区域是虚拟机自己的实现细节(在用户视角里任何内存区域都是不可见的),更不是孤立封闭的,所以某个区域里的对象完全有可能被位于堆中其他区域的对象所引用,这时候就需要将这些关联区域的对象也一并加入GC Roots集合中去,才能保证可达性分析的正确性 。(跨代引用,(记忆集,卡表))
引用
在JDK 1.2版之后,Java对引用的概念进行了扩充,将引用分为强引用( Re-)、软引用(Soft )、弱引用(Weak )和虚引用( )4种,这4种引用强度依次逐渐减弱 。
·强引用是最传统的“引用”的定义,是指在程序代码之中普遍存在的引用赋值,即类似“ obj=new ()”这种引用关系 。无论任何情况下,只要强引用关系还存在,垃圾收集器就永远不会回收掉被引用的对象 。
·软引用是用来描述一些还有用,但非必须的对象 。只被软引用关联着的对象,在系统将要发生内存溢出异常前,会把这些对象列进回收范围之中进行第二次回收,如果这次回收还没有足够的内存,才会抛出内存溢出异常 。在JDK 1.2版之后提供了类来实现软引用 。
·弱引用也是用来描述那些非必须对象,但是它的强度比软引用更弱一些,被弱引用关联的对象只能生存到下一次垃圾收集发生为止 。当垃圾收集器开始工作,无论当前内存是否足够,都会回收掉只被弱引用关联的对象 。在JDK 1.2版之后提供了类来实现弱引用 。