JVM 第3章垃圾收集器与内存分配策略

1 概述

程序计数器、虚拟机栈、本地方法栈这3个区域随线程而生,随线程而灭。每个栈帧中分配多少内存基本是在类结构确定下来时就已知的,因此这几个区域的内存分配和回收都具备确定性。而java堆和方法区是线程共享的内存,且一个接口中的多个实现类需要的内存可能不一样,一个方法中的多个分支需要的内存也可能不一样,只有在程序运行期间才能知道会创建哪些对象,这部分内存的分配和回收都是动态的,垃圾收集器关注的是这部分内存。

2 判断对象是否“存活”的方法:

2.1 引用计数法

定义:给对象添加一个引用计数器,每当有一个地方引用它,计数器就加1;当引用失效时,计数器就减1;计数器为0的对象是不可能再被使用的。

特点:实现简单,判定效率高。但java虚拟机没用它,原因是它很难解决对象之间相互循环引用的问题。
如:

objA.instance=objB.instance;
objB.instance=objA.instance;
objA=null;
objB=null;

因为objA和objB互相引用,所以它们的引用计数器永远不会为0,导致无法通知GC收集器回收它们。

2.2 可达性分析算法

定义:用在Java,C#,Lisp中。通过称为“GC Roots”的对象作为起始点,从这些节点向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有引用链相连时,该对象就不可用。

可作为GC Roots的对象包括:

  • 虚拟机栈(栈帧中的本地变量表)中引用的对象
  • 方法区中静态属性引用的对象
  • 方法区中常量引用的对象
  • 本地方法栈中JNI(Native方法)引用的对象

2.3 引用的分类

定义:reference类型的数据中存储的数值代表的是另外一块内存的起始地址,就称这块内存代表着一个引用。
引用的类型:

  • 强引用:类似“Object obj=new Object()”这类的引用,只要强引用还在,垃圾收集器不会回收被引用的对象。
  • 软引用:用于描述一些有用但非必要的对象。在系统发生内存溢出异常之前,将把软引用关联着的对象列进回收范围之中进行第二次回收。如果这次回收还没有足够的内存,将抛出内存溢出异常。用SoftReference类实现软引用。
  • 弱引用:用于描述非必须的对象。被弱引用关联的对象只能生存到下一次垃圾收集发生之前。当垃圾收集器工作时,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。用WeakReference类实现弱引用。
  • 虚引用:也称幽灵引用或幻影引用,是最弱的引用关系。不对对象的生存时间构成影响,也无法通过虚引用取得一个对象实例。一个对象设置虚引用关联的唯一目的是这个对象被收集器回收时收到一个系统通知。用PhantomReference类实现虚引用。

3.4 生存还是死亡

即使可达性算法中不可达的对象,也并非非死不可,这时处于缓刑阶段,要真正回收至少需要经历2次标记:
1)如果对象进行可达性分析后,发现没有与GC Roots相连接的引用链,这时进行第一次标记并且进行一次筛选,刷选条件是此对象是否有必要执行finalize()方法。当对象没有覆盖该方法,或者该方法已经被虚拟机调用过,虚拟机将2种情况视为“没必要执行”。
如果这个对象被判定为有必要执行finalize()方法,那么这个对象将会放在F-Queue队列中,并稍后由虚拟机自动建立的、低优先级的Finalizer线程去执行它。
稍后GC将对F-Queue中的对象进行第二次标记,若对象要在finalize()中拯救自己–只要重新与引用链上的任何一个对象建立关联即可,如把把自己(this关键字)赋值给某个类变量或者对象的成员变量,那么第二次标记时它将被移除“即将回收”的集合;如果这时候没有拯救自己,那就真的被回收了。
注意:每个对象的finalize()方法只会被执行一次,如果拯救过一次,下次进行GC时,将不会再次执行finalize()进行自我拯救了。另外,finalize()运行代价高昂,不确定性大,无法保证各个对象的调用顺序,所以要避免使用。

3.5 回收方法区

java虚拟机规范中指出不要求在方法区(HotSpot虚拟机中的永久代)进行垃圾收集,因为在方法区的垃圾收集性价比比较低:在堆中,尤其新生代,垃圾收集可回收70%~95%的空间,而永久代的垃圾收集效率远低于此。

