Java 虚拟机之垃圾回收
主要针对的区域是java堆和方法区,相对于程序计数器,虚拟机栈和本地方法栈这三个随线程而生随线程而灭,其所需内存大小基本上在类结构确定的时候便已经确定的区域来说,这两个区域需要的内存只有在程序运行时才知道,所以这部分的内存分配和回收都是动态的,gc的主要区域也是这部分。其中gc最频繁的区域时java堆。
一 对象存活判定算法
1.1 引用计数法
给对象添加一个应用计数器,每当有一个地方引用时,就加1,当引用失效时,引用计数器就减1。当引用计数器为0时便表示这个对象不可能再被引用了,就可以进行回收了。
弊端:当两个对象互相循环引用时,此时除了他们之间的这种互相引用关系之外再无其他引用,这种情况这两个对象实际上是已经不存在的,但是却无法回收。 比如A.i=B,B.i=A;只有A在访问B,B在访问A,除此再没有其他地方再引用他们,他们是两个孤立的群体,是应该被回收的对象。但是他们的引用计数器没有为0,导致不能被回收。
1.2 可达性分析
基本思路是:以gc roots为起点,从这个起点往下搜索,搜索所走过的路径称为引用链,当一个对象到gc roots没有任何引用链的时候,这个对象到gc roots便不可达,也就是对象是不可用的。
可达性分析.png可作为gc roots的对象:
-
虚拟机栈(栈栈中的本地变量表)中引用的对象
-
方法区中类静态属性引用的对象
-
方法区中常量引用的对象
-
本地方法栈中JNI引用的对象
二 垃圾搜集算法
2.1 标记清除
分为两个阶段:
-
标记所有需要回收的对象
-
统一清除所有标记的对象
不足:
-
标记和清除两个阶段的效率都不高
-
会产出大量不连续的内存碎片,导致大对象无法申请到内存而提出触发gc
2.2 复制算法
将内存分为相等的两块,每次只使用其中一块,当一块用完了之后就将这一块存活的对象复制到另一块,然后把这一块清理掉。
不足:
- 浪费内存,只有50%可以用
java 中的新生代98%都是“朝生夕死”,所以不需要按照1:1来分配。是eden 2Survivor 按照8:1 这样只有10%被浪费。
2.3 标记整理
分为两步:
-
进行标记,
-
将存活的对象向一端移动,然后清理到端边界以外的内存
不足:不快
2.4 分代收集
对于存活率低的采用复制算法,对于存活率高的采用另外两种。java虚拟机采用的是:新生代采用复制算法,老年代采用标记清除或者标记整理算法。
三 垃圾收集器
3.1 Serial,Serial Old收集器
Serial 针对新生代,采用的是复制算法,Serial old针对老年代,采用标记整理算法
-
一条线程器收集
-
发生垃圾收集过程中必须 stop the word,暂停所有的工作线程。
3.2 ParNew,ParNew Old收集器
ParNew 针对新生代,采用的是复制算法,ParNew old针对老年代,采用标记整理算法
Serial 的多线程版本
3.3 Parallel Scavenge 收集器
针对新生代,采用的是复制算法
cms等收集器的关注点是尽可能控制gc时用户线程的停顿时间,而 这个收集器的目标是达到一个可控制的吞吐量。吞吐量=运行用户代码时间/(运行用户代码时间+gc时间)。
3.4 CMS
目标:获得最短停顿时间
算法:标记清除
步骤:
-
初始标记 (stop ,时间短)
-
并发标记(和用户线程并发执行,时间长)
-
重新标记(stop ,时间短)
-
并发标记(和用户线程并发执行,时间长)
可见只有在两个耗时很短的过程才 stop the word,极大的减少了停顿时间
不足:
-
对cpu资源非常敏感,虽然在两个耗时长的gc阶段不会stop ,但是会因为占用一些资源而变慢。
-
无法处理浮动垃圾,在我们标记的过程中会有新的垃圾产生,产生的这一部分垃圾只能等待下一次gc了。
-
采用标记清除,会有大量的空间碎片,无法为大对象分配时,会触发full gc。
G1收集器
-
并行和并发
-
分代搜集
-
空间整合:整体上看是标记整理算法,局部看是复制算法
-
可预测的停顿
他会把内存分成一个个小区域,在这个小区域之间采用复制算法,然后又把这些小区域看着是一个单独的个体,他们之间采用标记整理算法。这样在小区域间采用复制算法,空间浪费就不那么严重。然后在他们之间又采用标记整理,时间上也不像对每一个单独的小个体进行标记整理那么耗时。
四 内存分配和回收策略
先来看看java堆的布局,java堆分为老年代和新生代两个区域,其中新生代又按照8:1的比例分为:eden 和 Survivor
java 堆布局.png对象的分配策略:
-
对象优先分配到eden,当发生一次minor gc后 进入 Survivor空间
-
大对象直接进入老年代
-
长期存活的对象进入老年代
-
动态对象年龄判断:在Survivor空间中相同年龄所有的对象大小总和大于Survivor空间的一半,年龄大于或者等于该年龄的对象直接进入老年代
-
空间分配担保:当出现minor gc后,发现Survivor 空间无法容纳存活的对象,这些对象就会进入老年代。但是如果这些对象大于老年代的内存,则这次minor gc就很危险。所以在发生 minor gc前,虚拟机会先检查老年代最大连续空间是否大于所有对象的总空间。如果成立,就需要确保 minor gc是否安全。如果虚拟机允许担保失败,会继续检查老年代最大可用的连续空间是否大于列次进入老年代的平均大小,小于,则会尝试minor gc,如果不行,再full gc。大于,full gc。如果不允许担保失败,则直接full gc。为了避免频繁full gc。一般都是设置允许担保失败的。
网友评论