JVM的常见面试问题

177 阅读6分钟

1. 谈谈你对JVM的理解?Java 8虚拟机和之前的变化更新?

JVM(Java Virtual Machine)的理解: JVM是执行Java字节码的引擎,它将Java程序转换为机器指令进行执行。JVM本质上是跨平台的,它通过将Java编译成字节码(.class文件)来实现一次编写,处处运行。JVM的主要职责包括:

  • 类加载:通过类加载器将字节码加载到内存中。
  • 字节码执行:通过解释器或JIT(即时编译器)将字节码转换为本地机器码执行。
  • 内存管理:JVM管理运行时内存区域,主要包括堆(Heap)、方法区(Method Area)、栈(Stack)等区域。
  • 垃圾回收(GC) :JVM负责清理无用对象,通过垃圾回收机制提高内存使用效率。

Java 8虚拟机与之前版本的变化

  1. 永久代(PermGen)被元空间(Metaspace)取代
    在Java 7及之前,方法区(Method Area)是使用永久代(PermGen)来存储类的元数据、常量池、静态变量等。永久代的内存大小是有限的,容易引发OutOfMemoryError: PermGen space
    在Java 8中,永久代被移除,取而代之的是元空间(Metaspace),它直接使用本地内存来存储类的元数据信息,因此不再需要手动设置永久代的大小,减少了OOM的几率。

  2. 垃圾回收器(GC)优化

    • Java 8默认的GC是Parallel GC,适合吞吐量优先的场景。
    • 引入了G1 GC(Garbage-First Garbage Collector),它是面向延迟敏感应用的垃圾回收器,适合大内存应用,并且提供了更好的暂停时间控制。
  3. Lambda表达式和函数式接口:Java 8引入了Lambda表达式以及函数式编程的支持,使代码更加简洁和灵活。这是编译时的改进,JVM的运行也能更好地适应这些变化。

  4. 默认方法:接口可以包含默认实现的方法,这个特性虽然属于语法层面,但JVM也需要适应这些新特性的处理。

2. 什么是OOM(OutOfMemoryError),什么是栈溢出StackOverFlowError?怎么分析?

OutOfMemoryError(OOM) : 这是当JVM在运行时无法为对象分配足够的内存时抛出的错误。OOM常见的几种情况:

  • Java Heap Space OOM:堆内存不足,无法创建新的对象。通常是因为大量对象持久驻留在内存中,无法被垃圾回收器清理。
  • Metaspace OOM:元空间溢出,通常是由于类的加载过多(例如动态生成类)。
  • Direct Buffer OOM:直接内存(Direct Memory)不足,多出现在NIO操作中,使用直接缓冲区时。

分析方法

  • 使用jmap命令生成Heap Dump文件:

    lua
    复制代码
    jmap -dump:live,format=b,file=dump.hprof <pid>
    

    然后可以使用MAT(Memory Analyzer Tool)分析dump.hprof文件,找出大量占用内存的对象。

  • 检查GC日志,使用参数-XX:+PrintGCDetails查看GC的详细情况,是否频繁发生Full GC。

StackOverflowError: 这是由于方法调用栈太深,导致栈空间耗尽而抛出的错误,通常由递归调用没有退出条件或者深层次的嵌套调用引起。

分析方法

  • 检查代码中的递归调用,确保有合理的退出条件。

  • 通过设置栈的大小限制,防止栈溢出:

    diff
    复制代码
    -Xss512k
    

    调整该参数可以限制每个线程的栈大小。

3. JVM的常用调优参数有哪些?

以下是一些常用的JVM调优参数:

  1. 堆内存设置

    • -Xms:设置JVM启动时的堆内存大小(初始大小)。
    • -Xmx:设置JVM最大可用的堆内存大小。
    diff
    复制代码
    -Xms512m -Xmx1024m
    
  2. 垃圾回收器参数

    • -XX:+UseG1GC:启用G1垃圾回收器,适用于大内存、低延迟的应用场景。
    • -XX:+UseConcMarkSweepGC:启用CMS垃圾回收器,适用于低停顿需求的应用场景。
    • -XX:MaxGCPauseMillis:设置GC停顿时间的目标上限。
    • -XX:+PrintGCDetails:输出GC的详细日志。
  3. 栈大小设置

    • -Xss:设置每个线程的栈大小,默认是1MB,可以调整以防止StackOverflowError。
    diff
    复制代码
    -Xss512k
    
  4. 元空间设置

    • -XX:MetaspaceSize:设置元空间初始大小。
    • -XX:MaxMetaspaceSize:设置元空间最大大小。
  5. 直接内存设置

    • -XX:MaxDirectMemorySize:限制直接内存的大小,特别在使用NIO或Netty时需要考虑。

4. 内存快照如何抓取,怎么分析Dump文件?

抓取内存快照

  • 使用jmap工具可以生成JVM的内存快照(Heap Dump):

    lua
    复制代码
    jmap -dump:format=b,file=heapdump.hprof <pid>
    

    或者在OOM时通过-XX:+HeapDumpOnOutOfMemoryError自动生成dump文件。

分析Dump文件

  • 使用MAT(Memory Analyzer Tool)分析Dump文件:

    • 导入Dump文件后,可以查看哪些对象占用了大量内存。
    • 可以使用MAT提供的“泄漏可疑报告”找到可能的内存泄漏点。
    • 查看GC Roots,判断对象的存活时间及其依赖链条。
  • 另外,VisualVM工具也可以用于分析内存泄漏、线程状态等信息。它提供了更加图形化的界面来查看JVM内存和线程的使用情况。

5. 谈谈JVM中类加载器的认识

类加载器(ClassLoader)负责将Java类加载到JVM内存中,采用了双亲委派模型

  • 双亲委派模型: 当一个类加载器收到类加载请求时,会先把请求委派给父类加载器,逐层向上委派,直到最顶层的根类加载器(Bootstrap ClassLoader)。如果父类加载器找不到该类,子类加载器才会尝试自己加载。

  • 常见的类加载器

    1. Bootstrap ClassLoader:负责加载JVM核心类库(如rt.jar)。
    2. Extension ClassLoader:负责加载扩展类库(如/lib/ext目录下的类)。
    3. Application ClassLoader:负责加载应用程序的类(如项目中的classpath下的类)。
  • 自定义类加载器: 可以继承ClassLoader实现自定义类加载器,用于特殊场景,例如动态加载远程类、模块化系统中的类隔离等。需要重写findClass()方法来指定如何查找和加载类。

  • 类加载器的重要性

    • 类加载器不仅负责类的加载,还涉及类的隔离和命名空间。例如在Tomcat等应用服务器中,不同的Web应用可以使用不同的类加载器,避免类冲突。
    • 双亲委派模型确保了核心类不会被自定义的类加载器篡改,保证了Java安全模型的可靠性。