方法区(永久代)回收对象有2类:废弃常量和无用的类。
1)废弃常量的判定:以常量池中的“abc”为例,没有String对象引用它,也没有其它地方引用它,发生GC时,“abc”常量会被清理。常量池中的其它他类(接口)、方法、字段的符号引用也与此类似。
2)“无用的类”的判定,同时满足3个条件:

  • 该类的实例都被回收,即java堆中不存在该类的任何实例
  • 加载该类的ClassLoader已经被回收
  • 该类对应的java.lang.Class对象没有在任何地方被引用,无法通过反射访问该类的方法

注:无效的类不是必须要回收的。在大量使用反射、动态代理和频繁自定义ClassLoader的场景都需要虚拟机具备类卸载的功能,以保证永久代不会溢出。

4 垃圾收集方法

4.1 标记–清除算法(老年代)

分2个阶段:
1)首先标记出需要收回的对象
2)标记完成后统一回收被标记的对象(前面讲过标记流程了)
在这里插入图片描述
不足:
1)标记和清除2个过程的效率不高
2)标记清除后产生大量内存碎片,可能无法为大对象分配空间时,找到足够的连续内存而提前触发另一次GC动作。

4.2 复制算法(新生代)

将内存按容量分为大小相同的2块,每次只用其中一块,当这块内存用完了,就将还存活的对象复制到另外一块上面,然后把已使用过的内存空间清理掉,这样使得每次清理都是对整个半区进行内存回收。
在这里插入图片描述
优点:没有碎片,实现简单,运行高效。
缺点:代价是将内存缩小为了原来的一半。

新生代中98%的对象是“朝生夕死”的,所以不需要按1:1划分内存,而是将内存划分为Eden和2个Survivor,其中Eden:Survivor=8:1。每次使用一个Eden和一个Survivor,回收时,将Eden和Survivor上存活的对象复制到另一个Survivor上,这样每次新生代中可用的内存空间为整个新生代容量的90%,只有10%的内存被浪费。当另一个Survivor空间没有足够空间存放上一次新生代存活下来的对象时,这些对象将直接通过分配担保机制进入老年代。

4.3 标记–整理算法(老年代)

复制收集算法在对象存活率高时要进行较多的复制操作,效率变低。根据老年代的特点,产生了“标记–整理”算法,标记过程与“标记–清除”一样,但后续不是直接清除可回收对象,而是让所有存活对象移动到一端,直接清理掉端边界以外的内存。
在这里插入图片描述

4.4 分代收集算法

当前商业虚拟机的垃圾收集都采用“分代收集”算法,根据对象存活周期的不同将内存划分位:新生代和老年代。新生代采用复制算法,只需要复制少量的存活对象就可完成收集;老年代中对象存活率高、没有额外空间对其担保,所以采用标记--整理算法或者标记--清除算法进行回收。

5 HotSpot的算法实现

5.1 枚举根节点(查找根节点GCRoot)

1)停止所有的java执行线程(“stop the world”)
可达性分析必须在一致性的快照中进行,一致性指的是不可以出现分析过程中对象引用关系还在不断变化的情况。这点是导致GC进行时必须停顿所有java线程的一个原因。
2)准确式GC:当系统停下来时,不需要一个不漏的检查完所有执行上下文和全局的引用位置。HotSpot的实现是:在特定位置上(即安全点),虚拟机通过OopMap数据结构在类加载时,将对象内什么偏移量上是什么类型的数据计算出来,并存储到其中,来达到这个目的。在OopMap的协助下,HotSpot可以快速且准确的完成GCRoots的枚举。

5.2 安全点 Safepoint

安全点:程序执行时只会在安全点发生GC。

安全点选定的依据:是否具有让程序长时间执行的特征。“长时间执行”的明显特征是指令序列复用,例如:方法调用、循环跳转、异常跳转等。

