之前说堆内存中有垃圾回收,比如Young区的Minor GC,Old区的Major GC,Young区和Old区的Full GC。 但是对于一个对象而言,怎么确定它是垃圾?是否需要被回收?怎样对它进行回收?等等这些问 题我们还需要详细探索。
因为Java是自动做内存管理和垃圾回收的,如果不了解垃圾回收的各方面知识,一旦出现问题我们很难进行排查和解决,自动垃圾回收机制就是寻找Java堆中的对象,并对对象进行分类判别, 寻找出正在使用的对象和已经不会使用的对象,然后把那些不会使用的对象从堆上清除 。
引用计数 对于某个对象而言,只要应用程序中持有该对象的引用,就说明该对象不是垃圾,如果一个对象没有任何指针对其 引用,它就是垃圾。
弊端:如果两个对象互相循环引用,会形成孤岛,导致垃圾无法回收。
解决:利用可达性分析
可达性分析
通过GC Root的对象,开始向下寻找,看某个对象是否可达 GC root遍历不可达的就是垃圾。
GC Root的条件:类加载器,Thread,Java虚拟机栈中的局部变量表(正在运行的方法的变量的指向),本地方法栈中的局部变量等
Full GC=Metaspace GC+Young GC+old GC (全局GC)
Full GC : 一般是由Old区引起的,由于老年代的的对象通常会比较多,因为标记 - 清理 - 整理(压缩)的耗时通常会比较长,会让应用出现卡顿的现象,这也是为什么很多应用要优化,尽量避免或减少 Full GC 的原因。
用合适的垃圾搜集算法进行回收,一般有如下几个算法:
此时堆中所有的对象都会被扫描一遍,从而才能确定需要回收的对象,比较耗时
缺点:
将内存划分为两块相等的区域,每次只使用其中一块,如下图所示:
当其中一块内存使用完了,就将还存活的对象复制到另外一块上面,然后把已经使用过的内存空间一次清除掉。
标记复制算法适用于新生代,因为它快
缺点:空间利用率降低。
标记复制收集算法在对象存活率较高时就要进行较多的复制操作,效率将会变低。更关键的是,如果不想浪费50%的空间,就需要有额外的空间进行分配担保,以应对被使用的内存中所有对象都有100%存活的极端情况,所以老年代一般不能直接选用这种算法。
标记过程仍然与"标记-清除"算法一样,但是后续步骤不是直接对可回收对象进行清理,而是让所有存活 的对象都向一端移动,然后直接清理掉端边界以外的内存。
让所有存活的对象都向一端移动,清理掉边界意外的内存。
总结:
如果说收集算法是内存回收的方法论,那么垃圾收集器就是内存回收的具体实现。
Serial收集器是最基本、发展历史最悠久的收集器,曾经(在JDK1.3.1之前)是虚拟机新生代收集的唯 一选择。
它是一种单线程收集器,不仅仅意味着它只会使用一个CPU或者一条收集线程去完成垃圾收集工作,更重要的是其在进行垃圾收集的时候需要暂停其他线程。
Serial Old收集器是Serial收集器的老年代版本,也是一个单线程收集器,不同的是采用"标记-整理算法",运行过程和Serial收集器一样。
其实就是 Serial 的多线程版本。和ParallelScanvenge一样都是新生代的垃圾收集器
Parallel Scavenge收集器是一个新生代收集器,它也是使用复制算法的收集器,又是并行的多线程收集器,看上去和ParNew一样,但是Parallel Scanvenge更关注系统的吞吐量。
吞吐量=运行用户代码的时间/(运行用户代码的时间+垃圾收集时间)
若吞吐量越大,意味着垃圾收集的时间越短,则用户代码可以充分利用CPU资源,尽快完成程序 的运算任务。
Parallel Old收集器是Parallel Scavenge收集器的老年代版本,使用多线程和标记-整理算法进行垃圾回收,也是更加关注系统的吞吐量。
Concurrent Mark Sweep-用于老年代的垃圾收集器
官网:docs.oracle.com/javase/8/do…
前面的垃圾收集器都是stop the world,能不能少一点呢?能不能业务代码线程和垃圾回收线程一起跑?
CMS初衷:为了尽可能减少stw的时间,追求一个停顿时间比较低的垃圾收集,它会更关注停顿时间。
但也不能全程一起跑,不能一边生成垃圾,一边回收垃圾,一定有垃圾收集器有自己跑的时候。
CMS采用的是标记清除算法,整个过程分为4步
由于整个过程中,并发标记和并发清除,收集器线程可以与用户线程一起工作,所以总体上来 说,CMS收集器的内存回收过程是与用户线程一起并发地执行的。
CMS 缺点:
1. 对 CPU 资源要求敏感。
CMS 回收器过分依赖于多线程环境,默认情况下,开启的线程数为(CPU 的数量 + 3)/ 4,当 CPU 数量少于 4 个时,CMS 对用户本身的操作的影响将会很大,因为要分出一半的运算能力去执行回收器线程。
2. CMS无法清除浮动垃圾。
浮动垃圾指的是CMS清除垃圾的时候,还有用户线程产生新的垃圾,这部分未被标记的垃圾叫做“浮动垃圾”,只能在下次 GC 的时候进行清除。
3. CMS 垃圾回收会产生大量空间碎片。
CMS 使用的是标记-清除算法,所有在垃圾回收的时候回产生大量的空间碎片。
官网:docs.oracle.com/javase/8/do…
特点:
G1的内存结构和传统的内存空间划分有比较的不同。G1将内存划分成了多个大小相等的Region(默认是512K),Region逻辑上连续,物理内存地址不连续。同时每个Region被标记成E、S、O、H,分别表示Eden、Survivor、Old、Humongous。其中E、S属于年轻代,O与H属于老年代。 示意图如下:
H表示Humongous。从字面上就可以理解表示大的对象(下面简称H对象)。当分配的对象大于等于Region大小的一半的时候就会被认为是巨型对象。H对象默认分配在老年代,可以防止GC的时候大对象的内存拷贝。
所有的垃圾回收,都是基于 1 个个 region 的。JVM 内部知道,哪些 region 的对象最少(即:该区域最空),总是会优先收集这些 region(因为对象少,内存相对较空,肯定快),这也是 Garbage-First 得名的由来,G 即是 Garbage 的缩写, 1 即 First。
整个过程分为4步:
初始标记和并发标记的作用与CMS一样
最终标记与CMS的重新标记一样 重新标记阶段是为了修正并发期间由于用户进行运作导致的标记变动的那一部分对象的标记记录。这个阶段的停顿时间一般会比初始标记阶段稍长一些,但远比并发标记的时间短,需要 Stop The World 。
筛选回收 首先对各个 Region 的回收价值和成本进行排序,根据用户所期望的 GC 停顿时间来制定回收计划。这个阶段可以与用户程序一起并发执行,但是因为只回收一部分 Region,时间是用户可控制的。这样可能会导致只能回收一部分,有一部分垃圾无法回收掉,只能期待下一次的回收,所以垃圾回收时间不要调的太严格。
G1在JDK7中出现,JDK8可以试用,JDK9默认,但是JDK11又出了个zGC,号称能少于10ms,他希望你的内存越大越好,支持TB级别的。现在商用并不不多。
串行收集器:Serial / Serial Old
并行收集器[吞吐量优先]:Parallel Scavenge / Parallel Old
并发收集器[停顿时间优先]:CMS / G1
吞吐量和停顿时间也是评价一个垃圾收集器性能好坏的指标。
相关JVM参数:
串行 :
- -XX:+UseSerialGC
- -XX:+UseSerialOldGC
并行(吞吐量优先):
- -XX:+UseParallelGC
- -XX:+UseParallelOldGC
并发收集器(响应时间优先) :
- -XX:+UseConcMarkSweepGC
- -XX:+UseG1GC
注:jdk8 开始,用 MetaSpace 区取代了 Perm 区(永久代),所以相应的 jvm 参数变成 -XX:MetaspaceSize 及 -XX:MaxMetaspaceSize。
一般情况下,不需要选择,如果不满足需求,可以调节堆的大小,如果还不行,那就按下面这种方式:
●If the application has a small data set (up to approximately 100 MB), then select the serial collector with the option
-XX: +UseSerialGC.
●If the application will be run on a single processor and there are no pause time requirements, then let the VM select the collector, or select the serial collector with the option-XX : +UseSerialGC
●If (a) peak application performance is the first priority and (b) there are no pause time requirements or pauses of 1 second or longer are acceptable, then let the VM select the collector, or select the parallel collector with-XX:+UseParallelGC.
●If response time is more important than overall throughput and garbage collection pauses must be kept shorter than approximately 1 second, then select the concurrent collector withXX:+UseConcMarkSweepGC
or ``-XX:+UseG1GC`.
JVM系列文章:
[JVM系列]三、一文搞懂JVM垃圾回收
[JVM系列]二、一文彻底搞懂 JVM运行时数据区 和 JVM内存结构
[JVM系列]一、源码->类文件->JVM过程详解(类文件解读/类加载机制/类加载器)