欢送关注公众号:五小竹
什么是垃圾
内存中不在被应用到的内存空间,就是垃圾。
Java中的内存是动态分配和主动回收的。学习垃圾收集机制,调优策略,可能帮忙咱们解决和应答各种工作中面临的内存透露问题。
Java 虚拟机运行时数据辨别为程序计数器、虚拟机栈、本地办法栈、堆、办法区。
其中程序计数器、虚拟机栈、本地办法栈这 3 个区域是线程公有的,会随线程沦亡而主动回收,所以不须要治理。
因而垃圾收集只须要关注堆和办法区。
Java的内存调配
堆上调配
- 如果启动了本地线程调配缓冲,将按线程优先调配在TLAB上。
- 对象优先在Eden上调配
- 大对象进入老年代
- 长期存活的对象进入老年代
栈上调配
基于逃逸剖析技术,如果一个对象始终在一个办法内。确定这个对象不会逃逸办法之外,那让这个对象在栈上分配内存,这样对象就会随着办法的完结而主动销毁,升高了垃圾回收的压力。
如何断定垃圾
援用计数法
给对象增加一个援用计数器,有对这个对象的援用就对计数加1,援用生效就减1,任何时刻计数器为0的对象就是断定为垃圾。
- 长处:实现简略,效率高。
- 毛病:不能解决对象循环援用的问题。在多线程环境下,援用计数变更也要进行低廉的同步操作,性能较低。
可达性分析法
目前支流的虚拟机才有的算法。他是从根节点(GC Root)向上来搜寻对象节点,搜寻走过的门路称为援用链,当一个对象到根节点之间没有联通的话,这个对象断定为垃圾。
GC Root对象
能够作为GC Roots的对象包含:
- 虚拟机栈中援用的对象
- 办法区类动态属性信用的对象
- 办法去常量援用的对象
- 本地办法栈中JNI(Native办法)援用的对象
援用类型
判断一个对象的存活跟援用无关,jdk1.2之前,援用只是指援用类型的数据中存储了另一块内存的地址。jdk1.2之后,java的援用分为以下四种。
- 强援用:Object obj = new Object();最常见的援用,这样通过new创立的会产生该对象的强援用。只有对象有强援用指向,并且GC Roots可达,那么就不会回收该对象。
- 软援用: SotfReference类来实现。示意一些还有用然而非必须的对象。在OOM前,垃圾收集器会把这些软援用指向的对象退出回收范畴,对于软援用关联的对象,只有在内存不足的时候才会回收。
- 弱援用:WeakReference,示意非必要对象,在YGC时候会被回收。因为YGC工夫不确定,所以弱援用随时都有可能被回收。
- 虚援用:PhantomReference,无奈通过该援用获取指向的对象。在任何时候都可能被垃圾回收器回收。虚援用次要用来跟踪对象被垃圾回收器回收的流动,必须和援用队列配合应用:
如
ReferenceQueue<String> queue = new ReferenceQueue<>();PhantomReference<String> phantomReference = new PhantomReference<>("Hello", queue);
当垃圾回收器筹备回收一个对象时,发现他有虚援用,就会在回收这对象的内存前,把这个虚援用退出队列,程序通过援用队列中是否有该虚援用,来理解对象是否要被垃圾回收器回收,而后能够在回收前做一些事件。
垃圾收集算法
标记-革除 (Mark-Sweep)
最根底的算法,第一步,先标记,从GC Roots登程,顺次标记对象援用关系,而后,将没有被标记的对象革除。
毛病:一个是效率问题,标记和革除两个过程效率都不高;另外,革除过后会产生大量不间断的内存碎片。导致当咱们须要调配大对象时候,无奈找到足够的间断内存而又触发一次GC。
复制(Copy)
复制算法:把内存分为两块大小雷同的区域,每次应用其中一块,当应用完了,就把这一块区域上还存活对象拷贝到另一块,而后把这一块革除掉。不会造成内存碎片,然而内存利用率不高,会造成一半的空间节约。
新生代上的对象大多数“朝生夕死”,HotSpot默认将新生代内存划分为一个大的Eden区和两个小的Surivor区。在GC时,将Eden区和一个Surivor区中存活的对象,复制到另外一个Surivor区。Eden区和两个Surivor区比例默认8:1:1,所以这时候只会节约10%的空间。
标记-整顿(Mark-Compact)
和标记革除一样,先标记,然而后续不间接回收对象清理,而是让存活的对象向一端挪动,而后间接清理掉端边界之外的内存。
因为复制算法在存活对象比拟多的时候,效率比拟低,且有空间节约,所以老年代个别会选用标记-整顿算法。
### 三种GC算法比照
回收算法 | 长处 | 毛病 |
---|---|---|
标记-革除 | 实现简略 | 存在内存碎片 |
复制 | 无碎片,性能好 | 内存使用率低 |
标记-整顿 | 无碎片 | 整顿过程开销大 |
分代收集
将下面的各种算法组合在一起,就地取材。个别咱们把堆空间分为新生代和老年代。依据他们的特定采纳不同的垃圾回收算法,在新生代,每次GC都会有大量对象死去,多数存活,所以采纳复制算法。只用付出多数对象的复制老本,不会造成不间断的内存碎片。而在老年代,对象存活率高,则采纳标记整顿或者标记革除。
垃圾收集器
术语
- STW: "Stop The World"简写,也叫全局进展。会造成服务暂停,没有响应。
- 串行收集: GC单线程内存回收,会暂停所有的用户线程。比方Serial, Serial Old
- 并行收集: 多线程进行并发GC,此时暂停用户线程。比方Parallel
- 并发收集:用户线程和GC线程同时执行,不须要进展用户线程.适宜对响应工夫有要求的场景。比方CMS收集器
Serial/Serial Old
Serial是一个单线程的收集器,它只会应用一个CPU或一个收集线程去做垃圾收集,并且在做垃圾收集时须要进行所有的工作线程,晓得收集工作完结。Serial进行用户线程,采纳复制算法收集年老代,Serial Old采纳标记-整顿算法收集老年代。
特点:单线程收集,STW
应用 -XX:+UseSerialGC 开启Serial + Serial Old
ParNew
Serial的多线程版本,应用多线程进行垃圾收集。他是新生代的垃圾收集器。须要配合老年代的CMS收集器应用。所以开启须要应用CMS,新生代默认ParNew.
能够通过 ==-XX:ParallelGCThreads== 参数来管制收集的线程数,过程也是STW
Parallel Scavenge/Paraller Old
Parallel Scavenge是新生代的收集器,采纳复制算法,多线程的收集器,Paraller Old是老年代收集器,也是多线程收集,采纳标记-整顿算法。
主要参数
- -XX:UseParallelGC 开启
- -XX:MaxGCPauseMillis 最大垃圾收集进展工夫
- -XX:GCTimeRatio 设置吞吐量大小
可控的吞吐量
通过参数 -XX:MaxGCPauseMillis 管制最大GC进展工夫。-XX:GCTimeRatio 设置吞吐量大小
晋升吞吐量能够高效利用CPU工夫,尽快实现程序工作。
自适应GC策略
解决提供了下面两个参数管制吞吐量大小以外,Parallel Scavenge还能够通过 -XX:+UseAdptiveSizePolicy, 开启指示仪GC策略。关上后,就不再须要手动设置新生代大小,Eden/Surivor比例等参数,虚构机会依据零碎运行状况,动静调整这些参数,从而达到最优的进展工夫,和最高吞吐量。
CMS
- 1.初始标记:只标记GC Roots能间接关联到的对象,会STW。
- 2.并发标记:进行GC Roots Tracing的过程,GC线程和用户线程同时执行。
- 3.从新标记 :修改并发标记期间,因程序运行导致标记发生变化的那些对象。会STW
- 4.并发革除:并发回收垃圾对象(GC线程和用户线程同时执行)
- 5.并发重置:清理本次CMS GC上下文信息,为下一次GC做筹备。
长处:低进展,并发执行
毛病:
- 因为并发执行,所以对CPU资源压力大。
- 无奈解决在收集过程中产生的浮动垃圾。
- 因为采纳标记-革除算法,所以会才是大量内存碎片。而导致在须要调配大对象是内存不足,触发FullGC。
应用 -XX:UseConcMarkSweepGC 开启 ParNew+CMS/SerialOld 收集器组合,即新生代采纳ParNew,老年代CMS,当CMS出错后,SerialOld备用。
为了解决内存碎片问题,CMS能够通过-XX:+UseCMSCompactAtFullCollection,强制JVM在FullGC实现后对老年代进行压缩,执行碎片整顿,同时会STW。想要缩小STW次数,能够配置-XX:+CMSFullGCsBeforeCompaction参数,在执行设置的次数后,JVM再在老年代进行空间整顿。
JDK9曾经将CMS标记为弃用,在JDK14中曾经将CMS删除。
G1
JDK7推出的新一代收集器,
是一个面向服务端利用的收集器。相比下面的收集器,
G1作用在整个堆,而其余的收集器都是只作用在新生代或老年代。
G1将Java Heap宰割成一些大小雷同的Region,通过参数-XX:G1HeapRegionSize指定Region的大小,取值范畴为1~32M,应为2的N次幂。G1对每个Region做了分类, 别离包含:Eden,Surivor,Old, Humongous, 其中Humongous相当于一个大的Old,用来寄存大对象。
如下图:G1的堆内存布局和传统的堆内存布局不同。
G1将空间分成多个区域,跟踪每个区域外面的垃圾沉积的价值大小,构建一个优先列表,优先收集垃圾最多的区域,这也是它为什么叫Garbage-First的起因。
G1与CMS相比的,有何特点
- 并发与并行:充沛的利用多CPU,缩短STW工夫。并发标记阶段可与用户线程并发执行,最终标记阶段GC线程可并行执行。
- 分代收集:G1能够不与其余垃圾收集器配合,独立实现整个GC堆的垃圾收集。
- 空间整合:G1整体看是采纳“标记-整顿”算法实现的,部分也有Eden和Surivor Region看上去是采纳“复制”算法来实现。整个过程防止了产生内存碎片。
- 进展工夫可管制:G1除了谋求低进展,还能建设可预测的进展工夫模型,能让用户指定在一个时间段内,耗费在收集上的工夫不超过一个时间段。
G1垃圾收集模式
Young GC
- 1.所有Eden Region都满了时,就会触发Young GC
- 2.Eden Region里的对象会转移到Surivor Region
- 3.原Surivor Region中的对象转移到另一个Surivor,或者降职到Old Region
- 4.闲暇Region会被放入闲暇列表,期待下次应用。
Mixed GC
当老年代占整个Heap的大小百分比达到一个阀值(-XX:InitialingHeapOccupancyPercent)时,默认45%,就会触发Mixed GC,
收集整个新生代以及局部老年代。
Mixed GC 回收过程
- 1.初始标记:只标记GC Roots能关联到的对象。批改TAMS的值,此阶段会STW.
- 2.并发标记: 从GC Root开始对堆内存中的对象进行可达性剖析,找出存活的对象,此过程能够和用户线程并发执行。
- 3.最终标记:修改在并发标记阶段用户线程持续运行导致标记产生变动的记录。此过程STW,然而能够并行执行。
- 4.筛选回收:对各个Region的回收价值和老本进行排序,依据用户冀望的进展工夫制订回收打算。
Full GC
当复制对象内存不够时,或无奈调配足够空间时,触发Full GC
Full GC模式是采纳Serial Old收集的,所以会STW。
如何缩小Full GC?
- 增大-XX:G1ReserverPercent,来减少预留内存。
- 缩小 -XX:InitialingHeapOccupancyPercent,当老年代达到这个值是就触发Mixed GC,
- 减少 -XX:ConcGCThreads 并发阶段的线程数。
总结
对以上介绍的垃圾回收器总结
- Serial 串行,作用于新生代,采纳复制算法
- ParNew 并行,作用于新生代,采纳复制算法
- Serial Old 串行,作用于老年代,标记-整顿算法
- Parallel 并行, 作用于新生代, 复制算法
- Parallel Old 并行, 作用于老年代, 标记-整顿算法
- CMS 并发, 作用于老年代, 标记-革除算法
- G1 并发+并行, 作用于整个堆,复制算法,标记-整顿
参考资料
《深刻了解Java虚拟机 JVM高级个性与最佳实际》