深入浅出 Java虚拟机(2)之运行时数据区

本文深入解析Java虚拟机(JVM)的运行时数据区,包括方法区、程序计数器、虚拟机栈、堆和本地方法栈的功能与作用。揭示了类加载后在JVM中的存储位置,以及各部分如何支撑字节码执行引擎工作。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

深入浅出 Java虚拟机(2)之运行时数据区

在上一期《 Java虚拟机(1)之类加载器》中,我们留下了一个非常重要的问题。

类加载器是加载类的,但类究竟是被加载到JVM的哪里去了呢?

它就是我们这一期的主角。JVM的存储模块,运行时数据区

一.名词解释

运行时数据区,可以拆分成两个词,即:

  • 运行时
  • 数据区
名词解释
运行时运行时,运行时,它是指谁在运行的时候?其实是JVM运行时,更通常的情况下,应该是控制模块,也就是字节码执行引擎运行的时候。
数据区数据区,强调了其存在的作用。即,它应该是提供给JVM执行指令后存取数据的内存区域。

因此,运行时数据区,是为字节码执行引擎类装载子系统提供存取内存数据的服务的。

所以,我们想要了解运行时数据区如何设计的,为啥要这样设计,我们就不得不提,字节码执行引擎到底是如何工作的。

二.字节码执行引擎工作流程

1.方法区

首先,用户必须显示的指定要执行的类,JVM才开始工作。

//编译Hello.java,生成class文件
javac Hello.java 

//运行Hello,并传入param0,param1,param2 这3个参数
java Hello param0 param1 param2

java Hello,这条命令就是指定要加载执行Hello.class文件。

根据之前的知识,类加载器会将该class文件载入运行时数据区中。

然后呢?

JVM就会调用它的main方法。

此时,如果你是字节码执行引擎,你会怎么做?

你当然要找到运行时数据区里找到这个main方法对象,然后调用执行。

因此,运行时数据区里需要有一个专门的区域,来存放方法对象。

我们把这个区域称之为方法区

PS1: 通过该区域,字节码执行引擎可以根据方法的符号引用,来找到方法的直接引用。

PS2: 类的许多信息也是被存储在这个区域

方法区只是一种概念,方法区有两种经典的实现方式。

  • java7及以前,使用永久代来作为方法区
  • java8及以后,使用元空间取代永久代来作为方法区

但是,它们有点不同

区别永久代元空间
内存使用JVM内存本地内存
字面量及类的静态变量存在于方法区中移动到其他地方了

2.程序计数器

既然,程序开始运行了,那么,我们就需要一块区域来记录程序当前运行的指令地址。

所以程序计数器就诞生了。

为啥说程序计数器需要一块区域,而不是说需要一个变量呢?

因为,在多线程的环境下,每个线程都需要记录自己当前运行的位置。

用更深奥的话来讲,程序计数器线程私有的。

即对于每一个线程,都需要一个程序计数器

我们在考虑运行时数据区的每块内容的时候,我们都会考虑,这块区域是不是线程私有的。

这对我们理解这块区域非常重要。

3.虚拟机栈

此时,main方法就被运行了。

main方法是程序的入口,我们称之为主线程。

在程序运行过程中,也可能会产生许多其他线程。

那么在运行时数据区中也需要有一块区域来存储它们,我们将这块区域称之为虚拟机栈

注意:虚拟机栈也是线程私有的。即每一个线程都会对应一个栈

为啥要用栈这种数据结构?

因为方法调用的过程,与栈这种数据结构真的是天作之合。

方法在链式调用的过程中,先调用的方法往往是最后一个返回的。

栈在存取数据的过程中,先入栈的元素往往是最后一个出栈的。

举个例子:

比如方法main调用方法A,方法A调用方法B,方法B调用方法C。

那么C往往是最先结束的。而B次之,而A次次之,main则是最后结束的。

所以,同个线程内每当一个方法被调用的时候,就有东西被被压入栈中。当方法执行完毕,这个东西就出栈销毁。

这个东西是啥?换句话说,栈里存储的元素是啥?

我们给它取个名字,就叫栈帧。它其实就是和运行时的方法对应。

比如,递归的时候,不断调用同一个方法,就有相同方法的不同栈帧,被压入栈内。

栈帧有啥用?

栈帧,可以记录方法运行过程中的一些信息。方便字节码执行引擎运行的。比如:

  • 局部变量表

  • 操作数栈

  • 其他控制信息

信息功能
局部变量表当局部变量被赋值的时候,那么栈帧内的局部变量表就可以做相应的记录。
操作数栈当要进行四则运算的时候,那么被运算的数,就会被丢入操作数栈中。等待计算处理
其他控制信息

4.堆

然而事实上,局部变量表存放不同数据类型的方式也不太一样,比如下面这段代码:

public class Demo {
    public static void main(String[] args) {
        int a = 3;
        Object b = new Object();
        
    }
}

其中局部变量表中的a,b都是局部变量,但是a,b所指向的数据则是不同的数据类型。

其中,3是基本数据类型,而new Object()是复合数据类型

事实上,复合数据类型的数据并不会被直接存放在虚拟机栈中,因为其大小难以确定,并且可以动态变化。

而基本数据类型的大小都是固定的,可以被存放在栈中。

所以,在int a = 3;的过程中,3是被放入栈内的。

而new Object()的数据本身不应该被放入栈内。

那复合数据类型不放入中,运行时数据区就亟待一个新的内存区域,来存放这种复合的数据类型的数据。

我们将这个区域,称之为

专门划出这个区域,具有非常深远的意义。

除了可以将上面的问题迎刃而解,并且还带来了其他好处。

其中,最显而易见的就是方便统一进行垃圾回收。

然而,堆中也是被做了非常详细的划分。可以移步下一期《深入浅出java虚拟机(3)之堆与垃圾回收》

这样,实际对象被保存在中。而局部变量进行复合数据类型的赋值的时候,栈帧局部变量表里存放的则是实际对象在中的地址。

5.本地方法栈

与虚拟机栈结构类似,唯一的区别是它是为JVM执行本地方法提供服务,也就是被native关键字标记的方法。本地方法,本身并不是java中方法。它往往是C或C++的代码,给Java调用其他语言的类库提供了帮助。

举个例子,Java创建线程的时候,就会使用到本地方法。因为java本身是没有能力创建线程的。

三.总结

通过本章,你大致了解了

  1. class文件究竟被加载到哪里去了?
  2. 运行时数据区到底分为哪几块,都是干什么的?
  3. 运行时数据区中哪几块是线程私有的,哪几块不是?
  4. 栈帧里有哪些信息,每块内容是干什么的?
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值