关于Java中的GC,简单来说就是垃圾收集器自动回收生命周期结束的对象,释放内存。
那么怎样确定对象是否存活呢?
可达性分析算法
现在主流的Java虚拟机大多使用这种可达性分析算法来判断对象是否需要进行垃圾回收。具体也就是,从GC Roots出发,向下搜索,形成一个完整的对象引用链。当某个对象没有任何到达GC Roots的引用链时,便认为这个对象的生命周期结束,是可以被回收的。
GC Roots对象指的哪些呢?
- 虚拟机栈中引用的对象
- 元数据区中静态变量引用的对象
- 元数据区中常量引用的对象
- 本地方法栈中native方法引用的对象
分代回收算法
现在的JVM大多使用分代回收算法,将堆内存分为新生代和老年代。对这两部分分别使用不同的回收算法。
老年代中存放大对象和长期存活的对象,且对象较多,一般使用标记-清除算法或者标记-整理算法。新生代存活对象较少,一般使用复制算法。
标记-清除
以可达性分析算法遍历所有引用,给活着的对象打上标记。遍历结束后,统一清除需要回收的对象。
这种算法的缺点在于:
- 标记和清除的效率不高
- 进行清除以后,内存空间是不连续的。
使用这种算法的虚拟机,在进行对象内存分配时,会维护一个内存空闲列表,记录哪些内存空间可用,再进行分配。
标记-整理
与标记-清除类似,先进行标记,之后把活着的对象移动到一端,排在一起,最后清除需要回收的对象。这样的话,可以让内存地址是连续的。
如果虚拟机使用这种算法进行GC的话,在为对象分配内存时,只需要按对象的大小进行指针移动,分配足够的空间便可。
复制算法
一般在新生代中使用这种算法。将新生代内存分为一个Eden和两个Survivor区域,比例为8:1:1。
在初始时,先把对象分配到较大的Eden区域。Eden区满以后,触发Minor GC,将活着的对象复制到一个Survivor区(假如为Survivor0),并且把对象年龄加1,然后清空Eden区。当Eden再次放满以后,再把Eden区和Survivor0中活着的对象复制到另一个Survivor区,加对象年龄,清空Eden区和Survivor0区。如此循环往复。如果对象年龄达到界限(默认15),视对象为长生命周期对象,移动到老年代。大对象在内存分配时,直接放到老年代,因为大对象做复制操作费效率。值得注意的是,在复制算法的过程中,如果空闲的Survivor区内存大小无法满足需要,会依赖老年代的空间进行担保。
复制算法得到的内存空间同样是连续的。