JVM——Java内存模型 (JDK1.8)

本文详细介绍了Java虚拟机(JVM)的内存结构,包括程序计数器、Java虚拟机栈、本地方法栈、堆、元数据区和直接内存。探讨了各区域的功能、特点及异常处理,帮助理解JVM内存管理。

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

Java

一. 程序计数器

1.什么是程序计数器

​ 程序计数器(Program Counter Register)是一块较小的内存空间,它可以看作是当前线程所执行的字节码行号 的指示器。** 用于存放下一条指令所在单元的地址的地方。**

2.程序计数器的作用
  1. 字节码解释器工作通过改变程序计数器的值来选取下一条需要执行的字节码指令。如:分支、循环、跳转、异常处理、线程恢复等基础功能。

  2. 多线程情况下,程序计数器表示当前线程执行的位置,从而在线程切换的时候知道此线程上一次执行的位置在哪里。

3.程序计数器的特点
  1. 一块较小的内存空间。

  2. 每个线程独立的程序计数器,各线程间的程序计数器互不影响,独立存储,称为“线程私有内存”。

  3. 生命周期随着线程的创建而创建,随着线程的结束而死亡。

  4. 如果当前线程执行的是Java方法,程序计数器记录的是当前正在执行的虚拟机字节码指令的地址。如果当前线程正在执行的是Native方法,程序计数器的值则为空(Undefined)。

  5. 此内存区域是Java虚拟机规范中没有规定任何的OutOfMemoryError(OOM)情况的区域。

二. Java虚拟机栈

1.什么是Java虚拟机栈

​ Java虚拟机栈描述的是Java方法执行的内存模型:每个方法在执行的同时都会创建一个栈帧(Stack Frame)用来存储局部变量表(存放了编译期可知的各种基本数据类型boolean、byte、char、short、int、float、long、double,对象引用reference类型:不等同对象本身,returnAddress类型:指向了一条字节码指令的地址)、操作数栈、动态链接、方法出口等信息。每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。

1.1 局部变量表

局部变量表包括 : 8个基本数据类型,对象引用,returnAddress类型。

基本数据类型:byte,char,int,short,long,boolean,float,double

对象引用reference: 对象实例的引用: Student s = new Student() ;这里s就是对象引用。使用一个slot存储。

可以从对象引用 直接或间接地查找到对象在java堆中的数据存放的起始地址索引,能直接或间接地查找到对象所属数据类型在方法区中的存储类型信息。

returnAddress: 使用一个slot存储,保存的是return后要执行的字节码的指令地址。

局部变量表 : 一组变量值的存储空间,用于存放方法参数和局部变量。在Class 文件的方法表的 Code 属性的 max_locals 指定了该方法所需局部变量表的最大容量。

局部变量表的作用: 就是记录执行该方法时会使用到的变量值,它可以说这个方法的数据池,是我们方法中变量的化身,相当于把我们方法中所需要的变量整合成一个数组对象或集合对象,这个对象的名称就叫做局部变量表。

