(一)强引用、软引用、弱引用、虚引用
- 强引用(Strong Reference)
- 最普遍的引用:Object obj = new Object();
- 抛出OutOfMemoryError 终止程序也不会回收具有强引用的对象;
- 通过将对象设置为null来弱化引用,让它被回收
- 软引用(soft reference)
- 对象处在有用但非必须的状态
- 只有当内存空间不足时,GC会回收该引用的对象的内存
- 可实现高速缓存
String str = new String(“abc”);
SoftReference softRef = new SoftReference(Str); //软引用
- 弱引用WeakReference
- 非必须对象,比软引用更弱一些
- GC时会被回收
- 被回收的概率也不大,因为GC线程优先级较低
- 适用于引用偶尔被使用且不影响垃圾收集的对象
String str = new String(“abc”);
WeakReference weakRefer = new WeakReference(str);
- 虚引用PhantomReference
- 不会决定对象的生命周期
- 任何时候都可能被垃圾收集器回收
- 跟踪对象被垃圾收集器回收的活动,起哨兵作用
- 必须和引用队列ReferenceQueue联合使用
ReferenceQueue queue = new ReferenceQueue();
PhantomReference ref = new PhantoReference(“abc”,queue);
(二)GC的算法有哪些?怎么使用
- 常见的垃圾回收算法有:复制算法,标记清除算法,标记压缩算法。
- 因为新生代中对象存活率低,复制算法主要在新生代,在from区和to区之间的拷贝。因为是将内存划分为两部分,内存回收的时候,将存活下来的对象复制到另一半内存,这样只用一半内存,就会导致内存的浪费。
- 标记清除算法:是直接将标记的垃圾清除掉,并没有发生内存位置的移动,也就是没有整理内存,就会导致内存位置不连续,产生很多的内存碎片,利用不起来,就会导致内存利用率低下。
- 标记压缩算法:它是把后面存活的区域拷贝到垃圾区和空闲区,没有碎片,但是标记清除的过程中做了一次整理和压缩,导致效率偏低。对于拷贝来说,内存的拷贝是非常快的(就是一个线性地址的拷贝),但是压缩就不是那么容易了,因为任何一块内存在移动的时候,如果是多线程,就要进行线程同步,如果是单线程,那么效率就更低了。
- 所以,可以得出结论,没有好的算法,只有合适的算法,于是就有了内存分代模型,针对各个分区的特性,采用分代回收垃圾,现在的jvm采用的算法就是分代收集算法。
- 新生代:朝生夕灭的对象(例如:方法的局部变量等)。
- 老年代:存活得比较久,但还是要死的对象(例如:缓存对象、单例对象等)。
- 永久代:对象生成后几乎不灭的对象(例如:加载过的类信息)。
- 分代垃圾回收
堆大小=新生代+老年代,新生代与老年代的比例为1:2,新生代细分为一块较大的Eden空间和两块较小的Survivor空间,分别被命名为from和to。(Edan:from:to=8:1:1
)
(三)轻GC和重GC什么时候会发生?
- 轻GC(
Minor GC
)
- Eden 空间占满, 触发 轻GC(
minor GC
),仍然存活的对象会被复制到S0 (survivor 0)
中去,这样 Eden 就被清空可以分配给新的对象。 - 又触发了一次 Minor GC ,S0 和 Eden 中存活的对象被复制到 S1 中,清空S0和 Eden区。默认情况下如果复制发生超过 16.次,JVM 会停止复制并把他们移到老年代中去。
- 重GC(
Full GC
)
- 老年代的空间被占满会触发老年代的Full GC。
- Full GC 是一个压缩处理过程, 所以它比 Minor GC 要慢很多。
- 有如下原因可能导致Full GC:
年老代(Tenured)被写满;
持久代(Perm)被写满;
System.gc()被显示调用;
上一次GC之后Heap的各域分配策略动态变化
(四)常见的垃圾收集器有哪些?
-
新生代收集器
1. Serial
2. ParNew
3. Parallel Scavenge -
老年代收集器
1. Serial Old
2. CMS
3. Parallel Old -
堆内存垃圾收集器:G1
CMS(Concurrent Mark Sweep) 收集器
- CMS 收集器是一种以最短回收停顿时间为目标的收集器,以 “ 最短用户线程停顿时间 ” 著称。整个垃圾收集过程分为 4 个步骤:
① 初始标记:标记一下 GC Roots 能直接关联到的对象,速度较快。
② 并发标记:进行 GC Roots Tracing,标记出全部的垃圾对象,耗时较长。
③ 重新标记:修正并发标记阶段引用户程序继续运行而导致变化的对象的标记记录,耗时较短。
④ 并发清除:用标记-清除算法清除垃圾对象,耗时较长。
整个过程耗时最长的并发标记和并发清除都是和用户线程一起工作,所以从总体上来说,CMS 收集器垃圾收集可以看做是和用户线程并发执行的。
- CMS 收集器也存在一些缺点:
- 对 CPU 资源敏感:默认分配的垃圾收集线程数为(CPU 数+3)/4,随着 CPU 数量下降,占用 CPU 资源越多,吞吐量越小
- 无法处理浮动垃圾:在并发清理阶段,由于用户线程还在运行,还会不断产生新的垃圾,CMS 收集器无法在当次收集中清除这部分垃圾。同时由于在垃圾收集阶段用户线程也在并发执行,CMS 收集器不能像其他收集器那样等老年代被填满时再进行收集,需要预留一部分空间提供用户线程运行使用。当 CMS 运行时,预留的内存空间无法满足用户线程的需要,就会出现 “ Concurrent Mode Failure ”的错误,这时将会启动后备预案,临时用 Serial Old 来重新进行老年代的垃圾收集。
- 因为 CMS 是基于标记-清除算法,所以垃圾回收后会产生空间碎片,可以通过 -XX:UserCMSCompactAtFullCollection 开启碎片整理(默认开启),在 CMS 进行 Full GC 之前,会进行内存碎片的整理。还可以用 -XX:CMSFullGCsBeforeCompaction 设置执行多少次不压缩(不进行碎片整理)的 Full GC 之后,跟着来一次带压缩(碎片整理)的 Full GC。
- 适用场景:重视服务器响应速度,要求系统停顿时间最短。可以使用 -XX:+UserConMarkSweepGC 来选择 CMS 作为老年代收集器。