JVM学习笔记(四)

上节学习了 垃圾收集器为什么重点关注堆与方法区,以及判断对象是否可用的两种算法(1.引用计数算法 2.根搜索算法)。以及java中,对于引用的定义。
这次接着学习、走起。冲冲冲。。。。。。

方法区的回收
      方法区(HotSpot 虚拟机中的永久代)里面存放着类的信息,常量,静态变量,即时编译的代码等。
      其实,GC 对于方法区的回收,“性价比”不高:在堆的新生代中,一次垃圾回收一般可以回收70%-92%的内存空间,而方法区的效率远低于此。
永久代(方法区)的垃圾收集主要回收两部分内容:废弃常量和无用的类。
废弃常量的定义:一个字符串“abc”已经进入了常量池,但是没有任何String对象引用常量池的中的“abc”常量,也没有其他地方引用了这个字面量。
无用的类的定义要满足三个条件:
      1.该类所有的实例都已经被回收,也就是java堆中不存在该类的任何实例。
      2.加载该类的ClassLoader 已经被回收。
      3.该类对应的java.lang.Class 对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。

虚拟机可以对满足上述三个条件的无用类进行回收,注意,这里只是说可以,不是跟对象一样,不使用了就一定会回收。是否对无用类进行回收,
HotSpot虚拟机提供了 -Xnoclassgc 参数进行控制,还可以使用 -verbose:class 以及 -XX:+TraceClassLoading、-XX:+TraceClassUnLoading查看类的
加载信息和卸载信息,在大量使用反射、动态代理、CGLib等bytecode 框架的场景,以及动态生成JSP和OSGi 这类频繁自定义ClassLoader 的场景都需要
虚拟机具备类卸载的功能,以保证永久代不会溢出。

垃圾收集算法

    1.标记-清除算法 
   
“标记-清除”(Mark-Sweep)算法是最基础的收集算法,跟他的名字一样,算法分为“标记”,“清除”两个阶段:首先标记处所有要回收的对象,在标记完成
后统一回收所有被标记的对象。之所以说他是最基础的算法,是因为后面的收集算法都是基于这个思路并且对其缺点进行改进而来的。
他主要的缺点有两个:
        1.效率问题,标记和清除的过程效率都不高。
        2.空间问题,标记清除后会产生大量不连续的内存碎片,而空间碎片太多,可能会导致程序在以后的运行过程中需要分配较大对象时,无法找到足够的连续内存
            而不得不提前触发领一次垃圾收集动作。

下面是算法示意图

 2.复制算法
   将可用内存按容量划分为大小相等的两块,每次只使用其中的一块,当这一块的内存用完了,就将还活着的对象复制到另一块上面,然后再把已使用过的内存空间一次清理掉。
这样使得每次都是对其中的一块内存进行回收,内存分配时也就不用考虑内存碎片等复制情况。主要移动堆定指针即可,按顺序分配内存即可,实现简单,运行高效。但是这种算法将|
可用内存缩小为原来的一半,代价有点高。
下面是算法示意图。

 

 现在的商业虚拟机都采用这种算法来回收新生代。将内存分为一块较大的Eden空间和两块较小的Survivor空间,每次使用Eden与其中一块Survivor。当回收时,
将Eden和Survivor中还存活的对象一次性拷到另外一块的Survivor空间中,最后清理掉Eden和和刚才用过的Survivor空间。

3.标记-整理算法
   复制收集算法在对象存活率较高时就要执行较多的复制操作,效率将会变度,更关键的是,如果不想浪费50%的内存,就需要有额外的空间进行分配担保,
以应对被使用的内存中所有对象都100&存活这一极端情况,所以老年底一般不采用这种算法。
   根据老年代的特点,诞生了“标记-整理”算法,标记过程与“标记-清除”算法一致,但后续步骤不是对可回收对象进行清理,而是让所有存活的对象向一端移动,
然后直接清理掉端边界以外的内存。
以下是示意图

 

4。分代收集算法
   当前商业虚拟机的垃圾收集都采用“分代收集算法”,根据对象的存活周期不同,将内存划分为几块,一般是把java堆分为新生代跟老年代。
。这样就可以根据各个年代的特点采用最适当的收集方式,
新生代:每次垃圾收集时都发现有大批量对象死去,只剩下少部分对象存活。所以选用复制算法。
老年代:对象存活率高,没有额外空间进行分配担保,可以使用“标记-清除”或者“标记-整理”算法来进行回收。

posted @ 2020-03-06 17:20  小羊小恩  阅读(146)  评论(0编辑  收藏  举报