大厂面试经:高频率JVM面试问题整理!

JVM(Java虚拟机)简单来讲就是运行Java代码的解释器,做为螺丝钉程序员JVM其实了解下就差很少啦,不懂JVM内部细节照样能写出优质的代码!可是一到造火箭、飞机的场景(面试)不懂JVM的你,会被面试官虐的体无完肤,本期内容列举常见的JVM面试题:程序员

  1. 说一JVM的内存结构是什么样子的?
  2. 何时对象能够被收回?
  3. 常见的垃圾回收器算法有哪些,各有什么优劣?
  4. 何时对象会进入老年代?
  5. 什么是空间分配担保策略?
  6. 如何优化减小Full GC?

面对这一大波JVM面试题,你真的Hold住吗?面试

JVM的内存结构是什么样子的?

JVM内存结构能够大体可划分为线程私有区域共享区域,线程私有区域由虚拟机栈、本地方法栈、程序计数器组成,而共享区域由堆、元数据空间(方法区)组成。算法

再有人问你JVM的内存结构就回想下上面的图,可是知道JVM的内存模型的样子仍是不行的,还要知道他们分别干什么的。数组

虚拟机栈/本地方法栈

当你碰到过StackOverflowException这个异常的时候,有没有思考下为何会出现这样的异常呢?答案就在虚拟机栈中,JVM会为每一个方法生成栈帧而后将栈帧压入虚拟机栈中。安全

举个粟子:假设JVM参数-Xss设置为1m,若是某个方法里面建立一个128kb的数组,那这个方法在同一个线程中只能递归4次,再递归第五次的时候就会报StackOverflowException异常,由于虚拟机栈的大小只有1m,每次递归都须要为方法在虚拟机栈中分配128kb的空间,很显示到第五次的时候就空间不足了。多线程

程序计数器

程序计数器是一个记录着当前线程所执行的字节码的行号指示器。JVM的多线程是经过CPU时间片轮转(即线程轮流切换并分配处理器执行时间)算法来实现的。也就是说,某个线程在执行过程当中可能会由于时间片耗尽而被挂起,而另外一个线程获取到时间片开始执行。架构

简单的说程序计数器的主要功能就是记录着当前线程所执行的字节码的行号指示器机器学习

方法区(元数据区)

方法区存储了类的元数据信息、静态变量、常量等数据。学习

堆(heap)

日常你们使用new关键字建立的对象都会进入堆中,堆也是GC重点照顾的区域,堆会被划分为:新生代、老年代,而新生代还会被进一步划分为Eden区和Survivor区:大数据

新生代中的Eden区和Survivor区,是根据JVM回收算法来的,只是如今大部分都是使用的分代回收算法,因此在介绍堆的时候会直接将新生代概括为Eden区和Survivor区。

小结

JVM内存模型小结:

  • JVM内存模型划分为线程私有区域共享区域
  • 虚拟机栈/本地方法栈负责存放线程执行方法栈帧
  • 程序计数器用于记录线程执行指令的位置
  • 方法区(元数据区)存储类的元数据信息、静态变量、常量等数据
  • 堆(heap)使用new关键字建立的对象都会进入堆中,堆被划分为新生代和老年代

何时对象能够被收回?

JVM判断对象回收有两种方式:引用记数GC Roots,引用记数比较简单,JVM为每一个对象维护一个引用计数,假设A对象引用计数为零说明没有任务对象引用A对象,那A对象就能够被回收了,可是引用计数有个缺点就是没法解决循环引用的问题。

GC Roots经过一系列的名为GC Roots的对象做为起始点,从这些节点开始向下搜索,搜索过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连时,则证实对象是不可用的。

在Java中,能够做为GC Roots的对象包括下面几种:

  • 虚拟机栈中引用的对象;
  • 方法区中类静态属性引用的对象;
  • 方法区中的常量引用的对象;
  • 本地方法栈中JNI(即通常说的Native方法)的引用的对象;

小结

总的来讲就是当一个对象经过GC Roots搜索不到时,说明对象能够被回收了,但何时回收还要看GC的心情!

常见的垃圾回收器算法有哪些,各有什么优劣?

标记清除

这种算法分两分:标记、清除两个阶段, 标记阶段是从根集合(GC Root)开始扫描,每到达一个对象就会标记该对象为存活状态,清除阶段在扫描完成以后将没有标记的对象给清除掉。

用一张图说明:

这个算法有个缺陷就是会产生内存碎片,如上图B被清除掉后会留下一块内存区域,若是后面须要分配大的对象就会致使没有连续的内存可供使用。

标记整理

标记整理就没有内存碎片的问题了,也是从根集合(GC Root)开始扫描进行标记而后清除无用的对象,清除完成后它会整理内存。

这样内存就是连续的了,可是产生的另一个问题是:每次都得移动对象,所以成本很高。

复制算法

复制算法会将JVM推分红二等分,若是堆设置的是1g,那使用复制算法的时候堆就会有被划分为两块区域各512m。给对象分配内存的时候老是使用其中的一块来分配,分配满了之后,GC就会进行标记,而后将存活的对象移动到另一块空白的区域,而后清除掉全部没有存活的对象,这样重复的处理,始终就会有一块空白的区域没有被合理的利用到。

两块区域交替使用,最大问题就是会致使空间的浪费,如今堆内存的使用率只有50%。

小结

JVM回收算法小结:

  • 标记清除速度快,可是会产生内存碎片;
  • 标记整理解决了标记清除内存碎片的问题,可是每次都得移动对象,所以成本很高;
  • 复制算法没有内存碎片也不须要移动对象,可是致使空间的浪费;

何时对象会进入老年代?

新建立出来的对象一开始都会停留在新生代中,但随着JVM的运行,有些存活的长的对象会慢慢的移动到老年代中。

根据对象年龄

JVM会给对象增长一个年龄(age)的计数器,对象每“熬过”一次GC,年龄就要+1,待对象到达设置的阈值(默认为15岁)就会被移移动到老年代,可经过-XX:MaxTenuringThreshold调整这个阈值。

一次Minor GC后,对象年龄就会+1,达到阈值的对象就移动到老年代,其余存活下来的对象会继续保留在新生代中。

动态年龄判断

根据对象年龄有另一个策略也会让对象进入老年代,不用等待15次GC以后进入老年代,他的大体规则就是,假如当前放对象的Survivor,一批对象的总大小大于这块Survivor内存的50%,那么大于这批对象年龄的对象,就能够直接进入老年代了。

如图上的A、B、D、E这四个对象,假如Survivor 2是100m,若是A + B + D的内存大小超过50m,如今D的年龄是10,那E都会被移动到老年代。实际上这个计算逻辑是这样的:年龄1 + 年龄2 + 年龄n的多个对象总和超过Survivor区的50%,那就会把年龄n以上的对象都放入老年代。

大对象直接进入老年代

若是设置了-XX:PretenureSizeThreshold这个参数,那么若是你要建立的对象大于这个参数的值,好比分配一个超大的字节数组,此时就直接把这个大对象放入到老年代,不会通过新生代。

这么作就能够避免大对象在新生代,多次躲过GC,还得把他们来复制来复制去的,最后才进入老年代,这么大的对象来回复制,是很耗费时间的。

什么是空间分配担保策略?

JVM在发生Minor GC以前,虚拟机会检查老年代最大可用的连续空间是否大于新生代全部对象的总空间,若是大于,则这次Minor GC是安全的若是小于,则虚拟机会查看HandlePromotionFailure设置项的值是否容许担保失败。若是HandlePromotionFailure=true,那么会继续检查老年代最大可用连续空间是否大于历次晋升到老年代的对象的平均大小,若是大于则尝试进行一次Minor GC,但此次Minor GC依然是有风险的;若是小于或者HandlePromotionFailure=false,则改成进行一次Full GC。

如何优化减小Full GC?

将前面的一些问题总结下来,而后应用到线上,那JVM应该如何优化减小Full GC呢?以标准的4核8G机器为例说明,首先系统预留4G,其余4G按以下规则分配 :

  • 堆内存:3g
  • 新生代:1.5g
  • 新生代Eden区:1228m
  • 新生代Survivor区:153m
  • 方法区:256m
  • 虚拟机栈:1m/thread

设置参数以下:

-Xms3072m
-Xmx3072m
-Xmn1536m
-Xss=1m
-XX:PermSize=256m
-XX:MaxPermSize=256m
-XX:HandlePromotionFailure
-XX:SurvivorRatio=8

估算系统每秒占用内存数量

在优化JVM以前,要先估算要系统每秒占用的内存数量,若有个日活百万的商场系统,每日下单量在20w左右,按照一天8个小时算,那订单服务的每秒大概会有500个请求,而后粗略的估算下每一个请求占用多少内存,计算出每秒要花费多少内存。

假设是每秒500个请求,每一个请求须要分配100k的空间,那1秒须要分配大约50m的内存。

计算下多长时间触发一次Minor GC

按照以前的估算1秒须要分配大约50m的内存的话,Eden区的空间是1228m那平均每25秒就要执行一次Minor GC。

检查下Survivor区是否足够

按照上面的模型,每25秒就要执行一次Minor GC,GC执行期间并不能回收掉全部的新生代中的对象,那每秒50m那每次GC执行期间还会剩下大约100m没法回收的对象会进入Survivor区,可是别忘记JVM有动态年龄判断机制,这样设置下来Survivor的空间明显小了一点,因此将新生代设置2048m,才能避免触发动态年龄判断

-Xms3072m
-Xmx3072m
-Xmn2048m
...

大对象直接进入老年代

大对象通常是长期存活和使用的对象,通常来讲设置1M的对象直接进入老年代,这样避免大对象一直处于新生代中来回复制,因此加上PretenureSizeThreshold=1m参数。

...
-XX:PretenureSizeThreshold=1m
...

合理设置对象年龄阈值

Minor GC后默认躲过15次垃圾回收后自动升入老年代,按照咱们的评估25秒触发一次Minor GC,若是按照MaxTenuringThreshold参数的默认值,躲过15次GC后,应该是6分钟以后的事了,结合当前业务场景这里能够下降一点,让那些本应该进入老年代的对象,尽快的进入老年代,避免复制成本和浪费新生代空间,从而致使新生代Survivor空间不足,引起Full GC。

...
-XX:MaxTenuringThreshold=6
...

《架构文摘》天天一篇架构领域重磅好文,涉及一线互联网公司应用架构(高可用、高性 能、高稳定)、大数据、机器学习等各个热门领域。