JVM面试八股文,整理了出来。排版不太好!
目录
JVM入门部分
为什么要学习JVM?
JVM是java虚拟机,java程序可以跨平台运行 都要归功于jvm。学习jvm可以使我们更好的理解java,以便于我们进行一些性能调优。
深入学习jvm后,我们可以通过jvm 进行一些线上问题,比如 java程序热部署。
你了解哪些JVM产品?
1、oracle 公司的 hotspot ,在开发过程中应用的是比较多的,比较大的特点是 GC机制,分为频繁GC 和 FULL、 GC(大GC)。我们知道 GC 线程执行时 程序是会暂停的,所以如果想优化程序 可以从 减少FULL GC次数方面下手。(注:hotspot是sun公司的产品 现已被oracle收购)
2、oracle 公司的 JRockit, 可以看做是兼容标准的JDK基础上的JVM,同原有的JVM相比,JRockit 声称在速度上有显著提升(甚至超过70%)和硬件成本的减少(超50%).
JRockit 对线程 和 网络方面 都做了大量的优化和技巧的工作。
JRockit 的GC机制 和 hotspot 的GC机制有很大的不同,其中之一就是没有频繁GC和FULL GC的概念,而且每次GC的操作时间较长。
3、IBM 公司的J9 , 这三个jvm产品是使用者最多的 jvm产品。市场定位与HotSpot接近,服务器端、桌面应用、嵌入式等多用途VM。
IBM J9VM 虚拟机的职责分离 与 模块化 做得比HotSpot 更优秀。
2017年左右IBM 将J9VM进行完全开源,并命名为OpenJ9 VM ,后捐给了Eclipse 并重新命名为 Eclipse OpenJ9。如果为了学习虚拟机技术 而去阅读源码,更加模块化的 OpenJ9 是比HotSpot 更好的选择。如果是为了使用java虚拟机时多一种选择,那可以通过AdoptOpenJDK来获得采用OpenJ9 搭配上 OpenJDK 其他的类库组成完整的JDK来使用。
4、阿里巴巴公司的 TaoBaoVM ,由AliJVM团队发布,国内使用Java最强大的公司。基于OpenJDK开发了自己定制的的 AlibabaJDK,简称AJDK。是整个阿里java体系的基石。
TaoBao JVM 基于OpenJDK HotSpot JVM 开发,发布的国内第一个优化、深度定制确开源的高性能服务器版Java虚拟机。
TaoBao VM 有什么优化呢? 创新的GCIH 技术 实现了off-heap 即 将生命周期较长的java对象 从heap中 移到heap之外,并且GC 不能管理GCIH内部的java对象,以此达到降低GC的回收频率 和 提升GC回收效率的目的。
Taobaovm 硬件严重依赖intel的cpu ,损失了兼容性,但提高了性能。
总结:对于一般的应用而言,oracle的 hotspot 就足够使用了。 对于对性能要求很高的应用而言,建议使用oracle (BEA)的 JRockit vm,如java版的游戏服务器。对于大规模的公司而言,建议实用IBM的 OpenJ9 vm ,它是一整套的解决方案,后期维护方便。
JVM的构成有哪几部分?
- 类加载子系统(负责将类读取到内存、校验类的合法性、对类进行初始化)
- 运行时数据区(负责存储类信息、对象信息、以及执行计算的区域)
- 执行引擎(负责从指定地址对应的内存中读取数据然后执行,同时还自带GC操作)
- 本地库接口(负责Java语言与其它语言之间进行通讯)
JVM类加载部分
你知道哪些类加载器?
- AppClassloader 应用加载器 一般就是加载用户本地类
- EXTClassLoader 拓展类加载器,负责加载EXT包中的类
- BootStrapClassLoader 负责加载基础类库中的类、如Object、String…
- 自定义类加载器:灵活指定加载源、对加载过程进行加密解密、自定义加载模式
为什么需要多个类加载器?
--多样化进行类加载吧 灵活指定加载源 、 自定义加载模式
什么是双亲委派类加载模型?
类的加载机制是 双亲委派加载模型。
双亲委派类加载模型可以理解为 从AppClassLoader 向上询问 是否已经加载过这个类 如果已经加载过、则不在进行加载。
假如没有加载过 则从 BootStrapClassLoader开始进行向下依次尝试 加载
假如可以 则进行加载
双亲委派方式加载类有什么优势、劣势?
优势:可以保证同一个类 在内存中 不会被多次加载。
劣势:对效率有影响、当有同包名 同名称的类时 会出现只加载一个类的情况。
描述一下类加载时候的基本步骤是怎样的?
- 查找(从指定路径中找到包名+类名对应的文件。 从硬盘找到)
- 读取(通过io字节输入流对.java进行读取)
- 校验(对内存中的类进行校验 初始化)
- 生成Class文件(生成出来Class 对象)
什么情况下会触发类的加载?
- 直接使用类加载器ClassLoader的loadClass加载
- 使用Class类的 forName(“包名.类名”)进行反射加载
- 直接A a = new A() 进行动态加载
类加载时静态代码块一定会执行吗?
不一定,静态代码块是否执行取决于是否执行了类的初始化操作
Ex: ClassLoader.getSystemClassLoader()//获取类加载器
.loadClass(“com.java.jvm.loader”)//loader类中的静态代码块不会被执行
如何理解类的主动加载和被动加载?
主动加载 通过访问本类 属性 或 方法 进行的加载 会初始化
被动加载 通过本类访问对应的父类 属性 方法时,本类属于被动加载。 不会初始化
为什么要自己定义类加载器,如何定义?
当系统提供的类加载器不满足我们的使用要求时,我们就要自己定义类加载器。
比如想打破双亲委派模型的加载机制时,就要重新定义
如何定义?
直接或间接继承ClassLoader
重写相关方法:findClass() 有 查找 校验 初始化 创建字节码对象的操作
loadClass() 一般不需要重写、如果想打破双亲委派加载模型则重写
内存中一个类的字节码对象可以有多个吗?
可以。
即使是同一个类,当他的类加载器不同时 生成的Class对象也不同。虽然有双亲委派模型 但是不同的加载器调用 也会有不同的Class对象
JVM运行内存部分
JVM运行内存是如何划分的?
线程共享区:
堆:
作用:存储对象
落地:
年轻代:
伊甸园区:新建的对象一般存储在这里
幸存区:有两个, 没有GC前都是空的 GC后有一块是空的 存活下来的对象存储在幸存区。
老年代:
大GC后存储到老年代、(多次GC没有回收掉的对象)
如果新建的对象内存占用过大 也会存储到老年代
问题:
堆内存溢出 – 对象太多 对象太大
堆内存泄露 – 解决 能用静态内部类不用实例内部类,实例内部类会默认保存外部类引用、减少static变量应用
方法区:
作用:存储类的字节码信息、存储常量池信息
落地:JDK1.8Metaspace – 堆外内存
问题:类太多了导致内存溢出
线程私有区:
栈:
作用:存储局部变量、方法参数、执行计算、存储方法返回值
落地:
Java方法栈:
局部变量表 – 存储方法内部定义的局部变量
操作数栈 - 方法内部进行的计算操作
动态连接
方法返回值
其它
本地方法栈
问题:栈内存溢出 – 一般是出现了无线递归
寄存器:
作用:记录线程要执行的下一条指令地址值
落地:JAVA寄存器、线程私有
问题:关于内存溢出 寄存器不会出现
JVM中的程序计数器用于做什么?
用来记录当前线程要执行的下一条指令的偏移量地址,每一个线程都有一个程序计数器
JVM虚拟机栈的结构是怎样的?
操作数栈 -(用于执行计算)
局部变量表 -(存储方法内部的局部变量)
方法返回值 -(存储方法返回值)
动态连接 -(方法中要访问的一些常量池数据,要调用的方法都
会对应出一个连接)
JVM虚拟机栈中局部变量表的作用是什么?
局部变量表 – 存储方法内部的局部变量
JVM虚拟机栈中操作数栈的做用是什么?
操作数栈 – 进行执行计算
JVM堆的构成是怎样的?
年轻代:伊甸园区、幸存区(2个)
老年代:
Java对象分配内存的过程是怎样的?
- 编译器通过逃逸分析(JDK8后默认开启)判定对象在堆上还是栈上进行存储分配
- 假如是在堆上分配则先判断是否能存储在年轻代中 如果对象占用内存过大 则存储在老年代
- 如果年轻代的伊甸园区满了 则进行yongGC 把存活的对象进行回收到幸存区
- 如果yongGC后幸存区满了 则把对象存储到老年代中
- 如果老年代存储满了 则会进行FullGC,当FullGC执行后还无法存储则抛出异常
JVM年轻代幸存区设置的比较小会有什么问题?
伊甸园区对象越来越多的时候,会频繁的yongGC,当幸存区内存太小了,则进行回收到老年代。老年代满了则执行FullGC
影响程序的执行效率
JVM年轻代伊甸园区设置的比例比较小会有什么问题?
还是会影响系统执行效率。会增加gc的次数。当GC执行的时候用户线程会短暂的暂停。
JVM堆内存为什么要分成年轻代和老年代?
通过分代的设计 减少GC的次数,提高系统的性能。
项目中最大堆和初始堆的大小为什么推荐设置为一样的?
Jvm调优中,为了避免程序在GC前后 带来的内存变化,所以推荐设置成一样的。如果在GC后 扩充了初始堆的内存,这样带来的系统开销可能是很大的,所以推荐开始时 即设置为最大值(阿里开发手册中也是推荐设置为一样)
什么情况下对象会存储到老年代?
1、如果初始化时 占用内存过大,年轻代没有足够的空间储存 ,则会直接储存到老年代中。
2、年轻代反复GC后 幸存区存储不下,则会储存到老年代
3、年轻代反复GC后,依然没有被GC掉(默认是15次后)。
Java中所有的对象创建都是在堆上分配内存的?
JDK1.8 后 jvm出现了逃逸算法,逃逸了的对象 ,会在堆中分配,如果为逃逸的对象,会直接存储在栈上,当然 只能是很小的对象,如果占用内存超过栈内存 则未逃逸对象还是会储存在堆中
如何理解JVM方法区以及它的构成是怎样的?
不同的版本称呼方法区的名称也不同,jdk1.7中称之为 持久代、jdk1.8中称之为 元空间metaspace.这个元空间 MetaSpace 可以是不占用堆中的内存的。
Jvm中方法区的作用有 存储类的字节码信息 、 存储常量池信息
JDK8中Hotsport虚拟机的方法区内存在哪里?
Jdk1.7中HotSpot的方法区被称之为永久代
Jdk1.8中hotspot删除了永久代的概念,改为 元空间MetaSpace
元空间不受jvm内存的影响 受系统内存的影响,存储在系统内存中。
什么是逃逸分析以及可以解决什么问题?
逃逸分析本质上是一种数据分析算法,基于这个算法判断对象是否发生了逃逸。未逃逸的对象可以直接分配在栈上 如果内存足够的话。
这样可以减少在堆上的内存分配,从而减少了GC的次数。使得程序更优化 提高执行效率。
主观上如何判断一个对象是否发生了逃逸?
如果分配在堆上则发生了逃逸。如果直接分配在栈 上 则是未发生逃逸。
如何理解对象的标量替换,为什么要进行标量替换?
标量替换是一种将对象打散分配在栈上的技术(将对象中成员以局部变量方式进行设计)
减少对象在堆中创建次数,减低GC 频率 从而提高系统执行效率。
什么是内存溢出以及导致内存溢出的原因?
内存中剩余空间 不足以给新对象分配空间了,继续分配的话 就会出现内存溢出。
出现内存溢出的原因可能有:
1、创建的对象太大,直接超过了伊甸园区 老年代 中的内存。
2、创建的对象太多,又有大量的内存泄露。
3、方法区的类太多,没有足够的空间存储新的类型。
4、方法出现了无线递归调用,可能会导致栈内存溢出
什么是内存泄漏以及导致内存泄漏的原因?
内存泄露:对象使用完后 本应该释放掉的内存,并没有被释放掉,即不用了的对象 还存在着引用。
常见的存储泄露有:
- 缓存使用不当(例如缓存中对象的引用都是强引用)
- 内部类的使用不当(例如实例内部类会默认保存外部类的引用)
- 大量的IO 链接操作 没有得到及时的关闭。
- 大量的使用static变量(static的生命周期不同于正常使用的变量)
JAVA中的四大引用类型有什么特点?
Java中为了更好地控制对象的生命周期,提高对象对内存的敏感度,设计了四种引用类型,按其在内存中的生命力强弱,可以分为 强引用 弱引用 软引用 虚引用。
其中 强引用 的生命力最强。其他引用 引用的生命力依次递减。JVM的GC系统被触发时,回应为对象的引用不同,执行不同的回收逻辑
JVM执行引擎部分
执行引擎的作用是什么?
JVM主要的任务 是装载字节码文件,但是字节码文件并不能直接被系统运行,需要被JVM加载 解释一下 才可以运行,那这个时候就轮到 执行引擎登场了。想要让JAVA程序运行起来,就必须需要jvm的执行引擎将class对象中的字节码 解释成 机器底层语言、即为将字节码解释/编译 成对应的计算机平台能读懂的指令。
执行引擎的执行过程如下:
执行引擎执行的过程中依赖于程序计数器中的地址 找到所对应的栈 地址。
在方法的执行过程中,通过栈中的局部变量表中储存的信息找到堆中的实例。
JIT是什么,要做什么?
谈论JIT之前,我们先提起解释器的一个概念。什么是解释器呢,java设计的初衷是跨平台执行 即一次编写 多次运行,想要实现跨平台性,就运用到了解释器。可以把解释器理解成“翻译者”即多个平台都可以使用。那么使用到解释器会有什么影响呢?会影响到系统的执行效率。
解释器需要在执行代码时 进行纯软件代码 模拟字节码执行,效率十分低。现在解释器的执行已经沦为低效的代名词。针对该问题,jvm平台支持一种称为即时编译的技术。它的目的是避免函数被解释执行,而是将整个函数体 编译为机器码。每次执行时只执行编译后的机器码,这样实现了提高程序的执行效率。
JIT是什么呢? JIT是编译器 可以实现字节码的实时编译。这种即时编译的技术,可以提高程序的执行效率。
那么为什么不把解释器 抛弃了 直接使用JIT编译器呢?
JVM执行代码主要有两种方式:
解释执行:一段代码,解释一行 执行一行。即解释器逐行的将字节码解释成机器语言 并执行
编译执行:事先已经被编译成机器码,直接执行,不需要解释。即jvm读取到字节码文件后 JIT直接把字节码文件编译成机器语言 不需要解释直接由CPU执行。
虽然JIT编译器能提高程序的执行效率,但是在执行机器指令之前,JIT还是要欲将字节码文件转成机器语言,因此造成的响应时间较长,而解释器可以直接 对字节码进行解释执行。
因此 HotSpotVM 采取解释器 、JIT共存的方式,在JVM执行过程中。二者相互协作,各自取长补短,尽力的去权衡编译本地代码所需的时长 和 直接解释代码所需的时长,保证执行的效率
何为GC,为什么要GC?
GC称之为垃圾回收。在JVM的执行引擎中 自带一个GC系统,此系统会按照一定的算法对内存进行监控 和 垃圾回收
如果程序运行中 一直不进行GC 则会出现大量的堆内存泄露,即用完的对象 指向没有消除 其会一直占用着堆空间。
如何判定对象是否为垃圾?
- 引用计数法(每个对象都有一个引用计数器,当被存在引用时 这个计数器就会加1,没有引用时 则为0)
- 可达性分析(从一些GC Root对象可以无法找到这个对象,此对象就是垃圾)
说说垃圾对象的回收策略?
从两个角度进行分析
算法:
标记清除法:先扫描内存中活着的对象并标记,再扫描并清楚内存中未标记的对象。
标记复制法:先准备两块内存 一块储存对象 一块空闲。GC执行时将活着的对象复制到空闲区,之后把原来的区删除
标记整理法:扫描活着的对象 并往一侧移动,之后将剩余没有被移动的空间清除。
线程策略:
单线程:回收垃圾的线程只有一个
多线程:并行(多核cpu同时执行GC线程) 或 并发(执行GC的用户线程并发执行)
你知道哪些GC算法?
GC算法:
标记清除法:先扫描内存中活着的对象并标记,再扫描并清楚内存中未标记的对象。
标记复制法:先准备两块内存 一块储存对象 一块空闲。GC执行时将活着的对象复制到空闲区,之后把原来的区删除
标记整理法:扫描活着的对象 并往一侧移动,之后将剩余没有被移动的空间清除。
JVM中有哪些垃圾回收器?
串行垃圾回收器
并行垃圾回收器
并发垃圾回收器
G1垃圾回收器
JAVA中的堆区为什么要分代?
堆区分为年轻代 老年代。年轻代又按照8:1:1 的比例分为 伊甸园区 幸存区1 幸存区2 。 这样的设置最终的目的是为了减少GC的次数,从而提高系统执行的效率。
服务频繁fullgc,younggc次数较少,可能原因?
- 对象创建时所占用的空间过大 伊甸园区存放不下,直接被存放到老年代中,老年代存储满时 会进行FULLGC,而没有过多的YOUNG GC
- 发生了内存泄露,创建了过多的没有指向的对象,young GC一直回收不掉,则会频繁的存入老年代中,从而触发FULL GC