1200字范文,内容丰富有趣,写作的好帮手!
1200字范文 > 【架构师面试-JVM原理-3】-JVM垃圾回收

【架构师面试-JVM原理-3】-JVM垃圾回收

时间:2018-08-21 19:16:51

相关推荐

【架构师面试-JVM原理-3】-JVM垃圾回收

1:GC基本功STW、吞吐量、Latency和FootPrint

1:STW

GC是妈妈在打扫房间,应用程序则是孩子在弄乱房间,如果打扫房间的速度小于弄乱房间的速度,怎么办?SWT(stopthe world)不许孩子再动。如果房子不清理,就会处现OOM(outof memory)

GC在说stop时,有些没有运行完的线程不夫立即停止,会执行到safepoint

2:GC的吞吐量

指程序工作的时间占比(没有STW)GC没有占用CPU的时间。

可以通过设置JVM参数控制。-XX:GCTmeRatio=99,表示吞吐量占比99,GC占1%

throughtput越高,gc所占有的CPU时间越少,GC效率越高

如何提高throughtput?给更大内存,更换GC算法

3:Latency

GC造成的停顿(STW)时间

4:FootPrint

最终应用对内存的需求,某应用内存使用速度100M/S,回收速度是80M/S,最终会导致out of memory,需要STW回收,如果每10秒做一次GC(SWT全部加收),此时峰值是200M内存(FootPrint),所以说内存的使用是动态变化的。

2:垃圾没有及时回收会怎么样

1:StackOverflowError

StackOverflowError栈溢出,一般由于递归过多,调用方法过多导致。

2:OutOfMemeryError

OutOfMemeryError堆内存溢出,即OOM,由于堆内存中没有被GC回收的对象过多导致。

3:异常分析

出现这种异常,一般手段是先通过内存映像分析工具(如Eclipse Memory Analyzer)对dump出来的堆转存快照进行分析,重点是确认内存中的对象是否是必要的,先分清是因为内存泄漏(Memory Leak)还是内存溢出(Memory Overflow)。

如果是内存泄漏,可进一步通过工具查看泄漏对象到GCRoots的引用链。于是就能找到泄漏对象是通过怎样的路径与GC Roots相关联并导致垃圾收集器无法自动回收。

如果不存在泄漏,那就应该检查虚拟机的参数(-Xmx与-Xms)的设置是否适当。

4:解决办法

导出Dump文件

提前对Java程序加上这些参数印dump文件 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=./

对正在运行的程序使用jmap

分析Dump文件

如果Dump文件不太大的话,可以传到 World-class heap Dump analysis - Java, Android memory dump analyzer 来分析

文件比较大,且想进行更加系统的分析,推荐使用MAT分析

3:如何判断对象是垃圾

1:引用计数(不用)

引用计数就是一个对象只要有其它对象引用了,计数就加1,当计数为0时就回收对象。

循环引用问题,有些对象循环引用,即使有对象不用,但计数不会为0,所以对象得不到回收。

2:根达法

Java中的Root,方法区中的常量、静态变量的引用,当前栈中的引用。

从根开始找对象,如果这个对象有路径可以到根集合中某个引用,这个对象不需要回收。而红色的3个循环引用,没有到达根集合的路径就可以回收了。

浮动垃圾:在每个内存回收的周期,都会从根结点重新再算一遍。可以少算,但不能多算(多算会导致还在用的对象被回收),这些少算的对象就是浮动垃圾。在下一个内存回收的周期会重新计算,把这些少算的对象找出来。

双色标记清除算法分两个阶段,标记(mark)阶段,上色。清除(sweep)阶段。在sweep阶段时要分析finalize类

问题:mutation

mutation是程序状态的变化,mutation又重新指向C,在多线程中会出现这样的情况,一个线程断开指向C,而另一个线程又指向C,因为线程之间是不知道对方所做的操作。此时由C开始需要重新标记。

3:三色标记清除

增加一种灰色,表示不确定性。

场景如下:在mark时,有mutation,不能执行sweep,所以将sweep删除,重新mark

此时从C(灰色不确定)开始,能达到的全部涂黑(不能回收的对象)

4:finalize类

finalize()是Object的protected方法,子类可以覆盖该方法以实现资源清理工作,GC在回收对象之前调用该方法。

finalize流程:当对象变成(GC Roots)不可达时,GC会判断该对象是否覆盖了finalize方法,若未覆盖,则直接将其回收。否则,若对象未执行过finalize方法,将其放入F-Queue队列,由一低优先级线程执行该队列中对象的finalize方法。执行finalize方法完毕后,GC会再次判断该对象是否可达,若不可达,则进行回收,否则,对象“复活”。

openclass.finalize/Finalization.java

