在阿里云建设一个网站的全流程,采购系统erp软件,电商网站建设内容,深圳博纳网站建设对于JVM的垃圾收集#xff08;GC#xff09;#xff0c;这是一个作为Java开发者必须了解的内容#xff0c;那么#xff0c;我们需要去了解哪些内容呢#xff0c;其实#xff0c;GC主要是解决下面的三个问题#xff1a; 哪些内存需要回收#xff1f; 什么时候回收GC这是一个作为Java开发者必须了解的内容那么我们需要去了解哪些内容呢其实GC主要是解决下面的三个问题 哪些内存需要回收 什么时候回收 如何回收
回答了这三个问题也就对于GC算法的原理有了最基本的了解。 1 如何判定哪些内存需要回收 在Java虚拟机的堆中会存放着很多的对象那么我们需要回收垃圾的时候是通过什么算法来判断哪些垃圾的生命周期已到需要回收呢接下来的几种算法将帮助你解决这几个问题。
引用计数算法
先讲讲第一个算法引用计数算法。 其实这个算法的思想非常的简单一句话就是给对象中添加一个引用计数器每当有一个地方引用它时计数器加1当引用失效时计数器减1任何时刻计数器为0的对象就是不可能再被使用的。 这些简单的算法现在是否还被大量的使用呢其实现在用的已经不多没有被使用的最主要的原因是他有一个很大的缺点很难解决对象之间循环引用的问题。 循环引用当A有B的引用B又有A的引用的时候这个时候即使A和B对象都为null这个时候引用计数算法也不会将他们进行垃圾回收。 public class Test_02 {public static void main(String[] args) {Instance instanceA new Instance();Instance instanceB new Instance();instanceA.instance instanceB;instanceB.instance instanceA;instanceA null;instanceB null;System.gc();Scanner scanner new Scanner(System.in);scanner.next();}
}class Instance{public Object instance null;
} 如果使用的是引用计数算法这是不能被回收的当然现在的JVM是可以被回收的。
可达性分析算法 这个算法的思想也是很简单的这里有一个概念叫做可达性分析如果知道图的数据结构这里可以把每一个对象当做图中的一个节点我们把一个节点叫做GC Roots如果一个节点到GC Roots没有任何的相连的路径那么就说明这个节点不可达也就是这个节点可以被回收。 上面图中虽然obj7、8、9相互引用但是到GC Roots不可达所以这种对象也是会被当做垃圾收集的。
在Java中可以作为GC Roots的对象包括以下几种 虚拟机栈栈帧中的局部变量表Local Variable Table中引用的对象。 方法区中类静态属性引用的对象。 方法区中常量引用的对象。 本地方法栈中JNI即一般说的Native方法引用的对象。 2 什么时候回收 在可达性分析算法中不可达的对象也不是一定会死亡的它们暂时都处于“缓刑”阶段要真正宣告一个对象“死亡”至少要经历两次标记过程。
step1判断有没有必要执行finalize()方法 如果对象在进行可达性分析后发现没有与GC Roots相连接的引用链那它将会被第一次标记并且进行一次筛选筛选的条件是此对象是否有必要执行finalize()方法。
另外有两种情况都视为“没有必要执行” 对象没有覆盖finaliza()方法。 finalize()方法已经被虚拟机调用过。
step2如何执行 如果这个对象被判定为有必要执行finalize()方法那么此对象将会放置在一个叫做 F-Queue 的队列中并在稍后由一个虚拟机自动建立的、低优先级的Finalizer线程去执行它。
step3执行死亡还是逃脱死亡
首先我们需要知道finalize()方法是对象逃脱死亡命运的最后一次机会稍后GC将对F-Queue 队列中的对象进行第二次小规模的标记。 逃脱死亡对象想在finalize()方法中成功拯救自己只要重新与引用链上的任何一个对象建立关联即可例如把自己this关键字赋值给某个类变量或者对象的成员变量这样在第二次标记时它将被移出“即将回收”的集合。 执行死亡对象没有执行逃脱死亡那就是死亡了。 3 如何回收 如何回收其实就是利用哪些算法进行回收垃圾收集算法这里讲几种大家平时也是看到的比较的算法分别为标记-清除算法、复制算法、标记-整理算法、分代回收算法。 这部分的内容其实在网上的文章比较多了而且基本上的差别不大所以从网上的文章选取下来当做一个小的总结大家可以参考这篇文章算是一个比较全的总结GC算法与内存分配策略。
标记清除Mark-Sweep算法 标记清除Mark-Sweep 算法是最基础的垃圾收集算法后续的收集算法都是基于它的思路并对其不足进行改进而得到的。顾名思义算法分成“标记”、“清除”两个阶段首先标记出所有需要回收的对象在标记完成后统一回收所有被标记的对象标记过程在前一节讲述对象标记判定时已经讲过了。
标记清除算法的不足主要有以下两点 空间问题标记清除之后会产生大量不连续的内存碎片空间碎片太多可能会导致以后在程序运行过程中需要分配较大对象时无法找到足够的连续内存而不得不触发另一次垃圾收集动作。 效率问题因为内存碎片的存在操作会变得更加费时因为查找下一个可用空闲块已不再是一个简单操作。
标记清除算法的执行过程如下图所示 复制Copying算法 为了解决标记-清除算法的效率问题一种称为“复制”Copying的收集算法出现了思想为它将可用内存按容量分成大小相等的两块每次只使用其中的一块。当这一块内存用完就将还存活着的对象复制到另一块上面然后再把已使用过的内存空间一次清理掉。 这样做使得每次都是对整个半区进行内存回收内存分配时也就不用考虑内存碎片等复杂情况只要移动堆顶指针按顺序分配内存即可实现简单运行高效。只是这种算法的代价是将内存缩小为原来的一半代价可能过高了。复制算法的执行过程如下图所示 标记整理Mark-Compact算法 复制算法在对象存活率较高时要进行较多的复制操作效率将会变低。更关键的是如果不想浪费50%的空间就需要有额外的空间进行分配担保以应对被使用的内存中所有对象都100%存活的极端情况所以在老年代一般不能直接选用复制算法。 根据老年代的特点标记整理Mark-Compact算法被提出来主要思想为此算法的标记过程与标记清除算法一样但后续步骤不是直接对可回收对象进行清理而是让所有存活的对象都向一端移动然后直接清理掉边界以外的内存。 具体示意图如下所示 分代收集Generational Collection算法 当前商业虚拟机的垃圾收集都采用分代收集Generational Collection算法此算法相较于前几种没有什么新的特征主要思想为根据对象存活周期的不同将内存划分为几块一般是把Java堆分为新生代和老年代这样就可以根据各个年代的特点采用最适合的收集算法 新生代 在新生代中每次垃圾收集时都发现有大批对象死去只有少量存活那就选用复制算法只需要付出少量存活对象的复制成本就可以完成收集。 老年代 在老年代中因为对象存活率高、没有额外空间对它进行分配担保就必须使用标记-清除或标记-整理算法来进行回收。 4 总结
这里用思维导图做一个小的总结。 参考 堆外内存的回收机制分析 https://www.jianshu.com/p/35cf0f348275 java调用本地方法--jni简介 https://blog.csdn.net/w1992wishes/article/details/80283403 咱们从头到尾说一次 Java 垃圾回收 https://mp.weixin.qq.com/s/pR7U1OTwsNSg5fRyWafucA 深入理解 Java 虚拟机 Java Hotspot G1 GC的一些关键技术 Java Hotspot G1 GC的一些关键技术 - 美团技术团队 附关于GC Roots的理解 所谓“GC roots”或者说tracing GC的“根集合”就是一组必须活跃的引用。 例如说这些引用可能包括
所有Java线程当前活跃的栈帧里指向GC堆里的对象的引用换句话说当前所有正在被调用的方法的引用类型的参数/局部变量/临时值。VM的一些静态数据结构里指向GC堆里的对象的引用例如说HotSpot VM里的Universe里有很多这样的引用。JNI handles包括global handles和local handles看情况所有当前被加载的Java类看情况Java类的引用类型静态变量看情况Java类的运行时常量池里的引用类型常量String或Class类型看情况String常量池StringTable里的引用
注意是一组必须活跃的引用不是对象。 Tracing GC的根本思路就是给定一个集合的引用作为根出发通过引用关系遍历对象图能被遍历到的可到达的对象就被判定为存活其余对象也就是没有被遍历到的就自然被判定为死亡。注意再注意tracing GC的本质是通过找出所有活对象来把其余空间认定为“无用”而不是找出所有死掉的对象并回收它们占用的空间。 GC roots这组引用是tracing GC的起点。要实现语义正确的tracing GC就必须要能完整枚举出所有的GC roots否则就可能会漏扫描应该存活的对象导致GC错误回收了这些被漏扫的活对象。 这就像任何递归定义的关系一样如果只定义了递推项而不定义初始项的话关系就无法成立——无从开始而如果初始项定义漏了内容的话递推出去也会漏内容。 那么分代有什么好处 对传统的、基本的GC实现来说由于它们在GC的整个工作过程中都要“stop-the-world”如果能想办法缩短GC一次工作的时间长度就是件重要的事情。如果说收集整个GC堆耗时太长那不如只收集其中的一部分 于是就有好几种不同的划分partitionGC堆的方式来实现部分收集而分代式GC就是这其中的一个思路。 这个思路所基于的基本假设大家都很熟悉了weak generational hypothesis——大部分对象的生命期很短die young而没有die young的对象则很可能会存活很长时间live long。 这是对过往的很多应用行为分析之后得出的一个假设。基于这个假设如果让新创建的对象都在young gen里创建然后频繁收集young gen则大部分垃圾都能在young GC中被收集掉。由于young gen的大小配置通常只占整个GC堆的较小部分而且较高的对象死亡率或者说较低的对象存活率让它非常适合使用copying算法来收集这样就不但能降低单次GC的时间长度还可以提高GC的工作效率。