在GC发生时,如何让所有的线程(不包括执行JNI调用的线程)都跑到最近的安全点上在停顿下来,2中方案:
1)抢先式中断:不需要线程的执行代码主动去配合,在GC发生时,首先中断所有的线程,如果发现线程没有中断在安全点上,就恢复线程,让它跑到安全点上。现在几乎没有虚拟机实现采用抢先式中断来暂停线程从而响应GC事件。
2)主动式中断:不直接对线程操作,简单的设置一个标志,各个线程执行时主动去轮询这个标志,发现中断标志为真时就自己中断挂起。轮询标志的地方和安全点是重合的,另外在加上创建对象需要分配内存的地方。

5.3 安全区域

作用:Safepoint机制保证了程序执行时,在不太长的时间内就会遇到可进入GC的Safepoint。但是如果程序“不执行”的时候呢?即没有分配CPU时(线程Sleep状态或Blocked状态),需要安全区域来解决。

定义:安全区域是指在一段代码片段之中,引用关系不会发生变化。在这个区域的任何地方开始GC都是安全的,可以把Safe Region看成是被扩展了的Safepoint。线程执行到Safe Region中的代码时,首先标识自己进入了Safe Region,JVM发起GC时,就不用管状态为Safe Region的线程了。线程要离开Safe Region时,它要检查系统是否完成了根节点枚举(或者整个GC过程),如果完成了,那线程就继续执行,否则就必须等待直到收到可以安全离开Safe Region的信号为止。

6 垃圾收集器(共7个)

如果说收集算法是内存回收的方法论,那么垃圾收集器就是内存回收的具体实现。图中,如果两个收集器之间存在连线,就说明它们可以搭配使用。

注:掌握内容有:特点、适用对象,其次终点掌握CMS和G1。
在这里插入图片描述

6.1 Serial收集器(新生代,复制算法)

■介绍:它是单线程的收集器,只会使用一个CPU或一条收集线程去完成垃圾收集工作,并且进行垃圾收集时,必须暂停其它所有的工作线程直到收集结束

■缺点:stop the world给用户带来的体验很差,因为垃圾收集的时候,用户线程可能停顿。

■优点:简单而高效(与其他收集器的单线程比),对于限定单个CPU的环境来说,Serial收集器由于没有线程交互的开销,专心做垃圾收集可以获得最高的单线程收集效率。

■应用场景:Client模式下的虚拟机

6.2 ParNew收集器(新生代,复制算法)

■介绍:ParNew收集器是Serial的多线程版本,除了使用多线程进行垃圾收集之外,其余行为包括Serial收集器可用所有控制参数(如:-XX:SurvivorRatio, -XX:PretenureSizeThreshold, -XX:HandlePromotionFailure等)、收集算法、stop the world、对象分配规则、回收策略等都与Serial收集器完全一样。

■应用场景:Server模式下的虚拟机除了Serial收集器外,只有它能与CMS收集器(老年代)配合工作。ParNew收集器是使用-XX:+UseConcMarkSweepGC选项后的默认新生代收集器,也可以使用-XX:+UseParNewGC选项来强制指定他。

ParNew收集器在单线程CPU的环境中不会比Serial收集器的效果好,当然随着CPU的数量的增加,它对于GC时系统资源的有效利用还是有好处的。它默认开启的收集线程数与CPU的数量相同,在CPU非常多的环境下,使用-XX:ParallelGCThreads参数来限制垃圾收集的线程数。

注意:并行和并发的区别
1)并行(Parallel):多条垃圾收集线程并行工作,但此时用户线程仍然处于等待状态。
2)并发(Concurrent):指用户线程和垃圾收集线程同时执行(但不一定是并行,可能交替执行),用户程序在继续运行,而垃圾收集程序运行在另一个CPU上。

6.2 Parallel Scavenge收集器(新生代,复制算法)

■介绍:吞吐量优先收集器是一个新生代收集器,采用复制算法,是一个并行的多线程收集器(多个线程一起回收垃圾,这时没有线程执行用于代码)Parallel Scavenge收集器的目标是达到可控制的吞吐量(CPU运行用户代码的时间/(用户代码的时间+垃圾收集的时间))。

■应用场景:高吞吐量可以高效利用CPU时间,尽快完成程序的运算,适合在后台运算而不需要太多交互的任务。