package openclass.finalize;public class Finalization {/*public static void main(String[] args) {Finalization f = new Finalization();System.out.println("First print: " + f);f = null;System.gc();//回收对象System.out.println("Second print: " + f);}*/public static Finalization finalization;@Overrideprotected void finalize() {System.out.println("Finalized");// 在垃圾回收时,将这个类当前对象this赋给成员变量finalization,让对象重生。finalization = this;}public static void main(String[] args) {Finalization f = new Finalization();System.out.println("First print: " + f);f = null;// 回收对象System.gc();// 休息一段时间,让上面的垃圾回收线程执行完成。就是要保证能执行完finalization = this;让对象重生try {Thread.currentThread().sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("Second print: " + f);System.out.println(f.finalization);}}

5:垃圾回收器

垃圾回收器的基本原理是什么?垃圾回收器可以马上回收内存吗?有什么办法主动通知虚拟机进行垃圾回收?

垃圾回收器通常是作为一个单独的低级别的线程运行,在不可预知的情况下对内存堆中已经死亡的或者长时间没有使用的对象进行清除和回收,程序员不能实时的调用垃圾回收器对某个对象或所有对象进行垃圾回收。

垃圾回收器不可以马上回收内存,程序员可以手动执行System.gc(),通知GC运行,但是Java语言规范并不保证GC一定会执行。

6:年轻代垃圾回收过程

假设在Eden中已经存满了4个对象,其中有3个已经成为无用对象,有1个存活对象。它就会被复制到一块survior区中。

假设复制到S0(FROM区)S1是TO区,清理所有Eden区的对象。这个存活对象的年龄设置为1,这就是第一次Minor处理垃圾的过程。

接下来,Eden区再次装满对象,有两个存活对象,此时会再次触发MinorGC

将Eden区和S0区中的对象拷贝到S1中,此时S0也从FROM区变成TO区,S1从TO区变成FROM区,同时将原来S0区中对象年龄加1,将Eden区年龄加1

拷贝完成后,eden和S0区域都会被清空,完成第二次MinorGC

如果Eden区又满了,触发第3次MinorGC,此时S1区有一个对象无用,

这时会将eden区和S1区的对象拷贝回S0区,同时将存活对象的年龄加1 。

拷贝后再次清空eden和s1,当对象每经过一次miniGC,年龄就加1,当年龄默认大于15时,对象就会成为老年代。有些较大的对象在eden区或suvior区装不下时也会直接放入老年代区。

miniorGC采用复制算法进行内存分配时,不需要考虑内存碎片。

对象如何升级到到老年代:

经历一定Minor次数依然存活的对象

Survivor区中存放不下的对象

新生成的大对象

常用的性能调优参数:

-XX:SurvivorRatio: Eden和Survivor的比值,默认8:1

-XX:NewRatio:老年代和年轻代内存大小的比例

-XX:+PretenuerSizeThreshold指定对象存入老年代经过MinorGC的次数

7:内存分配与垃圾回收

JVM的内存可以分为堆内存和非堆内存。

堆内存分为年轻代和老年代。年轻代又可以进一步划分为一个Eden(伊甸)区和两个Survivor(幸存)区组成。如下图所示:

JVM堆内存的分配

lJVM初始分配的堆内存由-Xms指定,默认是物理内存的1/64。JVM最大分配的堆内存由-Xmx指定,默认是物理内存的1/4。

l默认空余堆内存小于40%时,JVM就会增大堆直到-Xmx的最大限制。空余堆内存大于70%时,JVM会减少堆直到 -Xms的最小限制。因此我们一般设置-Xms和-Xmx相等以避免在每次GC 后调整堆的大小。

l通过参数-Xmn2G可以设置年轻代大小为2G。通过-XX:SurvivorRatio可以设置年轻代中Eden区与Survivor区的比值,设置为8,则表示年轻代中Eden区与一块Survivor的比例为8:1。注意年轻代中有两块Survivor区域。

JVM非堆内存的分配

lJVM使用-XX:PermSize 设置非堆内存初始值,默认是物理内存的1/64。由-XX:MaxPermSize设置最大非堆内存的大小,默认是物理内存的1/4。

堆内存上对象的分配与回收

l创建的对象会优先在Eden分配,如果是大对象(很长的字符串数组)则可以直接进入老年代。虚拟机提供一个-XX:PretenureSizeThreadhold参数,大于这个参数值的对象直接在老年代中分配,避免在Eden区和两个Survivor区发生大量的内存拷贝。

l长期存活的对象将进入老年代,每一次MinorGC(年轻代GC),对象年龄就大一岁,默认15岁晋升到老年代,通过-XX:MaxTenuringThreshold设置晋升年龄。

8:常用垃圾回收算法有哪些?

GC最基础的算法有三种:

标记 -清除算法、复制算法、我们常用的垃圾回收器一般都采用分代收集算法。

标记 -清除算法,“标记-清除”(Mark-Sweep)算法,如它的名字一样,算法分为“标记”和“清除”两个阶段:首先标记出所有需要回收的对象,在标记完成后统一回收掉所有被标记的对象。

复制算法,“复制”(Copying)的收集算法,它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。

分代收集算法,“分代收集”(Generational Collection)算法,把Java堆分为新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法。

9:JVM中的垃圾收集器

1、Serial收集器(不用):串行收集器是最古老,最稳定以及效率高的收集器,可能会产生较长的停顿,只使用一个线程去回收。

2、ParNew收集器(不用):ParNew收集器就是Serial收集器的多线程版本。

3、Parallel收集器(并行)(不用):Parallel Scavenge收集器类似ParNew收集器,Parallel收集器更关注系统的吞吐量。

4、CMS收集器(并行)(逐渐不用):CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。Jdk14不支持了

5、G1收集器(在用):是一款面向服务器的垃圾收集器,主要针对配备多颗处理器及大容量内存的机器,以极高概率满足GC停顿时间要求的同时,还具备高吞吐量性能特征。将大内存拆分成多个小内存,让并发更容易处理。

G1相比较CMS的改进

算法: G1基于标记-整理算法, 不会产生空间碎片,分配大对象时不会无法得到连续的空间而提前触发一次FULL GC。

停顿时间可控: G1可以通过设置预期停顿时间(Pause Time)来控制垃圾收集时间避免应用雪崩现象。

并行与并发:G1能更充分的利用CPU,多核环境下的硬件优势来缩短stop the world的停顿时间。

G1收集器的应用场景

G1垃圾收集算法主要应用在多CPU大内存的服务中,在满足高吞吐量的同时,尽可能的满足垃圾回收时的暂停时间。

就目前而言、CMS还是默认首选的GC策略、可能在以下场景下G1更适合:

服务端多核CPU、JVM内存占用较大的应用(至少大于4G)

应用在运行过程中会产生大量内存碎片、需要经常压缩空间

想要更可控、可预期的GC停顿周期,防止高并发下应用雪崩现象

6、ZGC是JDK 11中推出的一款低延迟垃圾回收器,它的设计目标包括:

停顿时间不超过10ms;

停顿时间不会随着堆的大小,或者活跃对象的大小而增加

支持8MB~4TB级别的堆(未来支持16TB)。

10:CMS垃圾收集器的垃圾收集过程

cms是基于“标记-清除”算法实现的,整个过程分为4个步骤:

初始标记(cms initial mark)

并发标记(cms concurrent mark)

重新标记(cms remark)

并发清除(cms concurrent sweep)

其中,初始标记、重新标记这两个步骤需要stw(stop the world)

初始标记只是标记一下GC root能直接关联到的对象速度很快

并发标记阶段就是进行Gc roots tracing的过程。

重新标记阶段则是为了修正并发标记期间因用户程序继续运作而导致的标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间一般

会比初始标记阶段稍长一些,但远比并发标记的时间短。

CMS缺点:

浮动垃圾:在垃圾回收时,又会产生新的垃圾。

空间碎片

11:JVM的引用类型有哪些?

强引用:普通存在 P p = new P(),只要强引用存在,垃圾收集器永远不会回收掉被引用的对象。当内存不足的时候,JVM宁可出现OutOfMemoryError错误停止,也需要进行保存,并且不会将此空间回收,在引用期间和栈有联系就无法被回收。

软引用:当内存不足的时候,进行对象的回收处理,往往用于高速缓存中;mybatis就是

弱引用:不管内存是否紧张,只要有垃圾了就立即回收。

虚引用:也称为幽灵引用,和没有引用是一样的

12:ThreadLocal造成内存泄漏的原因

ThreadLocal为每个线程维护一个本地变量。

采用空间换时间,它用于线程间的数据隔离,为每一个使用该变量的线程提供一个副本,每个线程都可以独立地改变自己的副本,而不会和其他线程的副本冲突。

ThreadLocal类中维护一个Map,用于存储每一个线程的变量副本,Map中元素的键为线程对象,而值为对应线程的变量副本。

ThreadLocalMap 中使用的 key 为 ThreadLocal 的弱引用,而 value 是强引用。

如果 ThreadLocal 没有被外部强引用的情况下,在垃圾回收的时候,key 会被清理掉,而 value 不会被清理掉。

这样ThreadLocalMap 中就会出现key为null的Entry。假如我们不做任何措施,value 永远无法被GC 回收,这个时候就可能会产生内存泄露。

ThreadLocalMap实现中已经考虑了这种情况,在调用 set()、get()、remove() 方法的时候,会清理掉 key 为 null 的记录。使用完 ThreadLocal方法后最好手动调用remove()方法

每次使用完ThreadLocal,都调用它的remove()方法,清除数据。

在使用线程池的情况下,没有及时清理ThreadLocal,不仅是内存泄漏的问题,更严重的是可能导致业务逻辑出现问题。所以使用ThreadLocal就跟加锁完要解锁一样用完就清理。

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。