垃圾收集器与内存分配策略(一)
垃圾收集器概述
首先对于Java程序来说,程序计数器,虚拟机栈和本地方法栈的生命周期是伴随着线程的开始到结束就消失,而对于方法区和堆内存空间是随着方法执行去动态分配内存,而所分配的内存并不会随着线程执行结束而被回收,所以我们需要垃圾收集器来处理这一部分内存
判断对象是否存活
引用计数器算法
概念
给对象添加一个引用计数器,每当一个地方引用到它的时候,就加1,每当一个引用失效的时候,就减1,直到对象的计数器为0的时候,就可以回收对象,释放内存,这样的算法我们称之为引用计数器算法
优缺点
优点:算法简单,效率高
缺点:无法处理循环引用的对象,如下所示
public class Test{ // 持有自己的引用 private Test o; public static void main(String[] args) { Test testA = new Test(); Test testB = new Test(); // 这样方式称之为循环引用 testA.o = testB; testB.o = testA; testA = null; testB = null; } }
可达性分析算法
概念
首先选取一个内存区域作为“GC ROOT” 作为根节点,然后从根节点依次向下遍历,能被根节点所访问到,那么就可以认为这个对象是“可达的”;无法遍历到的那么就是“不可达的”,不可达的对象是可以进行回收的数据
在Java中可以作为“GC ROOT”的对象
- 虚拟机栈(通俗来说就是方法中的对象)中的对象
- 方法区中静态属性引用的对象
- 方法区常量引用的对象(这个笔者也不是特别清楚是啥东西)
- 本地方法栈中引用的对象(本地方法,笔者目前接触的不多)
在Java1.2版本后引用的概念
在Java1.2之前对于引用的定义是:如果reference类型的数据中存储一个对象的内存地址,这样的一种方式我们称之为引用,那么反之就是不引用;这样的定义虽然清楚,但是功能不完善,比如说当我们面对这样一类对象:内存不紧张的时候,就缓存到内存中,如果内存紧张的时候,那么就回收这一部分数据,这样对象的场景上述对象分类是无法满足这样的场景的;于是Java1.2之后的版本推出了引用的新定义
- 强引用:类似“Object o = new Object()”这样的为强引用,这样类型的引用GC永远不会回收这样的引用
- 弱引用:描述一些有用但不是非必须的对象引用;这样的引用是会在JVM发生内存溢出之前,再次垃圾回收(之前对于弱引用对象有过一次回收了)
- 软引用:软引用对象可以用来描述一些非必须的对象引用;但是这样的对象引用会在最近的一次垃圾回收中被回收
- 虚引用:用来描述一种最弱的引用,这一种引用无法从引用定位到对象的实例
Java垃圾回收过程
Java对于可以回收的对象,并不是一次标记;它使用的是两次标记,在一次标记中会使用finalize()方法进行筛选;对于jvm判断是否使用finalize()方法有两种情况:1.对象没有覆盖finalize()方法;2.jvm已经执行过finalize()方法;这样的两种情况jvm都不会在执行finalize()方法
回收方法区
回收常量条件(例如现在我们需要回收一个“abc”的字符串)
- 没有任何一个String对象为“abc”
- 没有任何地方引用了这个字面量(这个不太理解)
回收一个类的条件
- 没有任何一个实例为这个类
- 这个类的ClassLoader(类加载器)被回收
- 这个类的Class对象没有在任何地方被使用
当然满足上述条件只是满足收回的条件,也并不一定会被回收
垃圾回收算法
标记-清除算法
实现思路:对内存中的需要回收对象首先进行标记,然后再进行清除
优点:算法简单,有效
缺点:效率低;容易产生内存碎片,继而产生二次回收
复制算法
实现思路:将内存划分为两块大小相等的内存区域,每次只使用其中一块内存作为使用区域,一旦需要回收,将已使用内存区域还存活的对象全部复制到另一块内存中,然后对剩下的需要回收的对象进行回收
优点:实现简单,高效
缺点:内存使用率低
目前常见的JVM都是使用这种算法进行回收新生代内存区域;但是有一点不同的是,它的内存比例划分是8:1:1;分别是Eden(新生代):Survivor(幸存代):Survivor(幸存代);可以使用的为新生代和一个幸存代;其中10%的幸存代作为Copy算法的未使用内存,当然一旦10%幸存代无法全部复制所有存活的对象,可以通过借用老年代内存暂时使用
标记-整理
复制算法在整理存活率低的对象是非常有效的,但是对于存活率非常高的对象(极端情况100%存活率),那么效率就非常低了;这时我们可以考虑使用标记-整理的算法:标记出需要回收的对象,然后将存活的对象向一端移动;然后清除端边界之外的所有内存
==分代收集算法==
根据对象的存活周期划分为不同的内存区域,不同的内存区域采用不同的内存回收方式回收;例如常见的JVM将内存区域划分为初生代和老年代,初生代使用复制算法清理,老年代使用“标记-整理”或者“标记-清除”两种方式清理