2020面试题总结JVM篇_一目斋

2020面试题总结JVM篇_一目斋
2020面试题总结JVM篇_一目斋

1、什么情况下会发生栈内存溢出。

在HotSpot虚拟机中是不区分虚拟机栈和本地方法栈,栈是线程私有的,它的生命周期与线程相同,每个方法在执行的时候都会创建一个栈帧,用来存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用直至执行完成的过程,就对应着一个栈桢在虚拟机栈中入栈到出栈的过程。本地方法栈与虚拟机栈相似,区别不过是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则为虚拟机使用到的Native方法服务。

栈内存溢出是指线程请求的栈深度大于虚拟机所允许的最大深度,则将抛出StackOverflowError异常(StackOverflowError不属于OOM异常)。最有可能的原因就是方法递归产生的这种结果。

另一个可能是引用了大的变量,在拓展栈时无法申请到足够的内存空间,则抛出OutOfMemoryError异常(这个属于内存溢出)。

2、JVM的内存结构,Eden和Survivor比例。

Java虚拟机在执行Java程序的过程中把它所管理的内存划分为若干个不同的数据区域,这些区域都有各自的用途。

?程序计数器。当前线程执行的字节码的行号指示器,是线程私有的。也是唯一一个不会发生内存溢出的区域。

?Java虚拟机栈。也是线程私有的,描述的是Java方法执行的内存模型,线程请求的栈深度大于虚拟机所允许的最大深度,则将抛出

StackOverflowError异常。

?本地方法栈。与虚拟机栈相似,区别不过是虚拟机栈为虚拟机执行Java 方法(也就是字节码)服务,而本地方法栈则为虚拟机使用到的Native

方法服务。

?Java堆。是Java虚拟机中管理的内存中最大的一块,所有线程共享区域,唯一目的就是存放对象实例。所有的对象实例以及数组都要在堆上分配

内存。Java堆也是垃圾回收器管理的主要区域,也被称为gc堆,收集

器基本都采用分代收集算法,Java堆中还可以细分为:新生代和老年代。

?方法区。所有线程共享区域,用于存储已经被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。很多人也愿意称之为

“永久代”。

?运行时常量池。是方法区的一部分,用于存放编译器生成的各种字面量和符号引用。

?直接内存。并不是虚拟机运行时数据区的一部分。例如NIO,它可以使用Native函数直接分配堆外内存,然后通过一个存储在Java堆中的

DirectByteBuffer对象作为这块内存的引用进行操作。这样避免了在Java

堆和Native堆中来回复制数据,提高了性能。

JVM中要对堆进行分代,分代的理由是优化GC性能,很多对象都是朝生夕死的,如果分代的话,我们把新创建的对象放到某一地方,当GC的时候先把这

块存“朝生夕死”对象的区域进行回收,这样就会腾出很大的空间出来。HotSpot JVM把新生代分为了三部分:1个Eden区和2个Survivor区(分别

叫from和to)。默认比例为8:1。一般情况下,新创建的对象都会被分配到Eden区(一些大对象特殊处理),这些对象经过第一次Minor GC后,如果仍然存活,将会被移到Survivor区。对象在Survivor区中每熬过一次Minor GC,年

龄就会增加1岁,当它的年龄增加到一定程度时,就会被移动到年老代中。

因为年轻代中的对象基本都是朝生夕死的(80%以上),所以在年轻代的垃圾回收算法使用的是复制算法,复制算法的基本思想就是将内存分为两块,每次只用

其中一块,当这一块内存用完,就将还活着的对象复制到另外一块上面。复制

算法不会产生内存碎片。

3、JVM内存为什么要分成新生代、老年代和持久代。新生代中为什么要分Eden和Survivor。

堆内存是虚拟机管理的内存中最大的一块,也是垃圾回收最频繁的一块区域,

我们程序所有的对象实例都存放在堆内存中。给堆内存分代是为了提高对象内

存分配和垃圾回收的效率。试想一下,如果堆内存没有区域划分,所有的新创

建的对象和生命周期很长的对象放在一起,随着程序的执行,堆内存需要频繁

进行垃圾收集,而每次回收都要遍历所有的对象,遍历这些对象所花费的时间

代价是巨大的,会严重影响我们的GC效率,这简直太可怕了。

有了内存分代,情况就不同了,新创建的对象会在新生代中分配内存,经过多

次回收仍然存活下来的对象存放在老年代中,静态属性、类信息等存放在永久

代中,新生代中的对象存活时间短,只需要在新生代区域中频繁进行GC,老年代中对象生命周期长,内存回收的频率相对较低,不需要频繁进行回收,永久

