JVM堆内存与垃圾收集器

3.1 谈谈Java中不同的引用类型?

Java里有不同的引用类型,分别是强引用、软引用、弱引用和虚引用;

强引用:Object object = new Object();即使内存溢出了也不会回收这个对象

软引用: SoftReference内存充足时不回收,内存不足时则回收;

弱引用: WeakReference 不管内存是否充足,只要GC一运行就会回收该引用对象;

虚引用: PhantomReference这个其实暂时忽略也行,因为很少用,它形同虚设,就像没有引用一样,其作用就是该引用对象被GC回收时候触发一个系统通知,或者触发进一步的处理;

3.2 JVM堆内存分代模型?

JVM堆内存的分代模型:年轻代、老年代;

大部分对象朝生夕死,少数对象长期存活;

 见图

3.3请介绍一下JVM堆中新生代的垃圾回收过程?

JVM里垃圾回收针对的是新生代,老年代,还有元空间/方法区(永久代)

不会针对方法的栈帧进行回收,方法一旦执行完毕,栈帧出栈,里面的局部变量直接就从内存里清理掉,也就是虚拟机栈不存在垃圾回收;

代码里创建出来的对象,一般就是两种:

1、一种是短期存活的,分配在Java堆内存之后,迅速使用完就会被垃圾回收;

2、一种是长期存活的,需要一直生存在Java堆内存里,让程序后续不停的去使用;

第一种短期存活的对象,是在Java堆内存的新生代里分配;

第二种长期存活的对象,通过在新生代S0区和S1区来回被垃圾回收15次后,进入Java堆内存的老年代中,这里的15次,我们也称为对象的年龄,即对象的年龄为15岁;

设置用于自适应GC大小调整的最大持续时间阈值。最大值为15。并行(吞吐量)收集器的默认值为15,CMS收集器的默认值为6。

java -XX:+PrintFlagsFinal打印jvm默认参数值

3.4 JVM对象动态年龄判断是怎么回事?

虚拟机并不是永远地要求对象的年龄必须达到了MaxTenuringThreshold才能晋升老年代;

结论-->动态年龄判断:

Survivor区的对象年龄从小到大进行累加,当累加到X年龄时的总和大于50%(可以使用-XX:TargetSurvivorRatio=?来设置保留多少空闲空间,默认值是50),那么比x大的都会晋升到老年代;

见图

3.5什么是老年代空间分配担保机制

新生代Minor GC后剩余存活对象太多,无法放入Survivor区中,此时就必须将这些存活对象直接转移到老年代去,如果此时老年代空间也不够怎么办?

1、执行任何一次Minor GC之前,JVM会先检查一下老年代可用内存空间,是否大于新生代所有对象的总大小,因为在极端情况下,可能新生代MinorGC之后,新生代所有对象都需要存活,那就会造成新生代所有对象全部要进入老年代;

2、如果老年代的可用内存大于新生代所有对象总大小,此时就可以放心大胆的对新生代发起一次Minor GC,因为 Minor GC之后即使所有对象都存活,Survivor区放不下了,也可以转移到老年代去;

见图

3、如果执行Minor GC之前,检测发现老年代的可用空间已经小于新生代的全部对象总大小,那么就会进行下一个判断,判断老年代的可用空间大小,是否大于之前每一次Minor GC后进入老年代的对象的平均大小,如果判断发现老年代的内存大小,大于之前每一次Minor GC后进入老年代的对象的平均大小,那么就是说可以冒险尝试一下Minor GC,但是此时真的可能有风险,那就是Minor GC过后,剩余的存活对象的大小,大于Survivor空间的大小,也大于老年代可用空间的大小,老年代都放不下这些存活对象了,此时就会触发一次“Full GC";

所以老年代空间分配担保机制的目的?也是为了避免频繁进行Full GC;

4、如果Full GC之后,老年代还是没有足够的空间存放Minor GC过后的剩余存活对象,那么此时就会导致“OOM”内存溢出﹔