■控制吞吐量的2个参数:
1)最大垃圾收集停顿时间-XX:MaxGCPauseMillis:GC停顿时间缩短是以牺牲吞吐量和新生代空间来换取的
2)吞吐量大小-XX:GCTimeRatio:垃圾收集时间/总时间,即吞吐量的倒数。

■GC自使用调节参数:开关-XX:+UseAdaptiveSizePolicy
1)打开时,只要把基本的内存数据设置好(如:-Xmx设置最大堆),然后使用MaxGCPauseMillis(更关注最大停顿时间)或者GCTimeRatio(更关注吞吐量)参数给虚拟机设立一个优化目标,虚拟机会动态调整具体细节参数(新生代大小(-Xmn)、Eden与Survivor区的比例(-XX:SurvivorRatio)、晋升老年代对象大小(-XX:PretenureSizeThreshold)),这种调节方式称为GC自适应的调节策略

■Parallel Scavenge收集器与ParNew收集器区别:
1)关注点不同
2)Parallel Scavenge收集器有自适应调节策略

6.4 Serial Old收集器(老年代)

■介绍:Serial Old收集器是Serial收集器的老年代版本,是一个单线程收集器,使用“标记--整理”算法。

■场景:主要意义在于:
1)给Client模式下的虚拟机使用
2)在Server模式下,2大用途:
a. 在JDK1.5以及之前版本中与ParallelScavenge收集器搭配使用
b. 作为CMS收集器的后备预案,在并发收集发生Concurrent Mode Failure时使用

6.5 Parallel Old收集器(老年代)

■介绍:是Parallel Scavenge收集器的老年代版本,使用多线程“标记--整理”算法(多个线程回收垃圾,没有线程执行用于代码)。

■使用场景:和Parallel Scavenge收集器组合适用注重吞吐量和CPU资源敏感的场合。

6.6 CMS收集器(老年代)

■CMS(Concurrent Mark Sweep)收集的目标是获取最短回收停顿时间

■应用场景:java应用集中在互联网站或者B/S系统的服务端上,这类应用尤其注重服务响应速度,希望停顿时间最短

■采用标记–清除算法,整个过程分4个步骤:
1)初始标记:仅仅标记一下GC Roots能直接关联到的对象,速度很快。(单线程)
2)并发标记:进行GC Roots可达性分析()
3)重新标记:修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录。
4)并发清除:停顿时间比初始标记稍长,但远比并发标记上的时间短。

注:初始标记和重新标记仍需“stop the world”。耗时最长的并发标记和并发清除过程,收集器线程与用户线程一起工作,所以总体来看,CMS收集器的内存回收过程是与用户线程并发执行的。

■CMS(并发低停顿收集器)缺点:
1)CMS收集器对CPU资源非常敏感(并发设计的程序都对CPU资源敏感)。在并发阶段,不会导致用户线程停顿,但是会因占用一部分线程而导致程序变慢,总吞吐量降低。CMS默认启动的回收线程数是(CPU数量+3)/4,即CPU在4个以上时,并发回收垃圾收集线程不少于25%【(4+3)/4=1;1/4=0.25】的CPU资源。当CPU数量不足4个时,CMS对用户程序的影响会变得很大,所以虚拟机提供了“增量式并发收集器 i-CMS”CMS的变种,就是在并发标记、清理的时候让GC线程、用户线程交替运行,尽量减少GC线程的独占资源的时间,这样整个垃圾收集时间会更长,但对用户的影响会变小。然而 i-CMS的效果很一般,已经被声明“deprecated”,不提倡用户使用。
2)CMS无法收集浮动垃圾,可能出现“Concurrent Mode Failure”失败而导致另一次Full GC的产生。 并发清理阶段,因与用户线程并行,产生的新的垃圾出,这部分垃圾就是浮动垃圾。由于垃圾收集阶段用户线程在运行,就需要预留足够的内存给用户线程使用,因此CMS不能像其他收集器那样等到老年代几乎填满了在进行收集。JDK1.5默认CMS收集器当老年代使用了68%就会被激活,如果老年代增长不是太快,可调高-XX:CMSInitiatingOccupanyFraction参数来提高出发百分比,以降低回收次数而获得好的性能,JDK1.6中,CMS阈值是92%,要是CMS预留的内存无法满足程序需求,会出现“Concurrent Mode Failure”失败,这时虚拟机启动后备方案:临时启用Serial Old收集器重新进行老年代的垃圾收集,这样停顿时间就很长了。所以-XX:CMSInitiatingOccupanyFraction参数设的太高容易导致大量“Concurrent Mode Failure”失败,性能反而降低。
3)CMS是基于“标记--清除”算法实现的收集器,收集结束的时候会有大量空间碎片产生,可能无法给大对象找到足够的、连续的内存,不得不提前触发Full GC。CMS提供了-::UseCMSCompactAtFullCollection开关参数(默认开启),开启碎片的合并整理过程,内存整理是无法并发的空间碎片问题没了,但是停顿时间变长了。虚拟机提供了-xx:CMSFullGCcBeforeCompaction参数用于设置执行多少次不压缩的Full GC后,跟着来一次带压缩的(默认值为0,即每次Full GC时都会进行碎片整理)。

