深入理解Java虚拟机第二章读书笔记:Java内存模型

本文详细介绍了Java虚拟机(JVM)的内存管理机制,包括程序计数器、虚拟机栈、本地方法栈、Java堆、方法区、运行时常量池及直接内存等关键概念。深入探讨了各内存区域的作用、生命周期及垃圾回收机制。

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

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

1.程序计数器(线程私有内存)

程序计数器是每个线程都有的一个内存区域。学过操作系统的都知道,一个CPU在同一时刻只能执行一个线程(以单核CPU为例),并且会按照一定的规则(例如时间片、优先级)轮流执行所有的线程。举个例子,现在有线程A和线程B,CPU轮流执行AB两个线程,首先线程A执行到一半,CPU就开始执行线程B了,那么执行完线程B以后,CPU怎么知道线程A上次执行到哪了呢?这个时候程序计数器就起作用了,它记录了该线程上一次执行到的那条字节码指令的地址。

(Java之所以具有良好的移植性,就是因为有Java虚拟机的存在,程序首先被编译成.class字节码文件,然后通过JVM来解释翻译成机器能够明白的二进制代码)这个过程中字节码解释器就是通过改变程序计数器中的值来选取下一条需要执行的字节码指令

如果线程正在执行的是一个Java方法,那么程序计数器记录的就是虚拟机正在执行的字节码指令的地址。

如果程序正在执行的是一个Native方法,那么程序计数器的值为空。

(什么是Native方法?根据Java的文档解释就是一个java调用了非java代码的接口,Native方法一般用于加载文件和动态链接库。由于Java语言无法直接访问系统底层信息,那么这个时候就要借助C语言来完成,附上一篇关于Native方法的文章链接https://www.cnblogs.com/HDK2016/p/7226840.html

2.Java虚拟机栈(线程私有内存)

Java虚拟机栈的生命周期与线程相同。虚拟机栈描述的是Java方法执行时的内存模型。每个方法在被执行时,会创建一个栈帧(Stack Frame)。一个方法从调用直至执行完成的过程,就对应着一个栈帧从入栈到出栈的过程。这个栈帧中包含以下几个部分:

  1. 局部变量表:常说的堆和栈,准确的来说其中的栈指的就是局部变量表部分。局部变量表中存放着编译时期各种可知的基本数据类型(boolean、int、byte、char、short、float、long、double)、对象引用(reference)。long和double会占用2个局部变量空间,其余类型只占用一个。局部变量表所需的空间在编译时期就已经确定好了大小,当栈帧进入虚拟机栈时就已经知道局部变量表在栈帧中的空间大小,并且在方法运行期间不会被改变。
  2. 操作数栈
  3. 动态链接
  4. 方法出口

3.本地方法栈(为虚拟机使用到的Native方法服务)

本地方法栈的功能与虚拟机栈极为相似,只不过虚拟机栈是为Java方法(即字节码)服务,而本地方法栈是为Native方法服务。

有的虚拟机甚至直接把本地方法栈和虚拟机栈合二为一。

4.Java堆(所有线程共享)

Java堆的空间在虚拟机启动时就被分配,是JVM管理的最大的一块内存。这块内存区域的唯一目的就是存放对象实例,所有的对象实例以及数组都要在堆上进行分配。

Java堆也是垃圾收集器管理的主要区域。从内存回收的角度来看,由于现在的收集器基本采用分代收集算法,所以堆还可以被细分为新生代和老年代。从内存分配的角度来看,由于要保证原子性,所以会给每个线程分配一个分配缓冲区,每个线程都在各自的分配缓冲区内创建属于自己线程的对象,当分配缓冲区的空间用完了,再向虚拟机重新请求扩充分配缓冲区大的大小。

根据Java虚拟机规范的规定,Java堆可以处于物理上不连续的内存空间中,只要逻辑上是连续的即可。当前主流的虚拟机都是按照可扩展来实现的(通过-Xmx和-Xms参数控制)。

5.方法区 Non-Heap(非堆)(所有线程共享)

方法区用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。

Java虚拟机规范中把方法区描述成堆的一个逻辑部分,但是实际上为了区分它和堆,给了它一个名字叫做Non-Heap(非堆)

方法区和堆一样,可以拥有物理上不连续的内存,并且可以选择不实现垃圾收集。这个内存区域的垃圾收集条件十分严苛。

6.运行时常量池(方法区的一部分)

Class文件中出了有类的版本、字段、方法、接口等描述信息外,还有一项信息就是常量池,用于存放编译期产生的各种字面量和符号引用。这一部分内容将在类加载之后,进入方法区的运行时常量池中存放。

7.直接内存

直接内存并不是虚拟机运行时数据区的一部分,也不是Java虚拟机规范中定义的内存区域,但是经常被引用。

在JDK1.4中新加入了NIO(New Input/Output)类,引入了一种基于通道与缓冲区的I/O方式,它可以直接使用Native函数库直接分配堆外面的内存,然后通过一个存储在Java堆中的DirectByteBuffer对象作为这块内存的引用来进行操作,从而避免了在Java堆中和Native堆中来回复制数据,在某些场景中能显著的提高性能。

直接内存不会受到堆内存大小的限制,但是会受到本机总内存以及分页文件大小的限制。


补充说明

JDK8版本中,元空间取代了永久代,那么什么又是永久代呢?在《Java虚拟机规范》中只定义了方法区的概念和作用,但是没有说怎么去实现它。众所周知,不同的产商会根据自己的需求去开发不同的JVM,所以各个JVM对《Java虚拟机规范》中提出的这些概念的具体实现也是不一样的,我们平常用的最多的就是HotSpot虚拟机,而在HotSpot虚拟机中它对方法区的实现就是永久代(两者关系类似于接口和实现类)。最后总结一下,方法区只是一种规范文档里的概念,而永久代是HotSpot虚拟机对概念的具体实现,在JDK8以前,使用永久代来实现方法区,JDK8开始使用元空间来实现方法区。

区别于永久代,元空间在本地内存中分配,在JDK8里,永久代区中的所有内容中,字符串常量移至堆内存,其他内容包括类元信息、字段、静态属性、方法、常量等都移动至元空间内。比如显示在常量池中的String,其实际对象时被保存在堆内存中的。

至此内存区域应该分为以下几个部分,如下图所示:

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值