3.6什么情况下对象会进入老年代?

1、躲过15次GC之后进入老年代,可通过JVM参数“-XX:MaxTenuringThreshold”来设置年龄,默认为15岁;

2、动态对象年龄判断;

3、老年代空间担保机制;

4、大对象直接进入老年代;

(大对象是指需要大量连续内存空间的Java对象,比如很长的字符串或者是很大的数组或者List集合,大对象在分配空间时,容易导致内存明明还有不少空间时就提前触发垃圾回收以获得足够的连续空间来存放它们,而当复制对象时,大对象又会引起高额的内存复制开销,为了避免新生代里出现那些大对象,然后屡次躲过GC而进行来回复制,此时JVM就直接把该大对象放入老年代,而不会经过新生代;我们可以通过JVM参数“-XX:PretenureSizeThreshold”设置多大的对象直接进入老年代,该值为字节数,比如“1048576”字节就是 1MB,该参数表示如果创建一个大于这个大小的对象,比如一个超大的数组或者List集合,此时就直接把该大对象放入老年代,而不会经过新生代;-XX:PretenureSizeThreshold参数只对Serial和 ParNew两款新生代收集器有效,其他新生代垃圾收集器不支持该参数,如果必须使用此参数进行调优,可考虑ParNew+CMS的收集器组合;)

3.7 JVM运行时数据区元空间的特点及作用?

1、在JDK1.8开始才出现元空间的概念,之前叫方法区/永久代;

2、元空间与Java堆类似,是线程共享的内存区域;

3、存储被加载的类信息、常量、静态变量、常量池、即时编译后的代码等数据;

4、元空间采用的是本地内存,本地内存有多少剩余空间,它就能扩展到多大空间,也可以设置元空间大小;

-XX:Metaspacesize=20M -XX:MaxMetaspaceSize=20m

5、元空间很少有GC垃圾收集,一般该区域回收条件苛刻,能回收的信息比较少,所以GC很少来回收;

3.8 JVM本机直接内存的特点及作用?

1、直接内存(Direct Memory)不属于JVM运行时数据区,是本机直接物理内存;

2、像在JDK1.4中新加入了NIO(New Input/Output)类,一种基于通道(Channel)与缓冲区(Buffer)的I/O方式,它可以使用Native 函数库直接分配堆外内存,然后通过一个存储在Java堆中的 DirectByteBuffer对象作为这块内存的引用进行操作,这样能在一些场景中显著提高性能,因为避免了在Java堆和Native堆中来回复制数据;

3、可能导致OutOfMemoryError异常出现;

3.9 JVM 本机直接内存溢出问题?

直接内存(Direct Memory)的容量大小可通过-XX:MaxDirectMemorySize参数来指定,

该参数表示设置新I/0(java.nio程序包)直接缓冲区分配的最大总大小(以字节为单位);

默认情况下,大小设置为0,这意味着JVM自动为NIO直接缓冲区分配选择大小;

由直接内存导致的内存溢出,无法生成Heap Dump文件,如果程序中直接或间接使用了NIO技术,那就可以重点考虑检查一下直接内存方面的原因;

-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=D:/dev/heapdump.hprofI

3.10 说几个与JVM内存相关的核心参数?

-Xms Java堆内存的大小;

-Xmx Java堆内存的最大大小;

-Xmn Java堆内存中的新生代大小,扣除新生代剩下的就是老年代的内存大小;

-XX:MetaspaceSize元空间大小;

-XX:MaxMetaspaceSize元空间最大大小;

-Xss每个线程的栈内存大小;

-XX:SurvivorRatio=8 设置eden区和survivor区大小的比例,默认是8:1:1;

-XX:MaxTenuringThreshold=5年龄阈值;

-XX:+UseConcMarkSweepGC指定CMS垃圾收集器;I

-XX:+UseG1GC指定使用G1垃圾回收器

