垃圾回收机制
哪些对象需要回收?
一个对象需不需要回收,首先看他有没有被其他对象引用,如果没有引用了,那么这个对象就会成为“垃圾”需要被回收。回收判定的方式有两种:可达性分析和引用计数法
可回收对象的判定
可达性分析
主要是找出GC Roots的引用链。如果不在引用链中,那么就是需要回收的对象。
GC Roots包括什么呢?
GC Roots相当于根对象,是会长时间保存下来的对象。
- 虚拟机栈中引用的对象(局部变量表中)
- 本地方法栈引用的对象(本地方法形成对象)
- 锁持有的对象
- 方法区常量引用的对象
- 静态变量引用的对象
4种引用类型
强引用:比如GCroot 以及new创建的对象,虚拟机不会回收强引用,宁愿抛出OOM
软引用:描述一些有用,但是非必须的对象。当内存满了以后会对这些对象进行回收
弱引用:每次GC 都会回收
虚引用:可以用作通知工作(一旦这个对象被回收,相应的用户线程会收到通知并对直接内存进行清理工作。)
引用计数法
通过判断对象的引用数量来决定是否回收。当一个对象被其他对象引用的时候,引用计数器就加1,当不在被其他对象引用的时候减一。但是引用计数法不能够解决对象之间相互循环引用的问题。可达性分析可以解决。
什么时候发生回收?
当JVM触发GC的时候会进行垃圾回收,垃圾回收主要发生在堆区,按照内存管理主要在两个位置:新生代和老年代
Minor GC
主要发生在新生代(eden:from:to =8:1:1),由于新生代存放的是“朝生夕死”的对象,Minor GC会比较频繁。
Full GC
主要发生在老年代,当old区的内存空间不足就会触发Full GC 进行垃圾回收。
触发Full GC的原因有哪些?
- 老年代内存空间不足
- 担保失败:新生代晋升到老年代会有一个担保机制(担保晋升的对象不超过老年代的可用内存),如果担保失败就会触发 Full GC
- System.gc()
- 元空间内存达到阈值
- 大对象导致老年代内存空间不足
对象何时晋升老年代
- 大对象会绕过新生代,直接在老年代分配。(-XX:PretenureSizeThreshold可以设置,超过这个值直接进入老年代)
- 长期存活的对象会进入老年代
- 动态对象年龄判断(年龄的累加对象之和大于Survivor内存空间的一半,超过该年龄的将会进入老年代)
- Survivor满了,通过担保机制进入老年代
以什么方式回收?
垃圾回收算法
复制算法:
发生在新生代。复制算法的思想是将内存分为两部分,每次只使用一部分,存活下来的会复制到另外一块上,然后将本部分清空。
对象只会在存在eden区和名为“From”的Survivor区, Survivor区“To”是空的。当出现Minor GC时候,就会将eden存活的复制到to区,而from区会根据其年龄来判断去向,如果超过阈值年龄(默认15岁)就会进入old区(老年代),没超过就会进入to区,然后from区就会被清空。from和to进行互换。保证to区一直是空闲的。
标记清除算法:
复制算法的缺点是空间利用率不足。标记-清除分为两个部分:标记-清除。缺点是存在碎片
**标记整理算法:**会将存活的对象移动到一端,对不存活的对象进行处理。
垃圾回收器
CMS
全称是并发标记清除,使用的是标记清除算,其设计目的是减少停顿时间。
因为gc线程和用户线程一起执行,可以有效减少停顿时间。
分为四个阶段:
初始标记:有STW停顿,标记GC roots
并发标记:标记可达对象,用户线程和GC线程一直执行
重新标记:有STW停顿,对并发标记过程中,导致记录发生变动,需要重新进行标记
并发清除:GC线程和用户线程一起工作。
CMS中由于GC线程和用户线程一起工作,所以在GC的时候,内存需要留有一些内存,防止在gc过程中,用户线程产生对象撑爆老年代。
**优点:**并发收集,低停顿时间(只有初始标记和重新标记才会STW)
**缺点:**标记删除会存在碎片
G1(Garbage-First )
G1弱化了分代的思想,更多的使用分区(Region),Region之间是复制算法,但实际整体上可看作是标记-整理算法。
G1将整个堆划分成一个个大小相等的块(每个块称为Region),每一块的内存是连续的。分代算法一样,G1 中每个块也会充当 Eden、Survivor、Old 三种角色,但是它们不是固定的,这使得内存使用更加地灵活。
执行垃圾收集时,和 CMS 一样,G1 收集线程和应用线程并发执行,标记结束后,G1 也就知道哪些区块基本上是垃圾,存活对象极少,G1 会先从这些区块下手,因为从这些区块能很快释放得到很大的可用空间,这也是为什么 G1 被取名为 Garbage-First 的原因。
G1 使用了停顿预测模型来满足用户指定的停顿时间目标,并基于目标来选择进行垃圾回收的区块数量。G1 采用增量回收的方式,每次回收一些区块,而不是整堆回收。