引用#
强引用:是最常见的引用类型,比如Object obj=new Object()这种new产生的引用就是强引用,一个对象如果还有强引用,那么垃圾回收器绝不会回收它。(用途:对象的一般状态)
软引用:软引用来表示对象是有用的,但不是必须的。如果一个对象只有软引用了,那么当内存不足,准备抛出内存溢出异常以前,会先把这些软引用的对象进行回收了,如果回收之后内存还是不够,这时才实际抛出内存溢出异常。(用途:对象缓存)
弱引用:弱引用就更低一级,用来描述一些非必须的对象。当一个对象只有弱引用的时候,只要发生垃圾回收gc,就会被回收。所以弱引用对象活不过下一次gc; (用途:对象缓存)
虚引用:最弱的一种引用,形同虚设。一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。为一个对象设置虚 引用关联的唯一目的只是为了能在这个对象被收集器回收时收到一个系统通知。虚引用必须和引用队列关联使用,当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会把这个虚引用加入到与之关联的引用队列中。
可达性分析算法#
通过 一系列称为“GC Roots”的根对象作为起始节点集,从这些节点开始,根据引用关系向下搜索,搜索过程所走过的路径称为“引用链”(Reference Chain),如果某个对象到GC Roots间没有任何引用链相连, 即GC Roots到这个对象不可达时,则证明此对象是不可能再被使用的。
记忆集 Remembered Sets 简介
当对堆进行部分内存区域回收的时候,就会存在跨区域引用的问题,在GC Roots这里讲过,如果存在跨区域的引用关系,那么这种引用即便不是“固定”Gc Roots范畴,那也应该纳入作为Gc Roots集合的补充,一起来进行可达性分析判断。( 例如所有堆内存的被划分为(A,B,C,D,E)五个区,当我们这次只对A,B进行回收时,就需要判断C,D,E中是否有引用A,B中的对象)
为了能够找出这种跨区的引用关系,一种直接的方式就是,将“回收区”以外的所有内存区域扫描一遍,看看哪些是有引用回收区里面的对象的。很显然,这种全域扫描的方式性能会极差,是不可接受的。所以就有了记忆集,记忆集列出了从外部指向本块的所有引用。 这种引用记录会在引用关系创建,更改时进行维护。当需要进行这种外部引用关系分析时,直接读取记忆集内容就行。
垃圾收集算法#
1. 标记-清除算法#

2. 标记-整理算法#

3. 复制#

4. 分代收集#
一般将堆分为新生代和老年代,新生代每次垃圾收集时都发现有大批对象死去,而每次回收后存活的少量对象,将会逐步晋升到老年代中存放
新生代:
- 绝大多数对象都是朝生夕灭的
- 复制算法
老年代:
- “大多数”是熬过越多次垃圾收集过程的对象。
- 标记 - 清除 或者 标记 - 整理 算法
垃圾收集器#

3.1 Serial收集器
- Serial 翻译为串行,也就是说它以串行的方式执行
- Serial 是新生代的垃圾收集器
- 算法:复制算法
3.2 ParNew收集器
- 垃圾收集时多线程并行
- ParNew是新生代的垃圾收集器
- 算法:复制算法
3.3 Parallel Scavenge收集器
- 吞吐量优先收集器
- 新生代垃圾收集器
- 算法:复制算法
3.4 Serial Old收集器
- 老年代收集器
- 算法:标记-整理算法
- gc时暂停所有用户线程。
3.5 Parallel Old收集器
- Parallel Old为Parallel Scavenge而生,只能搭配Parallel Scavenge。
- Parallel Old采用多线程
- 算法:标记-整理
3.6 CMS收集器
初始标记: 仅仅只是标记一下 GC Roots 能直接关联到的对象,速度很快,需要“Stop The World”。
并发标记: 进行 GC Roots Tracing 的过程,它在整个回收过程中耗时最长,不需要“Stop The World”,可以与用户线程并发。
重新标记: 为了修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,需要“Stop The World”。比初始标记时间长,比并发标记时间短。
并发清除: 清除掉判定为死亡的对象,不需要“Stop The World”,可以与用户线程并发。
3.7 G1
G1依然还是采用了分代设计,但是之前的一些垃圾收集器有很大差别,不会在为新生代,老年代等分配规定大小的区域,而是将整个堆分成一个个大小固定的Region区域,每一个Region都可以是新生代,老年代,Eden空间,Survivor空间的角色。所以Region成为了垃圾收集的最小单元,每一次回收都会是Region的整数倍大小。
4. 内存分配与回收策略#
对象优先在Eden分配:大多数情况下,对象在新生代Eden区中分配。当Eden区没有足够空间进行分配时,虚拟机将发起一次Minor GC。
大对象直接进入老年代:大对象就是指需要大量连续内存空间的Java对象,最典型的大对象便是那种很长的字符串,或者元素数量很庞大的数组。大对象会直接进入老年代,可以设想一下,如果大对象被分配在新生代,又因为新生代多采用复制算法,所以如果一个大对象能存活很久的话,那么复制开销将会是非常大的。
长期存活的对象将进入老年代:对象头里面存储了对象的分代年龄,新生带的对象每经历一次Minor GC 年龄就会增加一岁,当年龄达到一定程度(默认15,-XX:MaxTenuringThreshold可配),就会晋升为老年代。
动态对象年龄判定:并不是永远要求对象的年龄必须达到- XX:MaxTenuringThreshold才能晋升老年代,如果在Survivor空间中相同年龄所有对象大小的总和大于 Survivor空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代,无须等到-XX:MaxTenuringThreshold中要求的年龄。
空间分配担保:上面可以看出,Minor GC有可能会导致一大批对象从新生代进入老年代,那老年代如果放不下怎么办?
每次Minor GC之前都得检查老年代的空间是否能容纳所有新生代对象,
如果可以那就安全。
如果不可以,则虚拟机会先查看- XX:HandlePromotionFailure参数的设置值是否允许担保失败(Handle Promotion Failure);
如果允许,那会继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小,
如果大于,将尝试进行一次Minor GC,尽管这次Minor GC是有风险的;
如果小于,就进行Full GC
如果不允许,那这时就要改为进行一次Full GC。
Reply by Email