3.11 如何计算一个对象的大小?

lucene给我们提供了一个工具方法可以计算;

3.12 堆为什么要分为新生代和老年代

因为年轻代和老年代不同的特点,需要采用不同的垃圾回收算法;

年轻代的对象,它的特点是创建之后很快就会被回收,所以需要用一种垃圾回收算法;

老年代的对象,它的特点是需要长期存活,所以需要另外一种垃圾回收算法;

所以需要分成两个区域来放不同的对象;

JVM划分出新生代、老年代之后,垃圾收集器可以每次只回收其中某一个或者某些部分的区域,同时也有了“Minor GC" “Major GC" “Full GC”这样的回收类型的划分;

Minor GC/Young GC :新生代收集

Major GC/Old GC:老年代收集

Full GC:整堆收集,收集整个Java堆和元空间/方法区的垃圾收集;

Mixed GC:混合收集,收集整个新生代以及部分老年代的垃圾收集,目前只有G1收集器会有这种行为;

3.13 Eden区与Survivor区的空间大小比值为什么默认是8:1:1?I

一个eden 区,新生代对象出生的地方;

两个survivor区,一个用来保存上次新生代GC存活下来的对象,还有一个空着,在新生代GC时把eden+survivor中存活对象复制到这个空的survivor 中;统计和经验表明,90%的对象朝生夕死存活时间极短,每次GC会有90%对象被回收,剩下的10%要预留一个survivor空间去保存;

3.14请介绍下JVM中的垃圾回收算法?

见图

3.14.1标记-清除算法

标记-清除算法是最基础的收集算法,后续的很多垃圾回收算法是基于该算法而发展出来的,

它分为′标记′和′清除′两个阶段;

1、标记

标记出所有需要回收的对象,在标记完成后,统一回收掉所有被标记的对象,也可以反过来,

标记出所有存活的对象,在标记完成后,统一回收所有未被标记的对象,标记过程就是对象是否属于垃圾的判定过程,基于可达性分析算法判断对象是否可以回收;

2、清除

标记后,对所有被标记的对象进行回收;

见图

优点:基于最基础的可达性分析算法,实现简单,后续的收集算法都是基于这种思想实现的;

缺点:

1、执行效率不稳定,如果Java堆中包含大量对象,而且其中大部分是需要被回收的,这时必须进行大量标记和清除的动作,导致标记和清除两个过程的执行效率都随对象数量增长而降低;

2、内存空间的碎片化问题,标记、清除之后会产生大量不连续的内存碎片,空间碎片太多可能会导致当以后在程序运行过程中需要分配较大对象时无法找到足够的连续内存而不得不提前触发另一次垃圾收集;

3.4.2复制算法

复制算法是标记-复制算法的简称,将可用内存按容量分为大小相等的两块,每次只使用其中一块,当这一块的内存用完了,就将还存活的对象复制到另外一块内存上,然后再把已使用过的内存空间一次清理掉;

见图

优点:实现简单,效率高,解决了标记-清除算法导致的内存碎片问题;

缺点:

1、代价太大,将可分配内存缩小了一半,空间浪费太多了;

2、对象存活率较高时就要进行较多的复制操作,效率将会降低;一般虚拟机都会采用该算法来回收新生代, 但是VM对复制算法进行」以进,JVM开汶技照1:1的比例来划分新生代的内存空间,因为通过大量的统计和研究表明,90%以上的对象都是朝生夕死的,所以JVM把新生代分为一块较大的Eden空间和两块较小的Survivor空间,

每次分配内存只使用Eden和其中一块Survivor,发生垃圾收集时,将Eden和Survivor中仍然存活的对象一次性复制到另外一块Survivor空间上,然后直接清理掉Eden和已用过的那块Survivor空间,HotSpot虚拟机默认Eden和Survivor的大小比例是8:1,也即每次新生代中可用内存空间为整个新生代容量的90%(Eden的80%加上一个Survivor的10%),只有另外一个Survivor空间即10%的新生代会被“浪费”,当然,90%的对象可被回收仅仅

