JVM - 运行时数据区域

文章详细介绍了Java内存模型的几个主要部分:程序计数器负责记录线程执行位置;栈用于存储线程的局部变量和方法调用,栈帧在方法执行完毕后自动出栈;堆存储对象实例,存在垃圾回收机制;方法区存储类信息和静态变量,静态变量需要考虑线程安全。此外,文章还提到了字符串常量池的重要性和内存溢出问题。

程序计数器

并发情况下,会发生线程之间的上下文切换,当 线程1 的CPU时间片用完后,需要程序计数器记录 线程1 的下一条JVM指令的地址,等下一次 线程1 继续运行的时,才能从正确的位置开始继续执行

程序计数器是线程私有的 , 既 一个线程计数器 记录一个 线程 的指令位置

而且程序计数器是运行时数据区域唯一一个不存在内存溢出的区域

线程是什么?实际上可以理解为线程就是一个栈

线程调用方法 就是复制方法入栈的过程

方法入栈后就会形成栈帧

比如下面的一段代码

public class JvmDemo {
    public static void main(String[] args) {
        System.out.println("开始");
        func();
        System.out.println("结束");
    }  
    public static void func(){
    }   
}

执行该程序会创建一个主线程,然后main方法入栈,示意图如下

在这里插入图片描述
接着mian方法执行到 func(); ,就会复制一份 func 方法入栈,如下

在这里插入图片描述
func方法执行完后出栈,如下

在这里插入图片描述
最后mian方法再出栈

我们说线程可以简单的认为就是栈,所以也很明显,栈是线程私有的

栈的容量有限,如果我们不断调用方法入栈,就会导致栈溢出 ( 比如递归时没有递归出口 )

栈分为 虚拟机栈本地方法栈

可以简单理解为 :
-入栈的方法由 java 语言编写 的为虚拟机栈
-入栈的方法由 C语言编写 的为本地方法栈

Object类中有很多本地方法,比如反射中用到的 public final native Class<?> getClass(); ,添加了native 关键字,使用C语言编写,为了能直接对接操作系统

关于栈的几个问题:

  1. 垃圾回收是否涉及栈内存? 不涉及 ,因为栈帧执行完就会自动出栈,无需垃圾回收
  2. 栈内存是否分配越大越好吗?错误 ,物理内存大小一定,栈内存越大,能同时纯在的栈就会越少,既 线程数会越少
  3. 方法内的局部变量是否是线程安全的? 是线程安全的,线程调用方法是复制方法入栈,所以每个线程都有自己的方法副本,也就有了自己的局部变量副本,所以在操作时,不会受到其他线程的干扰

堆 – 存储对象实例

比如下面代码

public class JvmDemo {
    public static void main(String[] args) {
        Person person = new Person();
    }
}

class Person{}

其在内存中的模型如下
在这里插入图片描述
person引用存在main方法的栈帧中,而person实例对象存在于堆区中

堆区是所有线程共享的 ,比如

public class JvmDemo {
    public static void main(String[] args) {
        Person person = new Person();
        Thread thread01=new Thread(()->{
            Person person01 = new Person();
        });
        thread01.start();
    }
}

class Person{}

则内存模型如下,为了突出重点,省略 thread01 引用和实例对象 在内存中的展示,以及 thread01.start(); 的调用过程
在这里插入图片描述
堆的内存有限,不能无限创建对象实例,否者会堆内存溢出

因此JVM存在堆区的垃圾回收机制,清除垃圾对象,垃圾对象 可以简单定义为没有引用指向的实例对象

比如

Person person = new Person;
person = new Person;

此时就没有引用指向第一个new出来的对象,那么它就会被垃圾回收

存在堆区中的对象实例是线程间共享的,对象中的 全局变量 需要考虑线程安全问题

比如

public class JvmDemo {
    public static void main(String[] args) throws InterruptedException {
        Person person = new Person();
        Thread thread01=new Thread(()->{
            for (int i=0;i<100000;i++){
                person.money++;
            }
        });
        thread01.start();
        for (int i=0;i<100000;i++){
            person.money++;
        }
        thread01.join();
        System.out.println(person.money);
    }
}
class Person{
    public int money=0;
}

输出的结果不一定是200000
在这里插入图片描述

方法区

方法区 – 存储 类信息静态变量、方法

在 jdk 1.8之前,方法区存在于JVM内存中,其实现方法被称为 永久代;jdk 1.8及之后,方法区从JVM内存移出到本地内存,其实现方法被称为 元空间

方法区是线程共享的,因此多个线程修改同一个类的静态变量的时候同样存在线程安全问题,比如

public class JvmDemo {
    public static void main(String[] args) throws InterruptedException {
        Thread thread01=new Thread(()->{
            for (int i=0;i<100000;i++){
                Person.money++;
            }
        });
        thread01.start();
        for (int i=0;i<100000;i++){
            Person.money++;
        }
        thread01.join();
        System.out.println(Person.money);
    }
}
class Person{
    public static int money=0;
}

输出结果同样不一定是200000,此处建议先学习一下什么是静态变量

方法区同样存在内存溢出的问题

知识延申 – 字符串常量池

