首页 > 编程语言 >深入探索JVM:理解Java程序在虚拟机中的存储和管理

深入探索JVM:理解Java程序在虚拟机中的存储和管理

时间:2024-01-13 21:23:50浏览次数:35  
标签:存储 Java JVMExample 对象 虚拟机 JVM 方法

大家好,我是大圣,很高兴又和大家见面。

今天给大家带来图解 JVM 系列的第四篇文章,我们写的 Java 程序是怎么在JVM 里面存储的。本次大纲如下:

file

前面知识回顾
上一篇 图解JVM系列:揭秘运行时数据区的设计与实现 文章说了JVM 运行时数据区的设计理念,我们是通过 冯·诺依曼结构 来类比引出JVM 运行时数据区的各个区域,如下图:

file

本文会通过 java 代码的案例,来解释这段代码在运行时数据区中的方法区、堆、虚拟机栈、本地方法栈、程序计数器里面是怎么存放的。从而引出JVM 运行时数据区各个区域存放的内容。

Java 程序在 JVM中的存储
大家先看下面这段代码:

public class JVMExample {
    // 静态变量存储在方法区
    private static String staticVariable = "Static Variable";

    // 实例变量存储在堆
    private String instanceVariable;

    public JVMExample(String instanceVariable) {
        this.instanceVariable = instanceVariable;
    }

    public static void main(String[] args) {
        // 局部变量存储在虚拟机栈
        int localVariable = 10;

        // 对象存储在堆,引用存储在虚拟机栈
        JVMExample example = new JVMExample("Instance Variable");

        // 调用实例方法
        example.instanceMethod(localVariable);

        // 调用静态方法
        staticMethod();
    }

    public void instanceMethod(int localVar) {
        // 局部变量存储在虚拟机栈
        System.out.println("Instance Method");
        System.out.println("Local Variable: " + localVar);
        System.out.println("Instance Variable: " + this.instanceVariable);
    }

    public static void staticMethod() {
        // 局部变量存储在虚拟机栈
        System.out.println("Static Method");
        System.out.println("Static Variable: " + staticVariable);
    }
}

代码解释和运行时数据区对应
类定义 JVMExample
private static String staticVariable = "Static Variable";

静态变量:存储在方法区。方法区用于存储类相关的信息,如类名、方法数据、静态变量等。

private String instanceVariable;

实例变量:每个JVMExample对象的实例变量都存储在堆内存中。堆内存主要用于存储所有的对象实例。

构造函数 JVMExample(String instanceVariable)
这个构造函数用来初始化新创建的对象的实例变量。

main 方法
int localVariable = 10;

局部变量:存储在虚拟机栈的栈帧中。每当调用一个方法时,都会为这个方法创建一个栈帧。

JVMExample example = new JVMExample("Instance Variable");

创建了JVMExample类的一个新对象。对象存储在堆内存中,而example(对象的引用)存储在虚拟机栈中。

example.instanceMethod(localVariable);

调用instanceMethod实例方法,并将localVariable作为参数传递。

staticMethod();

调用静态方法staticMethod。

instanceMethod 实例方法
打印传入的局部变量和实例变量的值。localVar作为参数传入,存储在虚拟机栈中,而this.instanceVariable引用了存储在堆中的对象的实例变量。

staticMethod 静态方法
打印静态变量staticVariable的值。静态变量存储在方法区,与类JVMExample相关联,而不是与任何对象实例相关联。

运行时数据区解释
方法区
结合上面的 JVMExample 类的代码,我们可以看到如何将这些概念应用到 Java 虚拟机(JVM)的方法区:

类信息:

在方法区中,JVMExample 类的信息被存储,这包括了类的名称 JVMExample、它的父类(在 Java 中默认为 Object 类,除非另有指定)、类中定义的方法(如 main、instanceMethod、staticMethod)、以及类的变量(如实例变量 instanceVariable 和静态变量 staticVariable)。

常量池:

常量池存储了编译期生成的字面量和符号引用。例如,在 JVMExample 类中,字符串 "Static Variable" 和 "Instance Variable" 作为字面量可能会存储在常量池中。

静态变量:

