JVM面试题

JVM相关面试题

1、内存模型以及分区,需要详细到每个区放什么?

JVM运行时数据分区有5块主要为: 方法区(线程共享)、堆区(线程共享)、虚拟机栈区(线程私有)、本地方法栈(线程私有)、程序计数器(线程私有)

  • 方法区: 主要是存储类信息、常量、静态变量、即时编译器编译后的代码等数据
  • : 初始化的对象,几乎所有的对象实例和数组都要在堆上分配

  • 虚拟机栈: 栈的结构是栈帧组成的,每个方法在执行的时候都会创建一个栈帧,每帧上都存储局部变量表,操作数栈,方法出口等信息,每个方法从调用到执行完毕都对应着相应一个栈帧在虚拟机栈中入栈和出栈。

  • 本地方法栈: 和虚拟机栈原理相似,但主要为虚拟机中native层方法服务

  • 程序计数器: 记录当前线程执行字节码的行号指示器

2、堆里面的分区:新生代(eden、survival-from、survival-to)+老年代,各自的特点。

堆里面分为新生代和老生代(java8 取消了永久代,采用了 Metaspace):

新生代包含 (Eden+Survivor-from区+Survivor-to区)按照8:1:1比例分配,内存回收时,如果用的是复制算法,从 Survivor-from 复制到 Survivor-to,当经过一次或者多次 GC 之后,存活下来的对象会被移动到老年代,当 JVM 内存不够用的时候,会触发Full GC,清理 JVM 老年区

当新生区满了之后会触发 YGC, 先把存活的对象放到其中一个 Survivor 区,然后进行垃圾清理。因为如果仅仅清理需要删除的对象,这样会导致内存碎片,因此一般会把 Eden 进行完全的清理,然后整理内存。那么下次 GC 的时候,就会使用下一个 Survive,这样循环使用。如果有特别大的对象,新生代放不下,就会使用老年代的担保,直接放到老年代里面。因为 JVM 认为,一般大对象的存活时间一般比较久远。

3、对象创建方法,对象的内存分配,对象的访问定位。

使用new创建一个对象

4、GC 的两种判定方法:

引用计数法:

指的是如果某个地方引用了这个对象就+1,如果失效了就-1,当为 0 就
会回收但是 JVM 没有用这种方式,因为无法判定相互循环引用(A 引用 B,B 引用 A)
而导致引用计数不为0的情况。

可达性分析法: 通过一种 GC ROOT 的对象(方法区中静态变量引用的对象等-static 变量)来判断,如果从每个GC ROOT节点开始由上向下遍历每一条引用链,如果某个对象都不在这些引用链中,说明此对象是可回收的

5、SafePoint 是什么

SafePoint俗称”安全点“,在OopMap的协助下,很容易准确完成GC Roots的枚举,但是OopMap中的内容变化指令变化太多,无法为每条指令都生成对应的OopMap,所以只能是在”特定的位置“记录信息,这些特定位置就是安全点;也就是程序执行时并非在所有地方都能停下来GC(GC停顿),只有达到安全点时才会触发GC停顿。

SafePoint安全点的选取:

  • 循环的末尾 (防止大循环的时候一直不进入safepoint,而其他线程在等待它进入safepoint)

  • 方法返回前

  • 方法调用之后

  • 抛出异常的位置

6、GC的三种收集方法: 标记-清除、标记-整理、复制算法的原理与特点,分别用 在什么地方,如果让你优化收集方法,有什么思路?

标记-清除: 先标记,标记完毕之后再清除,效率不高,会产生碎片; 一般用于生命周期比较长的老年代对象回收

复制算法: 复制算法将可用内存按容量划分为相等的两部分,然后每次只 使用其中的一块,当一块内存用完时,就将还存活的对象复制到第二块内存上,然 后一次性清楚完第一块内存,再将第二块上的对象复制到第一块。回收效率高且没有碎片但是内存代价高,可是如果根据一定比例分配,还是能达到一定利用率的。比如一般用于新生代中内存,新生代特点,大部分对象都会销毁,只有小部分存活对象,所以新生代按照8:1:1比例再次分为: Eden、Survivor-from、Survivor-to区,每一次都会有一块预留Survivor内存区给,存活下来少部分对象会被复制到这里。如果存活部分对象Survivor内存区满了,就直接进入老年代区。这样下来新生代区复制算法效率可以达到90%

