15. JVM 内存模型——JVM基础(1)


Java虚拟机(JVM)内存模型分为两部分:
1. JVM内存结构(运行时数据区域):描述JVM内存的物理划分;
2. Java内存模型(JMM, Java Memory Model):定义多线程环境下内存访问的规则和保障机制。
在这里插入图片描述


一、JVM内存结构(运行时数据区域)

JVM将内存划分为以下核心区域,每个区域有明确的功能和生命周期:

1. 程序计数器(Program Counter Register)
  • 作用:记录当前线程执行的字节码指令地址(分支、循环、跳转等)。
  • 特点
    • 线程私有,生命周期与线程绑定。
    • 唯一不会发生内存溢出(OOM)的区域。
  • 示例场景:多线程切换时,保存当前线程执行位置以便恢复。

2. 虚拟机栈(Java Virtual Machine Stacks)
  • 作用:存储方法调用的栈帧(局部变量表、操作数栈、动态链接、方法出口等)。
  • 线程私有:每个线程有自己的栈。
  • 栈帧结构
    • 局部变量表:存放基本类型(intboolean等)和对象引用。
    • 操作数栈:执行字节码指令的临时数据存储区。
    • 动态链接:指向运行时常量池的方法引用。
    • 方法出口:记录方法返回地址。
  • 异常
    • StackOverflowError:栈深度超出限制(如递归调用未终止)。
    • OOM:无法申请足够内存扩展栈(较少见)。
  • 配置参数-Xss(如-Xss1m设置栈大小为1MB)。

3. 本地方法栈(Native Method Stack)
  • 作用:为JVM调用本地(Native)方法(如C/C++库)服务。
  • 特点:与虚拟机栈类似,但服务于Native方法。
  • 异常:同虚拟机栈。

4. 堆(Heap)
  • 作用:存储所有对象实例和数组(new关键字创建的对象)。
  • 线程共享:所有线程共享堆内存。
  • 分区(JDK 8及之后):
    • 新生代(Young Generation)
      • Eden区:对象初次分配区域。
      • Survivor区(S0/S1):存放经过Minor GC后存活的对象。
    • 老年代(Old Generation):长期存活的对象(经过多次GC后晋升)。
    • 元空间(Metaspace,JDK 8+):取代永久代(PermGen),存储类元数据、常量池等。
  • GC机制
    • Minor GC:清理新生代。
    • Major GC/Full GC:清理整个堆(包括老年代)。
  • 异常OutOfMemoryError(堆内存不足)。
  • 配置参数
    • -Xms:初始堆大小(如-Xms256m)。
    • -Xmx:最大堆大小(如-Xmx2g)。
    • -XX:NewRatio:新生代与老年代比例(如-XX:NewRatio=2表示老年代是新生代的2倍)。

5. 方法区(Method Area)
  • 作用:存储类信息、常量、静态变量、即时编译器编译后的代码。
  • 实现演变
    • JDK 7及之前:永久代(PermGen),存在OOM风险。
    • JDK 8+:元空间(Metaspace),使用本地内存(Native Memory),默认无上限。
  • 异常OutOfMemoryError(元空间内存不足)。
  • 配置参数
    • -XX:MetaspaceSize:初始元空间大小。
    • -XX:MaxMetaspaceSize:最大元空间大小(默认无限制)。

6. 运行时常量池(Runtime Constant Pool)
  • 作用:存放类文件中的常量(字面量、符号引用等)。
  • 位置:方法区的一部分。
  • 示例:字符串常量池(String Table)位于堆中(JDK 7+)。

二、Java内存模型(JMM)

JMM定义了多线程环境下共享变量的访问规则,解决可见性原子性有序性问题。

1. 主内存与工作内存
  • 主内存:所有线程共享的堆内存。
  • 工作内存:线程私有的本地内存(缓存、寄存器等),存储共享变量的副本。
  • 交互规则
    • 线程对共享变量的操作需通过工作内存与主内存交互。
    • 线程间不可直接访问对方的工作内存。

2. 内存屏障(Memory Barriers)

JMM通过内存屏障禁止指令重排序,保障可见性和有序性:

  • Load Barrier:确保读取操作前所有其他内存操作已完成。
  • Store Barrier:确保写入操作对其他线程可见。

3. Happens-Before规则

定义操作间的先行关系,确保多线程执行的逻辑正确性:

  1. 程序顺序规则:同一线程内的操作按代码顺序执行。
  2. 锁规则:解锁操作先行于后续的加锁操作。
  3. volatile规则volatile变量的写操作先行于后续的读操作。
  4. 传递性规则:若A先于B,B先于C,则A先于C。

4. volatile关键字
  • 可见性:强制线程从主内存读取最新值,修改后立即刷新到主内存。
  • 禁止指令重排序:通过插入内存屏障实现。
  • 适用场景:状态标志位(如boolean flag)、单例模式的双重检查锁(Double-Checked Locking)。

5. synchronized与锁
  • 原子性:通过锁机制保障代码块或方法的原子执行。
  • 可见性:解锁前将变量刷新到主内存,加锁时清空工作内存中的副本。