类的静态变量 staticVariable 存储在方法区。这意味着 staticVariable 不属于 JVMExample 类的任何特定实例,而是与类本身关联的,因此在方法区中全局存储。

方法数据:

对于类中定义的每个方法,方法区存储了它们的字节码(即 Java 代码编译后的形式)、方法的参数(例如 instanceMethod 的参数 localVar)、返回值类型(在这个例子中,所有方法都返回 void,即无返回值)以及访问修饰符(如 public、private 等)。


在Java虚拟机(JVM)中,堆是运行时数据区的一个主要组成部分,主要用于存储对象实例和数组。在上面的 JVMExample 类中,与堆相关的数据主要包括:

对象实例:

当创建一个类的实例时,例如通过 new JVMExample("Instance Variable"),这个实例对象存储在堆内存中。这包括该对象的所有实例变量(非静态变量),在本例中是 instanceVariable。

堆是所有线程共享的内存区域,因此从任何线程都可以访问这些对象(前提是有它们的引用)。

数组:

如果在程序中创建了任何类型的数组(例如 int[]、Object[] 等),这些数组也会存储在堆中。

像对象实例一样,数组也是由所有线程共享的。

在堆内存中,对象和数组不仅仅是存储它们自身的数据(例如字段值),还包括一些额外的信息,如对象的类信息、对象的监视器(用于同步)等。由于堆是一个动态分配的内存区域,它可以在运行时根据需要扩展或收缩。

垃圾回收主要发生在堆内存中。当对象不再被任何线程引用时,JVM的垃圾回收器会自动清理这些对象,释放内存空间,以便该空间可以重新用于未来的对象分配。

因此,在 JVMExample 示例中,每当创建 JVMExample 类的一个新实例时,这个新对象就会在堆内存中分配空间。这使得JVM能够有效地管理各个对象的生命周期,同时优化内存使用和回收。

Java 虚拟机栈
在JVM(Java虚拟机)中,虚拟机栈(JVM Stack)是每个线程私有的内存区域,主要用于存储局部变量、操作数栈、动态链接信息、方法出入口等信息。结合上面提供的 JVMExample 类,我们可以具体探讨虚拟机栈中的数据类型:

局部变量:

在方法内部定义的变量称为局部变量。例如,在 main 方法中定义的 int localVariable = 10; 和 instanceMethod 方法中的 int localVar 都是局部变量。

这些变量存储在虚拟机栈的栈帧中,每个方法调用时都会创建一个新的栈帧。

对象引用:

虽然对象本身存储在堆内存中,但对这些对象的引用(即变量,它们保存了对象在堆中的地址)存储在虚拟机栈上。例如,在 main 方法中的 JVMExample example 是一个对象引用,指向堆中的 JVMExample 实例。

方法调用的信息:

当一个方法被调用时,相关的信息(如输入参数、返回地址等)存储在虚拟机栈的栈帧中。在 main 方法调用 instanceMethod 和 staticMethod 方法时,这些方法的调用信息被压入虚拟机栈。

操作数栈:

在执行方法时,虚拟机栈还用于存储操作数栈,这是一个用于存储临时变量和操作数的数据结构。例如,计算表达式时使用的临时变量和操作数。

动态链接信息:

每个栈帧还包含动态链接信息,用于支持方法调用中的动态绑定。

在 JVMExample 类中,当 main 方法执行时,它的局部变量 localVariable 和对象引用 example 存储在虚拟机栈中。当调用 instanceMethod 方法时,该方法的局部变量 localVar 也存储在虚拟机栈的一个新栈帧中。同样,任何在这些方法中进行的操作(如计算或方法调用)都会涉及虚拟机栈的操作。

总的来说,虚拟机栈是执行Java程序的核心组件之一,负责存储局部变量、控制方法执行流程、处理方法调用和返回操作等。这种设计有助于快速的方法调用处理和局部变量的高效管理。

本地方法栈和程序计数器
在Java虚拟机(JVM)中,本地方法栈(Native Method Stack)和程序计数器(Program Counter, PC)是两个关键的运行时数据区域,它们与虚拟机栈(JVM Stack)有着不同的作用和存储内容,结合上面的 JVMExample 类来具体了解它们存储的数据。

本地方法栈(Native Method Stack):

本地方法栈类似于虚拟机栈,但它是为虚拟机使用的本地(Native)方法服务的。

当JVM使用到本地方法(即使用Java Native Interface(JNI)调用的非Java代码,如C/C++代码)时,本地方法栈就会被使用。

在上面的 JVMExample 类中,并没有直接调用本地方法,所以本例中不涉及本地方法栈的使用。但如果有的代码中调用了像System.loadLibrary()这样的JNI方法,那么本地方法栈就会被使用。

程序计数器(Program Counter, PC):

程序计数器是一个较小的内存空间,它存储当前线程所执行的字节码的行号指示器。每个线程都有自己的程序计数器。

在任何时刻,每个线程都在执行一个方法(称为当前方法)。如果这个方法不是本地方法,程序计数器就存储正在执行的字节码指令的地址;如果是本地方法,程序计数器的值则是undefined。

在上面的 JVMExample 类中,当 main 方法、instanceMethod 或 staticMethod 被执行时,程序计数器会指向当前正在执行的字节码指令的地址。

总结来说,虽然上面的 JVMExample 类中没有直接涉及本地方法的调用,但如果有本地方法的调用,本地方法栈会被用于存储本地方法调用的状态。程序计数器则在任何时候都在工作,指向当前线程正在执行的字节码的具体位置,确保线程执行顺序的准确性和线程切换后能恢复到正确的执行位置。

在这里我给大家画了一张,上面的示例代码里面的对象在JVM运行时数据区里面的分布图,如下:

file

堆内存的深入讲解
在上篇文章中,我们只是说了在 JVM 运行时数据区里面有堆这个部分,用来存储实例对象的。其实堆在运行时数据区当中还会继续细分的,大家看我下面举得例子:

举例理解
想象一下,JVM的堆内存就像一个城市的垃圾处理系统。在这个系统中,有不同的处理区域,每个区域处理不同类型的垃圾。

年轻代(Young Generation) - 这就像城市中的临时垃圾收集点。新产生的垃圾(在JVM中,这对应于新创建的对象)首先被放在这里。这个区域相对较小,垃圾(对象)在这里不会停留太久。

Eden区:这就像一个主要的垃圾投放点。新垃圾被丢进这里。一旦这个区域满了,就需要进行一次清理(对应于JVM中的Minor GC)。

两个Survivor区(S0和S1):它们像是用于临时存放那些从主垃圾投放点筛选出来的、还需要进一步处理的垃圾。在JVM中,这对应于在一次Minor GC之后仍然存活的对象。

老年代(Old Generation) - 这可以想象成城市的主要垃圾填埋场。只有那些在临时收集点存放了很长时间,经过多次筛选仍然需要保留的垃圾(在JVM中,这对应于那些经历了多次Minor GC后仍然存活的对象)才会被转移到这里。这个区域更大,垃圾在这里停留的时间更长,清理频率也更低。

专业解释
JVM 运行时数据区里面堆的划分如下:

file

在JVM(Java虚拟机)的运行时数据区中,堆(Heap)是一个非常重要的部分,主要用于存储Java程序中创建的对象和数组。堆的内存由所有线程共享,是垃圾回收的主要区域。下面是关于堆及其子区域的详细介绍:

堆(Heap):

它是JVM内存管理的主要区域,几乎所有的对象实例都在这里分配内存。

堆的大小可以调整,这对性能调优至关重要。

堆内存分为年轻代(Young Generation)和老年代(Old Generation)。

年轻代(Young Generation):

新创建的对象通常首先被分配到年轻代。

年轻代包含了Eden区和两个Survivor区(通常被称为S0和S1)。

当Eden区满时,会进行一次Minor GC(垃圾回收),将还存活的对象转移到一个Survivor区(比如S0)。

Eden区(Eden Space):

是年轻代中对象最初分配的地方。

当对象在Eden区存活足够长的时间后,它们会被移动到Survivor区。

Survivor区(S0和S1):

这两个区域用于存储从Eden区转移过来的存活对象。

Survivor区分为两个部分:S0(Survivor Space 0)和S1(Survivor Space 1)。

在一次Minor GC后,存活的对象会从Eden区移到一个Survivor区(比如S0),在下一次GC中,这些对象可能会被移动到另一个Survivor区(比如S1)或者老年代。

老年代(Old Generation):

存储长时间存活的对象。

当年轻代中的对象经历了多次GC依然存活时,它们会被移到老年代。

老年代的垃圾回收频率通常低于年轻代,因为假设老年代中的对象通常会存活较长时间。

通过这样的设计,JVM能够有效管理内存。新对象被频繁创建和销毁,所以它们被放在年轻代,这样就可以快速清理掉那些很快就不再需要的对象。

而那些存活时间更长的对象则被移动到老年代,因为它们不太可能很快被回收,所以可以减少对这部分内存的频繁清理,提高效率。这就像在城市垃圾处理中,通过不同的处理区域和方法来优化处理效率和空间使用。

总结
本文回顾
本文说了通过一个 Java 代码入手,然后讲解了 JVM 运行时数据区各个区域存储的是 Java 代码里面的哪些代码,让大家对 JVM的运行时数据区有了更一步的认识。

然后又说了堆内存的再次划分,其中堆的话划分为 Young 区和 Old 区,Young 里面又分为Eden区和两个Survivor区(S0和S1),大家可以记上面话的堆的那张图。

下篇文章我会用一个对象实例,把它从实例化,创建,GC,Old 区回收这一整个流程讲一遍,大家还会有新的认知,就是从不同的角度给大家讲解 JVM 运行时数据区这块的内容,来让大家理解。

本文重点内容
本文的重点我觉得就是以后我们写完 Java 代码之后,我们要知道这个代码在 JVM 里面是怎么存储的。

比如哪些对象堆上面,哪些东西存储在栈上面,每次调用一个方法都会创建一个栈帧,一个栈帧里面包括哪些内容,哪些内容存储在方法区里面,堆内存的划分为什么要划分出来这么多区域,解决了什么问题。

大家面对自己写的代码要会画这个代码在 JVM 运行时数据区里面的分布图,比如我上面画的示例代码的分布图一样,这样可以更好地帮助我们分析程序。

预告与彩蛋
预告
下一篇文章会再说运行时数据区的一些东西,比如一个对象的整个生命周期,栈帧,GCRoot,强引用等,为后面讲JVM 垃圾回收器打个基础。因为这里面涉及到很多的概念,如果都放在一篇文章来说很容易看不下去,就劝退了。

还有一个我想说的就是这个 JVM 不是看一遍就学会了,因为这个东西里面涉及到很多概念性的东西,所以通常都要多看几遍,然后隔一段时间再看一遍 。

JVM 里面肯定还有没有讲到的,但是我讲的都是 JVM 里面最重要的东西了,但是我讲的肯定有不足的地方,大家如果想再深入的学习,可以自己再找资料看看。

彩蛋
如果你看到这里,你真的很棒了,没有被这么多枯燥的概念劝退。下面我想出一个 Java 的题目,大家可以看一下,题目如下:

public class Parent {

    public int value;

    public Parent(int value) {
        this.value = value;
    }

    public void addValue() {
        value += 2;
    }
}

class Child extends Parent {

    public int value;

    public Child(int value) {
        super(value);
        this.value = value;
    }

    public void addValue() {
        value += 6;
    }
}

class Main {
    public static void main(String[] args) {
        Parent parent = new Child(3);
        parent.addValue();
        System.out.println("value的值为:" + parent.value);
    }
}

问题如下:
1.请问打印出来value的值是多少?
2.请画出来这个程序在 JVM 里面的分布

欢迎大家来讨论,带上自己的答案来私聊我,如果两题都答对的话,可以来找我领红包。

标签:存储,Java,JVMExample,对象,虚拟机,JVM,方法
From: https://www.cnblogs.com/dream-lie/p/17962958

相关文章

  • VMware虚拟机安装银河麒麟桌面版V10系统
    1镜像下载1.1打开官网百度搜索或浏览器地址栏直接输入地址1.2找到镜像下载选择桌面版操作系统进行安装我们日常办公使用的是桌面版的系统,选择桌面版操作系统进行下载1.3申请试用现在安装系统需要填写申请试用表,填写完之后,才会调转到镜像下载随着国产系统的日益发展,......
  • 用jacoco统计JAVA项目测试代码覆盖率
    一、概述Jacoco统计的是全量代码覆盖率。它不仅支持生成单元测试的覆盖率,也支持监控生成接口测试,功能测试的覆盖率。在新一代精准测试技术流的影响中,各大型单位对覆盖率的追求越来越迫切。作为一款开源产品,它主机面向Java语言,能够在字节码层面给出覆盖率,同时也能将字节码关联到......
  • NUS CS1101S:SICP JavaScript 描述:四、元语言抽象
    原文:4MetalinguisticAbstraction译者:飞龙协议:CCBY-NC-SA4.0...魔法就在于文字——Abracadabra,开门,以及其他——但一个故事中的魔法词在另一个故事中并不神奇。真正的魔法是理解哪些词起作用,何时起作用,以及为什么起作用;诀窍就是学会这个诀窍。...而这些词是由我们字母表......
  • NUS CS1101S:SICP JavaScript 描述:五、使用寄存器机进行计算
    原文:5ComputingwithRegisterMachines译者:飞龙协议:CCBY-NC-SA4.0我的目标是表明天堂机器不是一种神圣的生命体,而是一种钟表(相信钟表有灵魂属性的人将制造者的荣耀归功于作品),因为几乎所有多种运动都是由一种最简单和物质力量引起的,就像钟表的所有运动都是由单一重力引起......
  • NUS CS1101S:SICP JavaScript 描述:前言、序言和致谢
    前言原文:Foreword译者:飞龙协议:CCBY-NC-SA4.0我有幸在我还是学生的时候见到了了不起的AlanPerlis,并和他交谈了几次。他和我共同深爱和尊重两种非常不同的编程语言:Lisp和APL。跟随他的脚步是一项艰巨的任务,尽管他开辟了一条优秀的道路。尽管如此,我想重新审视他在这本书......
  • NUS CS1101S:SICP JavaScript 描述:三、模块化、对象和状态
    原文:3Modularity,Objects,andState译者:飞龙协议:CCBY-NC-SA4.0变化中安宁(即使它在变化,它仍然保持不变。)——赫拉克利特变化越大,越是相同。——阿方斯·卡尔前面的章节介绍了构成程序的基本元素。我们看到了原始函数和原始数据是如何组合成复合实体的,我们也了解......
  • Java Web中文乱码的问题
    本编文章列举了JavaWeb编程中所有可能导致中文乱码的问题以及解决方法1、html页面乱码首先第一点便是html页面乱码问题例如:此时我们需要在项目中找到该网页的html文件,在html文件头部中找到metacharset="";然后将meta中的charset改为charset="UTF-8"如下图示:2、request乱码在debug......
  • Java使用modbus4j通过串口modbus-rtu协议 连接设备 demo
    前言项目中需要使用串口来连接操控烟雾报警器且只能使用modbus-rtu协议在找了一堆资料后终于成功了在此呈上代码和资料链接【ModBus】modbus之modbus4j的使用和流程原理解析(5)-CSDN博客使用modbus4j通过串口解析modbus协议(java)_javamodbus4j-CSDN博客 串口通讯需要使用modbus4j......
  • 《Java编程思想第四版》学习笔记54--关于UncaughtExceptionHandler
    Java中在处理异常的时候,通常的做法是使用try-catch-finally来包含代码块,但是Java自身还有一种方式可以处理——使用UncaughtExceptionHandler。它能检测出某个线程由于未捕获的异常而终结的情况。当一个线程由于未捕获异常而退出时,JVM会把这个事件报告给应用程序提供的UncaughtExce......
  • Java实现在线编辑预览office文档
    目录1在线编辑1.1PageOffice简介1.2前端项目1.2.1配置1.2.2页面部分1.3后端项目1.3.1pom.xml1.3.2添加配置1.3.3controller2在线预览2.1引言2.2市面上现有的文件预览服务2.2.1微软2.2.2GoogleDrive查看器2.2.3阿里云IMM2.2.4XDOC文档预览2.2.5OfficeWeb3652......