6.7 G1收集器(新生代+老年代)

◆工作原理:多线程标记-整理算法

◆适用对象:面向服务端应用
◆历史地位:当今收集器技术发展的最前沿成果之一

◆4个特点:

a、并行与并发能充分利用多CPU、多核环境下的硬件优势,使用多个cpu来缩短stop-the-world停顿时间。部分其他收集器需要停顿java线程的操作,G1收集器可以通过并发方式让java程序继续执行。

b、分代收集可以独立管理整个GC堆。同时根据不同对象(新创建的对象、已经存活一段时间的对象、熬过多次GC的旧对象)使用不同处理方式。

c、空间整合:整体看,基于“标记-整理”算法;局部看,基于“复制”算法。不会产生碎片。

d、可预测停顿时间降低停顿时间是CMS和G1共同的关注点,但G1除了追求低停顿,还能预测停顿时间,让使用者明确指定在长度为M毫秒的时间片段内,消耗在垃圾收集上的时间不超过N毫秒。原理:

注1:可预测实质:有计划的避免在整个java堆进行全区域的垃圾收集。而G1之前的收集器的收集范围都是对整个新生代和整个老年代

注2:可预测原理:后台维护一个优先列表,列表里面存放了G1跟踪各个Region里面的垃圾堆积的价值的大小。每次回收,根据优先列表,优先回收价值最大的Region。这保证了G1在有限时间内尽可能高的收集效率

◆内存布局:采用G1管理内存时,内存被分为很多个大小相等的独立区域(Region)。新生代、老年代不再是物理隔离,它们是一部分Region的集合。

注1: Remembered Set用于解决问题:Region之间的对象引用;其他收集器的新生代和老生代之间的对象引用。

注2:Remembered Set工作机制:G1中每个Region都维护一个RememberedSet,虚拟机发现程序在对Reference类型的数据进行写操作时,会产生一个Write Barrier暂时中断写操作,检查Reference引用的对象是否处于不同的Region之中。如果是,便通过CardTable把相关引用信息记录到被引用对象所属的Region的Remembered Set中。当进行垃圾收集时,在GC根节点的枚举范围中加入Remembered Set即可保证不对全堆扫描也不会有遗漏。

和CMS对比:都立足于低停顿。 G1虽然不错,但距离成熟发布版本是时间比较短,经历的实际应用的考验比较少。所以在现有的收集器没有出现什么问题的情况下,没有理由选择G1。如果追求低停顿可选择CMS,如果追求高吞吐量G1不会带来好的表现。

◆运作步骤:
1)初始标记停顿程序单线程标记耗时很短):只是标记GC Roots直接关联到的对象,且修改(next top at mark start)值,让下一阶段用户程序并发运行时,能在可用的region中创建对象。
2)并发标记(与用户程序并发执行):从GC Roots对堆中对象进行可达性分析,找出存活对象,耗时较长,与程序并发进行。
3)最终标记(停顿程序,并行执行):虚拟机把并发标记期间因用户程序继续执行而导致标记产生变动的标记记录记录在Remembered Set Logs中,此阶段把logs中数据合并到Remembered Set中。
4)筛选回收(与用户程序并发执行,并行执行):先对各个region的回收价值和成本排序在根据用户期望的GC停顿时间制定回收计划

