文章目录
垃圾标记
垃圾标记阶段:
gc只回收死亡对象,所以在垃圾回收之前,首先需要区分出堆中哪些是存活对象,哪些是已经死亡的对象,这个过程就是垃圾标记阶段。
垃圾标记的两种方式:
判断对象是否存活一般有两种方式:引用计数算法和可达性分析算法
引用计数算法
什么是引用计数算法?
为每个对象设置一个整型的引用计数器
。用于记录对象被引用的个数。
比如有一个对象A
只要有任何一个对象引用了A,则A的引用计数器就+1;
当引用失效时,引用计数器就-1。
只要对象A的引用计数器的值为0
,即表示对象A不再被使用,可进行回收。
引用计数算法有一个致命缺点:就是它无法处理循环引用
的情况,导致在Java的垃圾回收器中没有使用这类算法。
什么是循环引用?
a引用b,b引用a
可达性分析算法(jvm选用)
可达性分析算法不存在循环引用
的问题,因此java选择使用可达性分析算法
算法的主要思想是:
首先确定一系列肯定不能回收的对象作为GC roots,比如虚拟机栈里面的引用对象、本地方法栈引用的对象等,然后以GC roots作为起始节点,从这些节点开始向下搜索,去寻找它的直接和间接引用的对象,当遍历完之后如果发现有一些对象不可到达,那么就认为这些对象已经没有用了,需要被回收
那些对象可以作为GCRoots的对象
-
虚拟机栈中引用的对象
比如:User user=new User(); -
方法区中类静态属性引用的对象
比如:static User user=new User(); -
方法区中常量引用的对象
比如:final User user=new User(); -
本地方法栈中JNI引用的对象
垃圾清除算法
当成功区分出内存中的存活对象和死亡对象后,GC接下来的任务就是执行垃圾回收,释放掉死亡对象所占用的内存空间。
目前在JVM中比较常见的三种垃圾收集算法是
- 复制算法
- 标记一清除算法
- 标记-清除-整理算法
复制算法(适合新生代)
算法设计原理
将内存空间分为两块,每次只使用其中一块,在垃圾回收时,假设现在使用A内存块,B内存块空闲。
将A中存活的对象复制到B中,然后清除A中的死亡对象,即完成一次垃圾收集。
下次垃圾收集时AB的角色交换,循环往复。
优点
- 没有内存碎片
- 回收速度快
缺点
需要两倍的内存空间
标记-清除算法
先标记、后清除
标记
垃圾收集器从根节点开始遍历,标记所有被引用
的对象。其实就是可达性分析算法
清除
垃圾收集器对堆内存从头到尾进行遍历,如果发现某个对象没有被标记为可达对象,则将其回收。
清除
并不是真的置空,而是把需要清除的对象地址
保存在空闲列表
里。下次有新对象需要占用空间时,判断空闲列表中记录的可以被清除的对象所占的位置空间是否够,如果够,就用新对象覆盖原有的对象。
标记清除如下图示:
缺点
有内存碎片
优点
空间开销比复制算法小
回收速度比标记整理算法小
中庸型算法
标记-清除-整理算法
就是在标记-清除算法的基础之上再加一个整理
优点
- 消除了复制算法当中,需要
两倍内存
的缺点。 - 消除了标记-清除算法中造成
内存碎片
的缺点。
缺点
需要标记和整理两个阶段,加上移动对象时,如果对象被其他对象引用,则还需要调整引用的地址,所以算法执行的时间是三种清除算法中最久的。
总结:标记整理算法 为了节约内存+不产生内存碎片
,牺牲了时间。
分代收集算法
根据新生代和老年代的特点不同,
新生代采用复制
算法
老年代采用标记-清除|标记-整理
算法
年轻代的特点:区域相对较小,对象生命周期短、存活率低,回收频繁。
老年代的特点:区域较大,对象生命周期长、存活率高,回收不及年轻代频繁。
增量收集算法(少量多餐)
如果一次性将所有的垃圾进行处理,会造成系统长时间的停顿,为解决这一问题,可以分多次进行垃圾收集,让垃圾收集线程和应用程序线程
交替执行,每次垃圾收集线程只收集一小片区域的内存空间,接着切换到应用程序线程。依次反复,直到垃圾收集完成。
分区算法(G1)
分代算法
按新生代和老年代分别回收垃圾
分区算法
将堆空间进一步划分成多个不同的小区间。每一个小区间都独立使用,独立回收。
如下将一块大堆空间划分为若干小区间:
复制、标清、标整、
特点总结:
复制 | 标记清除 | 标记整理 | |
---|---|---|---|
速率 | 最快 | 中等 | 最慢 |
空间开销 | 多(2倍空间) | 少,但有内存碎片 | 少,没有内存碎片 |
使用场景:
新生代基本采用复制算法,老年代采用标记整理算法。cms采用标记清理
新生代为何适合用复制算法?
因为复制算法需要复制存活对象,所以存活对象越多,复制的越多,效率越低下。老年代有大量的对象存活,就不适合用复制算法。
相反,新生代的对象大多朝生夕死,所以一次通常可以回收70% - 99% 的内存空间。相应的需要复制的存活对象就很少。很适合使用复制算法。
老年代为何采用标记整理算法
因为老年代存活对象多,不适合复制,标清又有内存碎片。
cms为何选择标清呢
因为cms同时回收新生代和老年代,二者都要兼顾,相比复制节约内存,相比标整更快。
内存分配:指针碰撞+空闲列表
垃圾回收之后就可以为新对象分配内存了,分配方式分两种:指针碰撞和空闲列表
选择哪种分配方式由Java堆是否规整
所决定,而Java堆是否规整又由所采用的垃圾收集器
是否带有压缩整理功能
决定
-
指针碰撞法
所有用过的内存在一边,空闲的内存放另外一边,中间放着一个指针
作为分界点的指示器
,分配内存时,仅需要把指针往空闲那边挪动一段与对象大小相等的距离即可。 -
空闲列表分配
意思是虚拟机维护了一个列表,记录哪些内存块是可用的,在分配的时候从列表中找到一块足够大的空间划分给对象实例,并更新列表上的内容。