继第一天了解了java虚拟机的内存情况和内存泄漏发生的场景后,今天我们将继续深入探讨java虚拟机的垃圾收集器与内存分配策略。
一、对象已死吗。
java堆内存中存放着几乎所有的java实例,这样,垃圾回收器在对内存进行回收之前,就需要考虑哪些对象可被回收,哪些不能。
1、引用计数算法。
给对象添加一个计数器,每当有一个地方引用它时,那么计数器就加1,当引用失效时,计数器就减1,任何时刻计数器为0的对象就是不可能再被引用的。但是好多主流的java虚拟机都没用引用这种方法,因为无法解决多对象之间循环调用问题。
2、可达性分析算法。
这个算法的基本思想就是通过一系列的“GCROOT”对象作为起始点,从这些节点开始向下搜索,搜索锁走过的路径成为引用链。当一个对象到达gcroot没有任何引用链相连时,这个对象可以被回收。
如图3-1所示,object5、object6、object7虽然互相有关联,但是他们到gcRoots是不可达的,所以他们将被判定为是可回收对象。
在java语言中,可作为gcRoots的对象包含以下几种:
①虚拟机栈中引用的对象;
②方法区中类静态属性引用的变量。
③方法区中常量引用的对象。
④本地方法栈中JNI(即一般说的native方法)引用的对象。
3、回收方法区。
永久代的垃圾回收只包含两部分内容:废弃常量和无用的类。
无用的类:该类所有的实例都已经被回收,也就是java堆中不存在该类的任何实例;加载该类的ClassLoader已经被回收;该类对应的java.lang.class对象没有任何地方被引用,无法在任何地方通过反射该类的方法。
是否对类回收 ,虚拟机提供了-Xnoclassgc参数尽行控制。
二、垃圾收集算法。
1、标记清除算法。
首先标记出所有需要回收的对象,在标记完成后统一尽行回收。例如上面提到的对象引用计数器法来判断当前对象是否存活。但是他的主要不足有两点:一个是效率问题,另一个就是空间问题:标记清除会产生连续的内存碎片,这样当以后程序在需要为较大对象分配连续内存空间的时候,不得不再次出发gc操作。
2、复制算法(回收新生代)
为了解决效率问题,一种称为复制的算法出现了。它将可用内存划分成两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另一块上面,然后在把已使用的内存空间一次性清理掉。只是这种算法的代价是将内存缩小为原来的一半,并且当存活的对象较多时,就要进行较多的复制操作,这样就导致效率很低。
3、标记整理算法。
根据老年代的特点,有人提出了标记整理的算法,标记过程和标记清除一样,但后续不能直接对对象进行回收,而是让所有存活的对象都像一边移动,然后直接清理掉边界以外的内存。
4、分代收集算法。
三、HotSpot虚拟机的垃圾收集器。
1、CMS垃圾收集器。
过程:初始标记->并发标记->重新标记->并发清除。
优点:并发收集低停顿。缺点:①对CPU资源非常敏感(在并发标记阶段,会占用一部分线程,从而导致应用程序变慢。 CMS启动的回收线程数是(cpu数量+3)/4);②无法处理浮动垃圾,从而导致full gc发生;③产生大量空间碎片。
2、G1收集器。
G1是一款面向服务端的垃圾收集器。
过程:初始标记->并发标记->最终标记->筛选回收
特点:①并行与并发②分代收集③空间整合④可预测的停顿。