7 GC日志格式

注:每种收集器的日志格式都有它们自身去实现,即可以不一样。但为方便用户阅读,虚拟机的设计者将各个收集器的日志都维持了一定的共性。下面对一个典型的例子说明一下各个部分的含义。
实例:
33.125: [GC [DefNew: 3324K->152K(3712K), 0.0025925 secs] 3324K->152K(11904K), 0.0031680 secs]
解释:
33.125:代表了GC发生的时间,这个数字的含义是从JVM启动以来经过的秒数。
[GC和[Full GC说明了垃圾回收的类型。如是Full GC时,表示这次GC发生了“Stop the World”(因为出现了分配担保失败之类的问题,所以才导致STW)。System.gc()方法出发的收集将显示[Full GC(System)。
[DefNew:表示GC发生的区域(新生代、老年代),区域名称与使用的GC收集器密切相关。
Serial收集器,新生代名:Default New Generation,区域名:[DefNew
ParNew收集器,新生代名为:Parallel New Genetaion,区域名:[ParNew
Parallel Scavenge收集器,新生代名为:PSYoungGen
老年代和永久代同理,名称由收集器决定。
3324K->152K(3712K)表示GC前该内存区域内已使用容量->GC后该内存区域已使用容量(该区域总容量)。0.0025925 secs表示该区域GC的耗时,有的收集器会给出详细的耗时,如:[Times: User=0.01 sys=0.00, real=0.02secs],这里user表示用户态消耗CPU时间,sys表示内核态消耗的CPU事件,real表示操作从开始到结束经过的墙钟时间。CPU时间和墙钟时间的区别是:墙钟时间包括各种非计算的等待耗时(等待磁盘I/O、等到线程阻塞),而CPU时间不包括这些耗时。当系统有多CPU或多核的话,多线程会叠加这些CPU时间。

3324K->152K(11904K)表示GC前java堆已使用容量->GC后java堆已使用容量(java堆总容量)。

8 内存分配与回收策略

java技术体系的自动内存管理可以归结为自动化解决了2问题:
1)给对象分配内存
2)回收分配给对象的内存
对象的内存分配,是在堆上分配(也可能经过JIT编译器编译后,被拆分为标量类型,从而分配在栈上)。

对象主要分配在新生代的Eden区,如果启动了本地线程分配缓冲,将按线程优先在TLAB上分配,细节取决于用哪种垃圾收集器组合和虚拟机中与内存相关的参数。

★对象分配内存的策略

1、对象优先在Eden分配

★对象优先分配在Eden,如果Eden内存不足,将触发一次Minor GC。

★区分新生代GC(MinorGC) && 老年代GC(Major GC/Full GC)

  • Minor GC:指发生在新生代的GC。因为java对象基本都是朝生夕灭,故Minor GC非常频繁速度也比较快
  • Major GC:指发生在老年代的GC。出现Major GC,通常伴随一次Minor GC,但非绝对。速度比Minor GC慢10倍以上。

2、大对象直接进入老年代

★大对象定义:需要大量连续内存的java对象(如:new byte[2*1024])。最典型的就是很长的字符串或者数组

★对象直接进老年代的原因:经常出现大对象容易导致内存还有不少空间时就提前触发GC以获取更大的连续空间来存储新对象

★参数:-XX:PretenureSizeThreshold参数。当对象所需内存大小大于这个参数值时,对象直接在老年代分配内存。

★目的:避免在Eden区及两个Suivivor区之间发生大量的内存复制(新生代采用复制算法收集内存)。

3、长期存活的对象将进入老年代

★JVM为每个对象都设置一个对象年龄计数器

年龄增长机制:如果对象在Eden区出生并经过第一次GC后仍存活并且能被Survivor容纳,将被移动到Survivor空间,并且年龄设置为1。以后在Survivor每经历过一次Minor GC,年龄就增长1岁,到15岁后(默认),将会被晋升为老年代。可以通过-XX:MaxTenuringThreshold参数设置年龄阈值。

4、动态对象年龄判定

★目的:为更好的适应不同程序的内存状况,JVM并不是用要求对象的年龄达到年龄阈值后才能进入老年代。

★实现原理:如果在Survivor区中,相同年龄的所有对象大小总和大于其空间大小的一半则年龄大于或等于该年龄的对象可以直接进入老年代。

5、空间分配担保

★工作机制:在发生MinorGC之前,JVM会检查老年代最大可用的连续空间是否大于新生代对象总空间
1)如果大于,则此次GC是安全的;
2)如果小于,JVM会查看HandlePromotionFailure设置值是否允许失败

  • 如果允许担保失败:JVM会继续检查老年代最大连续内存大小是否大于历次晋升到老年代对象的平均大小
    • 如果大于,就尝试进行一次Minor GC,虽然这样做是有风险的。
    • 如果小于,就进行一次Full GC(Major GC)。
  • 不允许担保失败:就进行一次Full GC(Major GC)。

★冒险
1)实质:老年代接纳Survivor容纳不下的存活下来的新生对象。
2)为什么说是冒险:因为新生代存活下来,进行Minor GC时,是将其从Eden区复制到Survivor区。考虑极端情况,当Eden区所有对象都存活下来了,那么就需要将所有的对象复制到未被使用的那个Survivor区。显然此时Survivor是装不下这么多对象的。此时装不下的对象就要被放到老年代当中。也就是老年代为其做了担保,但是老年代剩余的空间不一定能容纳存活下来的对象,所以Minor GC不一定成功,即有风险。

注:JDK6后,HandlePromotionFailure参数就不起作用了,判断的规则和HandlePromotionFailure开启是的规则是一样的。


  • 年轻代(堆的一部分)

    • minor gc:复制算法、并行收集器
    • eden、from survivor、to survivor
    • 在to survivor活过15岁时,进入老年代
    • 大对象进入老年代
    • 动态年龄规划
    • -Xms 初始堆大小。如:-Xms256m
    • -Xmx 最大堆大小。如:-Xmx512m
    • -Xmn 新生代大小。通常为 Xmx 的 1/3 或 1/4。新生代 = Eden + 2 个 Survivor 空间。实际可用空间为 = Eden + 1 个 Survivor,即 90%
    • -XX:NewRatio 新生代与老年代的比例,如 –XX:NewRatio=2,则新生代占整个堆空间的1/3,老年代占2/3
    • -XX:SurvivorRatio 新生代中 Eden 与 Survivor 的比值。默认值为 8。即 Eden 占新生代空间的 8/10,另外两个 Survivor 各占 1/10
  • 老年代(堆的一部分)

    • major gc(full gc):标记–清除算法(产生碎片,容易出现为较大对象分配内存时,提前gc)、标记–整理算法
    • 几乎每个对象都是在Survivor 区域中熬过来的
    • full gc的频率低于minor gc的频率,full gc用时更长
  • jdk 1.7 方法区(永久代)

    • 静态文件(java类、方法)
    • 对垃圾回收没有显著影响,但是有些应用可能动态生成或者调用一些class
    • -XX:PermSize 永久代(方法区)的初始大小
    • -XX:MaxPermSize 永久代(方法区)的最大值
  • jdk 1.8 元数据区

    • 1.8把1.7中的方法区替换成了元数据区
    • -XX:MetaspaceSize 当到达 XX:MetaspaceSize 所指定的阈值后会开始进行清理该区域
    • -XX:MaxMetaspaceSize
    • 如果本地空间的内存用尽了会收到java.lang.OutOfMemoryError: Metadata space 的错误信息
  • -Xss JDK1.5+ 每个线程堆栈大小为 1M,一般来说如果栈不是很深的话, 1M 是绝对够用了的。

  • -XX:+PrintGCDetails 打印 GC 信息

  • -XX:+HeapDumpOnOutOfMemoryError 让虚拟机在发生内存溢出时 Dump 出当前的内存堆转储快照,以便分析用

ps:终于写完了,天啊,真心累啊~~~喜欢的话,点个赞呗O(∩_∩)O哈哈~

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值