原文地址:https://www.cnblogs.com/1024Community/p/honery.html#341-%E5%B9%B4%E8%BD%BB%E4%BB%A3young-generation%E7%9A%84%E5%9B%9E%E6%94%B6%E7%AE%97%E6%B3%95
https://blog.csdn.net/yhyr_ycy/article/details/52566105
https://blog.csdn.net/ymrfzr/article/details/51354380
按照套路是要先装装X,谈谈JVM垃圾回收的前世今生的。说起垃圾回收(GC),大部分人都把这项技术当做Java语言的伴生产物。事实上,GC的历史比Java久远,早在1960年Lisp这门语言中就使用了内存动态分配和垃圾回收技术。设计和优化C++这门语言的专家们要长点心啦~~
猿们都知道JVM的内存结构包括五大区域:程序计数器、虚拟机栈、本地方法栈、堆区、方法区。其中程序计数器、虚拟机栈、本地方法栈3个区域随线程而生、随线程而灭,因此这几个区域的内存分配和回收都具备确定性,就不需要过多考虑回收的问题,因为方法结束或者线程结束时,内存自然就跟随着回收了。而Java堆区和方法区则不一样、不一样!(怎么不一样说的朗朗上口),这部分内存的分配和回收是动态的,正是垃圾收集器所需关注的部分。
程序计数器:当前线程所执行的字节码行号指示器,用于分支,循环,跳转,异常处理等。程序计数器也是所有JVM内存区域中唯一一个没有定义OutOfMemoryError的区域。
虚拟机栈:每个方法被执行的时候都会同时创建一个栈帧( Stack Frame)用于存储局部变量表、操作栈、动态链接、方法出口等信息。每一个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。
局部变量表存放了编译期可知的8种基本数据类型.对象引用( reference 类型,它不等同于对象本身,根据不同的虚拟机实现,它可能是一个指向对象起始地址的引用指针,也可能指向一个代表对象的句柄或者其他与此对象相关的位置)和 returnAddress 类型(指向了一条字节码指令的地址) 局部变量表所需的内存空间在编译期间完成分配,当进入一个方法时,这个方法需要在帧中分配多大的局部变量空间是完全确定的,在方法运行期间不会改变局部变量表的大小。
如果线程请求的栈深度大于虚拟机所允许的深度,将抛出 StackOverflowError 异常
如果虚拟机栈扩展时无法申请到足够的内存时会抛出 OutOfMemoryError 异常。
本地方法栈:和虚拟机栈,差不多,只不过虚拟机栈为java方法提供服务,本地方法栈为native方法服务。
堆:被所有线程共享的一块区域,该内存区域的唯一目的就是存放对象实例。如果在执行垃圾回收之后,仍没有足够的内存分配,也不能再扩展,将会抛出OutOfMemoryError:Java Heap Space异常。
方法区:被所有线程共享的一款区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。这个区域的内存回收目标主要是针对常量池的回收和对类型的卸载.当方法区使用的内存超过它允许的大小时,就会抛出OutOfMemory:PermGen Space异常。
非JVM规范的内存区域:直接内存
直接内存并不是虚拟机规范定义的数据区的一部分,也不是虚拟机运行时数据区的一部分。但是这部分内存也被频繁的使用,而且也可能导致OOM。
在JDK1.4中新加入了NIO类,引入了一种基于通道与缓冲区的I/O方式。它可以使用Native函数库直接分配堆外内存,然后通过一个存储在Java堆中的DirectByteBuffer对象作为这块内存的引用进行操作。这样能在一些场合中显著提高性能,因为避免了在Java堆和Native堆中来回复制数据。
垃圾收集器在对堆区和方法区进行回收前,首先要确定这些区域的对象哪些可以被回收,哪些暂时还不能回收,这就要用到判断对象是否存活的算法!(面试官肯定没少问你吧)
2.1.1 算法分析
引用计数是垃圾收集器中的早期策略。在这种方法中,堆中每个对象实例都有一个引用计数。当一个对象被创建时,就将该对象实例分配给一个变量,该变量计数设置为1。当任何其它变量被赋值为这个对象的引用时,计数加1(a = b,则b引用的对象实例的计数器+1),但当一个对象实例的某个引用超过了生命周期或者被设置为一个新值时,对象实例的引用计数器减1。任何引用计数器为0的对象实例可以被当作垃圾收集。当一个对象实例被垃圾收集时,它引用的任何对象实例的引用计数器减1。
2.1.2 优缺点
优点:引用计数收集器可以很快的执行,交织在程序运行中。对程序需要不被长时间打断的实时环境比较有利。
缺点:无法检测出循环引用。如父对象有一个对子对象的引用,子对象反过来引用父对象。这样,他们的引用计数永远不可能为0。
2.1.3 是不是很无趣,来段代码压压惊
public class ReferenceFindTest {
public static void main(String[] args) {
MyObject object1 = new MyObject();
MyObject object2 = new MyObject();
object1.object = object2;
object2.object = object1;
object1 = null;
object2 = null;
}
}
这段代码是用来验证引用计数算法不能检测出循环引用。最后面两句将object1
和object2
赋值为null
,也就是说object1
和object2
指向的对象已经不可能再被访问,但是由于它们互相引用对方,导致它们的引用计数器都不为0,那么垃圾收集器就永远不会回收它们。
可达性分析算法是从离散数学中的图论引入的,程序把所有的引用关系看作一张图,从一个节点GC ROOT开始,寻找对应的引用节点,找到这个节点以后,继续寻找这个节点的引用节点,当所有的引用节点寻找完毕之后,剩余的节点则被认为是没有被引用到的节点,即无用的节点,无用的节点将会被判定为是可回收的对象。
在Java语言中,可作为GC Roots的对象包括下面几种:
a) 虚拟机栈中引用的对象(栈帧中的本地变量表);
b) 方法区中类静态属性引用的对象;
c) 方法区中常量引用的对象;
d) 本地方法栈中JNI(Native方法)引用的对象。
无论是通过引用计数算法判断对象的引用数量,还是通过可达性分析算法判断对象的引用链是否可达,判定对象是否存活都与“引用”有关。在Java语言中,将引用又分为强引用、软引用、弱引用、虚引用4种,这四种引用强度依次逐渐减弱。
在程序代码中普遍存在的,类似 Object obj = new Object()
这类引用,只要强引用还存在,垃圾收集器永远不会回收掉被引用的对象。
用来描述一些还有用但并非必须的对象。对于软引用关联着的对象,在系统将要发生内存溢出异常之前,将会把这些对象列进回收范围之中进行第二次回收。如果这次回收后还没有足够的内存,才会抛出内存溢出异常。
也是用来描述非必需对象的,但是它的强度比软引用更弱一些,被弱引用关联的对象只能生存到下一次垃圾收集发生之前。当垃圾收集器工作时,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。
也叫幽灵引用或幻影引用(名字真会取,很魔幻的样子),是最弱的一种引用关系。一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。它的作用是能在这个对象被收集器回收时收到一个系统通知。
不要被概念吓到,也别担心,还没跑题,再深入,可就不好说了。小编罗列这四个概念的目的是为了说明,无论引用计数算法还是可达性分析算法都是基于强引用而言的。
即使在可达性分析算法中不可达的对象,也并非是“非死不可”,这时候它们暂时处于“缓刑”阶段,要真正宣告一个对象死亡,至少要经历两次标记过程。
第一次标记:如果对象在进行可达性分析后发现没有与GC Roots相连接的引用链,那它将会被第一次标记;
第二次标记:第一次标记后接着会进行一次筛选,筛选的条件是此对象是否有必要执行finalize()
方法。在finalize()
方法中没有重新与引用链建立关联关系的,将被进行第二次标记。
第二次标记成功的对象将真的会被回收,如果对象在finalize()
方法中重新与引用链建立了关联关系,那么将会逃离本次回收,继续存活。猿们还跟的上吧,嘿嘿。
猿们,方法区存储内容是否需要回收的判断可就不一样咯。方法区主要回收的内容有:废弃常量和无用的类。对于废弃常量也可通过引用的可达性来判断,但是对于无用的类则需要同时满足下面3个条件:
ClassLoader
已经被回收;java.lang.Class
对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。
讲了半天,主角终于要粉墨登场了。
标记-清除算法采用从根集合(GC Roots)进行扫描,对存活的对象进行标记,标记完毕后,再扫描整个空间中未被标记的对象,进行回收,如下图所示。标记-清除算法不需要进行对象的移动,只需对不存活的对象进行处理,在存活对象比较多的情况下极为高效,但由于标记-清除算法直接回收不存活的对象,因此会造成内存碎片。
复制算法的提出是为了克服句柄的开销和解决内存碎片的问题。它开始时把堆分成 一个对象 面和多个空闲面, 程序从对象面为对象分配空间,当对象满了,基于copying算法的垃圾 收集就从根集合(GC Roots)中扫描活动对象,并将每个 活动对象复制到空闲面(使得活动对象所占的内存之间没有空闲洞),这样空闲面变成了对象面,原来的对象面变成了空闲面,程序会在新的对象面中分配内存。
标记-整理算法采用标记-清除算法一样的方式进行对象的标记,但在清除时不同,在回收不存活的对象占用的空间后,会将所有的存活对象往左端空闲空间移动,并更新对应的指针。标记-整理算法是在标记-清除算法的基础上,又进行了对象的移动,因此成本更高,但是却解决了内存碎片的问题。具体流程见下图:
分代收集算法是目前大部分JVM的垃圾收集器采用的算法。它的核心思想是根据对象存活的生命周期将内存划分为若干个不同的区域。一般情况下将堆区划分为老年代(Tenured Generation)和新生代(Young Generation),在堆区之外还有一个代就是永久代(Permanet Generation)。老年代的特点是每次垃圾收集时只有少量对象需要被回收,而新生代的特点是每次垃圾回收时都有大量的对象需要被回收,那么就可以根据不同代的特点采取最适合的收集算法。
3.4.1 年轻代(Young Generation)的回收算法
a) 所有新生成的对象首先都是放在年轻代的。年轻代的目标就是尽可能快速的收集掉那些生命周期短的对象。
b) 新生代内存按照8:1:1的比例分为一个eden区和两个survivor(survivor0,survivor1)区。一个Eden区,两个 Survivor区(一般而言)。大部分对象在Eden区中生成。回收时先将eden区存活对象复制到一个survivor0区,然后清空eden区,当这个survivor0区也存放满了时,则将eden区和survivor0区存活对象复制到另一个survivor1区,然后清空eden和这个survivor0区,此时survivor0区是空的,然后将survivor0区和survivor1区交换,即保持survivor1区为空, 如此往复。
c) 当survivor1区不足以存放 eden和survivor0的存活对象时,就将存活对象直接存放到老年代。若是老年代也满了就会触发一次Full GC,也就是新生代、老年代都进行回收。
d) 新生代发生的GC也叫做Minor GC,MinorGC发生频率比较高(不一定等Eden区满了才触发)。
3.4.2 年老代(Old Generation)的回收算法
a) 在年轻代中经历了N次垃圾回收后仍然存活的对象,就会被放到年老代中。因此,可以认为年老代中存放的都是一些生命周期较长的对象。
b) 内存比新生代也大很多(大概比例是1:2),当老年代内存满时触发Major GC即Full GC,Full GC发生频率比较低,老年代对象存活时间比较长,存活率标记高。
3.4.3 持久代(Permanent Generation)的回收算法
用于存放静态文件,如Java类、方法等。持久代对垃圾回收没有显著影响,但是有些应用可能动态生成或者调用一些class,例如Hibernate 等,在这种时候需要设置一个比较大的持久代空间来存放这些运行过程中新增的类。持久代也称方法区,具体的回收可参见上文2.5节。
猿们加油跟上,离offer不远啦!!!
下面一张图是HotSpot虚拟机包含的所有收集器,图是借用过来滴:
-XX:+UseSerialGC
来强制指定。-XX:+UseParallelGC
来强制指定,用-XX:ParallelGCThreads=4
来指定线程数。一、七种垃圾收集器
(1) Serial(串行GC)-XX:+UseSerialGC
(2) ParNew(并行GC)-XX:+UseParNewGC
(3) Parallel Scavenge(并行回收GC)
(4) Serial Old(MSC)(串行GC)-XX:+UseSerialGC
(5) CMS(并发GC)-XX:+UseConcMarkSweepGC
(6) Parallel Old(并行GC)-XX:+UseParallelOldGC
(7) G1(JDK1.7update14才可以正式商用)
二.1~3用于年轻代垃圾回收:年轻代的垃圾回收称为minor GC
三.4~6用于年老代垃圾回收(当然也可以用于方法区的回收):年老代的垃圾回收称为full GC
G1独立完成"分代垃圾回收"
注意:并行与并发
并行:多条垃圾回收线程同时操作
并发:垃圾回收线程与用户线程一起操作
四、常用五种组合
Serial/Serial Old
ParNew/Serial Old:与上边相比,只是比年轻代多了多线程垃圾回收而已
ParNew/CMS:当下比较高效的组合
Parallel Scavenge/Parallel Old:自动管理的组合
G1:最先进的收集器,但是需要JDK1.7update14以上
五. Serial/Serial Old
年轻代Serial收集器采用单个GC线程实现"复制"算法(包括扫描、复制)
年老代Serial Old收集器采用单个GC线程实现"标记-整理"算法
Serial与Serial Old都会暂停所有用户线程(即STW)
说明:
STW(stop the world):编译代码时为每一个方法注入safepoint(方法中循环结束的点、方法执行结束的点),在暂停应用时,需要等待所有的用户线程进入safepoint,之后暂停所有线程,然后进行垃圾回收。
适用场合:
CPU核数<2,物理内存<2G的机器(简单来讲,单CPU,新生代空间较小且对STW时间要求不高的情况下使用)
-XX:UseSerialGC:强制使用该GC组合
-XX:PrintGCApplicationStoppedTime:查看STW时间
六.ParNew/Serial Old:
ParNew除了采用多GC线程来实现复制算法以外,其他都与Serial一样,但是此组合中的Serial Old又是一个单GC线程,所以该组合是一个比较尴尬的组合,在单CPU情况下没有Serial/Serial Old速度快(因为ParNew多线程需要切换),在多CPU情况下又没有之后的三种组合快(因为Serial Old是单GC线程),所以使用其实不多。
-XX:ParallelGCThreads:指定ParNew GC线程的数量,默认与CPU核数相同,该参数在于CMS GC组合时,也可能会用到
七.Parallel Scavenge/Parallel Old:
特点:
年轻代Parallel Scavenge收集器采用多个GC线程实现"复制"算法(包括扫描、复制)年老代Parallel Old收集器采用多个GC线程实现"标记-整理"算ParallelScavenge与Parallel Old都会暂停所有用户线程(即STW)
说明:
吞吐量:CPU运行代码时间/(CPU运行代码时间+GC时间)CMS主要注重STW的缩短(该时间越短,用户体验越好,所以主要用于处理很多的交互任务的情况)Parallel Scavenge/Parallel Old主要注重吞吐量(吞吐量越大,说明CPU利用率越高,所以主要用于处理很多的CPU计算任务而用户交互任务较少的情况)
参数设置:
-XX:+UseParallelOldGC:使用该GC组合
-XX:GCTimeRatio:直接设置吞吐量大小,假设设为19,则允许的最大GC时间占总时间的1/(1+19),默认值为99,即1/(1+99)
-XX:MaxGCPauseMillis:最大GC停顿时间,该参数并非越小越好
-XX:+UseAdaptiveSizePolicy:开启该参数,-Xmn/-XX:SurvivorRatio/-XX:PretenureSizeThreshold这些参数就不起作用了,虚拟机会自动收集监控信息,动态调整这些参数以提供最合适的的停顿时间或者最大的吞吐量(GC自适应调节策略),而我们需要设置的就是-Xmx,-XX:+UseParallelOldGC或-XX:GCTimeRatio两个参数就好(当然-Xms也指定上与-Xmx相同就好)
注意:
-XX:GCTimeRatio和-XX:MaxGCPauseMillis设置一个就好
不开启-XX:+UseAdaptiveSizePolicy,-Xmn/-XX:SurvivorRatio/-XX:PretenureSizeThreshold这些参数依旧可以配置,以resin服务器为例
-Xms2048m -Xmx2048m -Xmn512m -Xss1m -XX:PermSize=256M -XX:MaxPermSize=256M -XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=15 -XX:+UseParallelOldGC -XX:GCTimeRatio=19 -XX:+PrintGCDetails -XX:+PrintGCTimeStamps View Code
适用场合:
很多的CPU计算任务而用户交互任务较少的情况不想自己去过多的关注GC参数,想让虚拟机自己进行调优工作
吞吐量优先和响应优先的垃圾收集器如何选择?
(1) 吞吐量优先的并行收集器
参数配置:
1, -Xmx4g -Xms4g -Xmn2g -Xss200k -XX:+UseParallelGC -XX:ParallelGCThreads=8
说明:选择Parallel Scavenge收集器,然后配置多少个线程进行回收,最好与处理器数目相等。
2,-Xmx4g -Xms4g -Xmn2g -Xss200k -XX:+UseParallelGC -XX:ParallelGCThreads=8 -XX:+UseParallelOldGC
说明:配置老年代使用Parallel Old
3,-Xmx4g -Xms4g -Xmn2g -Xss200k -XX:+UseParallelGC -XX:MaxGCPauseMills=100
说明:设置每次年轻代垃圾回收的最长时间。如何不能满足,那么就会调整年轻代大小,满足这个设置
4,-Xmx4g -Xms4g -Xmn2g -Xss200k -XX:+UseParallelGC -XX:MaxGCPauseMills=100 -XX:+UseAdaptiveSizePolicy
说明:并行收集器会自动选择年轻代区大小和Survivor区的比例。
(2)响应时间优先的并发收集器
1, -Xmx4g -Xms4g -Xmn2g -Xss200k -XX:+UseConcMarkSweepGC -XX:+UseParNewGC
说明:设置老年代的收集器是CMS,年轻代是ParNew
2,-Xmx4g -Xms4g -Xmn2g -Xss200k -XX:+UseConcMarkSweepGC -XX:CMSFullGCsBeforeCompaction=5 -XX:+UseCMSCompactAtFullCollection
说明:首先设置运行多少次GC后对内存空间进行压缩,整理。同时打开对年老代的压缩(会影响性能)
spark面试顺便问道,Spark Streaming应该选择何种垃圾收集器?
--driver-java-options和 spark.executor.extraJavaOptions这两个参数将driver和Executor垃圾回收器设置为cms,以提高响应速度。
8.1 新对象预留新生代
由于fullGC(老年代)的成本远比minorGC(新生代和老年代)的成本大,所以给应用分配一个合理的新生代空间,尽量将对象分配到新生代减小fullGC的频率
8.2 大对象进入老年代
将大对象直接分配到老年代,保持新生代对象的结构的完整性,以提高GC效率, 以通过-XX:PretenureSizeThreshold设置进入老年代的阀值
8.3 稳定与震荡的堆大小
稳定的对大小是对垃圾回收有利的,方法将-Xms和-Xmx的大小一致
8.4 吞吐量优先
尽可能减少系统执行垃圾回收的总时间,故采用并行垃圾回收器
-XX:+UseParallelGC或使用-XX:+UseParallelOldGC
8.5 降低停顿
使用CMS回收器,同时减少fullGC的次数
九、获取gc信息的方法
9.1 -verbose:gc或者-XX:+PrintGC 获取gc信息
9.2 -XX:+PrintGCDetails 获取更加详细的gc信息
9.3 -XX:+PrintGCTimeStamps 获取GC的频率和间隔
9.4 -XX:+PrintHeapAtGC 获取堆的使用情况
9.5 -Xloggc:D:\gc.log 指定日志情况的保存路径
十、jvm调优实战-tomcat启动加速
在tomcat的bin/catalina.bat文件的开头添加相关的配置
六:监控工具
监控工具:一般问题定位,性能调优都会使用到。
(一)、jps
Jps是参照Unix系统的取名规则命名的,而他的功能和ps的功能类似,可以列举正在运行的饿虚拟机进程并显示虚拟机执行的主类以及这些进程的唯一ID(LVMID,对应本机来说和PID相同),他的用法如下:
Jps [option] [hostid]
jps -q 只输出LVMID
jps -m 输出JVM启动时传给主类的方法
jps -l 输出主类的全名,如果是Jar则输出jar的路径
jps -v 输出JVM的启动参数
(二)、jstat
jstat主要用于监控虚拟机的各种运行状态信息,如类的装载、内存、垃圾回收、JIT编译器等,在没有GUI的服务器上,这款工具是首选的一款监控工具。其用法如下:
jstat [option vmid [interval [s|ms] [vount] ] ]
jstat 监控内容 线程好 刷新时间间隔 次数
jstat –gc 20445 1 20 :监视Java堆,包含eden、2个survivor区、old区和永久带区域的容量、已用空间、GC时间合计等信息
jstat –gcutil 20445 1 20:监视内容与-gc相同,但输出主要关注已使用空间占总空间的百分比
jstat –class 20445 1 20:监视类的装载、卸载数量以及类的装载总空间和耗费时间等
.......-gccapcity......:监视内容与-gc相同,但输出主要关注Java区域用到的最大和最小空间
.......-gccause........:与-gcutil输出信息相同,额外输出导致上次GC产生的原因
.......-gcnew..........:监控新生代的GC情况
.......-gcnewcapacity..:与-gcnew监控信息相同,输出主要关注使用到的最大和最小空间
.......-gcold..........:监控老生代的GC情况
.......-gcoldcapacity..:与-gcold监控信息相同,输出主要关注使用到的最大和最小空间
.......-gcpermcapacity.:输出永久带用到的最大和最小空间
.......-compiler.......:输出JIT编译器编译过的方法、耗时信息
.......-printcompilation:输出已经被JIT编译的方法
(三)、jinfo
jinfo的作用是实时查看虚拟机的各项参数信息jps –v可以查看虚拟机在启动时被显式指定的参数信息,但是如果你想知道默认的一些参数信息呢?除了去查询对应的资料以外,jinfo就显得很重要了。jinfo的用法如下:
Jinfo [option] pid
(四)、jmap
map用于生成堆快照(heapdump)。当然我们有很多方法可以取到对应的dump信息,如我们通过JVM启动时加入启动参数 –XX:HeapDumpOnOutOfMemoryError参数,可以让JVM在出现内存溢出错误的时候自动生成dump文件,亦可以通过-XX:HeapDumpOnCtrlBreak参数,在运行时使用ctrl+break按键生成dump文件,当然我们也可以使用kill -3 pid的方式去恐吓JVM生成dump文件。Jmap的作用不仅仅是为了获取dump文件,还可以用于查询finalize执行队列、Java堆和永久带的详细信息,如空间使用率、垃圾回收器等。其运行格式如下:
Jmap [option] vmip
监控堆栈信息主要用来定位问题的原因,生成堆栈快照
.......-dump......:生成对应的dump信息,用法为-dump:[live,]format=b,file={fileName}
.......-finalizerinfo......:显示在F-Queue中等待的Finalizer方法的对象(只在linux下生效)
.......-heap......:显示堆的详细信息、垃圾回收器信息、参数配置、分代详情等
.......-histo......:显示堆栈中的对象的统计信息,包含类、实例数量和合计容量
.......-permstat......:以ClassLoder为统计口径显示永久带的内存状态
.......-F......:虚拟机对-dump无响应时可使用这个选项强制生成dump快照
例子:jmap -dump:format=b,file=yhj.dump 20445
(五)、jstack
Jstack用于JVM当前时刻的线程快照,又称threaddump文件,它是JVM当前每一条线程正在执行的堆栈信息的集合。生成线程快照的主要目的是为了定位线程出现长时间停顿的原因,如线程死锁、死循环、请求外部时长过长导致线程停顿的原因。通过jstack我们就可以知道哪些进程在后台做些什么?在等待什么资源等!其运行格式如下:
Jstack [option] vmid
-F 当正常输出的请求不响应时强制输出线程堆栈
-l 除堆栈信息外,显示关于锁的附加信息
-m 显示native方法的堆栈信息
(六)、jconsole
在JDK的bin目录下,监控内存,thread,堆栈等
(七)、jprofile
类似于jconsole,比jconsole监控信息更全面,内存,线程,包,cup 类,堆栈,等等
由于对象进行了分代处理,因此垃圾回收区域、时间也不一样。GC有两种类型:Minor GC和Full GC。
一般情况下,当新对象生成,并且在Eden申请空间失败时,就会触发Minor GC,对Eden区域进行GC,清除非存活对象,并且把尚且存活的对象移动到Survivor区。然后整理Survivor的两个区。这种方式的GC是对年轻代的Eden区进行,不会影响到年老代。因为大部分对象都是从Eden区开始的,同时Eden区不会分配的很大,所以Eden区的GC会频繁进行。因而,一般在这里需要使用速度快、效率高的算法,使Eden去能尽快空闲出来。
对整个堆进行整理,包括Young、Tenured和Perm。Full GC因为需要对整个堆进行回收,所以比Minor GC要慢,因此应该尽可能减少Full GC的次数。在对JVM调优的过程中,很大一部分工作就是对于Full GC的调节。有如下原因可能导致Full GC:
(1)调用System.gc时,系统建议执行Full GC,但是不必然执行
(2)老年代空间不足
(3)方法区空间不足
(4)通过Minor GC后进入老年代的平均大小大于老年代的可用内存
(5)由Eden区、From Space区向To Space区复制时,对象大小大于To Space可用内存,则把该对象转存到老年代,且老年代的可用内存小于该对象大小