jvm面试题


https://blog.csdn.net/yanpenglei/article/details/119406377 参考

https://www.cnblogs.com/dolphin0520/p/3613043.html JVM的内存区域划分 写的很好

 

 

 

 

 

1.  JVM 默认使用的java 虚拟机是HotSpot。

 

2. Java的双亲委托机制是什么?

 

它的意思是,除了顶层的启动类加载器以外,其余的类加载器,在加载之前,都会委派给它的父加载器进行加载。这样一层层向上传递,直到祖先们都无法胜任,它才会真正的加载。

 

Java默认是这种行为。当然Java中也有很多打破双亲行为的骚操作,比如SPI(JDBC驱动加载),OSGI等。

优点:

避免类的重复加载, 确保一个类的全局唯一性

Java 类随着它的类加载器一起具备了一种带有优先级的层级关系, 通过这种
层级关系可以避免类的重复加载, 当父亲已经加载了该类时, 就没有必要子
ClassLoader 再加载一次

 

2.JVM使用“类”的生命周期是:

装载、链接、初始化、使用、卸载
1.装载:
定要找到Class文件所在的全路径,然后装载到内存中J,ava又是一门面向对象的开发语这个过程用到了类加载器 ClassLoader。

2.连接 该阶段又包含了验证、准备和解析3个过程,如下。

(1)验证

   校验.class文件的正确性。

(2)准备

     2.1给static静态变量分配内存,并初始化static的默认值。

    private static int a=10; 此时a变量会被赋值为默认值
     2.2 符号引用和直接引用
   符号引用:4f62 6a65 6374 ----->loading 二进制流是没有办法被cpu直接解析的 (.class  变量 方法等等里面全是二进制的)
  直接应用:0x00ab---0x00ba 物理内存
3.初始化:给static变量 赋予实际的值
private static int a=10; a=10;
4.使用:对象的初始化、对象的垃圾回收、对象的销毁.

3、GC Roots 有哪些?

1、 GC Roots 是一组必须活跃的引用。用通俗的话来说,就是程序接下来通过直接引用或者间接引用,能够访问到的潜在被使用的对象。

2、 GC Roots 包括:Java 线程中,当前所有正在被调用的方法的引用类型参数、局部变量、临时值等。也就是与我们栈帧相关的各种引用。所有当前被加载的 Java 类。Java 类的引用类型静态变量。运行时常量池里的引用类型常量(String 或 Class 类型)。JVM 内部数据结构的一些引用,比如 sun.jvm.hotspot.memory.Universe 类。用于同步的监控对象,比如调用了对象的 wait() 方法。JNI handles,包括 global handles 和 local handles。

3、 这些 GC Roots 大体可以分为三大类,下面这种说法更加好记一些:活动线程相关的各种引用。类的静态变量的引用。JNI 引用。

4、 有两个注意点:我们这里说的是活跃的引用,而不是对象,对象是不能作为 GC Roots 的。GC 过程是找出所有活对象,并把其余空间认定为“无用”;而不是找出所有死掉的对象,并回收它们占用的空间。所以,哪怕 JVM 的堆非常的大,基于 tracing 的 GC 方式,回收速度也会非常快。

4.jvm怎么判断哪些对象应该回收

 

  1 确定垃圾对象

 

引用计数算法和可达性分析算法。

因为引用计数法的算法是这样的:在对象中添加一个引用计数器,每当一个地方引用它时,计数器就加一;当引用失效时,计数器值就减一;任何时刻计数器为零的对象就是不可能再被使用的。

但是这样的算法有个问题,是什么呢?

不经意间来一波自问自答。让面试官听的一愣一愣的。

就是不能解决循环依赖的问题。

并拿着自己准备的纸和笔快速的画出下面这样的图:

 

 

Object 1和Object 2其实都可以被回收,但是它们之间还有相互引用,所以它们各自的计数器为1,则还是不会被回收。

所以,Java虚拟机没有采用引用计数法。它采用的是可达性分析算法。

可达性分析算法的思路就是通过一系列的“GC Roots”,也就是根对象作为起始节点集合,从根节点开始,根据引用关系向下搜索,搜索过程所走过的路径称为引用链,如果某个对象到GC Roots间没有任何引用链相连。

用图论的话来说就是从GC Roots到这个对象不可达时,则证明此对象是不可能再被使用的。所以此对象就是可以被回收的对象。

 

 

  2。垃圾收集算法进行垃圾收集

 

标记-清除算法

分为标记和清除阶段,首先从每个 GC Roots 出发依次标记有引用关系的对象,最后清除没有标记的对象。

执行效率不稳定,如果堆包含大量对象且大部分需要回收,必须进行大量标记清除,导致效率随对象数量增长而降低。

存在内存空间碎片化问题,会产生大量不连续的内存碎片,导致以后需要分配大对象时容易触发 Full GC。

标记-复制算法

为了解决内存碎片问题,将可用内存按容量划分为大小相等的两块,每次只使用其中一块。当使用的这块空间用完了,就将存活对象复制到另一块,再把已使用过的内存空间一次清理掉。主要用于进行新生代。

实现简单、运行高效,解决了内存碎片问题。代价是可用内存缩小为原来的一半,浪费空间。

HotSpot 把新生代划分为一块较大的 Eden 和两块较小的 Survivor(s0,s1),每次分配内存只使用 Eden 和其中一块 Survivor(s0和s1 永远只有一个有值)。垃圾收集时将 Eden 和 Survivor 中仍然存活的对象一次性复制到另一块 Survivor 上,然后直接清理掉 Eden 和已用过的那块 Survivor。HotSpot 默认Eden 和 Survivor 的大小比例是 8:1,即每次新生代中可用空间为整个新生代的 90%。

标记-整理算法

标记-复制算法在对象存活率高时要进行较多复制操作,效率低。如果不想浪费空间,就需要有额外空间分配担保,应对被使用内存中所有对象都存活的极端情况,所以老年代一般不使用此算法。

老年代使用标记-整理算法,标记过程与标记-清除算法一样,但不直接清理可回收对象,而是让所有存活对象都向内存空间一端移动,然后清理掉边界以外的内存。

标记-清除与标记-整理的差异在于前者是一种非移动式算法而后者是移动式的。如果移动存活对象,尤其是在老年代这种每次回收都有大量对象存活的区域,是一种极为负重的操作,而且移动必须全程暂停用户线程。如果不移动对象就会导致空间碎片问题,只能依赖更复杂的内存分配器和访问器解决。

 

5. GC 的回收流程是怎么样的。

 

 

6、说说Java 垃圾回收机制

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

7、分代收集算法

当前主流 VM 垃圾收集都采用”分代收集” (Generational Collection)算法, 这种算法会根据对象存活周期的不同将内存划分为几块, 如 JVM 中的新生代、老年代、永久代, 这样就可以根据各年代特点分别采用最适当的 GC 算法。 不同分区,采用不同的GC算法。 具体算法看6.

7、JVM 内存区域

JVM 内存区域主要分为线程私有区域【程序计数器、虚拟机栈、本地方法区】、线程共享区域【JAVA 堆、方法区】、直接内存。

线程私有数据区域生命周期与线程相同, 依赖用户线程的启动/结束 而 创建/销毁(在 Hotspot VM 内, 每个线程都与操作系统的本地线程直接映射, 因此这部分内存区域的存/否跟随本地线程的生/死对应)。

线程共享区域随虚拟机的启动/关闭而创建/销毁。

直接内存并不是 JVM 运行时数据区的一部分, 但也会被频繁的使用: 在 JDK 1.4 引入的 NIO 提供了基于Channel与 Buffer的IO方式, 它可以使用Native函数库直接分配堆外内存, 然后使用DirectByteBuffer 对象作为这块内存的引用进行操作(详见: Java I/O 扩展), 这样就避免了在 Java堆和 Native 堆中来回复制数据, 因此在一些场景中可以显著提高性能。

8、MinorGC,MajorGC、FullGC都什么时候发生?

MinorGC在年轻代空间不足的时候发生,MajorGC指的是老年代的GC,出现MajorGC一般经常伴有MinorGC。

FullGC有三种情况。

1、 当老年代无法再分配内存的时候

2、 元空间不足的时候

3、 显示调用System.gc的时候。另外,像CMS一类的垃圾回收器,在MinorGC出现promotion failure的时候也会发生FullGC

9、垃圾收集器对比

1.Serial GC  Serial 即串行的意思,也就是说它以串行的方式执行,它是单线程的收集器,只会使用一个线程进行垃圾收集工作,GC 线程工作时,其它所有线程都将停止工作会产生 stop the world  。

  stop the world  停顿时间会相对比较长。

  结果:对于客户端而言,响应时间变慢了。
新老代之分:
Serial(new) 标记复制算法  适用于新生代
Serial(Old)   标记整理算法、适用于老年代。
2. Parallel收集器  
  同样是多线程的收集器,其它收集器目标是尽可能缩短垃圾收集时用户线程的停顿时间,而它的目标是提高吞吐量(吞吐量 = 运行用户程序的时间 / (运行用户程序的时间 + 垃圾收集的时间))产生 stop the world。
Parallel(new) 标记复制算法  适用于新生代
Parallel(Old)   标记整理算法、适用于老年代。