6. final字段的内存语义
  • 初始化安全:正确构造的对象,其final字段对所有线程可见。
  • 禁止重排序:构造函数内对final字段的写入不会重排序到构造函数外。

三、常见问题与调优

1. 内存溢出(OOM)场景
  • 堆溢出:对象过多(如大列表未释放)。
  • 元空间溢出:加载过多类(如动态生成类未卸载)。
  • 栈溢出:深度递归调用。
2. 调优建议
  • 堆内存:根据应用需求设置-Xms-Xmx,避免频繁Full GC。
  • 线程栈:减少-Xss值(如256k)以支持更多线程。
  • 元空间:限制-XX:MaxMetaspaceSize防止无限增长。
  • GC策略:根据停顿时间和吞吐量需求选择G1、ZGC等垃圾回收器。

四、示例代码与参数配置

public class MemoryModelDemo {
    private static volatile boolean flag = false; // volatile保障可见性

    public static void main(String[] args) {
        new Thread(() -> {
            while (!flag) {} // 循环直到flag为true
            System.out.println("Flag changed to true");
        }).start();

        new Thread(() -> {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            flag = true; // 修改flag值
        }).start();
    }
}

启动参数示例

java -Xms512m -Xmx2g -Xss256k -XX:MaxMetaspaceSize=256m MemoryModelDemo

总结

  • 内存结构:理解堆、栈、方法区等区域的功能和配置是调优的基础。
  • JMM规则:掌握volatilesynchronizedhappens-before规则是编写正确并发程序的关键。
  • 调优目标:平衡内存分配、GC频率和线程并发能力,避免OOM和性能瓶颈。
### JVM 内存模型详解 JVM内存模型是理解 Java 应用程序运行机制的重要部分。它主要包括以下几个核心区域: #### 1. **方法区 (Method Area)** 方法区是 JVM 中的一个重要组成部分,它是各线程共享的内存区域,在 JVM 启动时创建[^3]。此区域主要用于存储已被虚拟机加载的类信息、常量、静态变量以及即时编译后的代码等数据[^5]。 在 JDK 1.7 及之前的版本中,方法区被称为“永久代”(Permanent Generation),而在 JDK 8 中,Oracle 对其进行了改进并更名为“元空间”(Metaspace)。这一改动的主要目的是解决永久代容易出现 OutOfMemoryError 的问题。如果应用程序加载了大量的第三方 jar 包或动态生成了许多反射类,则可能导致方法区溢出,从而引发 `java.lang.OutOfMemoryError` 错误。 #### 2. **堆 (Heap)** 堆是 JVM 所管理的最大一块内存区域,也是所有线程共享的一块内存区域。堆的作用是用来存放对象实例和数组[^2]。当一个新的对象被创建时,如果没有显式的分配到其他特定区域(如栈中的局部变量),那么该对象会被放置于堆中。 堆的空间可以设置为固定大小或者可扩展的形式,具体取决于应用的需求。如果堆内存不足且无法再扩展,则会抛出 `OutOfMemoryError` 异常。 #### 3. **Java 虚拟机栈 (Java Virtual Machine Stacks)** 每个线程都有自己的独立栈,这些栈在线程创建时就被初始化完成。每当一个新方法被执行时,都会为其创建一个对应的栈帧(Stack Frame)。栈帧包含了方法执行期间所需的各种信息,比如局部变量表、操作数栈、动态链接、返回地址等[^4]。 需要注意的是,由于每个线程都有自己单独的栈结构,因此它们之间不会相互干扰。但如果某个线程请求的栈深度超过了允许范围,则会发生 StackOverflowError;反之,如果单个线程占用太多栈空间而导致资源耗尽,则可能触发 OutOfMemoryError。 #### 4. **本地方法栈 (Native Method Stacks)** 类似于 Java 虚拟机栈,不过这是专门为 Native 方法服务而设计的。某些情况下,为了提高性能或其他特殊需求,开发者可能会编写 C/C++ 等原生语言实现的功能模块并通过 JNI 接口调用至 Java 层面。此时就需要借助本地方法栈来支持这类跨平台交互行为。 #### 5. **程序计数器 (Program Counter Register)** 这是一个较小规模却非常重要的组件——记录当前线程所执行字节码指令的位置。对于 Java 来说,每条语句最终都被翻译成一系列低级机器指令序列供 CPU 解读处理。所以通过维护这样一个简单的整型数值即可追踪正在运行的具体位置。 --- ### 总结 综上所述,JVM 内存模型由多个不同职责分工明确的部分共同构成,彼此协作保障整个系统的正常运转。其中既包括像堆这样负责大规模数据存储的核心单元,也有诸如程序计数器这般小巧精悍却又不可或缺的小部件。只有深入掌握这些基础知识才能更好地优化我们的开发实践,并有效应对可能出现的各种异常状况。 ```python class MemoryModelExample: def __init__(self, value): self.value = value def show_value(self): print(f"The stored value is {self.value}") example_instance = MemoryModelExample(42) example_instance.show_value() ``` 上述代码展示了如何在一个简单 Python 类中模拟类似的概念:实例化对象时将其属性保存起来以便后续访问,这与 JVM 堆的工作方式有异曲同工之妙。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值