系列文章目录
1:JVM核心知识
2.GC算法
2.1:GC的背景与一般原理
目录
前言
本系列主要针对想进一步进阶Java的开发者,本文介绍GC的背景与一般原理。
一、为什么要有GC?
本质上是内存资源的有限性,如果我们一直申请内存而不释放,那么内存一定会很快就慢,GC算法就是Java用来解决内存问题的。
二、GC原理概述
1.一个例子
我们知道Java中的对象是能够自动释放的,怎么实现这个自动释放呢?
很朴素的一种想法就是每个对象维护一个引用次数,被引用的话次数加1,取消引用了次数减1,那么我们释放内存的时候可以找到引用计数为0的。可以通过这个引用计数原理去实现一个最简单的GC算法。
但是有种情况,对象与对象之间有依赖,导致一个环,使得大家计数永远不为0,这样就无法回收,从而导致内存泄漏。
既然引用计数无法解决循环依赖,那么我们该怎么办呢?
这个办法是引用跟踪,先选取一些根对象,一串跟踪下来,找所有可达对象组成集合,不可达的对象被清除。
2.怎么实现引用跟踪呢?
标记清除算法:
Mark,Sweep,Compress
这是并行GC,CMSGC的基本原理。其优势是可以处理循环依赖,只扫描部分对象。
Marking(标记): 遍历所有的可达对象,并在本地内存(native)中分门别类记下。
Sweeping(清除): 这一步保证了,不可达对象 所占用的内存,在之后进行内存分配时可以重用
除了清除,还要做压缩,因为清除后会产生内存碎片,需要进行压缩处理。
3.怎么才能标记和清除清楚上百万对象呢?
我们在标记和清除的过程中,可能会有线程给这些对象产生了新的依赖关系。
所以我们必须要让全世界停止,即STW(Stop The World)
STW就是在JVM中暂停,频率越低越好,速度越快越好,这是后面不同GC算法优化的核心出发点。
4.分代假设
实际上,根据上面的说明我们已经能够实现一种GC算法了,但是有一个核心问题是:
我们不能用统一的方法管理对象,因为有的对象刚生成就被丢弃,而有的对象要持续很久,基于此,GC算法使用了分代假设。
有年轻代和老年代,用不同策略去优化这两块区域。这对应了我们前面所说的内存模型,大家可以回忆一下
对象分配在新生代的 Eden 区, 标记阶段 Eden 区存活的对象就会复制到存活区,注意这里是复制,如果是移动的话,把数据清除掉就没有了。
新对象从Eden->S0
等一次Eden满,S0做一次回收,Eden做一次回收,都放在S1中
再等一次Eden满,S1做一次回收,Eden做一次回收,都放在S0中
故而在任何一个时刻,S0与S1一定是一个空,一个有数据的,当对象在S0与S1之间存活过了一定时间之后,就会到达老年代,这个时间可以设置。
注意,从存活区到老年代是移动而不是复制,因为默认老年代里的都是长期存活的。
5.GC roots
经过前面的分析我们可以发现,标记的效率是很重要的,我们可以先选定一些根,然后逐步扩张,他们被称为GC roots
那么哪些东西能作为GC roots呢?
1. 当前正在执行的方法里的局部变量和输入参数
2. 活动线程(Active threads)
3. 所有类的静态字段(static field)
4. JNI 引用
这一阶段是标记阶段,很快就能做完,而且与堆内存的大小无关,只与当前存活的对象的数量有关。
总结
以上就是今天要讲的内容,本文介绍了GC的背景与一般原理,下面将会介绍不同的GC算法,敬请期待。