java自动垃圾回收机制

前言:相比C++,java做的一大改进是将复杂的内存管理抽离出来交给jvm去处理,让码农不再时刻盯着内存泄漏的问题,可以更专注于业务逻辑的开发。

 

java的GC机制是和其内存模型相关联的,而GC的核心内存区域是内存中的堆区。

java堆区按对象的存活时间被分为了年轻代(eden区+s0区+s1区)和老年代(tentired区),java堆的按代区分其实是为了其垃圾回收的分代收集机制打开了方便之门。java的GC收集器会在不同的分代上使用不同的垃圾收集策略。

 

GC其实主要需要解决两个问题:哪些是垃圾?如何清理垃圾?

在解决这两个问题上涉及到下面的方法论:

1.垃圾对象判定方法

引用计数法:在C++的智能指针中使用了这种方式去做内存的自动回收。即在对象生成时维护一个对该对象引用次数的计数器,对象初次生成时计数器值为1,每增加一个到该对象的引用,计数器加1,每减少一个引用(如引用变量赋值null,或引用变量离开作用域),计数器减1,计数器为零时,对象内存会被自动回收。该方法的问题是存在内存泄漏的隐患,如对象相互引用、循环引用等情况

相互引用:

public class ReferenceCountingGc {
    Object instance = null;
	public static void main(String[] args) {
		ReferenceCountingGc objA = new ReferenceCountingGc();
		ReferenceCountingGc objB = new ReferenceCountingGc();
		objA.instance = objB;
		objB.instance = objA;
		objA = null;
		objB = null;

	}
}

  例子中两个new出来的对象ReferenceCountingGc由于通过内部的变量instance引用着对方,两个对象的引用计数都为1。但是在程序中已经不会再用到这两个对象,这样就出现了内存泄漏。

循环引用:

public class ReferenceCountingGc {
    Object instance = null;
	public static void main(String[] args) {
		ReferenceCountingGc objA = new ReferenceCountingGc();
		objA.instance = objA ;
		objA = null;
	}
}

  例子中new出来的对象ReferenceCountingGc通过内部的变量instance引用自身对象,最后引用计数也为1。最后该对象内存也出现泄漏

基于引用计数法出现的问题,目前的jvm都不采用这种方式,而是使用下面的可达性分析的方式

可达性分析法:

通过一系列的称为 “GC Roots” 的对象作为起点,搜索这些节点引用的对象,以及这些引用对象内部属性引用的对象,相当于从一个树形结构的根部进行遍历。节点所走过的路径称为引用链,当一个对象处于引用链上,就被判断为可用对象。其它的对象就是要被清理的无用对象。目前jvm都是采用这种方式进行垃圾对象的判断的。

补充说明:引用计数和可达性分析我觉得有点像黑名单和白名单两种思路。引用计数的方式采用了黑名单的方式来处理问题,把计数为零相当于一个黑名单条件,满足条件的对象是需要清理的垃圾对象,处于黑名单中。可达性分析法是将处于引用链的对象加入白名单,白名单外的对象被清理。我们在进行数据过滤时经常会用到黑白名单的思路,白名单由于对有效数据的限制条件更严格,这种过滤方式往往也更精确。

2.垃圾对象清理方法

java在GC过程中会依据年轻代和永久代中对象的不同特点采用不同的对象清理方式,即所谓的分代收集策略,其中涉及到的方法有下面几种。

2.1 标记-清除算法:

通过可达性分析方式找到所以需要回收的对象,并进行标记,在标记完成后统一回收所有被标记的对象。

主要问题是容易参数内存碎片。

2.2 复制算法

它可以将内存分为大小相同的两块,每次使用其中的一块。当这一块的内存使用完后,就将还存活的对象复制到另一块去,然后再把使用的空间一次清理掉。这样就使每次的内存回收都是对内存区间的一半进行回收。

目前java堆中的年轻代在进行young GC时采用的这种方式,其中划分出的两块内存s0区和s1区就是这样用的,每次GC都会切换身份,一个存放存活对象,一个存放新生对象。

2.3 标记-整理算法

标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象回收,而是让所有存活的对象向一端移动,然后直接清理掉端边界以外的内存。以此避免内存碎片。

java堆的老年代一般采用这种方式进行GC

 

前面讲的都是jvm在GC过程中在寻找垃圾和清理垃圾方面使用到的方法论,那么jvm具体是如何执行GC的呢?

jvm其实是通过一个叫垃圾收集器的东西进行GC的,垃圾收集器在jvm启动后的外部体现就是一个或多个运行中的线程(针对单线程收集器或多线程收集器)。收集器线程会在整个jvm生命周期内判断需要GC的时间点并执行GC的逻辑,而这些逻辑的理论基础就是上面提到的那些方法论。

目前jvm中使用到的垃圾收集器主要有下面几种,通过java命令启动应用的时候jvm会使用默认的搜集器,根据实际应用场景也可以在启动应用时,通过命令行参数设置使用哪张搜集器

Serial 收集器:单线程,新生代采用复制算法,老年代采用标记-整理算法。
ParNew 收集器:Serial 收集器的多线程版本,新生代采用复制算法,老年代采用标记-整理算法。
Parallel Scavenge 收集器:多线程,新生代采用复制算法,老年代采用标记-整理算法。
Serial Old 收集器:Serial 收集器的老年代版本,单线程
Parallel Old 收集器:Parallel Scavenge 收集器的老年代版本,多线程
CMS 收集器:多线程,并发收集,标记-清除
G1 收集器:多线程,并发收集,复制加标记整理

 

posted @ 2019-07-23 12:39  二十二花生  阅读(2783)  评论(0编辑  收藏  举报