代中回收效果太差,一般不进行垃圾回收,还可以根据不同年代的特点采用合

适的垃圾收集算法。分代收集大大提升了收集效率,这些都是内存分代带来的

好处。

HotSpot将新生代划分为三块,一块较大的Eden空间和两块较小的Survivor

空间,默认比例为8:1:1。划分的目的是因为HotSpot采用复制算法来回收

新生代,设置这个比例是为了充分利用内存空间,减少浪费。新生成的对象在Eden区分配(大对象除外,大对象直接进入老年代),当Eden区没有足够的空间进行分配时,虚拟机将发起一次Minor GC。

4、JVM中一次完整的GC流程是什么样子的,对象如何晋升到老年代,说说

你知道的几种主要的JVM参数。

GC开始时,对象只会存在于Eden区和From Survivor区,To Survivor区是空的(作为保留区域)。GC进行时,Eden区中所有存活的对象都会被复制到To Survivor区,而在From Survivor区中,仍存活的对象会根据它们的年龄值决定去向,年龄值达到年龄阀值(默认为15,新生代中的对象每熬过一轮垃圾回收,年龄值就加1,GC分代年龄存储在对象的header中)的对象会被移到老年代中,没有达到阀值的对象会被复制到To Survivor区。接着清空Eden区和From Survivor区,新生代中存活的对象都在To Survivor区。接着,From Survivor区和To Survivor区会交换它们的角色,也就是新的To Survivor区就

是上次GC清空的From Survivor区,新的From Survivor区就是上次GC的To Survivor区,总之,不管怎样都会保证To Survivor区在一轮GC后是空的。GC 时当To Survivor区没有足够的空间存放上一次新生代收集下来的存活对象时,需要依赖老年代进行分配担保,将这些对象存放在老年代中。

对象晋升老年代有三种可能:

?当对象达到成年,经历过15次GC(默认是15,可配置),对象就晋升到老年代了。

?大的对象会直接在老年代创建。

?新生代的Survivor空间内存不足时,对象可能直接晋升到老年代。

jvm参数:

?-Xms:初始堆大小

?-Xmx:堆最大内存

?-Xss:栈内存

?-XX:PermSize初始永久代内存

?-XX:MaxPermSize最大永久带内存

5、你知道哪几种垃圾收集器,各自的优缺点,重点讲下cms和G1,包括原理,流程,优缺点。

常见的垃圾收集器主要有以下四种:

?串行收集器(Serial、ParNew收集器):简单高效,但它在进行垃圾收集时,必须暂停其他所有的工作线程,直到它收集结束,中间停顿时间

长。

?并行收集器(Parallel Scavenge收集器):吞吐量优先,主要关注点在于精确控制吞吐量,即减少GC停顿时间,但收集次数变多。

?CMS:以获取最短回收停顿时间为目标的收集器,并发标记-清除,主要步骤有,初始标记,并发标记,重新标记和并发清除。其中,整个过程

耗时最长的并发标记和并发清除过程收集器线程都可以和用户线程一起

工作,CMS收集器的内存回收过程始于用户线程一起并发执行的。重新

标记是为了修正并发标记期间因用户程序继续运作而导致标记产生变动

的那一部分对象的标记记录。但缺点有,CMS收集器对CPU资源非常

敏感,并且无法处理浮动垃圾。

?G1:可预测停顿的收集器,并发标记-整理,主要步骤分为,初始标记,并发标记,最终标记和筛选回收。G1把内存“化整为零”,并且可以分代

收集。注意:CMS是清除,所以会存在很多的内存碎片。G1是整理,

所以碎片空间较小。

6、垃圾回收算法的实现原理。

垃圾收集算法主要分为以下三种:

?标记-清除算法:首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象。

?复制算法:将可用内存按容量分为两块(Eden和Survivor空间),每次只使用一块,当这一块内存用完后,就将还活着的对象复制到另外一块

上面,然后再把已使用过内存空间一次清理掉。

?标记-整理算法:标记过程与“标记-清除”算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直

接清理掉边界外的内存。

需要注意的是,“标记-清除”算法存在两个不足:

?一个是效率问题,标记和清除两个过程的效率都不高;

?另一个是空间问题,标记清除之后会产生大量不连续的内存碎片,空间碎片太多可能会导致以后在程序运行过程中需要分配较大对象时,无法

找到足够的连续内存而不得不提前触发另一个垃圾收集动作。作为对比,复制算法每次都是对整个半区进行内存回收,内存分配时也就不用考虑

相关主题
相关文档
最新文档