JVM技术八股文

JVM面试八股文,整理了出来。排版不太好!

目录

JVM入门部分

为什么要学习JVM?

你了解哪些JVM产品?

JVM的构成有哪几部分?

JVM类加载部分

你知道哪些类加载器?

为什么需要多个类加载器?

什么是双亲委派类加载模型?

双亲委派方式加载类有什么优势、劣势?

描述一下类加载时候的基本步骤是怎样的?

什么情况下会触发类的加载?

类加载时静态代码块一定会执行吗?

如何理解类的主动加载和被动加载?

为什么要自己定义类加载器,如何定义?

内存中一个类的字节码对象可以有多个吗?

JVM运行内存部分

JVM运行内存是如何划分的?

JVM中的程序计数器用于做什么?

JVM虚拟机栈的结构是怎样的?

JVM虚拟机栈中局部变量表的作用是什么?

JVM虚拟机栈中操作数栈的做用是什么?

JVM堆的构成是怎样的?

Java对象分配内存的过程是怎样的?

JVM年轻代幸存区设置的比较小会有什么问题?

JVM年轻代伊甸园区设置的比例比较小会有什么问题?

JVM堆内存为什么要分成年轻代和老年代?

项目中最大堆和初始堆的大小为什么推荐设置为一样的?

什么情况下对象会存储到老年代?

Java中所有的对象创建都是在堆上分配内存的?

如何理解JVM方法区以及它的构成是怎样的?

JDK8中Hotsport虚拟机的方法区内存在哪里?

什么是逃逸分析以及可以解决什么问题?

主观上如何判断一个对象是否发生了逃逸?

如何理解对象的标量替换,为什么要进行标量替换?

什么是内存溢出以及导致内存溢出的原因?

什么是内存泄漏以及导致内存泄漏的原因?

JAVA中的四大引用类型有什么特点?

JVM执行引擎部分

执行引擎的作用是什么?

JIT是什么,要做什么?

何为GC,为什么要GC?

如何判定对象是否为垃圾?

说说垃圾对象的回收策略?

你知道哪些GC算法?

JVM中有哪些垃圾回收器?

JAVA中的堆区为什么要分代?

服务频繁fullgc,younggc次数较少,可能原因?


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的构成有哪几部分?

  1. 类加载子系统(负责将类读取到内存、校验类的合法性、对类进行初始化)
  2. 运行时数据区(负责存储类信息、对象信息、以及执行计算的区域)
  3. 执行引擎(负责从指定地址对应的内存中读取数据然后执行,同时还自带GC操作)
  4. 本地库接口(负责Java语言与其它语言之间进行通讯)

JVM类加载部分

你知道哪些类加载器?

  1. AppClassloader 应用加载器 一般就是加载用户本地类
  2. EXTClassLoader 拓展类加载器,负责加载EXT包中的类
  3. BootStrapClassLoader 负责加载基础类库中的类、如Object、String…
  4. 自定义类加载器:灵活指定加载源、对加载过程进行加密解密、自定义加载模式

为什么需要多个类加载器?

--多样化进行类加载吧 灵活指定加载源 、 自定义加载模式

什么是双亲委派类加载模型?

类的加载机制是 双亲委派加载模型。

双亲委派类加载模型可以理解为 从AppClassLoader 向上询问 是否已经加载过这个类 如果已经加载过、则不在进行加载。

假如没有加载过 则从 BootStrapClassLoader开始进行向下依次尝试 加载

假如可以 则进行加载

双亲委派方式加载类有什么优势、劣势?

优势:可以保证同一个类 在内存中 不会被多次加载。

劣势:对效率有影响、当有同包名 同名称的类时 会出现只加载一个类的情况。

描述一下类加载时候的基本步骤是怎样的?

  1. 查找(从指定路径中找到包名+类名对应的文件。 从硬盘找到)
  2. 读取(通过io字节输入流对.java进行读取)
  3. 校验(对内存中的类进行校验 初始化)
  4. 生成Class文件(生成出来Class 对象)

什么情况下会触发类的加载?

  1. 直接使用类加载器ClassLoader的loadClass加载
  2. 使用Class类的 forName(“包名.类名”)进行反射加载
  3. 直接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对象分配内存的过程是怎样的?

  1. 编译器通过逃逸分析(JDK8后默认开启)判定对象在堆上还是栈上进行存储分配
  2. 假如是在堆上分配则先判断是否能存储在年轻代中 如果对象占用内存过大 则存储在老年代
  3. 如果年轻代的伊甸园区满了 则进行yongGC 把存活的对象进行回收到幸存区
  4. 如果yongGC后幸存区满了 则把对象存储到老年代中
  5. 如果老年代存储满了 则会进行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、方法出现了无线递归调用,可能会导致栈内存溢出

什么是内存泄漏以及导致内存泄漏的原因?

内存泄露:对象使用完后 本应该释放掉的内存,并没有被释放掉,即不用了的对象 还存在着引用。

常见的存储泄露有:

  1. 缓存使用不当(例如缓存中对象的引用都是强引用)
  2. 内部类的使用不当(例如实例内部类会默认保存外部类的引用)
  3. 大量的IO 链接操作 没有得到及时的关闭。
  4. 大量的使用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. 引用计数法(每个对象都有一个引用计数器,当被存在引用时 这个计数器就会加1,没有引用时 则为0)
  2. 可达性分析(从一些GC Root对象可以无法找到这个对象,此对象就是垃圾)

说说垃圾对象的回收策略?

从两个角度进行分析

算法:

    标记清除法:先扫描内存中活着的对象并标记,再扫描并清楚内存中未标记的对象。

    标记复制法:先准备两块内存 一块储存对象 一块空闲。GC执行时将活着的对象复制到空闲区,之后把原来的区删除

    标记整理法:扫描活着的对象 并往一侧移动,之后将剩余没有被移动的空间清除。

线程策略:

    单线程:回收垃圾的线程只有一个

    多线程:并行(多核cpu同时执行GC线程) 或 并发(执行GC的用户线程并发执行)

你知道哪些GC算法?

GC算法:

    标记清除法:先扫描内存中活着的对象并标记,再扫描并清楚内存中未标记的对象。

    标记复制法:先准备两块内存 一块储存对象 一块空闲。GC执行时将活着的对象复制到空闲区,之后把原来的区删除

    标记整理法:扫描活着的对象 并往一侧移动,之后将剩余没有被移动的空间清除。

JVM中有哪些垃圾回收器?

串行垃圾回收器

并行垃圾回收器

并发垃圾回收器

G1垃圾回收器

JAVA中的堆区为什么要分代?

堆区分为年轻代 老年代。年轻代又按照8:1:1 的比例分为 伊甸园区 幸存区1 幸存区2 。 这样的设置最终的目的是为了减少GC的次数,从而提高系统执行的效率。

服务频繁fullgc,younggc次数较少,可能原因?

  1. 对象创建时所占用的空间过大 伊甸园区存放不下,直接被存放到老年代中,老年代存储满时 会进行FULLGC,而没有过多的YOUNG GC
  2. 发生了内存泄露,创建了过多的没有指向的对象,young GC一直回收不掉,则会频繁的存入老年代中,从而触发FULL GC

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值