JVM面试杂知识

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

1. 元空间 (Metaspace)

在代码执行前,JVM 需要加载 String 类和包含这行代码的类。

  • 动作 :类加载器将类的结构信息(方法、字段定义)、运行时常量池存入元空间。
  • 存储内容String 类的模板信息,以及字节码指令。
2. 堆:字符串常量池 (String Constant Pool)

执行到 "abc" 这个字面量时:

  • 动作 :JVM 会先检查字符串常量池中是否存在内容为 "abc" 的对象。
  • 存储内容 :如果池中没有,则在堆的字符串常量池区域 创建一个 "abc" 对象实例。如果已经存在,则直接复用。
3. 堆:普通对象区域 (Java Heap)

执行到 new String(...) 时:

  • 动作 :无论常量池中是否已有 "abc"new 关键字都会强制在堆的普通对象区域(非池化区域)开辟一块新内存。
  • 存储内容 :创建一个全新的 String 对象实例。这个对象的内部 value 属性会指向刚才常量池中 "abc" 对象的底层数组。
4. 虚拟机栈 (JVM Stack)

执行到 String s 和赋值操作 = 时:

  • 动作 :在当前线程方法的栈帧 中,为局部变量 s 分配空间。
  • 存储内容 :将堆中新创建的 String 实例的内存地址 存入局部变量表的 s 变量中。
5. 程序计数器 (Program Counter)

在整个执行过程中:

  • 动作 :程序计数器不断更新,指向 ldc(从常量池加载)、new(分配内存)、invokespecial(调用构造函数)、astore(存入局部变量)等字节码指令的偏移量。

方法区/元空间 中方法执行过程

1.找到元空间中该方法的定义

2.根据定义在虚拟机栈中创建栈帧

3.程序计数器中创建对应计数器

4.线程执行

5.执行完退出,清理该方法栈帧,恢复计数器为调用前位置


四种引用类型

在 Java 中,引用类型的设计核心在于控制垃圾回收器(GC)对对象内存的回收时机 。通过 java.lang.ref 包下的类,程序可以根据内存状态干预对象的生命周期。

1. 强引用 (Strong Reference)

这是 Java 程序中最普遍的引用方式,即通过 new 关键字创建并赋值给变量。

  • 实现方式Object obj = new Object();
  • 回收算法逻辑 :只要 GC Roots 可达性分析链中存在强引用指向该对象,GC 即使触发也不会回收该对象。
  • 内存影响 :当堆空间不足以支撑新的强引用对象分配时,JVM 抛出 java.lang.OutOfMemoryError
  • 释放方式 :将变量显式赋值为 null,使其失去与 GC Roots 的连接。
2. 软引用 (Soft Reference)

软引用用于描述还有用但并非必须的对象。

  • 实现类java.lang.ref.SoftReference
  • 回收算法逻辑
    1. 当内存充足时,GC 不会回收软引用对象。
    2. 当系统内存不足(即将发生 OOM)时,JVM 会在抛出异常前将这些对象列入二次回收范围并执行清理。
  • 参数控制 :可通过 -XX:SoftRefLRUPolicyMSPerMB 控制软引用在内存中的存活时长。
  • 应用场景 :实现内存敏感的高速缓存
3. 弱引用 (Weak Reference)

弱引用的强度比软引用更低,它不影响对象的生存周期。

  • 实现类java.lang.ref.WeakReference
  • 回收算法逻辑 :在垃圾收集器工作时,无论当前内存是否足够,只要发现该对象仅被弱引用指向,就会立即回收其内存。
  • 存活时间:仅能生存到下一次垃圾收集发生之前。
  • 应用场景 :防止内存泄漏 。典型实现包括 ThreadLocalMap 的 Key 以及 WeakHashMap
4. 虚引用 (Phantom Reference)

虚引用是最弱的引用,它不对对象的生命周期产生任何实质性影响。

  • 实现类java.lang.ref.PhantomReference
  • 核心特性
    1. 无法通过虚引用获取对象实例(调用 get() 永远返回 null)。
    2. 必须 关联一个引用队列(ReferenceQueue)。
  • 回收算法逻辑:对象被回收时,JVM 会将该虚引用加入关联的队列中。
  • 应用场景 :监控对象的回收状态。主要用于管理直接内存(Direct Memory) ,在 Cleaner 机制中用于释放堆外资源。

内存泄漏常见情况

1. 静态集合类(Static Collections)

静态变量的生命周期与类加载器一致(通常随应用启动而生,随应用停止而死)。

  • 原因 :如果使用 static 修饰 ListMap 等集合,并不断向其中添加对象,这些对象会因为被静态变量强引用而永远无法被 GC 回收。

  • 示例

    java 复制代码
    public class BadCache {
        private static final List<Object> CACHE = new ArrayList<>();
        public void add(Object obj) {
            CACHE.add(obj); // 只要类不卸载,obj 永远在堆里
        }
    }
2. ThreadLocal 内存泄漏

这是面试和生产环境中最隐蔽的泄漏点,尤其是在使用线程池时。

  • 原因ThreadLocalMapKey 是弱引用 ,但 Value 是强引用 。当 ThreadLocal 变量被回收后,Key 变为 null,但 Value 依然被当前线程(Thread)持有。如果线程不结束(线程池复用线程),这些 Value 就会一直堆积。
  • 对策 :必须在使用完后显式调用 remove()
3. 未关闭的资源(Resources Not Closed)

数据库连接、网络套接字(Socket)、文件流等资源。

  • 原因 :这些资源通常涉及操作系统的直接内存 或句柄。如果不手动调用 close(),虽然 Java 包装对象可能被回收,但底层物理连接可能一直占用系统资源,直到发生 Finalizer 调用(这非常不可靠)。
  • 对策 :使用 try-with-resources 语法。
4. 内部类持有外部类引用
  • 非静态内部类 (Non-static Inner Class)和匿名内部类(Anonymous Inner Class)都会隐式地持有外部类实例的强引用。
  • 原因:如果内部类的生命周期长于外部类(例如内部类被交给了一个长周期的线程执行),那么外部类即使不再被使用,也无法被回收。
  • 对策 :尽量使用静态内部类
5. 错误的 hashCode()equals() 实现
  • 原因 :在使用 HashMapHashSet 时,如果作为 Key 的对象没有正确重写 hashCode()equals(),或者在存入后修改了参与计算 Hash 值的字段。

  • 后果 :你会发现无法通过 remove() 删除该对象,导致对象在 Map 中不断累积。

    java 复制代码
    Map<Point, String> map = new HashMap<>();
    Point p = new Point(1, 1);
    map.put(p, "data");
    p.setX(2); // 修改了 Hash 相关字段
    map.remove(p); // 无法删除,因为 Hash 变了,找不到原桶位置
6. 改变生命周期的缓存(Caching)

正如你之前看到的例子,如果将对象放入缓存后没有过期策略(TTL)或清理机制。

  • 原因:缓存往往是全局共享的,如果只进不出,最终会耗尽堆内存。
  • 对策 :使用 WeakHashMap、软引用(SoftReference)或者成熟的缓存库(如 Caffeine、Guava Cache)。
7. 监听器与回调(Listeners and Callbacks)
  • 原因:在 GUI 编程或事件驱动模型中,如果你在长生命周期的组件(如单例)中注册了短生命周期对象的监听器,但销毁时忘记注销。
  • 后果:长生命周期组件会一直持有短生命周期对象的引用。

相关推荐
2301_793804692 小时前
数据分析与科学计算
jvm·数据库·python
酉鬼女又兒2 小时前
零基础入门前端JavaScript Object 对象完全指南:从基础到进阶(可用于备赛蓝桥杯Web应用开发赛道)
开发语言·前端·javascript·职场和发展·蓝桥杯
2301_816651222 小时前
Python游戏中的碰撞检测实现
jvm·数据库·python
cm6543202 小时前
Python Lambda(匿名函数):简洁之道
jvm·数据库·python
yuhaiqiang2 小时前
AI 正在偷走大家的独立思考能力……
前端·后端·面试
程序员三藏3 小时前
Selenium无法定位元素的几种解决方案
自动化测试·软件测试·python·selenium·测试工具·职场和发展·测试用例
郝学胜-神的一滴4 小时前
Leetcode 969 煎饼排序✨:翻转间的数组排序艺术
数据结构·c++·算法·leetcode·面试
发现一只大呆瓜12 小时前
React-彻底搞懂 Redux:从单向数据流到 useReducer 的终极抉择
前端·react.js·面试
零雲12 小时前
java面试:了解抽象类与接口么?讲一讲它们的区别
java·开发语言·面试