JVM是java从业者,必须要迈过的坎,不管你是初级、中级还是高级,都是必须掌握的,而且在面试中,jvm也是必考题,如果你不深入了解话,那去面试找工作肯定是有点难受的。
本文将重点介绍面试过程中常见的 JVM 题目,将面试题分为三大类:基础题目,进阶题目,实战题目。
1.1 JDK、 JRE、JVM 的关系是什么?
什么是 JVM ?
英文名称 ( Java Virtual Machine ),就是 JAVA 虚拟机, 它只识别 .class 类型文件,它能够
将 class 文件中的字节码指令进行识别并调用操作系统向上的 API 完成动作。
什么是 JRE ?
英文名称( Java Runtime Environment ),Java 运行时环境。
它主要包含两个部分:JVM 的标准实现和 Java 的一些基本类库。相对于 JVM 来说,JRE多出
来一部分 Java 类库。
什么是 JDK?
英文名称( Java Development Kit ),Java 开发工具包。
JDK 是整个 Java 开发的核心,它集成了 JRE 和一些好用的小工具。
例如:javac.exe、java.exe、jar.exe 等。
这三者的关系:一层层的嵌套关系。JDK > JRE > JVM。
1.2 JVM 的内存模型以及分区情况和作用
黄色部分为线程共有,蓝色部分为线程私有。
方法区
用于存储虚拟机加载的类信息,常量,静态变量等数据。
堆
存放对象实例,所有的对象和数组都要在堆上分配。
是 JVM 所管理的内存中最大的一块区域。
栈
Java 方法执行的内存模型:存储局部变量表,操作数栈,动态链接,方法出口等信息。
生命周期与线程相同。
本地方法栈
作用与虚拟机栈类似,不同点本地方法栈为 native 方法执行服务,虚拟机栈为虚拟机执行的
Java 方法服务。
程序计数器
当前线程所执行的信号指示器。是 JVM 内存区域最小的一块区域。执行字节码工作时就是利
用程序计数器来选取下一条需要执行的字节码指令。
1.3 JVM 对象创建步骤流程是什么?
整体流程如下图所示:
第 1 步:虚拟机遇到一个 new 指令,首先将去检查这个指令的参数是否能在常量池中定位到
这个类的符号引用, 并且检查这个符号引用的类是否已经被加载&解析&初始化。
第 2 步:如果类已经被加载那么进行第 3 步; 如果没有进行加载, 那么就就需要先进行类的加载。
第 3 步:类加载检查通过之后, 接下来进行新生对象的内存分配。
第 4 步:对象生成需要的内存大小在类加载完成后便可完全确定,为对象分配空间等同于把一
块确定大小的内存从 Java 堆中划分出来
第 5 步:内存大小的划分分为两种情况:
第一种情况:JVM 的内存是规整的, 所有的使用的内存都放到一边, 空闲的内存在另外一
边, 中间放一个指针作为分界点的指示器。 那么这时候分配内存就比较简单, 只要讲指针向
空闲空间那边挪动一段与对象大小相同的距离。 这种就是“指针碰撞”。
第二种情况:JVM 的内存不是规整的, 也就是说已使用的内存与未使用的内存相互交错。 这
时候就没办法利用指针碰撞了。 这时候我们就需要维护一张表,用于记录那些内存可用, 在
分配的时候从列表中找到一块足够大的空间划分给对象实例, 并更新到记录表上。
第 6 步:空间申请完成之后, JVM 需要将内存的空间都初始化为 0 值。
如果使用 TLAB, 就可以在 TLAB 分配的时候就可以进行该工作。
第 7 步: JVM 对对象进行必要的设置。 例如, 这个对象是哪个类的实例、对象的哈希码、GC 年代等信息。
第 8 步:完成了上面的步骤之后 从 JVM 来看一个对象基本上完成了, 但从 Java 程序代码绝对来看, 对象创建才刚刚开始, 需要执行 方法, 按照程序中设定的初始化操作初始化, 这时候一个真正的程序对象生成了。
1.4 垃圾回收算法有几种类型? 他们对应的优缺点又是什么?
标记-清除算法
标记—清除算法包括两个阶段:“标记”和“清除”。
标记阶段:确定所有要回收的对象,并做标记。
清除阶段:将标记阶段确定不可用的对象清除。
缺点:
1.标记和清除的效率都不高。
2.会产生大量的碎片,而导致频繁的回收。
复制算法
内存分成大小相等的两块,每次使用其中一块,当垃圾回收的时候, 把存活的对象复制到零
一块上,然后把这块内存整个清理掉。
缺点:
1.需要浪费额外的内存作为复制区。
2.当存活率较高时,复制算法效率会下降。
标记-整理算法
标记—整理算法不是把存活对象复制到另一块内存,而是把存活对象往内存的一端移动,然后
直接回收边界以外的内存。
缺点: 算法复杂度大,执行步骤较多.
分代收集算法
目前大部分 JVM 的垃圾收集器采用的算法。根据对象存活的生命周期将内存划分为若干个不同的区域。
一般情况将堆区划分为新生代( Young Generation 和老年代( Tenured Generation ),永久代( Permanet Generation )。
老年代的特点是每次垃圾收集时只有少量对象需要被回收,而新生代的特点是每次垃圾回收时
都有大量的对象需要被回收,那么就可以根据不同代的特点采取最适合的收集算法。
Young:存放新创建的对象,对象生命周期非常短,几乎用完可以立即回收,也叫 Eden 区。
Tenured: young 区多次回收后存活下来的对象将被移到 tenured 区,也叫 old 区。
Perm:永久带,主要存加载的类信息,生命周期长,几乎不会被回收。
缺点: 算法复杂度大,执行步骤较多。
1.5 简单介绍一下什么是类加载机制?
Class 文件由类装载器装载后,在 JVM 中将形成一份描述 Class 结构的元信息对象,通过该
元信息对象可以获知 Class 的结构信息:如构造函数,属性和方法等。
虚拟机把描述类的数据从 class 文件加载到内存,并对数据进行校验,转换解析和初始化,最
终形成可以被虚拟机直接使用的 Java 类型,这就是虚拟机的类加载机制。
1.6 类的加载过程是什么?简单描述一下每个步骤:
类加载的过程包括了:
第一步:加载
查找并加载类的二进制数据。
加载是类加载过程的第一个阶段,虚拟机在这一阶段需要完成以下三件事情:
·通过类的全限定名来获取其定义的二进制字节流
将字节流所代表的静态存储结构转化为方法区的运行时数据结构
在 Java 堆中生成一个代表这个类的 java.lang.Class 对象,作为对方法区中这些数据的访问入口
第二步:验证
确保被加载的类的正确性。
这一阶段是确保 Class 文件的字节流中包含的信息符合当前虚拟机的规范,并且不会损害虚拟机自身的安全。
包含了四个验证动作:文件格式验证,元数据验证,字节码验证,符号引用验证。
第三步:准备
为类的静态变量分配内存,并将其初始化为默认值。
准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些内存都将在方法区中分配。
第四步:解析
把类中的符号引用转换为直接引用。
解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程,解析动作主要针对类或接
口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符 7 类符号引用进行。
第五步:初始化
类变量进行初始化
为类的静态变量赋予正确的初始值,JVM 负责对类进行初始化,主要对类变量进行初始化。
2.1 Java 语言怎么实现跨平台的?
我们编写的 Java 源码,编译后会生成一种 .class 文件,称为字节码文件。
字节码不能直接运行,必须通过 JVM 翻译成机器码才能运行。
JVM 是一个”桥梁“,是一个”中间件“,是实现跨平台的关键。Java 代码首先被编译成字
节码文件,再由 JVM 将字节码文件翻译成机器语言,从而达到运行 Java 程序的目的。
2.2 JVM 数据运行区,哪些会造成 OOM 的情况?
2.3 详细介绍一下对象在分带内存区域的分配过程?
JVM 会试图为相关 Java 对象在 Eden 中初始化一块内存区域。
当 Eden 空间足够时,内存申请结束;否则到下一步。
JVM 试图释放在 Eden 中所有不活跃的对象(这属于 1 或更高级的垃圾回收)。释放后若
Eden 空间仍然不足以放入新对象,则试图将部分 Eden 中活跃对象放入 Survivor 区。
Survivor 区被用来作为 Eden 及 Old 的中间交换区域,当 Old 区空间足够时,Survivor 区的
对象会被移到 Old 区,否则会被保留在 Survivor 区。
当 Old 区空间不够时,JVM 会在 Old 区进行完全的垃圾收集。
完全垃圾收集后,若 Survivor 及 Old 区仍然无法存放从 Eden 复制过来的部分对象,导致
JVM 无法在 Eden 区为新对象创建内存区域,则出现 “ out of memory ” 错误。
2.4 G1 与 CMS 两个垃圾收集器的对比
细节方面不同
1.G1 在压缩空间方面有优势。
2.G1 通过将内存空间分成区域(Region)的方式避免内存碎片问题。
3.Eden, Survivor, Old 区不再固定、在内存使用效率上来说更灵活。
4.G1 可以通过设置预期停顿时间(Pause Time)来控制垃圾收集时间避免应用雪崩现象。
5.G1 在回收内存后会马上同时做合并空闲内存的工作、而 CMS 默认是在 STW(stop the world)的时候做。
6.G1 会在 Young GC 中使用、而 CMS 只能在 O 区使用。
整体内容不同:
CMS 的缺点是对 cpu 的要求比较高。
G1 是将内存化成了多块,所以对内存的大小有很大的要求。
CMS 是清除,所以会存在很多的内存碎片。
G1 是整理,所以碎片空间较小。
2.5 线上常用的 JVM 参数有哪些?
数据区设置
· Xms:初始堆大小
· Xmx:最大堆大小
· Xss:Java 每个线程的Stack大小
· XX:NewSize=n:设置年轻化大小
· XX:NewRatio=n:设置年轻代和年老代的比值。
如:为 3,表示年轻代与年老代比值为 1:3,年轻代占整个年轻代年老代和的 1/4。
· XX:SurvivorRatio=n:年轻代中 Eden 区域两个 Survivor 区的比值。
注意 Survivor 区有两个。
如:3,表示 Eden:Survivor=3:2,一个 Survivor 区占整个年轻代的 1/5。
· XX:MaxPermSize=n:设置持久代大小。
收集器设置
· XX:+UseSerialGC:设置串行收集器
· XX:+UseParallelGC::设置并行收集器
· XX:+UseParalledlOldGC:设置并行年老代收集器
· XX:+UseConcMarkSweepGC:设置并发收集器
GC日志打印设置
· XX:+PrintGC:打印 GC 的简要信息
· XX:+PrintGCDetails:打印 GC 详细信息
· XX:+PrintGCTimeStamps:输出 GC 的时间戳
2.6 对象什么时候进入老年代?
对象优先在 Eden 区分配内存
当对象首次创建时, 会放在新生代的 eden 区, 若没有 GC 的介入,会一直在 eden 区,GC
后,是可能进入 survivor 区或者年老代
大对象直接进入老年代
所谓的大对象是指需要大量连续内存空间的 Java 对象,最典型的大对象就是那种很长的字符
串以及数组,大对象对虚拟机的内存分配就是坏消息,尤其是一些朝生夕灭的短命大对象,写
程序时应避免。
长期存活的对象进入老年代
虚拟机给每个对象定义了一个对象年龄(Age)计数器,对象在 Survivor 区中每熬过一次
Minor GC,年龄就增加 1,当他的年龄增加到一定程度(默认是 15 岁), 就将会被晋升到
老年代中。
3.1 CPU 资源占用过高
1.top 查看当前 CPU 情况,找到占用 CPU 过高的进程 PID=123。
2.top -H -p123 找出两个 CPU 占用较高的线程,记录下来 PID=2345, 3456 转换为十六进制。
3.jstack -l 123 > temp.txt 打印出当前进程的线程栈。
4.查找到对应于第二步的两个线程运行栈,分析代码。
3.2 OOM 异常排查
1.使用 top 指令查询服务器系统状态。
2.ps -aux|grep java 找出当前 Java 进程的 PID。
3.jstat -gcutil pid interval 查看当前 GC 的状态。
4.jmap -histo:live pid 可用统计存活对象的分布情况,从高到低查看占据内存最多的对象。
5.jmap -dump:format=b,file= 文件名 [pid] 利用 Jmap dump。
6.使用性能分析工具对上一步 dump 出来的文件进行分析,工具有 MAT 等。
为了不影响观看只选取了其中的一部分,我也整理了一份 架构师全套视频教程 和关于java的系统化资料,从Javase- ssm-springcloud,包括java核心知识点、面试专题和20年最新的互联网真题、电子书等都有,想学习Java或者转行,大学生都非常实用,免费分享给大家~有需要的朋友可以点一点下方链接免费领取!
链接:点这里!!!暗号:CSDN