3.CMS 收集器(只适用老年代)
CMS(Concurrent Mark Sweep),收集器几乎占据着 JVM 老年代收集器的半壁江山,它划时代的意义就在于垃圾回收线程几乎能做到与用户线程同时工作。

使用标记-清除算法收集老年代垃圾。

工作流程主要有如下 4 个步骤:

初始标记: 仅仅只是标记一下 GC Roots 能直接关联到的对象,速度很快,需要停顿(Stop-the-world)
并发标记: 进行 GC Roots Tracing 的过程,它在整个回收过程中耗时最长,不需要停顿
重新标记: 为了修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,需要停顿(Stop-the-world)
并发清除: 清理垃圾,不需要停顿
在整个过程中耗时最长的并发标记和并发清除过程中,收集器线程都可以与用户线程一起工作,不需要进行停顿。只有在初始标记,重新标记的时候会进行短暂的停顿。

但 CMS 收集器也有如下缺点:

吞吐量低
无法处理浮动垃圾
标记 - 清除算法带来的内存空间碎片问题
显式的使用该垃圾收集器作为老年代垃圾收集器的方式:-XX:+UseConcMarkSweepGC

4.G1垃圾收集器(适用新老年代)

https://blog.csdn.net/iva_brother/article/details/87886525

       把G1单独拿出来的原因是其比较复杂,在JDK 1.7确立是项目目标,在JDK 7u2版本之后发布,并在JDK 9中成为了默认的垃圾回收器。通过“-XX:+UseG1GC”启动参数即可指定使用G1 GC。

G1从整体看还是基于标记-清除算法的,但是局部上是基于复制算法的。这样就意味者它空间整合做的比较好,因为不会产生空间碎片。G1还是并发与并行的,它能够充分利用多CPU、多核的硬件环境来缩短“stop the world”的时间。G1还是分代收集的,但是G1不再像上文所述的垃圾收集器,需要分代配合不同的垃圾收集器,因为G1中的垃圾收集区域是“分区”(Region)的,不同的Region配置不同的垃圾回收算法。G1的分代收集和以上垃圾收集器不同的就是除了有年轻代的ygc,全堆扫描的full GC外,还有包含所有年轻代以及部分老年代Region的Mixed GC。G1还可预测停顿,通过调整参数,制定垃圾收集的最大停顿时间。

1分区的概念
G1的堆区在分代的基础上,引入分区的概念。G1将堆分成了若干Region,以下和”分区”代表同一概念。(这些分区不要求是连续的内存空间)Region的大小可以通过G1HeapRegionSize参数进行设置,其必须是2的幂,范围允许为1Mb到32Mb。 JVM的会基于堆内存的初始值和最大值的平均数计算分区的尺寸,平均的堆尺寸会分出约2000个Region。分区大小一旦设置,则启动之后不会再变化。如下图简单画了下G1分区模型。

 

 

JVM常见面试题
(1)内存泄漏与内存溢出的区别

内存溢出(out of memory),是指程序在申请内存时,没有足够的内存空间供其使用,出现out of memory;比如申请了一个integer,但给它存了long才能存下的数,那就是内存溢出。

内存泄露(memory leak),是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光。

memory leak会最终会导致out of memory!