标记整理: 标记完毕之后,让所有存活的对象向一端移动,不会产生碎片,但效率不高;一般用于生命周期比较长的老年代对象回收

7、GC 收集器有哪些? CMS 收集器与 G1 收集器的特点。

串行收集器(Serial收集器): 串行收集器使用一个单独的线程进行收集,GC时服务有停顿时间-新生代收集器,采用复制算法

ParNew收集器: 也就是Serial收集器的多线程版本,使用多条线程进行垃圾回收。-新生代收集器,采用复制算法

Parallel Scavenge收集器: 也是新生代收集器,多线程收集,相比ParNew收集尽可能缩短了GC停顿的时间-新生代收集器,采用复制算法

CMS 收集器是基于“标记—清除”算法实现的,经过多次标记才会被清除-老年代收集器

G1 从整体来看是基于“标记—整理”算法实现的收集器,从局部(两个 Region 之间)上来看是基于“复制”算法实现的-老年代收集器

8、Minor GC 与 Full GC 分别在什么时候发生?

YGC(MGC): 对新生代堆进行gc。频率比较高,因为大部分对象的存活寿命较短,在新生代里被回收。性能耗费较小。

FGC(Full GC):全堆范围的gc。默认堆空间使用到达80%(可调整)的时候会触发fgc。以我们生产环境为例,一般比较少会触发fgc,有时10天或一周左右会有一次。

什么时候执行YGC和FGC?

新生代Eden区内存不够用时候发生 MGC 也叫 YGC,

old空间(老年代)不足,perm空间(永久代)不足,调用方法System.gc() ,ygc时的悲观策略, dump live的内存信息时(jmap –dump:live),都会执行full gc

9、几种常用的内存调试工具:jmap、jstack、jconsole、jhat

  • jstack 可以看当前栈的情况

  • jmap 查看内存

  • jhat dump 堆的信息

  • mat(eclipse 的也要了解一下)

10、如何判断一个对象是否存活?(或者GC对象的判定方法)

判断一个对象是否存活有两种方法:

  • 引用计数法 所谓引用计数法就是给每一个对象设置一个引用计数器,每当有一个地方引用这个对象 时,就将计数器加一,引用失效时,计数器就减一。当一个对象的引用计数器为零时,说 明此对象没有被引用,也就是“死对象”,将会被垃圾回收. 引用计数法有一个缺陷就是无法解决循环引用问题,也就是说当对象 A 引用对象 B,对象 B 又引用者对象 A,那么此时 A,B 对象的引用计数器都不为零,也就造成无法完成垃圾回 收,所以主流的虚拟机都没有采用这种算法。

  • 可达性算法(引用链法)
    该算法的思想是:从一个被称为 GC Roots 的对象开始向下搜索,如果一个对象到 GC Roots 没有任何引用链相连时,则说明此对象不可用。

    在 java 中可以作为 GC Roots 的对象有以下几种:

    虚拟机栈(栈帧中的本地变量表)中引用的对象、方法区类静态属性引用的对象、方法区常量池引用的对象、本地方法栈 JNI 引用的对象。

    一个对象比不一 定会被回收。当一个对象不可达 GC Root 时,这个对象并 不会立马被回收,而是出于一个死缓的阶段,若要被真正的回收需要经历两次标记 如果对象在可达性分析中没有与 GC Root 的引用链,那么此时就会被第一次标记并且进行 一次筛选,筛选的条件是是否有必要执行 finalize()方法。当对象没有覆盖 finalize()方法,或者已被虚拟机调用过,那么就认为是没必要的
    如果该对象有必要执行 finalize()方法,那么这个对象将会放在一个称为 F-Queue 的对队 列中,虚拟机会触发一个 Finalize()线程去执行,此线程是低优先级的,并且虚拟机不会承 诺一直等待它运行完,这是因为如果 finalize()执行缓慢或者发生了死锁,那么就会造成 F- Queue 队列一直等待,造成了内存回收系统的崩溃。GC 对处于 F-Queue 中的对象进行 第二次被标记,这时,该对象将被移除”即将回收”集合,等待回收。

11、简述 java 垃圾回收机制?

在 java 中,程序员是不需要显示的去释放一个对象的内存的,而是由虚拟机自行执行。在 JVM 中,有一个垃圾回收线程,它是低优先级的,在正常情况下是不会执行的,只有在虚 拟机空闲或者当前堆内存不足时,才会触发执行,扫面那些没有被任何引用的对象,并将 它们添加到要回收的集合中,进行回收。

12、java 中垃圾收集的方法有哪些?

  • 标记-清除: 这是垃圾收集算法中最基础的,根据名字就可以知道,它的思想就是标记哪些要被 回收的对象,然后统一回收。这种方法很简单,但是会有两个主要问题:

    1.效率不高,标记和清除的效率都很低;

    2.会产生大量不连续的内存碎片,导致以后程序在 分配较大的对象时,由于没有充足的连续内存而提前触发一次 GC 动作。

  • 复制算法: 为了解决效率问题,复制算法将可用内存按容量划分为相等的两部分,然后每次只 使用其中的一块,当一块内存用完时,就将还存活的对象复制到第二块内存上,然 后一次性清楚完第一块内存,再将第二块上的对象复制到第一块。但是这种方式,内存的代价太高,每次基本上都要浪费一般的内存。 于是将该算法进行了改进,内存区域不再是按照 1:1 去划分,而是将内存划分为 8:1:1 三部分,较大那份内存交 Eden 区,其余是两块较小的内存区叫 Survior 区。 每次都会优先使用 Eden 区,若 Eden 区满,就将对象复制到第二块内存区上,然 后清除 Eden 区,如果此时存活的对象太多,以至于 Survivor 不够时,会将这些对 象通过分配担保机制复制到老年代中。(java 堆又分为新生代和老年代)

  • 标记-整理 该算法主要是为了解决标记-清除,产生大量内存碎片的问题;当对象存活率较高 时,也解决了复制算法的效率问题。它的不同之处就是在清除对象的时候现将可回 收对象移动到一端,然后清除掉端边界以外的对象,这样就不会产生内存碎片了。

  • 分代收集 现在的虚拟机垃圾收集大多采用这种方式,它根据对象的生存周期,将堆分为新生 代和老年代。在新生代中,由于对象生存期短,每次回收都会有大量对象死去,那 么这时就采用复制算法。老年代里的对象存活率较高,没有额外的空间进行分配担 保,所以可以使用标记-整理 或者 标记-清除。

13、java中的内存模型

java 内存模型(JMM)是线程间通信的控制机制.

JMM 定义了主内存和线程之间抽象关系。 线程之间的共享变量存储在主内存(main memory)中,每个线程都有一个私有的本地 内存(local memory),本地内存中存储了该线程以读/写共享变量的副本.

本地内存是JMM 的一个抽象概念,并不真实存在。它涵盖了缓存,写缓冲区,寄存器以及其他的硬件和编译器优化。

14、java 类加载过程(类的生命周期)

java 类加载需要经历一下 7 个阶段:

  • 加载
    加载时类加载的第一个过程,在这个阶段,将完成一下三件事情:

    1. 通过一个类的全限定名获取该类的二进制流。

    2.将该二进制流中的静态存储结构转化为方法去运行时数据结构。

    1. 在内存中生成该类的 Class 对象,作为该类的数据访问入口。
  • 验证

    验证的目的是为了确保 Class 文件的字节流中的信息不回危害到虚拟机.在该阶段主要完成 以下四种验证:

    1. 文件格式验证:验证字节流是否符合 Class 文件的规范,如主次版本号是否在当前虚拟 机范围内,常量池中的常量是否有不被支持的类型.
    2. 元数据验证:对字节码描述的信息进行语义分析,如这个类是否有父类,是否集成了不 被继承的类等。
    3. 字节码验证:是整个验证过程中最复杂的一个阶段,通过验证数据流和控制流的分析, 确定程序语义是否正确,主要针对方法体的验证。如:方法中的类型转换是否正确,跳转 指令是否正确等。
    4. 符号引用验证:这个动作在后面的解析过程中发生,主要是为了确保解析动作能正确执 行。
  • 准备

    准备阶段是为类的静态变量分配内存并将其初始化为默认值,这些内存都将在方法区中进 行分配。准备阶段不分配类中的实例变量的内存,实例变量将会在对象实例化时随着对象 一起分配在 Java 堆中。

  • 解析

    该阶段主要完成符号引用到直接引用的转换动作。解析动作并不一定在初始化动作完成之 前,也有可能在初始化之后。

  • 初始化

    初始化时类加载的最后一步,前面的类加载过程,除了在加载阶段用户应用程序可以通过 自定义类加载器参与之外,其余动作完全由虚拟机主导和控制。到了初始化阶段,才真正 开始执行类中定义的 Java 程序代码。

  • 使用

  • 卸载

15、类加载器双亲委派模型机制

当一个类收到了类加载请求时,不会自己先去加载这个类,而是将其委派给父类,由父类去加载,如果此时父类不能加载,反馈给子类,由子类去完成类的加载。

16、什么是类加载器,类加载器有哪些?

  • 启动类加载器(Bootstrap ClassLoader)用来加载 java 核心类库,无法被 java 程序直接 引用。

  • 扩展类加载器(extensions class loader):它用来加载 Java 的扩展库。Java 虚拟机的 实现会提供一个扩展库目录。该类加载器在此目录里面查找并加载 Java 类。

  • 系统类加载器(system class loader):它根据 Java 应用的类路径(CLASSPATH) 来加载 Java 类。一般来说,Java 应用的类都是由它来完成加载的。可以通过 ClassLoader.getSystemClassLoader()来获取它。

17、简述 java 内存分配与回收策率以及 Minor GC 和 Major GC

  • 对象优先在堆的 Eden 区分配。

  • 大对象直接进入老年代.

  • 长期存活的对象将直接进入老年代.

  • 当 Eden 区没有足够的空间进行分配时,虚拟机会执行一次 Minor GC(YGC). Minor Gc 通 常发生在新生代的 Eden 区,在这个区的对象生存期短,往往发生 Gc 的频率较高, 回收速度比较快;

  • Full Gc/Major GC 发生在老年代,一般情况下,触发老年代 GC 的时候不会触发 Minor GC,但是通过配置,可以在 Full GC 之前进行一次 Minor GC 这样可以加快老年代的回收速度。

18、哪些情况下对象会被垃圾回收机制处理掉

1.所有实例都没有活动线程访问。
2.没有被其他任何实例访问的循环引用实例。

3.Java 中有不同的引用类型。判断实例是否符合垃圾收集的条件都依赖于它的引用类型。

19、JVM老年代和新生代的比例

Java 中的堆是 JVM 所管理的最大的一块内存空间,主要用于存放各种类的实例对象


在 Java 中,被划分成两个不同的区域:新生代 ( Young )、老年代 ( Old )。

新生代 ( Young ) 又被划分为三个区域Eden、From Survivor、To Survivor。
这样划分的目的是为了使 JVM 能够更好的管理堆内存中的对象,包括内存的分配以及回收。


堆大小 = 新生代 + 老年代。

其中,堆的大小可以通过参数 –Xms、-Xmx 来指定。
(本人使用的是 JDK1.6,以下涉及的 JVM 默认值均以该版本为准。)


默认的,新生代 ( Young ) 与老年代 ( Old ) 的比例的值为 1:2 ( 该值可以通过参数 –XX:NewRatio 来指定 ),即:新生代 ( Young ) = 1/3 的堆空间大小老年代 ( Old ) = 2/3 的堆空间大小

其中,新生代 ( Young ) 被细分为 Eden 和 两个 Survivor 区域,这两个 Survivor 区域分别被命名为 from 和 to,以示区分。
默认的,Edem : from : to = 8 : 1 : 1 ( 可以通过参数 –XX:SurvivorRatio 来设定 ),即: Eden = 8/10 的新生代空间大小,from = to = 1/10 的新生代空间大小。
JVM 每次只会使用 Eden 和其中的一块 Survivor 区域来为对象服务,所以无论什么时候,总是有一块 Survivor 区域是空闲着的。
因此,新生代实际可用的内存空间为 9/10 ( 即90% )的新生代空间

