运行时数据区域

java虚拟机在执行java程序的过程中会把它所管理的内存划分为若干个不同的数据区域。这些区域都有各自的用途及创建和销毁时间,有的随着虚拟机进程的启动而存在,有些区域则是依赖用户线程的启动和结束而建立和销毁。

这里有个图

其中方法区和堆,是所有线程共享的。虚拟机栈、本地方法栈和程序计数器由线程独享的。


  1. 程序计数器

程序计数器是一块较小的内存空间,它的作用可以看作是当前线程所执行的字节码的行号指示器。

在任意时刻,一条Java虚拟机线程只会执行一个方法的代码,这个正在被线程执行的方法称为该线程的当前方法。如果这个方法不是 native 的,那 PC 寄存器就保存 Java 虚拟机正在执行的 字节码指令的地址,如果该方法是 native 的,那 PC 寄存器的值是 undefined。

该区域是唯一不会抛出OutOfMemoryError的区域。

2. java虚拟机栈

每一条 Java 虚拟机线程都有自己私有的 Java 虚拟机栈(Java Virtual Machine Stack) ,这个栈与线程同时创建,用于存储栈帧。每个方法被执行的时候都会同时创建一个栈帧,用于存储局部变量表、操作数栈、动态连接、方法出口等信息。方法的调用和结束对应着一个栈帧的入栈和出栈。java虚拟机栈在内存上可以是不连续的。

局部变量表存放了编译期可知的各种基本数据类型、对象引用和returnAddress类型。其中64位长的long和double会占据2个局部变量空间(slot)。每个栈帧内的局部变量表的空间大小在创建的时候就已确定,在消亡前不会再发生变化。

但是java虚拟机栈可以是固定的也可以是动态扩展、收缩的。

3.本地方法栈

Java虚拟机实现可能会使用到传统的栈(通常称之为“C Stacks”)来支持native方法 (指使用 Java 以外的其他语言编写的方法)的执行,这个栈就是本地方法栈。

当 Java 虚拟机使用其他语言(例如 C 语言)来实现指令集解释器时,也会使用到本地 方法栈。如果 Java 虚拟机不支持 native 方法,并且自己也不依赖传统栈的话,可以无需支持本 地方法栈,如果支持本地方法栈,那这个栈一般会在线程创建的时候按线程分配。

4.堆

在 Java 虚拟机中,堆(Heap)是可供各条线程共享的运行时内存区域,也是供所有类实例 和数组对象分配内存的区域。

Java虚拟机启动的时候就被创建,它存储了被自动内存管理系统(Automatic Storage Management System,也即是常说的“Garbage Collector(垃圾收集器)”)所管理的各种对象,这些受管理的对象无需,也无法显式地被销毁。

Java 虚拟机并未假设采用什么具体的技术去实现自动内存管理系统。虚拟机实现者可以根据系统的实际需要来选择自动内 存管理技术。

Java 堆的容量可以是固定大小的,也可以随着程序执行的需求动态扩展,并在不需 要过多空间时自动收缩。Java 堆所使用的内存不需要保证是连续的。

5.方法区

可以被多个线程在运行时共享的内存区域。用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。

方法区在虚拟机启动的时候被创建,虽然方法区是堆的逻辑组成部分,但是简单的虚拟机实现 可以选择在这个区域不实现垃圾收集。方法区的容量可以是固定大小的,也可以随着程序执行的需求动态扩展, 并在不需要过多空间时自动收缩。方法区在实际内存空间中可以是不连续的。

6.运行时常量池

运行时常量池是方法区的一部分。是每一个类或接口的常量池的运行时表示形式,它包括了若干种不同的常量:从编译期可知的数值字面量到必须运行期解析后才能获得的方法或字段引用。运行时常量池扮演了类似传统语言中符号表(Symbol Table)的角色,不过它存储数据范围比通常意义上的符号表要更为广泛。

每一个运行时常量池都分配在 Java 虚拟机的方法区之中,在类和接口被加载到 虚拟机后,对应的运行时常量池就被创建出来。

7.栈帧

栈帧(Frame)是用来存储数据和部分过程结果的数据结构,同时也被用来处理动态链接 (Dynamic Linking)、方法返回值和异常分派(Dispatch Exception)。

栈帧随着方法调用而创建,随着方法结束而销毁——无论方法是正常完成还是异常完成(抛出 了在方法内未被捕获的异常)都算作方法结束。栈帧的存储空间分配在 Java 虚拟机栈之中,每一个栈帧都有自己的局部变量表(Local Variables)、操作数栈(Operand Stack)和指向当前方法所属的类的运行时常量池的引用。

在一条线程之中,只有目前正在执行的那个方法的栈帧是活动的。这个栈帧就被称为是当前栈 帧(Current Frame),这个栈帧对应的方法就被称为是当前方法(Current Method),定义 这个方法的类就称作当前类(Current Class)。对局部变量表和操作数栈的各种操作,通常都 指的是对当前栈帧的对局部变量表和操作数栈进行的操作。

栈帧是线程本地私有的数据,不可能在一个栈帧之中引用另外一条线程的栈帧。

1)局部变量表

栈帧中局部变量表的长度由编译期决定,并且存储于类和接口的二进制表示之中,既通过方法的 Code 属性保存及提供给栈帧使用。

局部变量使用索引来进行定位访问,第一个局部变量的索引值为零,局部变量的索引值是从零 至小于局部变量表最大容量的所有整数。

Java 虚拟机使用局部变量表来完成方法调用时的参数传递,当一个方法被调用的时候,它的 参数将会传递至从 0 开始的连续的局部变量表位置上。特别地,当一个实例方法被调用的时候, 第 0 个局部变量一定是用来存储被调用的实例方法所在的对象的引用(即 Java 语言中的“this” 关键字)。后续的其他参数将会传递至从 1 开始的连续的局部变量表位置上。

2)操作数栈

操作数栈为后进先出型栈。操作数栈所属的栈帧在刚刚被创建的时候,操作数栈是空的。Java 虚拟机提供一些字节码指 令来从局部变量表或者对象实例的字段中复制常量或变量值到操作数栈中,也提供了一些指令用于 从操作数栈取走数据、操作数据和把操作结果重新入栈。在方法调用的时候,操作数栈也用来准备 调用方法的参数以及接收方法返回结果。

在任意时刻,操作数栈都会有一个确定的栈深度,一个 long 或者 double 类型的数据会占用 两个单位的栈深度,其他数据类型则会占用一个单位深度。

3)动态连接

每一个栈帧内部都包含一个指向运行时常量池的引用来支持当前方法 的代码实现动态链接(Dynamic Linking)。在 Class 文件里面,描述一个方法调用了其他方法, 或者访问其成员变量是通过符号引用(Symbolic Reference)来表示的,动态链接的作用就是 将这些符号引用所表示的方法转换为实际方法的直接引用。类加载的过程中将要解析掉尚未被解析 的符号引用,并且将变量访问转化为访问这些变量的存储结构所在的运行时内存位置的正确偏移 量。

由于动态链接的存在,通过晚期绑定(Late Binding)使用的其他类的方法和变量在发生 变化时,将不会对调用它们的方法构成影响。



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值