文章
努力加载图片中...
Java 虚拟机面试题学习
  • 4356 字

  • 6 分钟

  • 3 次

  • 2025-10-23
标签:

JVM的内存模型介绍一下

程序计数器记录执行位置,线程私有,不会 OOM;
虚拟机栈本地方法栈管理方法调用,其中本地方法栈是为 Native 方法服务,线程私有,可能栈溢出;
是最大的一块内存区域,是共享区,用于存放对象实例,会 OOM;
元空间在本地内存,存已被虚拟机加载的类信息、常量、静态变量等数据,也会 OOM; 运行时常量池是元空间的一部分,用于存放编译期生产的字面量和符号引用,会 OOM。 另外,还有直接内存,通过 NIO 使用,不归 JVM 管,但也可能 OOM。

OMM:Out Of Memory,来源为 OutOfMemoryError。直译为内存用完了,意思是 JVM 没有足够的内存来为对象分配空间且垃圾回收器没有空间可回收时,会抛出错误

JVM内存模型里的堆和栈有什么区别?

堆和栈的主要区别在用途和生命周期:
是线程私有的,用来执行方法,存储局部变量和调用信息,这些信息在方法结束时就释放,速度快,空间小,但可能出现栈溢出;
是线程共享的,用来存放对象实例,生命周期由垃圾回收机制控制,空间大但速度慢,对象不再引用时才会被回收。
简单说:栈管方法执行,堆管对象存储

栈中存的到底是指针还是对象?

栈中不存储对象本身,只存储基本类型变量对象的引用,对象实例本身是在堆上分配的。

堆分为哪几部分呢?

Java 堆主要分为新生代老年代
新生代又分为 Eden 区和 Survivor 区,新对象通常在 Eden 区分配,经历 Minor GC (新生代垃圾回收)后存活的进入 Survivor 区。

经过多次回收仍存活的对象会移动到老年代。老年代存放长期存活对象,发生 Major GC 时才会回收。

除此之外呢,还有元空间大对象区。 元空间用于存储类的元数据信息,如类的结构信息(如字段、方法信息等)。

对于需要大量连续内存空间的对象,例如大数组会被直接分配在老年代或专门的大对象区。

如果有个大对象一般是在哪个区域?

大对象通常会直接分配到老年代。

大对象通常需要连续的内存空间,如果在新生代中频繁分配和回收大对象,容易产生内存碎片,导致后续分配大对象时可能因为内存不连续而失败。老年代的空间相对较大,更适合存储大对象,有助于减少内存碎片的产生。

程序计数器的作用,为什么是私有的?

程序计数器的作用是记录当前线程所执行的字节码指令的地址,但每个线程都有自己的执行路径,切换线程时需要独立保存执行位置,确保恢复时能继续正确执行,所以需要线程私有。

方法区中的方法的执行过程?

当调用一个方法时,JVM 会在 Java 虚拟机栈中为它创建一个栈帧,里面包含局部变量表、操作数栈等;然后解释器执行该方法的字节码指令;执行完毕后,可能会返回一个结果给调用者,清理当前栈帧,恢复调用者的执行环境。

方法区中还有哪些东西?

  • 类信息:包括类的结构信息、类的访问修饰符、父类与接口等信息。
  • 常量池:存储类和接口中的常量,包括字面值常量、符号引用,以及运行时常量池。
  • 静态变量:存储类的静态变量,这些变量在类初始化的时候被赋值。
  • 方法字节码:存储类的方法字节码,即编译后的代码。
  • 符号引用:存储类和方法的符号引用,是一种不同于直接引用的引用类型。
  • 运行时常量池:存储着在类文件中的常量池数据,在类加载后在方法区生成该运行时常量池。
  • 常量池缓存:用于提升类加载的效率,将常用的常量缓存起来方便使用。

String保存在哪里呢?

String 保存在字符串常量池中,不同于其他对象,它的值是不可变的,且可以被多个引用共享。

String s = new String(“abc”)执行过程中分别对应哪些内存区域?

如果abc这个字符串常量不存在,则创建两个对象,分别是abc这个字符串常量,以及new String这个实例对象。如果abc这字符串常量存在,则只会创建一个对象。

引用类型有哪些?有什么区别?

强引用:最常见,只要引用存在就不会被回收;
软引用:内存不足时才回收,适合做缓存; SoftReference 弱引用:只要发生GC就被回收,常用于避免内存泄漏; WeakReference 虚引用:最弱,必须配合队列使用,主要用于跟踪对象是否被回收,比如管理堆外内存。 PhantomReference + ReferenceQueue

弱引用了解吗?举例说明在哪里可以用?

弱引用通过 WeakReference 实现,只要发生 GC,弱引用对象都会被回收。

常用于缓存或避免内存泄漏,比如 ThreadLocal 的 Key 就使用弱引用,防止线程长时间运行时 Key 无法释放。

内存泄漏和内存溢出的理解?

内存泄漏是指无用对象被引用,无法被GC回收,导致可用内存逐渐减少,比如给静态集合一直添加对象等。

内存溢出是 JVM 申请内存时,无法找到足够的内存,触发 OutOfMemoryError,比如创建大量对象或递归太深。

内存泄漏不一定会立刻报错,但长期积累会引发内存溢出。

jvm 内存结构有哪几种内存溢出的情况?

JVM 常见的内存溢出有四种:

  1. 堆溢出Java heap space,存在大对象分配或内存泄漏;
  2. 栈溢出StackOverflowError,递归太深;或线程太多导致无法创建新栈;
  3. 元空间溢出Metaspace,加载类过多,如动态生成类;
  4. 直接内存溢出Direct buffer memory,NIO 使用堆外内存过多。

遇到过堆溢出的情况吗?如何解决?

  1. 捕获内存快照:通过 JVM 参数-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=./heapdump.hprof,让程序在发生 OOM 时自动生成堆快照文件。
  2. 分析快照文件:使用 MAT(Memory Analyzer Tool)或 JProfiler 等工具分析快照,重点看哪些对象占用了大量内存、是否存在内存泄漏(如对象长期被无用引用持有,无法回收)。

栈溢出的情况呢?

  1. 排查递归逻辑:检查是否存在无限递归或递归层级过深的问题,添加正确的终止条件,或减少递归深度。必要时可将递归改写为迭代(如用循环替代),因为迭代不会持续创建新栈帧。
  2. 调整栈内存大小:通过 JVM 参数-Xss(如-Xss256k)增大栈内存容量。但这种方式要谨慎,栈内存过大会导致线程可创建数量减少(总内存固定时,单个线程栈越大,能创建的线程数越少)。
  3. 优化方法栈帧:减少方法内局部变量的数量,避免在方法中创建过大的对象或数组,将大对象的创建移到堆中(通过 new 关键字),降低单个栈帧的内存占用。

有具体的内存泄漏和内存溢出的例子么请举例及解决方案?

不会

创建对象的过程?

  1. 遇到 new 指令时,先检查类是否在常量池中定位到一个类的符号引用,并且这个引用所代表的类是否已被加载,否则先执行类加载过程;
  2. JVM 在 Java 堆中为对象分配内存;
  3. 将分配的内存空间都初始化为零值;
  4. 设置对象头,如类的元数据信息在哪,是哪个类的实例、哈希码、GC 分代年龄等;
  5. 最后执行构造函数 <init>,真正初始化对象。

对象的生命周期

  • 创建:对象通过关键字new在堆内存中被实例化,构造函数被调用,对象的内存空间被分配。
  • 使用:对象被引用并执行相应的操作,可以通过引用访问对象的属性和方法,在程序运行过程中被不断使用。
  • 销毁:当对象不再被引用时,通过垃圾回收机制自动回收对象所占用的内存空间,完成对象的销毁过程。

类加载器有哪些?

JVM 有四种类加载器:
启动类加载器:负责加载 java的核心库,C++ 实现,是 JVM 的一部分,不能 Java 程序直接使用;
扩展类加载器:加载 Java 扩展目录下的 jar 包和类库,Java 语言实现,继承自 ClassLoader 类;
系统类加载器:加载用户类 Classpath 下的类库,是我们写代码时默认使用的类加载器,父类是扩展类加载器;
自定义类加载器:开发者可继承 ClassLoader 实现,用于从网络、数据库等加载类。

Java 中 双亲委派 是什么?有啥用?

双亲委派是指:当类加载器收到加载请求时,先委派给父加载器去加载,只有父加载器无法完成时,自己才尝试加载。

它保证了像 String 这样的核心类不会被自定义类替换,确保了系统的安全性和类的唯一性。

加载顺序是:自定义 → 应用 → 扩展 → 启动类加载器。这是JVM类加载的基石。

双亲委派模型的作用

双亲委派的核心作用有三点:
第一,保证类的唯一性,确保了所有加载请求都会传递到启动类加载器,避免重复加载;
第二,防止核心类被篡改,启动类加载器只加载信任的类路径中的类,这样可以防止不可信的类假冒核心类;
第三,清晰层次划分,不同类型的类都被不同的加载器加载,确保各个层级类加载器的职责清晰。

讲一下类加载过程?

类加载过程分为五个阶段:

  1. 加载:找到 .class 文件,生成 Class 对象,放到方法区;

  2. 链接

    • 验证:进行文件格式校验、元数据验证、字节码验证、符号引用验证,确保字节码安全合法
    • 准备:给静态变量分配内存,设默认值;
    • 解析:虚拟机将常量池的「符号引用」直接替换为「直接引用」;
  3. 初始化:执行类的构造器方法,赋真实初始值,运行静态代码块。

类的卸载需要什么条件?

  1. 该类所有的实例都已经被回收。
  2. 加载该类的 ClassLoader 已经被回收。
  3. 类对应的 Java.lang.Class 对象没有任何地方被引用。

什么是 Java 里的垃圾回收?如何触发垃圾回收?

垃圾回收(Garbage Collection, GC)是自动管理内存的一种机制,它负责自动释放不再被程序引用的对象所占用的内存,这种机制减少了内存泄漏和内存管理错误的可能性。

触发方式:

  1. 内存不足。
  2. 手动请求。
  3. 对象数量或内存使用达到阈值。

判断垃圾的方法有哪些?

判断垃圾主要有两种方法:
第一种是引用计数法,为对象分配一个引用计数器,对象被引用就+1,引用失效就 -1,为0 就可以被回收。

第二种是可达性分析算法,从一组垃圾收集根 GC Roots(如栈变量、静态变量)出发,向下追溯它们引用的对象,以及它们引用的其他对象,能访问到的对象是存活的,也就是可达的,访问不到的就是不可达的,就是垃圾。这是目前 JVM 使用的算法。

垃圾回收机制是什么,是为了解决了什么问题?

垃圾回收(Garbage Collection, GC)是自动管理内存的一种机制,它负责自动释放不再被程序引用的对象所占用的内存,这种机制减少了内存泄漏和内存管理错误的可能性。

它主要是为了解决传统语言中内存泄漏内存溢出的问题,避免程序员手动释放内存带来的错误。

垃圾回收算法有哪些?

常见的垃圾回收算法有四种:
标记-清除:通过可达性分析,标记需要回收的对象,然后统一清除,效率不高,且会产生碎片;
复制算法:将内存分为两块,每次申请内存就使用其中一块,内存不够时,将存活对象复制到另一块区域,然后清理已使用的内存,无碎片,但是内存利用率不足;
标记-整理:通过可达性分析,标记需要回收的对象,标记后把存活对象移动到内存的一端,移动结束后清理剩余部分,用于存活对象较多的情况;
分代回收:分代收集是将内存划分成了新生代和老年代,在新生代申请内存,当经历一次 GC 之后如果对还存活,那么对象的年龄 +1。当年龄超过一定值后,如果对象还存活,那么该对象会进入老年代。新生代用复制,老年代用整理或清除。

垃圾回收器有哪些?

  1. Serial收集器,新生代单线程收集器,复制算法
  2. ParNew收集器,新生代收并行集器,复制算法
  3. Parallel Scavenge收集器,新生代并行收集器,复制算法
  4. Serial Old收集器,老年代单线程收集器,标记-整理算法
  5. Parallel Old收集器,老年代并行收集器,标记-清除算法
  6. CMS(Concurrent Mark Sweep)收集器,老年代并行收集器,标记-清除算法
  7. G1(Garbage First)收集器, Java堆并行收集器,标记-整理算法

标记清除算法的缺点是什么?

  1. 标记和清除过程的效率都不高
  2. 标记清除之后会产生大量不连续的内存碎片,碎片太多在分配大对象时,可能会由于无法找到最够的连续内存而不得不提前一次 GC 动作

垃圾回收算法哪些阶段会stop the world?

先不记。

minorGC、majorGC、fullGC的区别,什么场景触发full GC

Minor GC 回收新生代,Eden区满时触发,频繁但快;
Major GC 主要回收老年代,老年代空间不足时发生;
Full GC 回收整个堆和元空间,最耗时。

触发 Full GC 的常见场景有:

  1. Minor GC 后对象进不了老年代;
  2. 老年代或元空间满了;
  3. 手动调用 System.gc()
    应尽量避免 Full GC,防止系统卡顿。

垃圾回收器 CMS 和 G1的区别?

  1. 使用范围:CMS 是老年代收集器;G1新生代和老年代都收集。
  2. STW 时间:CMS 是以最小停顿时间为目标的收集器,G1 收集器可预测垃圾回收的停顿时间。
  3. 垃圾碎片:CMS 使用标记-清除算法,G1 是标记-整理算法。

什么情况下使用CMS,什么情况使用G1?

CMS:

  • 低延迟需求。
  • 老年代手记。
  • 碎片化管理。

G1:

  • 大堆内存。
  • 对内存碎片敏感。
  • 比较平衡的性能。

G1回收器的特色是什么?

G1回收器的核心特色有四点:
第一,采用分区设计,弱化分代概念;
第二,使用标记-整理算法,不会产生内存碎片;
第三,支持停顿时间可控,可设定最大暂停时间;
第四,并发并行,充分利用多核优势。

GC只会对堆进行GC吗?

堆和方法区。


来源:Java虚拟机面试题 | 小林coding | Java面试学习

作者: Xigrut发布时间: 2025-10-23 09:50:46上次编辑时间: 2026-06-18 19:26:40 许可协议: CC BY-NC-SA 4.0
留言区