20、Art和Dalvik对比

  • ART 在应用安装时会进行预编译 (ahead of time compilation, AOT),将字节码编译成机器码并存储在本地,这样每次运行程序时就无需编译了,提升了效率。

    缺点是:1).安装耗时更长了;2).占用更多存储空间。

    7.0 之后,ART 引入 JIT,安装时不会将字节码全部编译成机器码,而是运行时将热点代码编译成机器码。

  • DVM 是为 32 位 CPU 设计的,ART 支持 64 位且兼容 32 位 CPU.

  • ART 对垃圾收集机制进行了改进,将 GC 暂停由 2 次改成了 1 次等。

  • ART 的运行时堆空间划分与 DVM 不同

  • 基于的架构不同:DVM 基于寄存器,相比于 JVM(基于栈),执行速度更快(因为无需到栈中读取数据)。

  • 执行的字节码不同:DVM 在执行的是 dex 文件,经过 class 经 dx 转换之后的。dex 会对 class 进行优化,整个 class,取出冗余信息,加快加载方式。

  • DVM 允许在有限的空间内同时运行多个进程

  • DVM 由 Zygote 创建和初始化
    Zygote 是一个 DVM 进程,当需要创建一个应用程序时,Zygote 通过 fork 自身来创建新的 DVM 实例。

  • DVM 有共享机制,不同应用在运行时可以共享相同的类。

  • DVM 早期没有使用 JIT 编译器,JIT 就是即时编译器,早期的 DVM 需要经过解释器将 dex 码编译成机器码,效率不高。2.2 之后使用了 JIT,会对热点代码进行编译,生成本地机器码,下次执行到相同的逻辑时,可以直接执行本地机器码,无需每次编译。

21、垃圾回收机制与调用System.gc()区别

System.gc() 函数的作用只是提醒虚拟机:开发者希望进行一次垃圾回收。但是它不能保证垃圾回收一定会进行,而且具体什么时候进行是取决于具体的虚拟机的,不同的虚拟机有不同的对策。

22、哪些对象可以被看做是GC Roots呢?

  • 虚拟机栈(栈帧中的本地变量表)中引用的对象;

  • 方法区中的类静态属性引用的对象,常量引用的对象;

  • 本地方法栈中JNI(Native方法)引用的对象;

23、对象不可达,一定会被垃圾收集器回收么?

一个对象比不一 定会被回收。当一个对象不可达 GC Root 时,这个对象并不会立马被回收,而是出于一个死缓的阶段,若要被真正的回收需要经历两次标记,如果对象在可达性分析中没有与 GC Root 的引用链,那么此时就会被第一次标记并且进行 一次筛选,筛选的条件是是否有必要执行finalize()方法。当对象没有覆盖 finalize()方法,或者已被虚拟机调用过,那么就认为是没必要的。

如果该对象有必要执行 finalize()方法,那么这个对象将会放在一个称为 F-Queue 的对队 列中,虚拟机会触发一个 Finalize()线程去执行,此线程是低优先级的,并且虚拟机不会承 诺一直等待它运行完,这是因为如果 finalize()执行缓慢或者发生了死锁,那么就会造成 F- Queue 队列一直等待,造成了内存回收系统的崩溃。GC 对处于 F-Queue 中的对象进行 第二次被标记,这时,该对象将被移除”即将回收”集合,等待回收。


   转载规则


《JVM面试题》 mikyou 采用 知识共享署名 4.0 国际许可协议 进行许可。
 上一篇
设计模式之单例模式 设计模式之单例模式
设计模式之单例模式一、模式介绍 单例模式是开发者最为常见的一种设计模式,也是23种设计模式中最为简单一种设计模式。大部分的开发者都知道它的使用和原理。单例模式顾名思义就是在应用这个模式时,单例对象的类必须是只有一个对象实例存在。在一些应用场
2019-12-31
下一篇 
Android中AsyncTask源码解析 Android中AsyncTask源码解析
Android中AsyncTask源码解析一、AsyncTask描述 AsyncTask是一种轻量级的异步任务处理类,它可以在线程池中执行后台任务,并且能把执行进度以及最终在线程池中执行异步任务结果传递主线程进行UI刷新,也就是我们常说的线
2019-12-29