JVM篇 写在前面的话
身边的很多人不喜欢JVM面试题,因为平时开发生活中接触的比较少,也很少会主动的进行JVM的调优,但是对于JVM的基本了解还是要有的,至少在面对堆内堆外内存溢出的时候不至于手足无措,这里还是推荐阅读《深入理解JVM虚拟机》,第一次阅读的同学一定会被里面晦涩难懂的专有词汇弄得头晕脑胀,但是一定要坚持读下去,不求完全理解,有一点记忆也是好的,我的推荐是先通读一次,留下印象,后续遇到问题的时候按照印象找答案(有点说偏了,回归正题)。
本篇章从JVM组成,JVM回收相关,调优等方面的常见面试题做了罗列,当中也有我的一些回答问题的建议,还是那句老话,面试不是笔试,要学会沟通表达自己,针对每一道题都要学会扩展,尤其在面对大厂面试的时候,主动的扩展有助于体现自己的深度,相关很多人都有体会,大厂的面试官问问题题喜欢一步步的深入一方面是了解你的知识深度另一方面也是考察你面对自己不了解的问题的思路(尤其阿里的面试官),很多同学都担心面试官会把问题问到自己不会位置,不要慌,深度不够思路来填,当然,必要的深度还是要有的。
正式开始
- Java的内存分区(高频面试题,注意回答的方式)
内存分区分为两个部分,一个是线程私有,另一个是线程共享的。
线程私有:程序计数器(指向下一条指令),虚拟机栈或者叫做Java 栈(局部变量,对象的引用),本地方法栈(JNI相关的虚拟机栈)
线程共享:Java堆(存储创建的对象),方法区(类信息,常量,静态变量) - Java对象的回收方式
回收方式:
(1).引用计数算法:对象引用计数,为0回收,没有解决对象循环依赖问题
(2).根搜索算法/G-ROOT回收算法(JVM使用的回收方式,一虚三方作为GC-ROOT)
【一虚三方:1. 虚拟机栈(栈桢中的本地变量表)中的引用的对象 ; 2. 方法区中的类静态属性引用的对象 ; 3. 方法区中的常量引用的对象 ; 4. 本地方法栈中JNI的引用的对象;】 - Java对象的回收算法(注意和回收方式的区别,回答回收方式或者回收算法的时候建议将两块都简单介绍一下,层层递进,息息相关)
(1).标记-清除算法:(标记需要删除的对象,所处位置的内存回收,会产生内存碎片,导致大对象不能找到连续的内存区间分配)
(2).复制算法:(存活对象转移到另外一块区域,以连续的内存区间依次存储,然后将原存在区域完全回收,不会产生内存碎片,但是可用内存减半)
(3).标记-整理算法:(不同于标记清除,标记整理会将存活对象向内存一端移动来进行整理,在进行内存回收,不产生内存碎片,单存活对象的移动需要消耗计算资源,适合于对象存活率高情况)
(4).分代收集算法:(也就是我们常说的新生代,老年代,持久代,针对不同的”代“,使用不同的回收算法) - CMS和G1(了解哪些哪些垃圾回收器,简单介绍一下)
CMS回收器(解决老年代的回收问题,重点是两次停顿,不要等面试官问,请主动将两次停顿相关内容介绍一下)
流程:(concurrent mark sweep并发标记清除) 预标记(swt)—并发标记—预清理—再标记(+CMSScavengeBeforeRemark swt)—并发清理(+CMSFullGCsBeforeCompaction)—重置CMS状态
重点:两次STW(stop the world),两次停顿的原因
初始标记阶段和再次标记阶段会产生Stop the world 停顿。 GC线程标记好了一个对象的时候,此时我们程序的线程又将该对象重新加入了“关系网”中,当执行二次标记的时候,该对象也没有重写finalize()方法,因此回收的时候就会回收这个不该回收的对象。 虚拟机的解决方法就是在一些特定指令位置设置一些“安全 点”,当程序运行到这些“安全点”的时候就会暂停所有当前运行的线程(Stop The World 所以叫STW),暂停后再找到“GC Roots”进行关系的组建,进而执行标记和清除。安全点的位置【1、循环的末尾 2、方法临返回前 / 调用方法的call指令后 3、可能抛异常的位置】
G1回收器
流程:初始标记 -> 并发标记 -> 最终标记 -> 筛选回收
垃圾收集器都存在 Stop The World 的问题,G1对这个问题进行了优化,G1对整个新生代和老年代一起回收,把堆划分为多个大小相等的独立区域region,使得每个region可以单独进行垃圾回收,通过记录每个region垃圾回收时间以及回收所获得的空间(通过过去回收的经验获得),并维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的region。
特点: 空间整合:基于标记-整理和复制,不会产生内存空间碎片 可预测的停顿:也可以并发执行
- 说一下GC吧,什么时候进行FullGC和MinorGC呢?(有时候也会问道MajorGC和YoungGC,回答方式类似,主要问题集中在大对象分配,系统调用,空间不足三个方面)
MinorGC:年轻代不存在足够连续空间分配给大对象,年轻代容量达到阈值。
FULLGC:老年代和年轻代没有足够的连续内存空间分配给大对象;System.gc()调用;永久代空间不足;老年代空间不足。 - JVM调优(一般大多数是没有JVM调优的经历,都是站在前人巨人的肩膀上直接建筑干活,但是并不意味着这一题不能答,答案介绍一种针对业务场景最简单的调优方式)
新生代和老年代的大小比例,new/old偏大,YGC周期变长,YGC时间变长,遇到大对象无法分配的时候,年老代导致的FGC也会变得很频繁 新生代和老年代的大小比例,new/old偏小,YGC频率变大
通过GC时间和GC频率考虑问题,当然也有堆大小的调整,垃圾回收器的参数调整,但是这些最好还是结合实战来说,空空而谈显得没有意义,结合实战有加分哦。
- 新生代分为几个区?使用什么算法进行垃圾回收?为什么使用这个算法?(把知道的一次性全答出来就好了,这块内容相对比较简单,也很容易讲的流畅,注意比例和使用两个survivor区的原因,答出来有加分)
eden和Survivor1和Survivor2(默认比例8:1,经验值,存活期端的对象占用比例接近10%),复制算法,不会产生内存碎片(使用两个survivor的原因),年轻代存活期短 - 虚拟机类加载机制(高频)
使用双亲委派模型,双亲委派模型要求除了顶层的启动类加载器外,其余的类加载器都应当有自己的父类加载器。
工作过程是: 如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成。 每一个层次的类加载器都是如此。因此,所有的加载请求最终都应该传送到顶层的启动类加载器中。 只有当父加载器反馈自己无法完成这个加载请求时(搜索范围中没有找到所需的类),子加载器才会尝试自己去加载。
对于任意一个类,都需要由加载它的类加载器和这个类本身一同确立其在虚拟机中的唯一性(保证了类加载的唯一性),确保了程序类加载过程的安全性。
-
Java栈什么时候会发生内存溢出,Java堆呢(关注栈和堆中存储的内容就很容易解答了)
所以我们可以理解为栈溢出就是方法执行是创建的栈帧超过了栈的深度。那么最有可能的就是方法递归调用产生这种结果。 heap space表示堆空间,堆中主要存储的是对象。如果不断的new对象则会导致堆中的空间溢出. -
由遇到过线上堆内存溢出问题吗,怎么解决的(堆内存溢出分为堆内内存和堆外内存两种,都答出来比较好哦)
堆外内存溢出定位
1)jmap定位是否是堆内还是堆外内存溢出
2)堆内查看GC情况, jstat -gc pid,jstat -gcutil pid统计GC信息
2.1)jmap dump堆内存分析,查看内存泄露在哪个类以及调用链情况
3)堆外内存泄露采用google的开源gperftools定时打印内存监控使用日志
3.1)找到占用资源大的JNI方法
3.2)btrace定位JNI方法的调用链(AOP编程)
堆内内存溢出定位(在上述2.1的结果获取到后使用内存分析工具分析,eclipse memory analyzer 或者JDK自带的Jhat工具进行分析)
-
JVM如何加载字节码文件
加载:
(1) 通过一个类的全限定名来获取定义此类的二进制字节流
(2) 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
(3) 在Java堆中生成一个代表这个类的java.lang.Class对象,作为方法区这些数据的访问入口
验证: 二进制字节流解析的数据结构是否符合class对象规范
准备: 准备阶段是正式为类变量分配并设置类变量初始值的阶段,这些内存都将在方法区中进行分配(变量都是类变量,实例变量在堆)
解析:符号引用替换为直接引用的过程。解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符7类符号引用进行 初始化: 这个阶段主要是对类变量初始化,是执行类构造器的过程。 换句话说,只对static修饰的变量或语句进行初始化。
如果初始化一个类的时候,其父类尚未初始化,则优先初始化其父类。 如果同时包含多个静态变量和静态代码块,则按照自上而下的顺序依次执行。 父类的()方法先与子类执行,父类的static语句,父类的非static语句块和构造方法,接下来是子类的非static语句块和构造方法 :
父类静态(代码块,变量赋值二者按顺序执行)->子类静态->父类构造代码块->父类构造方法->子类构造代码块->子类构造方法 -
类加载器如何卸载字节码(ClassLoader->Class->Instance都没有被引用)
类加载器没有被引用
类对象没有被引用
没有该类的实例对象存在
满足这三个条件,虚拟机会自动卸载该类。 -
补充问题:(大家可以思考一下,我会在下一篇文章的开头回答)
tomcat的类加载机制违反了双亲委派原则,为什么,又是如何进行类加载的
什么叫做持久代,持久代在JDK1.8中有什么变化
(今天就简单写到这,后续有关JVM面试相关的内容我会直接在这篇文章中补充,提前预告下,下一篇是JAVA语法基础面试题)