是大部分情况下,我们无法百分百保证每次回收都只有不多于10%的对象存活,因此JVM还有一个空间担保机制的安全设计,当Survivor空间不足以容纳一次Minor GC之后存活的对象时T就需要依赖其它内存区域(实际上就是老年代)进行空间分配担保(Handle Promotion,也就是冒险 Minor GC一下) ;

3.4.3 标记-整理算法

标记-整理算法是根据老年代的特点而产生的;

1、标记

标记过程与上面的标记-清理算法一致,也是基于可达性分析算法进行标记;

2、整理

和标记-清理不同的是,该算法不是针对可回收对象进行清理,而是根据存活对象进行整理,

让存活对象都向一端移动,然后直接清理掉边界以外的内存;

而标记-清除算法不移动存活对象,导致有大量不连续空间,即内存碎片,而老年代这种每次回收都有大量存活对象的区域,移动存活对象并更新所有引用这些对象的引用,这是一种比较耗时的操作,而且这种对象移动操作必须全程暂停用户应用程序才能进行,像这样的停顿我们也称为“Stop The World”即STW;

但是即便是移动存活对象是耗时的操作,但是如果不这么做,那么在充满内存碎片的空间中分配对象,又影响了对象的分配和访问的效率,所以JVM权衡两者之后,还是采用了移动存活对象的方式,也就是对内存进行了整理;

另外像cms垃圾收集器,平时多数时间都采用标记-清除算法,暂时容忍内存碎片的存在,直到内存空间的碎片化程度已经大到影响对象分配时,再采用标记-整理算法收集一次,以获得规整的内存空间,所以像基于标记-清除算法的CMS收集器面临空间碎片过多时就会进行一次整理;

见图

优点:

1、不会像复制算法那样划分两个区域,提高了空间利用率;

2、不会产生不连续的内存碎片;

缺点:

效率问题,除了像标记-清除算法的标记过程外,还多了一步整理过程,效率变低;

3.4.4分代收集算法

现在一般虚拟机的垃圾收集都是采用“分代收集”算法;

根据对象存活周期的不同将内存划分为几块,一般把java堆分为新生代和老年代,JVM根据各个年代的特点采用不同的收集算法;新生代中,每次进行垃圾回收都会发现大量对象死去,只有少量存活,因此采用复制算法,只需要付出少量存活对象的复制成本就可以完成收集;老年代中,因为对象存活率较高,采用标记-清理、标记-整理算法来进行回收;

3.15请介绍一下JVM垃圾收集器

见图

如上图,一共有7种作用于不同分代的垃圾收集器,如果两个收集器之间存在连线,则说明它

们可以搭配使用,垃圾收集器所处区域表示它是属于新生代收集器还是老年代收集器;

新生代收集器: Serial、ParNew、Parallel Scavenge

老年代收集器:CMS、Serial Old、Parallel Old

整堆收集器:G1

垃圾收集器的最前沿成果:ZGC和Shenandoah

3.15.1 Serial收集器

新生代收集器,最早的收集器,单线程的,收集时需暂停用户线程的工作,所以有卡顿现象,

效率不高,致使java语言的开发团队一直在改进垃圾收集器的算法和实现,但Serial 收集器

简单,不会有线程切换的开销,是Client模式下默认的垃圾收集器,-client, -server;

参数:-XX:+UseSerialGC

3.15.2 ParNew收集器

它是新生代收集器,就是Serial收集器的多线程版本,大部分基本一样,单CPU下,ParNew

还需要切换线程,可能还不如Serial;

Serial和ParNew收集器可以配合CMS收集器,前者收集新生代,后者CMS收集老年代,

"-XX:+UseConcMarkSweepGC":指定使用CMS后,会默认使用ParNew作为新生代垃圾收集器;

