【JVM学习笔记】垃圾回收及算法

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/java_faep/article/details/81358135

如何判定对象为垃圾对象?

        引用计数法

        可达性分析法

如何回收?

        回收的策略:标记-清除算法、复制算法、标记-整理算法、分代收集算法

        垃圾回收器:Serial、Parnew、Cms、G1

何时回收?

引用计数算法

在对象中添加一个引用计数器,当有地方引用这个对象的时候,引用计数器的值就+1,当引用失效的时候,计数器就-1。不用这种方式,可能会存在对象互相引用的情况,栈内的引用可以清除,但互相引用清楚不掉。

运行程序时打印GC日志信息

-verbose:gc 打印简要信息

-xx:+PrintGCDetail 打印详细信息

可达性分析法

定义GC Root,通过GCRoot向下搜索,搜索走过的路径称为引用链,当一个对象到GC Root没有任何引用链相连时,则判断该对象是不可用的。

可以作为GCRoot的对象:

1、虚拟机栈

2、方法区的类属性所引用的对象

3、方法区中常量所引用的对象

4、本地方法栈中所引用的对象

标记-清除算法

两个过程:一个是标记、一个是清除

效率不高、另外回收后会有大量的不连续空间碎片,这就会导致之后程序需要分配大块连续的内存时,无法找到足够的连续内存而不得不提前进行另一次垃圾回收动作。

复制算法

为了解决效率问题,于是就出现了“复制算法”它可以将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。这样使得每次都是对整个半区进行内存回收,内存分配时也就不用考虑内存碎片等复杂情况,只要移动堆顶指针,按顺序分配内存即可,实现简单,运行高效。

只是这种算法的代价是将内存缩小为了原来的一半,未免太高了一点。

堆的划分:

       新生代:

              Eden 伊甸园

              Survivor 存货去

              Tenured Gen

      老年代

复制算法的优化:

现在的商业虚拟机都采用这种收集算法来回收新生代

新生代中的对象98%是“朝生夕死”的,所以并不需要按照1:1的比例来划分内存空间,而是将内存分为一块较大的Eden空间和两块较小的Survivor空间,每次使用Eden和其中一块Survivor。

当回收时,将Eden和Survivor中还存活着的对象一次性地复制到另外一块Survivor空间上,最后清理掉Eden和刚才用过的Survivor空间。

HotSpot虚拟机默认Eden和Survivor的大小比例是8:1,也就是每次新生代中可用内存空间为整个新生代容量的90%(80%+10%),只有10%的内存会被“浪费”。

98%的对象可回收只是一般场景下的数据,没有办法保证每次回收都只有不多于10%的对象存活,当Survivor空间不够用时,需要依赖其他内存(这里指老年代)进行分配担保(Handle Promotion)。

标记-整理算法

在标记-清除算法基础上做了改进,主要针对老年代的垃圾回收,标记阶段是相同的标记出所有需要回收的对象,在标记完成之后不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,在移动过程中清理掉可回收的对象,这个过程叫做整理。

标记-整理算法相比标记-清除算法的优点是内存被整理以后不会产生大量不连续内存碎片问题。

复制算法在对象存活率高的情况下就要执行较多的复制操作,效率将会变低,而在对象存活率高的情况下使用标记-整理算法效率会大大提高。

分代收集算法

是标记整理算法和复制算法的结合,根据内存中对象的存活周期不同,将内存划分为几块,java的虚拟机中一般把内存划分为新生代和年老代,当新创建对象时一般在新生代中分配内存空间,当新生代垃圾收集器回收几次之后仍然存活的对象会被移动到年老代内存中,当大对象在新生代中无法找到足够的连续内存时也直接在年老代中创建。

猜你喜欢

转载自blog.csdn.net/java_faep/article/details/81358135