局部变量表的基本单位: 变量槽(Variable Slot

​ 正常来说一个slot的占用32位的长度内存,可以存放 boolean、byte、char、short、int、float、reference 和 returnAddress 8种类型,而 对于64位的 long 和 double 变量而言,虚拟机会为其分配两个连续的 Slot 空间。

1.2 操作数栈
jvm指令是基于操作栈的,也就是说,运算过程是在操作栈中进行的。

操作数栈也常被称为操作栈。在Class 文件的Code 属性的 max_stacks 指定了执行过程中最大的栈深度。Java 虚拟机的解释执行引擎称为“基于栈的执行引擎”,这里的栈就是指操作数栈。

java代码如下:

int a=1;
int b=2;
int c=a+b;
123

iload_0 // 将局部变量表0号索引的值入操作数栈

iload_1 // 将局部变量表1号索引的值入操作数栈

iadd // 操作数栈去除前两位相加,放入栈顶

istore_2 // 操作数栈顶元素出栈,放入局部变量表2号索引

1.3 动态链接
Class字节码的常量持中存有大量的符号引用,在运行期才将符号引用变成直接引用(也就是指向数据),可以是方法或者字段的引用。

符号引用和直接引用在运行时进行解析和链接的过程,叫动态链接。

  • 一个方法调用另一个方法,或者一个类使用另一个类的成员变量时,需要知道其名字
  • 符号引用就相当于名字,这些被调用者的名字就存放在Java字节码文件里(.class 文件)。
  • 名字是知道了,但是Java真正运行起来的时候,如何靠这个名字(符号引用)找到相应的类和方法。需要解析成相应的直接引用,利用直接引用来准确地找到。
1.4 方法出口
即本方法执行后下一步指令的地址,方法正常退出时,调用者PC计数器的值就可以作为返回地址,异常退出时,返回地址是要通过异常处理器来确定。
2.Java虚拟机栈的特点
  1. 每个线程私有的,生命周期和线程相同。

  2. 每个方法在执行的同时创建一个栈帧。

  3. 当进入一个方法时,这个方法需要在帧中分配的局部变量空间是完全确定的,在运行期间不会改变局部变量表的大小。

  4. Java虚拟机规范中,对这个内存区域规定了两种异常状况。如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常。如果虚拟机可以动态扩展,扩展时无法申请到足够的内存空间,则会抛出OutOfMemoryError异常。

三. 本地方法栈

1.什么是本地方法栈

​ 本地方法栈与Java虚拟机栈所发挥的作用非常相似,它们的区别不过是Java虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈为虚拟机执行Native方法服务。

2.本地方法栈的特点

​ 和Java虚拟机栈特点相同。

四.堆

1.什么是堆

​ 堆是JVM内存占用最大,管理最复杂的一个区域。其唯一的用途就是存放对象实例:所有的对象实例及数组都在堆上进行分配。1.7后,字符串常量池从永久代中剥离出来,存放在堆中。

2.堆的特点
  1. Java堆是Java虚拟机所管理的内存空间中最大的一块。
  2. Java堆是所有线程共享的一块内存区域。
  3. 在虚拟机启动时创建。
  4. 所有对象的实例以及数组都要在堆上分配。
  5. Java堆是垃圾收集器管理的主要区域。也被称“GC堆”
  6. Java堆可以处理物理上不连续的内存空间中,只要逻辑上连续即可。可以扩展,如果在队中没有内存完成实例分配,并且堆无法再扩展时,将抛出OOM。

五.元数据区

1.什么是元数据区

​ 持久代的空间被彻底地删除了,它被一个叫元空间的区域所替代了。持久代删除了之后,很明显,JVM会忽略PermSize和MaxPermSize这两个参数,还有就是你再也看不到java.lang.OutOfMemoryError: PermGen error的异常了。原来类的静态变量和Interned Strings 都被转移到了java堆区,

​ 只有class元数据才在元空间。

​ JDK 8的HotSpot JVM现在使用的是本地内存来表示类的元数据,这个区域就叫做元空间。

元数据区存储的是每个class的信息:

1.类加载器引用(classLoader)

2.运行时常量池 除了字符串常量池

所有常量、字段引用、方法引用、属性

3.字段数据

每个方法的名字、类型(如类的全路径名、类型或接口) 、修饰符(如public、abstract、final)、属性

4.方法数据

每个方法的名字、返回类型、参数类型(按顺序)、修饰符、属性

5.方法代码

每个方法的字节码、操作数栈大小、局部变量大小、局部变量表、异常表和每个异常处理的开始位置、结 束位置、代码处理在程序计数器中的偏移地址、被捕获的异常类的常量池索引。

六 .直接内存

1.什么是元数据区

​ NIO的Buffer提供了一个可以不经过JVM内存直接访问系统物理内存的类——DirectBuffer。 DirectBuffer类继承自ByteBuffer,但和普通的ByteBuffer不同,普通的ByteBuffer仍在JVM堆上分配内存,其最大内存受到最大堆内存的限制;而DirectBuffer直接分配在物理内存中,并不占用堆空间,其可申请的最大内存受操作系统限制。

2. 区别和应用场景
  1. 直接内存的读写操作比普通Buffer快,但它的创建、销毁比普通Buffer慢。

  2. 因此直接内存使用于需要大内存空间且频繁访问的场合,不适用于频繁申请释放内存的场合。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值