"-XX:+UseParNewGC":强制指定使用ParNew;

"-XX:ParallelGCThreads=2":指定垃圾收集的线程数量,ParNew默认开启的收集线程与CPU的数量相同

3.15.3 Parallel Scavenge收集器

它是新生代收集器,基于复制算法,并行的多线程收集器(与ParNew收集器类似),侧重

于达到一个可控的吞吐量,虚拟机运行100分钟,垃圾收集花1分钟,则吞吐量为99%,有时候我们也把该垃圾收集器叫吞吐量垃圾收集器或者是吞吐量优先的垃圾收集器;

它提供一个参数设置吞吐量:而且这个垃圾收集器是jvm默认的垃圾收集器;

-XX: MaxGCPauseMillis 该参数设置大于0的毫秒数,每次GC的时间将尽量保持不超过设置的值,但是这个值也不是设置得越小就越好,GC暂停时间越短,那么GC的次数会变得更频繁;

3.15.4 SerialOld收集器

它是Serial收集器的老年代版本,同Serial一样,单线程,可在Client模式下使用,也可在

Server模式下使用,采用标记-整理算法,Serial Old收集器也可以作为

CMS 收集器发生失败时的后备预案,在并发收集发生Concurrent Mode Failure时使用;

3.15.5 Parallel Old收集器

Parallel Old是Parallel Scavenge的老年代版本,多线程;标记整理算法,它是在jdk1.6开始才提供;在注重吞吐量和CPU资源的情况下, Parallel Scavenge新生代+Parallel Old老年代是一个很好的搭配;

3.15.6 CMS收集器

CMS全称Concurrent Mark Sweep,是一款老年代的垃圾收集器,它是追求最短回收停顿

时间为目标的收集器,互联网B/S结构的服务器端特别适合此收集器;

我们知道垃圾回收会带来Stop the World (stw)的问题,会导致系统卡死时间过长,很多响应无法处理,所以CMS垃圾回收器采取的是垃圾回收线程和系统工作线程尽量同时执行的模式来处理的,基于标记-清除算法;

CMS垃圾收集器的运作过程,分为4个阶段:

1、初始标记(stw,标记一下GC Roots能直接关联到的对象,那么这些对象也就是需要存活的对象,速度很快);

2、并发标记(不会stw,追踪GC Roots的整个链路,从GC Roots的直接关联对象开始遍历整个对象引用链路,这个过程耗时较长但是不需要停顿用户线程,可以与垃圾收集线程一起并发运行)

3、重新标记(stw,修正并发标记期间,因用户程序继续运行而导致标记产生变化的那一部分对象的标记记录,这个阶段的停顿时间通常会比初始标记阶段稍长一些,但也远比并发标记阶段的时间短,它其实就是对在第二阶段中被系统程序运行变动过的少数对象进行标记,所以运行速度很快的)

4、并发清除(不会stw,清理删除掉标记阶段判断的已经死亡的对象,这个阶段其实是很耗时的,但由于不需要移动存活对象,并且这个阶段也是与用户线程同时并发执行的)其中初始标记和重新标记需要暂停用户线程(Stop The World),其它阶段都是是并发执行,所以总体上暂停时间更短;

CMS垃圾收集器的缺点:

1、并发收集会占用CPU资源,特别是cpu数量小的服务器下,会占用用户线程,导致性能下降,CMS默认启动的回收线程数是(处理器核心数量+3)/4;

2、会产生浮动垃圾,因为你并发清除的时候用户线程可能还在产生垃圾,这些垃圾没有清除,

而且你不能让老年代填满了再清除,你要给用户线程留一定空间,所以jdk1.5默认是老年代

68%了就触发回收,jdk1.6则提升到92%;

3.15.7 G1收集器

3.15.7.2 G1垃圾收集器的基本原理

G1是一款可以让我们设置垃圾回收的预期停顿时间的垃圾收集器,设置参数是

-XX:MaxGCPauseMillis,默认值是200ms;

其实我们对内存合理分配,优化jvm参数,就是为了尽可能减少新生代(Minor GC),或者是整个老年代(Major GC),或者是整个Java堆(Full GC),尽量减少GC带来的系统停顿,避免影响系统处理请求,G1可以指定垃圾回收导致的系统停顿时间不能超过多久,不管垃圾的多与少,垃圾回收的时间都不要超过我们设置的值,G1全权给你负责,保证达到这个目标,这相当于我们就可以直接控制垃圾回收对系统性能的影响了;所以G1垃圾收集器是尽量把垃圾回收对系统造成的影响控制在你指定的时间范围内,同时在有限的时间内尽量回收尽可能多的垃圾对象,这就是G1垃圾收集器的核心原理;

3.15.7.3 G1垃圾收集器如何做到可预测的停顿时间?

1、这与G1垃圾收集器独特的设计有关,它最大的特点就是把Java整个堆内存拆分为多个大小相等的Region;区域、分区

 

见图

2、G1它会追踪每个Region的回收价值,即它会计算每个Region里的对象有多少是垃圾,如果对这个Region进行垃圾回收,需要耗费多长时间,可以回收掉多少垃圾?

3、G1收集器之所以能建立可预测的停顿时间模型,是因为它将Region作为垃圾回收的最小单元,即每次可以选择一部分Region进行收集,避免在整个Java堆中进行全区域的垃圾收集,让G1收集器去跟踪各个Region里面的垃圾的“回收价值”,然后根据用户设定的收集停顿时间(使用参数-XX:MaxGCPauseMillis指定,默认值是200毫秒),然后在后台维护一个优先级列表,优先处理回收价值大的那些Region,这也是“Garbage First”名字的由来,

这种使用Region划分堆内存空间,基于回收价值的回收方式,保证了G1收集器在有限的时间内尽可能收集更多的垃圾;

比如:G1通过追踪发现1个Region 中的垃圾对象有10MB,回收它需要耗费500毫秒,另一个Region中的垃圾对象有20MB,回收它需要耗费100毫秒,那么G1垃圾收集器基于回收价值计算会选择回收20MB只需要100毫秒的Region;

G1的新生代也有Eden和Survivor,其触发垃圾回收的机制也是类似的,随着不停在新生代Eden对应的Region中放对象,JVM就会不停的给新生代加入更多的Region,直到新生代占据堆大小的最大比例60%;

假设堆4G,最大2048个region,每个region为2M,新生代最大60%=2.4G;

一旦新生代达到了设定的占据堆内存的最大大小60%,按照上面的数据大概就是有1200个Region,里面的Eden可能占据了1000个Region,每个Survivor是100个Region,而且Eden区满了,此时触发新生代的GC,G1就会依然用复制算法来进行垃圾回收,进入一个"Stop the World"状态,然后把Eden对应的Region中的存活对象复制到S0对应的Region中,接着回收掉Eden对应的Region中的垃圾对象;

但这个过程与之前是有区别的,因为G1是可以设定目标GC停顿时间的,也就是G1执行GC的时候最多可以让系统停顿多长时间,可以通过“-XX:MaxGCPauseMills”参数来设定,默认值是200ms,那么G1就会通过对每个Region追踪回收它需要多少时间,可以回收多少对象来选择回收一部分Region,保证GC停顿时间控制在指定范围内,尽可能多地回收对象;

3.15.7.5 G1垃圾收集器中的大对象?

Region中有一类特殊的Humongous [hju.' manges]区域,专门用来存储大对象;

G1认为只要大小超过了一个Region容量一半的对象即可判定为大对象,

每个Region的大小可以通过参数-XX:G1HeapRegionSize设定,取值范围为1MB~ 32MB,而对于那些超讨了整个Region容量的超级大对象,将会被存放在N个连续的Humongous Region之中,G1的大多数行为都把 Humongous Region作为老年代的一部分来进行看待;

3.15.7.6 G1垃圾处理器的内存大小如何设置

每个region = 1m ~32m,最多有2048个region;

G1对应的是一大堆的Region内存区域,最多可以有2048个Region

比如说堆大小是4G(4096MB),那么每个Region的大小就是2MB,Region的取值范围是1M-32M,可以通过参数“-XX:G1HeapRegionSize”指定每个Region是多少兆;

比如说堆大小是4G(4096MB),刚开始,默认新生代对堆内存的占比是5%,也就是占据200MB左右的内存,对应大概是100个Region,可以通过“-XX:G1NewSizePercent”来设置新生代初始占比,一般默认值即可,因为在系统运行中,JVM会不停的给新生代增加更

多的 Region ,但是最多新生代的占比不会超过60%,可以通过“-XX:G1MaxNewSizePercent”设置,并且一旦Region进行了垃圾回收,此时新生代的

Region 数量就会减少,这些都是动态的;

新生代:老年代= 60% :40%

3.15.7.7 G1垃圾收集器新生代还有Eden和 Survivor吗?

G1垃圾收集器依然有新生代、老年代的概念,新生代里依然有Eden和Survivor的划分,G1是从CMS发展过来的,以后是要完全取代CMS垃圾收取器的,从jdk9开始G1已经是默认的垃圾收集器,之前的很多技术原理在G1中依然可用,我们知道新生代有一个参数

"-XX:SurvivorRatio=8”,所以G1还是可以区分出来属于新生代的Region里哪些属于Eden,哪些属于Survivor;

比如新生代刚开始初始化时有100个Region,那么可能有80个Region是Eden,10个Region分别是两个Survivor,所以G1中依然有Eden和Survivor的概念,它们会各自占据不同的 Region;只不过随着对象不停的在新生代里分配,属于新生代的Region 会不断增加,Eden和Survivor对应的Region也会不断增加;

5% -- 60%

3.15.7.8 G1垃圾收集器的新生代垃圾回收

G1的新生代也有Eden和Survivor,其触发垃圾回收的机制也是类似的,随着不停在新生代Eden对应的Region中放对象,JVM就会不停的给新生代加入更多的Region,直到新生代占据堆大小的最大比例60%;

假设堆4G,最大2048个region,每个region为2M,新生代最大60%=2.4G;

-旦新生代达到了设定的占据堆内存的最大大小60%,按照上面的数据大概就是有1200个Region,里面的Eden可能占据了1000个Region,每个Survivor是100个Region,而且Eden区满了,此时触发新生代的GC,G1就会依然用复制算法来进行垃圾回收,进入一个"Stop the World”状态,然后把Eden对应的Region 中的存活对象放入SO对应的Region但这个过程与之前是有区别的,因为G1是可以设定目标GC停顿时间的也就是G1执行GC的时候最多可以让系统停顿多长时间,可以通过“-XX:MaxGCPauseMills”参数来设定,默认值是200ms,那么G1就会通过对每个Region追踪回收它需要多少时间,可以回收多少对象来选择回收一部分Region,保证GC停顿时间控制在指定范围内,尽可能多地回收对象;

3.15.7.9 G1垃圾收集器的老年代垃圾回收

1、初始标记,需要Stop the World,不过仅仅标记一下GC Roots直接能引用的对象,这个过程速度很快,而且是借用进行MinorGC的时候同步完成的,所以G1收集器在这个阶段实际并没有额外的停顿;

2、并发标记,不需要Stop the World,这个阶段会从GC Roots开始追踪所有的存活对象,初始标记阶段仅仅只是标记GC Roots直接关联的对象,而在并发标记阶段,就会进行GCRoots追踪,从这个GC Root对象直接关联的对象开始往下追踪,追踪全部的存活对象,这个阶段是很耗时的,但可以和系统程序并发运行,所以对系统程序的影响不大;

3、重新标记(最终标记),需要Stop the World,用户程序停止运行,最终标记一下有哪些存活对象,有哪些是垃圾对象;

4、筛选回收,需要Stop the World,对各个Region 的回收价值和成本进行排序,根据用户所期望的停顿时间来制定回收计划,可以自由选择任意多个Region构成回收集,然后把决定回收的那一部分Region的存活对象复制到空的Region中,再清理掉整个日Region的全部空间,这里的操作涉及存活对象的移动,是必须暂停用户线程,由多条收集器线程并行完成的;从整体上看,G1垃圾收集像是一种标记-整理算法,它不存在内存碎片问题,实际上它是一种复制算法,G1收集器除了并发标记外,其余阶段也是要完全暂停用户线程的,所以它并不是纯粹地追求低延迟,而是给它设定暂停目标,使其在延迟可控的情况下获得尽可能高的吞吐量;

3.15.7.10 G1垃圾收集器的混合垃圾回收?

混合垃圾收集即mixed gc,它不是一个old gc,除了回收整个young region,还会回收一部分的old region,是回收一部分老年代,而不是全部老年代,可以选择部分old region进行收集,从而可以对垃圾回收的耗时时间进行控制;G1有一个参数,是“XX:InitiatingHeapOccupancyPercent”,它的默认值是45%,即如果老年代占据了堆内存的45%的 Region的时候,此时就会尝试触发个新生代+老年代一起回收的混合回收阶段;

3.5.7.11 G1回收失败时的Full GC

在进行Mixed回收的时候,无论是年轻代还是老年代都基于复制算法进行回收,把各个Region中存活的对象复制到其他空闲的Region中;如果万一出现复制时没有空闲Region可以存放存活对象了,就会停止系统程序,然后采用单线程进行标记清除和压缩整理,空闲出来一批Region,这个过程很慢;与CMS中的“Concurrent Mode Failure”失败会导致FullGC类似,如果内存回收的速度赶不上内存分配的速度,G1收集器也要被迫暂停用户线程,导致FullGC而产生长时间Stop The World

3.5.7.12什么时候使用G1垃圾收集器?

-XX:+UseG1GC

1、针对大内存、多处理器的机器推荐采用G1垃圾收集器,比如堆大小至少6G或以上;

2、超过50%的堆空间都被活动数据占用;

3、在要求低延迟的场景,也就是GC导致的程序暂停时间要比较少,0.5-1秒之间;

4、对象在堆中分配频率或者年代升级频率变化比较大,防止高并发下应用雪崩现象的场景;

3.15.8 ZGC收集器

-XX:+UseZGC

ZGC (Z Garbage Collector)是一款由Oracle公司研发的,以低延迟为首要目标的一款垃圾收集器,它是基于动态Region内存布局,(暂时)不设年龄分代,使用了读屏障、染色指针和内存多重映射等技术来实现可并发的标记-整理算法的收集器在JDK 11新加入,还在实验阶段,主要特点是:回收TB级内存(最大4T),停顿时间不超过10ms;

3.16什么是内存泄漏?什么是内存溢出?

内存溢出:OutOfMemory

它是指程序在申请内存时,没有足够的内存空间供其使用,抛出OutOfMemory错误;比如申请了一个8MB空间,但是当前内存可用空间只有5MB,那么就是内存溢出;即:OutOfMemoryError,是指没有空闲内存,垃圾收集器回收后也不能提供更多的内存空间;

内存泄露:Memory Leak

它是指程序运行后,没有释放所占用的内存空间,一次内存泄漏可能不会有很大的影响,但长时间的内存泄漏,堆积到一定程度就会产生内存溢出;

(1)单例对象,生命周期和应用程序一样长,如果单例对象持有对外部对象的引用的话,那么这个外部对象是不能被回收的,则会产生内存泄露;

(2)一些资源未闭也会导致内存泄漏,比如数据库连接,网络连接socket和IO流的连接都必须在finally 中close,否则不能被回收的;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值