(2)Young GC会有stw(stop‐the‐world 程序停止

)吗?
不管什么GC,都会发送 stop‐the‐world,区别是发生的时间长短,主要取决于不同的垃圾收集器。
(3)Major gc和Full gc的区别
Major GC在很多参考资料中是等价于Full GC 的,我们也可以发现很多性能监测工具中只有Minor GC 和Full GC。一般情况下,一次 Full GC
将会对年轻代、老年代、元空间以及堆外内存进行垃圾回收。
触发Full GC的原因其实有很多:
当年轻代晋升到老年代的对象大小,并比目前老年代剩余的空间大小还要大时,会触发 Full GC;当老年代的空间使用率超过某阈值时,会触发 Fu
ll GC;当元空间不足时(JDK1.7永久代不足),也会触发 Full GC;当调用 System.gc() 也会安排一次 Full GC。
(4)判断垃圾的方式
引用计数
可达性分析
(5)为什么要区分新生代和老年代
当前虚拟机的垃圾收集都采用分代收集算法,这种算法没有什么新的思想,只是根据对象存活周期的不同将内存分为几块。
一般将 java 堆分为新生代和老年代,这样我们就可以根据各个年代的特点选择合适的垃圾收集算法。
比如在新生代中,每次收集都会有大量对象死去,所以可以选择复制算法,只需要付出少量对象的复制成本就可以完成每次垃圾收集。
而老年代的对象存活几率是比较高的,而且没有额外的空间对它进行分配担保,所以我们必须选择“标记‐清除”或“标记‐整理”算法进行垃圾收集。
(6)方法区中的类信息会被回收吗?
方法区主要回收的是无用的类,那么如何判断一个类是无用的类的呢?判定一个常量是否是“废弃常量”比较简单,而要判定一个类是否是“无用的
类”的条件则相对苛刻许多。类需要同时满足下面 3 个条件才能算是 “无用的类”
a‐该类所有的实例都已经被回收,也就是 Java 堆中不存在该类的任何实例。
b‐加载该类的 ClassLoader 已经被回收。
c‐该类对应的 java.lang.Class 对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。
(7)CMS与G1的区别
CMS 主要集中在老年代的回收,而 G1 集中在分代回收,包括了年轻代的 Young GC 以及老年代的 Mixed GC。
G1 使用了 Region 方式对堆内存进行了划分,且基于标记整理算法实现,整体减少了垃圾碎片的产生。
在初始化标记阶段,搜索可达对象使用到的 Card Table,其实现方式不一样。
G1可以设置一个期望的停顿时间。
(8)为什么需要Survivor区
如果没有Survivor,Eden区每进行一次Minor GC,存活的对象就会被送到老年代。
这样一来,老年代很快被填满,触发Major GC(因为Major GC一般伴随着Minor GC,也可以看做触发了Full GC)。老年代的内存空间远大于新生代,进行一次Full GC消耗的时间比Minor GC长得多。执行时间长有什么坏处?频发的Full GC消耗的时间很长,会影响
大型程序的执行和响应速度。
(9)为什么需要两个Survivor区
最大的好处就是解决了碎片化。也就是说为什么一个Survivor区不行?第一部分中,我们知道了必须设置Survivor区。
假设现在只有一个Survivor区,我们来模拟一下流程:
刚刚新建的对象在Eden中,一旦Eden满了,触发一次Minor GC,Eden中的存活对象就会被移动到Survivor区。
这样继续循环下去,下一次Eden满了的时候,问题来了,此时进行Minor GC,Eden和Survivor各有一些存活对象,
如果此时把Eden区的存活对象硬放到Survivor区,很明显这两部分对象所占有的内存是不连续的,也就导致了内存碎片化。
永远有一个Survivor是空的,另一个非空的Survivor无碎片。
(10)为什么Eden:S1:S2=8:1:1
新生代中的对象大多数是“朝生夕死”的
(11)堆内存中的对象都是所有线程共享的吗?
JVM默认为每个线程在Eden上开辟一个buffer区域,用来加速对象的分配,称之为TLAB,全称:Thread Local Allocation Buffer。
对象优先会在TLAB上分配,但是TLAB空间通常会比较小,如果对象比较大,那么还是在共享区域分配。
(12)Java虚拟机栈的深度越大越好吗?
线程栈的大小是个双刃剑,如果设置过小,可能会出现栈溢出,特别是在该线程内有递归、大的循环时出现溢出的可能性更大,如果该值设置过大,
就有影响到创建栈的数量,如果是多线程的应用,就会出现内存溢出的错误。
(13)垃圾收集器一般如何选择
a‐用默认的
b‐关注吞吐量:使用Parallel
c‐关注停顿时间:使用CMS、G1
d‐内存超过8GB:使用G1
e‐内存很大或希望停顿时间几毫秒级别:使用ZGC
(14)什么是方法内联?
正常调用方法时,会不断地往Java虚拟机栈中存放新的栈帧,这样开销比较大,其实jvm虚拟机内部为了节省这样开销,可以把一些方法放到同一个
栈帧中执行。
(15)什么样的情况下对象会进入Old区?
a‐大对象
b‐到了GC年龄阈值
c‐担保机制
d‐动态对象年龄判断
(16)聊聊Minor GC、Major GC、Full GC发生的时机
Minor GC:Eden或S区空间不足或发生了Major GC
Major GC:Old区空间不足
Full GC:Old空间不足,元空间不足,手动调用System.gc())
(17) 什么时候会发生垃圾收集
GC是由JVM自动完成的,根据JVM系统环境而定,所以时机是不确定的。
当然,我们可以手动进行垃圾回收,比如调用System.gc()方法通知JVM进行一次垃圾回收,但是具体什么时刻运行也
无法控制。
也就是说System.gc()只是通知要回收,什么时候回收由JVM决定。但是不建议手动调用该方法,因为GC消耗的资源
比较大。
1)当Eden区或者S区不够用了: Young GC 或 Minor GC
2)老年代空间不够用了: Old GC 或 Major GC
3)方法区空间不够用了: Metaspace GC
4)System.gc()
Full GC=Young GC+Old GC+Metaspace GC
 
 


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM