标签:sdn 赋值 反向 pretty desc rda 取消 sky 生产环境
假设还没看过第一篇的朋友请移步:JAVA内存回收(1)—深入浅出Java垃圾回收机制
不论什么垃圾收集算法必须完毕两件事情。首先,它必须检測出垃圾对象。其次,它必须回收垃圾对象所占用的堆空间并使之对程序又一次可用。
垃圾检測通常通过定义一个根引用集并计算其可达对象集的方式来实现。一个对象,假设能够通过某条始于根引用的引用路径而被运行程序訪问到的话,则称其为可达的(reachable)。对程序而言。根引用始终是能够訪问的。
一个对象假设是可达的,则称其为活动对象。否则就被称为垃圾,由于它对程序的未来运行不再有不论什么影响。
根引用集的构成取决于JVM的详细实现,但总是包含全部栈帧中局部变量区和操作数栈所持有的引用以及保存在静态变量中的引用。
根引用的另外一个来源是常量池,已装载类的常量池可能保存有对堆中一些字符串的引用,这些字符串往往是类名、父类名、父接口名、域名、域签名、方法名和方法签名等。根引用还有一个可能来源就是那些传递给本地方法(native method)但尚未被其释放的引用。根引用的还有一个潜在来源是JVM的运行时数据区,由于某些实现会把JVM运行时数据区的一部分放在堆上,比如方法区中的类数据本身。
可达对象集包含全部通过根引用能够直接或间接被程序訪问到的对象。从技术角度讲,可达对象集是“指向”关系下根引用集的传递闭包。
区分活动对象和垃圾的两个基本方法是引用计数和追踪。JDK中的标准垃圾收集器全部採用了追踪的办法,尽管详细形式各不相同。
引用计数是垃圾收集的早期策略。在这样的方法中,堆中的每一个对象都有一个引用计数。当对象被创建并且指向其引用被赋值给一个变量后,该对象的引用计数被设置为1。以后每当其引用被赋值给一个不同的变量时,该对象的引用计数就加1。
当持有该对象引用的变量离开其作用域或者被赋给一个新值时。该对象的引用计数就减1。
任一对象一旦其引用计数变为0。就成为垃圾。
一个对象一旦被当作垃圾收集后,它所引用的全部其它对象的引用计数必须对应递减。这样。对一个对象的垃圾收集可能引发连续的对其它对象的垃圾收集。
该方法的优点是。引用计数收集器算法简单,适于做增量收集,对于程序不能被长时间打断的实时环境特别适合。另外。收集过程也有助于改进引用局部性。
坏处就是,引用计数无法检測出不可达的循环结构(两个或多个对象之间相互引用)。由于它们的引用计数永远不会为0。
还有一个坏处就是每次增减引用计数都带来额外开销。并且该算法还须要编译器的高度配合。正是由于这些固有缺陷,引用计数算法在生产环境中非常少使用。
追踪收集器从根引用開始探寻并描画对象引用图。探寻过程中遇到的对象会以某种方式被打上标记。
通常来说该标记既可保存在对象本身,也可保存在单独的位图中。
探寻结束后未被标记的对象就是不可达的对象,可被当作垃圾收集。
主要的追踪算法被称作“标记并清理”(mark and sweep)。
该名字指出了垃圾收集过程的两个阶段。在标记阶段。垃圾收集器遍历引用树,标记每一个遇到的对象。在清理阶段,未被标记的对象被释放,对应内存被返还待用。在JVM中,清理阶段必须包含对象的了结(finalization)。
标记并清理算法实现简单,能够轻易回收循环结构。并且不存在为维护引用计数而付出的额外开销和对编译器的依赖。可是它也有不足,当中最大的问题是,在清理阶段,堆中的全部对象,不论是否可达,都会被訪问。
一方面这对于可能有页面交换的堆所依赖的虚存系统有着非常负面的性能影响;还有一方面。由于当中非常大一部分对象可能是垃圾,这就意味着垃圾收集器把大量精力都花费在检查和处理垃圾上面了。不管从哪个角度来看。该算法都可能产生收集暂停时间过长、收集开销偏大的问题。标记并清理收集器的还有一个不足是它easy导致堆的碎片化,从而引发引用局部性或者大对象分配失败等方面的问题。
在主流的商用程序语言(Java、C#。甚至包含前面提到的古老的Lisp)的主流实现中,都是称通过可达性分析(Reachability Analysis)来判定对象是否存活的。这个算法的基本思路就是通过一系列的称为“GC Roots”的对象作为起始点,从这些节点開始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有不论什么引用链相连(用图论的话来说,就是从GC Roots到这个对象不可达)时,则证明此对象是不可用的。如图3-1所看到的,对象object 5、object 6、object 7尽管互相有关联,可是它们到GC Roots是不可达的,所以它们将会被判定为是可回收的对象。
在Java语言中,可作为GC Roots的对象包含以下几种:
虚拟机栈(栈帧中的本地变量表)中引用的对象。
方法区中类静态属性引用的对象。
方法区中常量引用的对象。
本地方法栈中JNI(即一般说的Native方法)引用的对象
JVM的垃圾收集器非常可能拥有一个对付堆碎片的策略。
标记并清理收集器通常採用的两种策略是压缩或拷贝。
这两种方法都是通过高速移动对象来降低堆碎片。压缩收集器把活动对象越过空暇内存区滑动到堆的一端,在这个过程中,堆的还有一端就变成了一块大的连续空暇区。
全部指向被移动对象的引用也被更新,指向新的位置。
为了更好的理解压缩过程,能够将堆比作书架的一格。当中一部分放满了不同厚度的图书。空暇空间就是图书之间的空隙。压缩就是将全部图书朝一个方向推移,以弥合全部空隙。
它从最靠近隔板的图书開始。将它推向隔板,然后将离隔板第二近的图书推向第一本图书。接着将第三本图书推向第二本图书,依此类推。最后,全部图书在一端,全部空暇空间在还有一端。
被移动对象的引用更新能够通过为对象引用加入一层间址而得到简化。对象引用不再直接指向堆中的对象。而是指向对象句柄表中的一个表项,该表项中的对象句柄才直接指向堆中的实际对象。
这样。当对象被移动时,仅仅需在对象句柄表中更新其句柄,运行程序中全部指向该对象的引用都不必再更新。这样的方法简化了消除堆碎片的工作,但添加了每一次对象存取的开销。
拷贝收集器相同使用追踪技术,它把全部的活动对象移动到一个新的区域,而原有区域就全部变成了空暇空间。由于被移动对象在新的区域中被紧挨着放置。因而在原有区域时对象之间可能存在的空隙也被消除。
对象的拷贝能够在追踪过程中即时进行,不必通过标记和清理两个单独的阶段来完毕。对象被实时复制到新的区域后,它在原有区域中的副本被一个转向指针(forwarding pointer)所代替,该指针指向该对象在新的区域中的副本。转向指针让垃圾收集器检測出那些引用(其所指对象已经被移动到新的区域中)并以转向指针的值更新之,从而使它们指向对象的新位置。
一个通用的拷贝收集器算法被称作“停止并拷贝”(stop and copy)。在这个方法中,堆被分成两个区域,不论什么时候都仅仅使用当中的一个区域。
对象在同一区域中分配,直到该区域的全部空间被耗尽。
此时,程序运行被中止。堆被遍历,遍历过程中遇到的活动对象被复制到堆的还有一个区域。当停止并拷贝过程完毕后,程序恢复运行。对象的内存将从堆的这个新区域中分配,直到它也被用尽。那时程序将被再次中止。堆被遍历。活动对象被拷贝回原来的区域。该方案的代价是所需内存是指定堆空间的两倍。由于不论何时都仅仅有一半的内存被使用。
拷贝收集算法的优点是仅仅訪问活动对象。垃圾对象不会被检查。自然也无需被换页到内存或被缓存。收集过程所用时间仅仅取决于活动对象的数量。这样既避免了不必要的收集开销。又最大限度地降低了收集暂停时间。只是。除了额外的内存消耗外,拷贝收集器还须要承担对象拷贝和引用更新所带来的成本添加。这一点在长寿对象较多时体现得更为明显,由于每次收集时它们都要被来回复制。
通过观察和实验,对多种语言所写的大多数应用程序而言,它们所创建的对象都具有例如以下特征:1)大部分对象的寿命都非常短。但总有一些对象会活得足够长;2)年长对象非常少引用年幼对象。以上事实也被称作“弱代假说”(weak generational hypothesis),它是分代收集算法的前提和基础。
在这样的方法中,对象依照年龄分成组(代),堆则被划分成两个或多个子堆,每一个子堆服务于一代对象,因此子堆也经常被称作某某代。最年幼代被进行最频繁的垃圾收集。
由于大部分对象的寿命都非常短,所以仅仅有非常小一部分的最年幼对象经历首次收集后还能存活。
假设一个最年幼对象在经历几次垃圾收集后依旧存活,那么它将被提升到寿命更高的一代(被移动到还有一个子堆中去)。
与相对年幼代比較。相对年长代被垃圾收集的频率总会有所降低。
随着对象在其当前代中的不断成熟(经历多次垃圾收集而不死),终于它们会被移动到较其当前代更为年长的一代中。
分代收集技术既可应用于拷贝算法,以解决它在处理长寿对象时效率低下的问题,也可应用于标记并清理算法。不管在哪种情况下,把堆划分为对象代都有助于提高最主要的垃圾收集算法的效率。
自适应收集算法利用例如以下事实:一些收集算法在某些情况下工作的更好,而还有一些则在其它情况下工作的更好。自适应收集器监控堆的当前状态并据此对其所用垃圾收集技术做对应调整。它可能仅仅在程序运行的同一时候对单一收集算法的參数做出调整。也可能从一种算法高速切换到还有一种算法,甚或还可能把堆划分为子堆并在不同的子堆上同一时候使用不同的算法。
使用自适应方法。JVM实现的设计者无需再选择某种特定的垃圾收集技术。他们能够使用多种技术,为每种算法分配最适合它的工作。
实际上,在现代JVM实现中,大部分垃圾收集子系统都具有某种程度的自适应能力,非常多时候我们仅仅须选择策略并设定目标便能得到惬意的结果。至于详细的收集算法选择和參数配置则可交由垃圾收集子系统自行处理。Sun的HotSpot JVM中的垃圾处理子系统就是这样运作的,详细细节有时间再专门讨论。
參考:
1)Cheney’s algorithm
2)Garbage Collection - Chapter 9 of Inside the Java Virtual Machine
3)Java theory and practice: A brief history of garbage collection
举报
1、Java虚拟机执行时的...
0条评论