JVM中还有一个特殊又重要的区域,就是 字符串常量池(StringTable),可以参考我的另外一篇博客

【Java 基础】你真的会用 String 吗?

要深入理解 JVM 原理,尤其是“运行数据区域”(Runtime Data Areas),使用可视化工具或模拟软件可以极大帮助学习。虽然目前没有完全图形化模拟整个 JVM 内部运行机制的“仿真器”像虚拟机那样运行字节码并实展示内存变化,但有一些工具和软件可以从不同角度帮助你观察、分析甚至“模拟”JVM 运行数据区域的行为。 以下是几种可以用于**观察和模拟 JVM 运行数据区域行为**的工具: --- ### 1. **JVisualVM(Java VisualVM)** - **功能**:监控 JVM 的堆内存、方法区(元空间)、线程、类加载情况等。 - **如何帮助理解运行数据区**: -查看 **堆(Heap)** 中对象分配与 GC 回收。 - 查看 **方法区(Metaspace)** 使用情况(替代永久代)。 - 监控 **线程栈(虚拟机栈)** 状态,查看每个线程调用栈。 - **使用方式**:JDK 自带,命令行输入 `jvisualvm` 启动。 --- ### 2. **JConsole** - **功能**:JDK 自带的监控工具,可查看内存、线程、类、MBeans。 - **重点区域**: - 内存页显示堆和非堆内存(包括 Metaspace)。 - 线程页可看到线程数量及栈轨迹。 - **适合场景**:简单直观地观察 JVM 内存区域变化。 --- ### 3. **Eclipse MAT(Memory Analyzer Tool)** - **功能**:分析堆转储文件(heap dump),查看对象在堆中的分布。 - **帮助理解**: - 可视化 **Java 堆** 中的对象引用关系。 - 分析 OOM(OutOfMemoryError)是否由堆溢出引起。 - **使用方式**:导入 `.hprof` 文件进行分析。 --- ### 4. **JVM Sandbox / Arthas(阿里巴巴开源)** - **功能**:动态诊断 Java 进程,不重启应用即可查看运行状态。 - **Arthas 示例命令**: ```bash # 查看JVM内存区域使用情况 dashboard # 查看堆信息 heap # 查看线程栈(对应虚拟机栈) thread # 查看类加载信息(方法区相关) classloader ``` - **优势**:可在生产环境安全使用,实观测运行数据区。 --- ### 5. **自定义代码 + 参数触发异常来“模拟”区域溢出** 通过编写特定 Java 程序并设置 JVM 参数,主动触发各区域的内存溢出,从而观察其行为,是最接近“模拟”的方式。 #### 示例:模拟堆内存溢出 ```java import java.util.ArrayList; import java.util.List; public class HeapOOM { static class OOMObject {} public static void main(String[] args) { List<OOMObject> list = new ArrayList<>(); while (true) { list.add(new OOMObject()); } } } ``` 启动参数: ``` -Xms10m -Xmx10m -XX:+HeapDumpOnOutOfMemoryError ``` #### 模拟元空间(方法区)溢出 ```java import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.MethodInterceptor; public class MetaspaceOOM { public static void main(String[] args) { while (true) { Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(OOMObject.class); enhancer.setUseCache(false); enhancer.setCallback((MethodInterceptor) (obj, method, args1, proxy) -> proxy.invokeSuper(obj, args1)); enhancer.create(); } } static class OOMObject {} } ``` 启动参数: ``` -XX:MetaspaceSize=64m -XX:MaxMetaspaceSize=64m ``` #### 模拟虚拟机栈溢出 ```java public class StackOverflow { private int depth = 0; public void recursion() { depth++; recursion(); } public static void main(String[] args) { try { new StackOverflow().recursion(); } catch (Throwable e) { System.out.println("Stack depth: " + new StackOverflow().depth); e.printStackTrace(); } } } ``` 启动参数: ``` -Xss128k ``` --- ### 6. **JVM 模拟器/教学工具(实验性)** - **JIVE(Java Instruction Verification and Evaluation)**:学术项目,提供 JVM 执行过程的可视化。 - **Jasmin / ASM Bytecode Viewer**:虽不是运行模拟器,但可查看字节码如何影响运行常量池和方法执行。 --- ### 总结 | 工具 | 能模拟的数据区 | 说明 | |------|------------------|------| | JVisualVM / JConsole | 堆、元空间、线程栈、运行常量池(间接) | 实监控 | | Eclipse MAT | 堆 | 分析堆对象 | | Arthas | 全部区域 | 生产级诊断 | | 自定义代码 + JVM 参数 | 堆、栈、方法区 | 主动触发异常,最直观教学方式 | --- ### 解释: JVM运行数据区域包括: 1. **程序计数器**(Program Counter Register) 2. **Java 虚拟机栈**(Stack) 3. **本地方法栈**(Native Method Stack) 4. **Java 堆**(Heap) 5. **方法区**(Method Area,含运行常量池) 6. **直接内存**(Direct Memory,非 JVM 规范定义但常被使用) 这些区域无法被单一软件完全“动画模拟”,但结合上述工具和代码实验,可以达到“准模拟”效果。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值