垃圾收集器
需要回收的对象实例
垃圾收集器在对堆进行回收时,首先要判断对象是否还存活。
判断对象是否存货的算法:
1、引用计数器(一般JVM都不用这个算法)
给对象添加一个引用计数器,每当一个地方引用他,程序计数器就加一,但引用失效时程序计数器减一,计数器为0的对象就是不再使用的对象。
优点:实现简单,判定效率高
缺点:很难解决对象之间循环引用的问题(例如两个对象互相引用,这样程序技术器永远不会为0)
2、可达性分析算法
算法的基本思想思路是通过一系列成为“GC ROOTS”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到GC ROOTS没有任何引用链相连说明这个对象是不可用的。
JAVA中可以作为GC ROOTS的对象
- 虚拟机栈中的引用对象
- 方法区中的静态属性引用对象
- 方法区中的常量引用对象
- 本地方法中的JNI引用的对象
判定为不可达对象被标记一次并进行筛选,刷选条件为此对象是否有必要执行finalize()方法。当对象没有覆盖finalize()方法或者虚拟机已经调用过这个方法,都视为没有必要执行。
视为有必要执行,那么这个对象会被放到一个F_Queue的队列中,由虚拟机建立、优先级低的finalizer线程去执行。(并不承诺等待它结束,避免发生死循环或者很慢造成其他对象等待,导致内存回收系统崩溃)
GC对F_QUEUE队列中的对象进行第二次标记,如果没有重新与引用链上的对象关联则被真正回收,否则将被移除“即将回收的集合”。
方法区回收:
在方法去中进行垃圾回收性价比比较低。
永久代主要回收两部分的内容:
- 废弃的常量:没有任何对象引用常量池中的常量,也没有其他地方引用这个字面量,常量就会被系统清出常量池。
- 无用的类:需要满足
- 该类的所有实例都被回收
- 加载该类的ClassLoader被回收
- 该类对应的Class对象没有在任何地方被访问,无法在任何地方通过反射访问该类的方法
满足这三个条件仅仅是可以回收,而不是必然要回收,是否必要回收,通过JVM参数进行设置。
在大量使用反射,动态代理,CGLib等字节码框架,动态生成JSP以及OSGI这类频繁自定义ClassLoader的场景都需要JVM具备卸载的功能,以保证永久代不会溢出。
垃圾收集算法
- 标记清除算法
思想:首先标记所有需要回收的对象,在标记完成后统一进行回收。
缺点:
- 效率低,标记删除两个过程的效率都不高
- 标记清除后产生大量不连续的内存碎片,空间碎片太多会导致后面需要分配较大对象的时候,无法找到足够的连续内存而不得提前触发另一次垃圾收集动作。
- 复制算法
思想:
将可用内存按容量划分为大小相等的两块,每次只使用其中的一块,当这一块用完,就将还存活着的对象复制到另一块,然后再把已使用过得内存空间一次清理掉。
优点:这样只需要对半个区进行内存回收,特不用考虑内存碎片问题,只要移动堆顶指针,按顺序分配内存,实现简单运行高效
缺点:将内存缩小了原来的一半
商业JVM都用这个算法来回收新生代,并不需要按照1:1来划分内存。而是将内存分为一块较大的Eden空间和两块较小的Survivor空间。Eden和Survivor的比例为8:1,当回收时一次性将Eden和Survivor中存活的对象复制到另一块Survivor。也就是每次新生代可用内存为整个新生代内存的90%,只有10%的会被浪费。我们没有办法保证每次回收有不超过10%的对象还存活着。当Survivor内存不够时需要其他内存(老年代)做担保进行分配担保。在对象存活率较高时就进行了比较多的复制操作,所以老年代不会选择这个算法。
- 标记整理算法
过程和标记清理一样,只不过后续不是直接对可回收对象进行清理,而是让所有存活对象向一端移动,直接清除掉端边界以外的内存。
- 分代收集算法
根据对象存活周期的不同分为几块,一般把Java堆分为新生代和老年代。这样可以根据各个年代的特点采用适合的算法。新生代中,每次都有大量的对象死去,只有少量存活就采用复制算法,只需要付出少量存活对象复制成本。老年代对象存活率高,没有额外的空间进行分配担保使用标记清除或标记整理算法来尽心回收。
垃圾收集器
手机算法是内存回收的方法论,垃圾收集器是内存回收的具体实现。
JVM对垃圾收集器没有明确的规范,所以各个JVM的垃圾收集器可能有较大的差别。
HotSpot中所包含的垃圾收集器
新生代
-
Serial收集器