当前位置: 首页>编程语言>正文

jvm面试题

作者有话说:目前正在跟新一系列的java面试题,持续不断更新。需要找工作或者不需要找工作的猴子们,都可以关注一下。着急的可以评论区留留言,面试文档以及简历模板。看到了我会发

1. 什么是JVM

学过Java的同学应该都知道,我只要打成了Jar之后,不论是在Windows、MacOS还是在Windows下都可以执行对吧。一次编译、处处运行「Write Once,Run Anywhere」

编辑

2. 说下对JVM内存模型的理解

JVM内存模型主要是指Java虚拟机在运行时所使用的内存结构。它主要包括堆、栈、方法区和程序计数器等部分。

此图很重要,大部分都是参照此图而来

jvm面试题,jvm面试题_JVM,第1张

jvm面试题,jvm面试题_JVM_02,第2张编辑

1.堆

        是JVM中最大的一块内存区域,用于存储对象实例。一般通过new关键字创建的对象都存放在堆中,堆的大小可以通过启动参数进行调整。堆被所有线程共享,但是它的访问是线程不安全的,需要通过锁机制来保证线程安全。

2.栈

        用于存储方法调用和局部变量。每个线程在运行时都会有一个独立的栈,栈中的每个方法调用都会创建一个栈帧,栈帧包含了方法的参数、局部变量和返回值等信息。栈的大小是固定的,并且栈中的数据是线程私有的,不会被其他线程访问。

3.方法区

        用于存储类的信息和静态变量。它是所有线程共享的内存区域,存储了类的结构信息、常量池、静态变量和方法字节码等。方法区的大小也可以通过启动参数进行调整。

4.程序计数器

        是每个线程私有的,用于记录当前线程执行的字节码指令的地址。每个线程都有一个独立的程序计数器,用于控制线程的执行流程。

3. JVM中哪些是线程共享区

jvm面试题,jvm面试题_JVM_03,第3张

jvm面试题,jvm面试题_Java_04,第4张编辑

堆区和方法区是所有线程共享的,

栈、本地方法栈、程序计数器是每个线程独有的

4. JVM运行时数据区的数据存放

简单的说就是不同的数据放在不同的地方

共分为五个部分:方法区、堆、虚拟机栈、程序计数器、本地方法栈。

jvm面试题,jvm面试题_JVM_05,第5张

jvm面试题,jvm面试题_Java_06,第6张编辑

5. 说说类加载器机制

当 Java 虚拟机将 Java 源码编译为字节码之后,虚拟机便可以将字节码读取进内存,从而进行解析、运行等整个过程,这个过程我们叫:Java 虚拟机的类加载机制。JVM 虚拟机执行 class 字节码的过程可以分为七个阶段:加载、验证、准备、解析、初始化、使用、卸载。

jvm面试题,jvm面试题_加载_07,第7张

jvm面试题,jvm面试题_Java_08,第8张编辑

jvm面试题,jvm面试题_JVM_09,第9张

jvm面试题,jvm面试题_JVM_10,第10张编辑

类加载机制是类加载器负责将类文件加载到JVM的内存中,使得类可以被实例化和调用。类加载器按照层级结构组织,形成了一个类加载器树。每个类加载器负责加载特定范围的类

  • 引导类加载器:它是JVM的一部分,用于加载Java核心类库,通常位于jre/lib/rt.jar中。
  • 扩展类加载器:负责加载jre/lib/ext目录下的JAR包。
  • 应用程序类加载器:也称为系统类加载器,负责加载应用程序classpath下的类。
  • 自定义类加载器:用户可以根据需要创建自己的类加载器,以加载特定位置或方式的类文件。

6. 什么是双亲委派模型

.

jvm面试题,jvm面试题_Java_11,第11张

jvm面试题,jvm面试题_Java_12,第12张编辑

       JVM在加载一个类时,会调用AppClassLoader的loadClass方法来加载这个类,不过在这个方法中,会先使用ExtClassLoader的loadClass方法来加载类,同样ExtClassLoader的loadClass方法中会先使用BootstrapClassLoader来加载类,如果BootstrapClassLoader加载到了就直接成功,如果BootstrapClassLoader没有加载到,那么ExtClassLoader就会自己尝试加载该类,如果没有加载到,那么则会由AppClassLoader来加载这个类。最后到Customer ClassLoader

7. 为什么要用双亲委派模型

  • 保证类的加载安全性:在加载类时,首先由启动类加载器尝试加载,如果找不到对应的类,再由扩展类加载器尝试加载,如果仍然找不到,则由应用程序类加载器尝试加载。这种从上至下的层级结构保证了核心类库优先被加载,从而保证 Java 类的加载安全性。
  • 避免类的重复加载:双亲委派机制避免了同一个类被重复加载到不同的 ClassLoader 中。在类加载过程中,如果一个类已经被父 ClassLoader 加载过,子 ClassLoader 就不再尝试重新加载,而直接使用父加载器加载的类。
  • 实现类加载的层级结构:双亲委派机制建立了类加载的层级结构,每个 ClassLoader 都有一个父加载器,除了启动类加载器,其他的 ClassLoader 都有自己的父加载器。这样形成了一个父子关系的加载链,父加载器可以加载子加载器加载不到的类。

8. 可以打破双亲委派机制吗

可以,但是不建议一般人操作,打破双亲委派模型可能会引入类加载的不安全性和不稳定性,因此应该谨慎使用

双亲委派模型通常是由Java虚拟机本身实现和强制执行的,目的是确保类加载的安全性和唯一性。但在某些情况下,你可以通过编写自定义类加载器来打破双亲委派机制

  1. 加载非标准类文件:如果你需要加载非标准的类文件,例如从数据库或网络中动态加载类,传统的双亲委派模型可能无法满足需求,因此你可能需要编写自己的类加载器。
  2. 实现类隔离:有时,你可能需要在同一个应用程序中加载多个版本的相同类,或者在不同的模块中加载相同的类,这时自定义类加载器可以帮助你实现类的隔离,防止类冲突。
  3. 动态更新类:一些应用程序需要在运行时动态更新类,这也可能需要绕过双亲委派模型,以便能够加载新版本的类。

要打破双亲委派模型,你需要编写自己的类加载器,并覆盖其loadClass方法。在这个方法中,你可以自行决定如何加载类,而不遵循双亲委派规则

9. 内存溢出与内存泄漏的区别

内存溢出:(内存不足)

 内存溢出是指程序尝试分配更多内存空间,而可用内存已经用尽,因此无法满足分配的请求。它通常发生在程序执行期间、内存溢出通常导致程序崩溃或抛出OutOfMemoryError异常

内存泄漏 :(程序bug导致内存用了不归还)

内存泄漏是指程序分配了一些内存,但在不再需要这些内存时没有释放它们。它通常不会导致程序立即崩溃,但随着时间的推移,占用的内存不断增加,最终可能导致程序变得非常缓慢或耗尽系统资源

预防内存溢出通常需要分配足够的内存,而预防内存泄漏需要确保及时释放不再需要的内存

10. JVM出现OOM异常会导致进程挂掉吗?

一般情况下,JVM 出现 OOM 异常会导致进程挂掉。

当 JVM 无法为新的内存分配请求提供足够的内存空间时,就会触发 OOM 异常。这通常意味着进程占用的内存资源超出了系统的限制。

出现 OOM 异常后,可能会有以下情况发生:

  1. 程序异常终止:进程可能会立即停止运行,导致正在进行的任务中断。
  2. 数据丢失:可能会导致程序状态和数据的丢失。

然而,在某些情况下,进程并不一定会立即挂掉:

  1. 特殊的处理机制:一些应用程序可能具有自己的处理逻辑,以尝试缓解 OOM 情况。
  2. 操作系统的影响:操作系统可能会对进程进行一定程度的保护和管理。

11. 说说你对垃圾收集器的理解

垃圾收集器是Java虚拟机的一部分,负责管理内存中的对象,以确保内存的有效使用和回收不再使用的对象。以下是对垃圾收集器的理解:负责自动管理对象的内存,防止内存泄漏,并提供不同的实现和配置选项,以满足不同类型的应用程序的性能需求。

  1. 内存管理:垃圾收集器负责分配、回收和释放内存。
  2. 自动回收:垃圾收集器自动识别不再被引用的对象,并将其标记为垃圾,然后释放这些垃圾对象占用的内存。这个过程是自动的,程序员无需手动释放内存。
  3. 内存泄漏防止:垃圾收集器可以防止内存泄漏,即程序中的对象无法被回收,导致内存消耗不断增加。通过垃圾收集器,不再使用的对象最终会被回收,释放内存。
  4. 性能影响:不同的垃圾收集器实现具有不同的性能特性。一些收集器专注于最小化停顿时间(低延迟),而其他收集器则专注于最大化吞吐量。选择合适的垃圾收集器取决于应用程序的性能需求。
  5. 分代垃圾收集:垃圾收集器通常使用分代策略,将堆内存分为不同的代(通常是年轻代和老年代),以便根据对象的生命周期采用不同的回收策略。年轻代通常使用快速的回收算法,而老年代则采用更复杂的算法。
  6. 垃圾回收算法:不同的垃圾收集器实现使用不同的垃圾回收算法,如标记-清除复制标记-整理等。这些算法有不同的优缺点,适用于不同类型的应用程序。

12. 怎么确定一个对象到底是不是垃圾?

1. 引用计数算法: 这种方式是给堆内存当中的每个对象记录一个引用个数。引用个数为0的就认为是垃圾。这是早期JDK中使用的方式。引用计数无法解决循环引用的问题。

2. 可达性算法: 这种方式是在内存中,从根对象向下一直找引用,找到的对象就不是垃圾,没找到的对象就是垃圾。

jvm使用的是可达性分析算法

13. 哪些可以作为可达性分析算法GC Roots

GC Roots是一组特殊的引用,它们被认为是程序中可访问对象的起始点,即从这些引用开始,可以追踪到所有仍然被程序引用的对象。

GC Roots通常包括以下几种类型的引用:

  1. 局部变量引用:在方法中定义的局部变量,包括方法的参数和局部变量,通常被视为GC Roots。这些变量的引用指向了对象的实例。
  2. 活动线程引用:正在运行的线程的引用通常被视为GC Roots。线程本地存储中的对象也是如此。
  3. 静态变量引用:静态变量是类的一部分,它们的引用也被视为GC Roots。静态变量存在于类加载器的内存中。
  4. NI 引用:通过Java Native Interface(JNI)创建的本地代码引用也可以被视为GC Roots。这些引用连接了Java堆内存和本地代码的内存。
  5. 虚拟机引导类加载器:虚拟机内部使用的类加载器引用也是GC Roots。它们通常是一些核心类或库。

14. JVM 内存为什么要分新生代,老年代,元空间

  1. 对象生命周期不同
  • 大多数对象在被创建后不久就会变得不可达,因此它们的生命周期很短。
  • 但也有一些对象具有较长的生命周期,它们可能在应用程序的整个生命周期内存在。
  1. 不同的垃圾回收算法
  • 针对不同生命周期的对象,JVM可以使用不同的垃圾回收算法。
  • 新生代通常使用复制算法,因为大多数对象很快就会变得不可达。这个算法可以快速回收不再使用的对象。
  • 老年代使用标记-清除或标记-整理算法,因为较长生命周期的对象不适合复制算法,需要更复杂的回收策略。
  1. 性能优化
  • 分代内存管理有助于提高垃圾回收的性能。由于新生代的对象生命周期短暂,因此垃圾回收发生在新生代的频率较高,但每次回收的内存量较小。
  • 老年代的垃圾回收发生频率较低,但每次回收的内存量较大。这减少了垃圾回收的停顿时间,提高了应用程序的响应性能。
  1. 内存碎片问题
  • 通过将内存分为新生代和老年代,可以减少内存碎片问题。在新生代中使用复制算法,内存会被分为较小的块,这有助于减少碎片。
  • 老年代使用标记-清除或标记-整理算法来处理较长生命周期的对象,进一步减少了碎片。
  1. 元数据管理(持久代或元空间):
  • 元数据主要存储在元空间中,将元数据信息单独管理,可以更好地控制和管理类加载和卸载,防止类加载器泄漏和元数据溢出等问题。

总之,分代内存管理是一种有效的策略,可以提高Java应用程序的性能和稳定性,通过根据对象的生命周期和不同的垃圾回收算法来合理管理内存,从而减少垃圾回收的成本和停顿时间,同时降低内存碎片问题。这有助于使Java应用程序更高效地运行。

15. JVM为什么使用元空间替换了永久代

虚拟机使用元空间替代了永久代是因为永久代在过去的实现中存在一些问题和限制,而元空间提供了更好的性能和灵活性。以下是一些详细的原因:

  1. 内存管理:永久代的内存管理是由虚拟机自身控制的,无法根据应用程序的需求进行动态调整。而元空间使用本地内存进行管理,可以根据应用程序的需求动态分配和释放内存,提高内存的利用率。
  2. 永久代内存溢出:在永久代中,存储类的元数据、常量池、静态变量等,当应用程序加载大量类或者使用大量字符串常量时,可能导致永久代内存溢出。而元空间不再有固定的大小限制,可以根据应用程序的需要自动扩展
  3. 类的卸载:在永久代中,由于类的卸载机制比较复杂,很难实现完全的类卸载。而元空间使用本地内存,可以更容易地实现类的卸载,减少内存的占用。
  4. 性能优化:元空间的实现采用了更高效的数据结构和算法,例如使用指针碰撞(Bump the Pointer)的方式分配内存,减少内存碎片化,提高内存分配的效率。此外,元空间还支持并发的类加载和卸载操作,提高了性能。

总的来说,元空间相对于永久代来说具有更好的内存管理、更高的性能和更灵活的特性,能够更好地满足现代应用程序的需求。因此,虚拟机选择使用元空间替代永久代。

16. 什么是STW?

STW: Stop-The-World,是在垃圾回收算法执行过程当中,需要将JVM内存冻结的一种状态。在STW状态下,JAVA的所有线程都是停止执行的-GC线程除外,native方法可以执行,但是,不能与JVM交互。GC各种算法优化的重点,就是减少STW,同时这也是JVM调优的重点。

17. 一个对象从加载到JVM,再到被GC清除,都经历了什么过程?

  1. 首先把字节码文件内容加载到方法区
  2. 然后再根据类信息在堆区创建对象
  3. 对象首先会分配在堆区中年轻代的Eden区,经过一次Minor GC后,对象如果存活,就会进入区。在后续的每次Minor GC中,如果对象一直存活,就会在Suvivor区来回拷贝,每移动一次,年龄加1
  4. 当年龄超过15后,对象依然存活,对象就会进入老年代
  5. 如果经过Full GC,被标记为垃圾对象,那么就会被GC线程清理掉

18. CPU百分百问题如何排查

排查CPU百分百问题通常需要一步一步地识别并解决潜在的原因

  1. 查看系统负载:使用系统监控工具比如top查看系统的负载情况。
  2. 确定是哪个进程导致CPU高占用:查找哪个进程或应用程序的CPU占用率很高。
  3. 查看日志文件:检查应用程序的日志文件,查找是否有异常或错误消息。
  4. 检查代码:检查代码以查找是否存在性能问题,例如死循环、低效的算法、内存泄漏等。
  5. 查看数据库查询:如果应用程序与数据库交互,查询可能导致CPU负载高。通过检查数据库的慢查询日志和优化查询来解决问题。
  6. 监控线程:如果是多线程应用程序,检查是否有某些线程占用了大量CPU资源。使用线程分析工具来识别问题线程。
  7. 查看网络连接:有时,网络请求和连接问题也可能导致CPU高占用。查看是否有异常的网络连接或请求。
  8. 使用性能分析工具:使用专业的性能分析工具来检测瓶颈。例如,Java应用程序可以使用Arthas、VisualVM等工具进行分析。
  9. 应用程序优化:根据排查的结果,对应用程序进行优化,修复性能问题。

19. 强引用、软引用、弱引用、虚引用的区别

在Java中,强引用、软引用、弱引用和虚引用是不同类型的引用,用于管理对象的生命周期。它们之间的主要区别在于对象被垃圾回收的条件和时机。

强引用是最常见的引用类型,通常通过赋值操作创建。当一个对象具有强引用时,垃圾回收器不会回收它,即使内存不足也不会回收。

软引用通过SoftReference类来表示。软引用用于描述那些内存不是必需的但仍然有用的对象。当内存不足时,垃圾回收器会尝试回收软引用对象,但只有在内存真正不足的情况下才会回收。软引用通常用于实现高速缓存,以便在内存不足时释放缓存中的对象。

弱引用通过WeakReference类来表示。弱引用用于描述那些不会阻止对象被垃圾回收的对象。如果一个对象只有弱引用指向它,那么垃圾回收器会在下一次运行时回收该对象。弱引用通常用于构建可以在对象不再被强引用时自动释放的数据结构,如哈希表的键。

虚引用通过PhantomReference类来表示。虚引用用于监控对象被垃圾回收的情况,但本身并不阻止对象被回收。虚引用通常与ReferenceQueue一起使用,当对象被回收时,虚引用会被放入引用队列中,以便应用程序可以了解到对象何时被回收。

20. 常用的JVM启动参数有哪些

  1. -Xmx:指定Java堆内存的最大限制。例如,-Xmx512m 表示最大堆内存为512兆字节。
  2. -Xms:指定Java堆内存的初始大小。例如,-Xms256m 表示初始堆内存为256兆字节。
  3. -Xss:指定每个线程的堆栈大小。例如,-Xss256k 表示每个线程的堆栈大小为256千字节。
  4. -XX:MaxPermSize(对于Java 7及之前的版本)或 -XX:MaxMetaspaceSize(对于Java 8及以后的版本):指定永久代(Java 7及之前)或元空间(Java 8及以后)的最大大小。
  5. -XX:PermSize(对于Java 7及之前的版本)或 -XX:MetaspaceSize(对于Java 8及以后的版本):指定永久代(Java 7及之前)或元空间(Java 8及以后)的初始大小。
  6. -Xmn:指定年轻代的大小。例如,-Xmn256m 表示年轻代大小为256兆字节。
  7. -XX:SurvivorRatio:指定年轻代中Eden区与Survivor区的大小比例。例如,-XX:SurvivorRatio=8 表示Eden区与每个Survivor区的大小比例为8:1。
  8. -XX:NewRatio:指定年轻代与老年代的大小比例。例如,-XX:NewRatio=2 表示年轻代和老年代的比例为1:2。
  9. -XX:MaxGCPauseMillis:设置垃圾回收的最大暂停时间目标。例如,-XX:MaxGCPauseMillis=100 表示垃圾回收的最大暂停时间目标为100毫秒。
  10. -XX:ParallelGCThreads:指定并行垃圾回收线程的数量。例如,-XX:ParallelGCThreads=4 表示使用4个线程进行并行垃圾回收。
  11. -XX:+UseConcMarkSweepGC:启用并发标记清除垃圾回收器。
  12. -XX:+UseG1GC:启用G1(Garbage First)垃圾回收器。
  13. -Dproperty=value:设置Java系统属性,可以在应用程序中使用 System.getProperty("property") 来获取这些属性的值。

21. JVM调优的一般步骤

  • 第1步:分析GC日志及dump文件,判断是否需要优化,确定瓶颈问题点;
  • 第2步:确定JVM调优量化目标;
  • 第3步:确定JVM调优参数(根据历史JVM参数来调整);
  • 第4步:调优一台服务器,对比观察调优前后的差异;
  • 第5步:不断的分析和调整,直到找到合适的JVM参数配置;
  • 第6步:找到最合适的参数,将这些参数应用到所有服务器,并进行后续跟踪。

22. 什么是三色标记

三色标记是一种用于并发垃圾收集的算法,常用于分代垃圾收集器中的老年代的垃圾回收过程中。它基于对象的可达性来判断对象是否存活,并标记出存活对象。

三色标记算法将对象分为三种状态:白色、灰色和黑色。

白色表示对象尚未被扫描,即未被标记为存活对象。

灰色表示对象已经被扫描,但其引用的其他对象尚未被扫描。

黑色表示对象已经被扫描,并且其引用的其他对象也已经被扫描。

垃圾收集器在开始垃圾回收时,将所有对象标记为白色。然后从根对象开始,递归地遍历对象图,将遇到的对象标记为灰色,并将其引用的对象添加到待扫描队列中。接着,垃圾收集器从待扫描队列中取出对象,将其标记为黑色,并将其引用的对象添加到待扫描队列中。这个过程会一直进行,直到待扫描队列为空。

最后,所有未被标记为黑色的对象即为垃圾对象,可以被回收。

三色标记算法的优点是可以在并发执行的情况下进行垃圾回收,减少停顿时间。它通过将对象分为三种状态,避免了在并发执行过程中的同时修改和访问对象的冲突。然而,三色标记算法也有一些缺点,如可能存在标记漏标和标记误标的情况,需要额外的处理来解决这些问题。

23. 什么是安全点

安全点(Safe Point)是指在Java程序执行过程中的某个特定位置,此时所有线程都处于安全状态,即没有执行关键的代码片段,如循环、方法调用等。在安全点上,垃圾回收器可以安全地进行垃圾回收操作,而不会对正在执行的线程产生影响。

为了进行垃圾回收,垃圾回收器需要暂停所有线程的执行,以便检查和回收不再使用的对象。然而,随意中断线程的执行可能会导致程序状态不一致或产生其他问题。因此,在安全点上,所有线程都会停止执行,等待垃圾回收器完成工作后再继续执行。

安全点的选择是由JVM控制的,通常在一些特定的位置上,例如方法调用、循环跳转、异常处理等。当线程达到安全点时,它会停止执行关键代码片段,并等待垃圾回收器完成垃圾回收操作后再继续执行。

安全点的存在可以减少垃圾回收对运行线程的影响,提高垃圾回收的效率。同时,安全点也是实现线程安全的关键之一,确保垃圾回收器和运行线程之间的正确交互。

总而言之,安全点是程序执行过程中的特定位置,用于确保垃圾回收器可以安全地进行垃圾回收操作,而不会对正在执行的线程产生影响。安全点的选择是由垃圾回收器控制的,通常在方法调用、循环跳转、异常处理等关键位置上。

24. 什么是指针碰撞

在Java中,指针碰撞是一种垃圾收集算法中用于分配内存的一种方式。它通常用于实现停顿时间较短的垃圾收集器,如复制算法和标记-清除算法。

指针碰撞的基本思想是将堆内存分为两个区域:一个是已分配的对象区域,另一个是未分配的空闲区域。通过一个指针来分隔这两个区域。当需要分配对象时,垃圾收集器将对象的大小与空闲区域的大小进行比较,如果空闲区域足够容纳对象,则将指针碰撞指针向前移动对象的大小,并返回指针碰撞指针的旧值作为对象的起始地址。如果空闲区域不足以容纳对象,则进行垃圾回收操作,释放一些内存后再进行分配。

指针碰撞的优点是分配内存的速度很快,只需简单地移动一个指针即可完成。而且由于已分配的对象区域和未分配的空闲区域是连续的,所以内存的利用率也比较高。

然而,指针碰撞算法的缺点是需要保证堆内存的连续性,即堆内存必须是一块连续的内存空间。这对于某些情况下的内存分配来说可能是一个限制,因为连续的内存空间可能会受到碎片化的影响,导致无法分配足够大的对象。因此,在实际应用中,指针碰撞算法通常与其他内存分配算法结合使用,以克服其局限性。


https://www.xamrdz.com/lan/59t1924585.html

相关文章: