堆内存划分:
老年代:
新生代:
- Eden区:
- Survivor区:
- (1)From
- (2)To
Minor GC和Full GC
Minor GC也称为Young GC 指发生新生代的垃圾收集动作,回收频率较高,回收速度快
Full GC也成为了Major GC 一般会回收老年代、年轻代、方法区的垃圾,回收速度比Minor GC慢十倍。
垃圾回收的算法
1. 标记清除算法
它分为两个阶段:
1) 标记阶段:垃圾收集器遍历堆内存,标记所有存活的对象。
2) 清除阶段:垃圾收集器遍历堆内存,清除所有未被标记的对象。
这总算法的主要缺点是会产生大量的内存碎片。
2. 标记整理算法
这种算法在标记清除算法的基础上进行了改进。
标记阶段和标记清除算法一致,但是在清除阶段,它不是简单的清除未被标记的对象,而是将所有存活对象向一端移动,然后直接清除边界以外的内存。
这种算法避免了内存碎片的问题,但移动对象的成本较高。
3. 复制算法
这种算法将堆内存分为两个相等的区域,每次只使用其中一个去区域。
垃圾收集时,它会遍历当前使用的区域,复制所有存活的对象到另一个区域,并清除当前区域的所有对象。
这种算法避免了内存碎片的问题,但是内存利用率较低。
4. 分代回收算法:
这种算法基于这样一个观察:大部分对象在创建后很快会变得不可达。
因此,它将堆内存分为新生代和老年代。
新生代的垃圾回收主要是复制算法
新创建的对象首先在Eden区(但是如果对象过大,则会直接进入老年区)
当Eden区的内存占比率比较高的时候,会发生一次minor gc(即下述的分配策略1)
minor gc的时间很短会stop the world (STW),采用复制算法将Eden区还有幸存区(Survivor)from中的GCROOT以及其标记数据复制到幸存区to,然后清理垃圾,再将幸存区from与幸存区to的位置调换,并且寿命+1
当寿命的超过阈值(默认15)以后,就会晋升到老年代。当老年代内存不足,就会发生Full GC,Full GC的STW时间会更长。(具体看后续分配策略1~7)
这些算法通常由JVM的垃圾收集器实现:
例如Serial收集器使用复制算法收集新生代,使用标记-整理算法收集老年代;
ParNew收集器也是如此。
Parallel Scavenge收集器使用复制算法收集新生代。
CMS收集器使用标记清除算法收集老年代。
G1收集器则结合了分代收集和标记整理算法,通过划分多个小区域实现。
Stop the World
"Stop the World"简称STW
是垃圾回收中的一个概念,指的是在进行垃圾回收时,所有的应用线程都会被暂停,直到垃圾回收过程完成。这是为了防止在垃圾回收过程中,应用线程修改了堆中的对象,导致垃圾回收器的工作被干扰。
"Stop the World"事件发生时,所有的用户线程都会被暂停,垃圾回收线程会独占CPU,进行垃圾回收工作。这个过程中,应用程序不会响应用户请求,用户会感觉到应用程序“卡住”了。
分配策略
1:优先在Eden区分配
多数情况下,对象在Eden区分配,当Eden区没有足够空间分配时,虚拟机将发起一次minorGC
2:大对象直接进入老年代
大对象:字符串、数组
对象如果超过设置值得大小,就会直接进入老年代
JVM参数-XX:PretenureSizeThreshold
这个参数只在Serial和ParNew下有效
这么做是为了避免大对象在分配内存时的赋值操作而降低效率
3:长期存活的对象直接进入老年代
虚拟机给每个对象一个年龄计数器,如果对象在Eden区出生并且经过minor GC存活,并且能够被Survivor容纳,将被移动到Survivor空间中,并且Age = 1,对象在Survivor空间每熬过一次minor GC Age+1;当Age(默认)增加到15,就会进入老年代。
4:minor GC后不能存放在Survivor区
如果对象不能被Survivor容纳,那么他一部分会在老年代,一部分还可能留在Survivor区
5:Eden和Survivor区比例默认8:1:1
6:对象动态年龄判断
如果当前Survivor区一批对象的总大小大于这块区域的50% ,那么大于等于这批年龄对象最大值的对象就会直接进入老年代;
这个规则是希望那些能够长期存活的对象今早进入老年代。
对象动态年龄判断一般是在minor GC后触发的
7:老年代空间分配担保机制
年轻代每一次minor GC之前都会计算老年代剩余可用空间,如果这个可用空间小于年轻代里所有的对象大小之和就会看-XX:handlePromotionFailure是否设置(JDK1.8以后默认设置)
如果设置了该参数,就看老年代剩余可用空间是否大于之前每次minor GC后进入老年代的对象平均大小
如果大于,就会进行一次minor GC;
如果小于,或者没有设置前面的参数,就会进行一次Full GC
如何判断对象是否存活
1、引用计数法
当对象B对A产生引用时,对象A的引用计数器的值+1,引用断开是引用计数器的值-1;
当AB循环引用时,引用计数器的值将无法为0;
当对象引用计数器的值为0时,即无其他对象引用,判断为死亡,可被回收,但是存在循环引用问题,导致对象一直存活;
2、可达性分析法
从GCRoot根开始向下搜索,直接或者间接可达的对象,即对象存活;
哪些对象可以作为GCRoot:
- 虚拟机栈引用对象
- 本地方法栈引用对象
- 静态属性引用对象
- 常量引用对象
对象引用分类
1、强引用:程序中普遍存在的对象引用
2、软引用:SoftReference实现,内存溢出前回收
3、弱引用:WeakReference实现,下一次垃圾回收
4、虚引用:PhantomReference实现,形同虚设的引用
- 强引用(Strong Reference):这是最常见的引用类型。如果一个对象具有强引用,那么垃圾回收器就永远不会回收它。当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足问题。
Object obj = new Object(); // obj是一个强引用
- 软引用(Soft Reference):如果一个对象只具有软引用,那么在系统将要发生内存溢出异常之前,将会把这些对象列进回收范围进行第二次回收。如果这次回收还没有足够的内存,才会抛出内存溢出异常。
import java.lang.ref.SoftReference;
Object obj = new Object();
SoftReference<Object> softRef = new SoftReference<>(obj);
obj = null; // 使得obj成为软引用
- 弱引用(Weak Reference):如果一个对象只具有弱引用,那么在垃圾回收器线程扫描它所管辖的内存区域的过程中,只要发现弱引用关联的对象,不管当前内存空间足够与否,都会回收它的内存。
import java.lang.ref.WeakReference;
Object obj = new Object();
WeakReference<Object> weakRef = new WeakReference<>(obj);
obj = null; // 使得obj成为弱引用
- 虚引用(Phantom Reference):虚引用主要用来跟踪对象被垃圾回收器回收的活动。虚引用与其他几种引用的一个区别在于:虚引用必须和引用队列(ReferenceQueue)联合使用。
import java.lang.ref.PhantomReference;
import java.lang.ref.ReferenceQueue;
Object obj = new Object();
ReferenceQueue<Object> refQueue = new ReferenceQueue<>();
PhantomReference<Object> phantomRef = new PhantomReference<>(obj, refQueue);
obj = null; // 使得obj成为虚引用
垃圾回收器
- 串行垃圾回收器,如:SerialGC,单核串行
- 吞吐量优先的垃圾回收其,如:ParallelGC,JDK1.8默认的垃圾回收其,多喝并行,吞吐量-cpu执行代码时间/cpu总的执行时间。
- 响应时间优先的垃圾回收器,如CMS,保证STW的时间尽可能的短。
- Garbage First垃圾回收器(G1),JDK1.9默认的垃圾回收器;
分区算法:将内存分为很多个E,S,O。
垃圾回收分为三个阶段:
新生代的垃圾回收+初始标记
新生代的垃圾回收+并发标记
老年代占内存高达45%以上,最终标记,E,S.O混合收集,拷贝存活
回收垃圾的时候,优先回收老年代最有回收价值的区域(并不回全部拷贝)
指定垃圾回收器
- `-XX:+UseSerialGC`:启用Serial垃圾回收器
- `-XX:+UseParallelGC`:启用Parallel垃圾回收器
- `-XX:+UseConcMarkSweepGC`:启用CMS(Concurrent Mark Sweep)垃圾回收器
- `-XX:+UseG1GC`:启用G1垃圾回收器