JVM学习系列1—探究Java虚拟机内存区域

1. 运行时数据区域

在这里插入图片描述

1. 1 程序计数器PCR

【为什么需要PCR】因为Java虚拟机是多线程轮流切换的,任何一个确定的时刻,处理器(内核)只会执行一条线程中的指令。所以在线程切换完之后为了让之前的线程恢复到正确的执行位置,需要每个线程里面私有一块小内存,也就是PCR,它的作用就是去存储当前线程正在执行的字节码指令地址,执行完当前指令后,程序计数器会更新值选取下一条要执行的指令的地址。

补充1:
如果执行的是一个java方法,那这个PCR记录的是当前执行的字节码指令的地址。
如果执行的是本地方法,计数器为undefined,因为此内存区域没有OutOfMemoryError。
补充2:
线程的切换是由操作系统和虚拟机共同完成的。当一个线程执行完毕后,操作系统会通知虚拟机,虚拟机再根据调度器的策略选择下一个要执行的线程,并将其程序计数器的值加载到处理器中,从而开始执行下一个线程的指令。

1.2 Java虚拟机栈

这里是一些基本简介,具体运行时的细节在第八章

Java虚拟机栈也是线程私有的,描述的是java方法执行的线程内存模型:
每个java方法(字节码)被执行的时候,java虚拟机会同步创建一个栈帧用来存储局部变量表,操作数栈,动态链接和方法出口。
这个区域内存情况有两种异常:1. 线程请求的栈深度大于虚拟机允许的深度,抛出StackOverflowError。 2. 虚拟机栈容量可以动态扩展(HotSpot不支持),当栈扩展时无法申请到足够的内存,抛出OutMemoryError。

1.2.1 局部变量表

存放了编译器可知的各种基本数据类型(boolean, int, byte, char, short, float, long, double),对象引用(指向一个对象的起始地址的引用指针或代表对象的句柄),returnAddress(是指向了一条字节码指令的地址)。
存储空间以局部变量槽来表示,并且局部变量定义了,不能不赋值,不像类的字段变量,在准备和初始化阶段都会有赋值操作。

1.2.2 操作数栈

字节码指令往操作数栈中写入提取内容

1.2.3 动态链接

运行期间转化为直接引用

1.3 本地(native)方法栈

类似于java栈,区别在于java栈服务于java方法,而他服务于虚拟机使用到的本地方法。

1.4 java堆

虚拟机启动时创建,所有线程共享,用来存放对象实例。(对象下文有解释)

以往来说,会分为新生代和老年代。

1.5 方法区

所有线程共享,用于存储已被虚拟机加载的类型信息,常量,静态变量或方法(可看第六章loading过程的三步),即时编译后的代码缓存。

1.5.1 运行时常量池介绍
方法区的其中一部分叫做运行时常量池。Class文件中除了有类的版本,字段,方法,接口外,还有就是*常量池表,用来存放编译器生成的各种字面量和符号引用。

运行时常量池有动态性,除了预置到Class文件中的常量池内容,运行期间新的常量也能进入常量池。
例子: String类的inter()方法

1.6 直接内存

不是虚拟机运行时的数据,但会导致OutOfMemoryError。
JDK1.4新加入的new input/output类,引入一种基于通道与缓存区的I/O方式,可调用Native库直接分配堆外内存,然后通过存在Java堆里的DirectByteBuffer对象作为这块内存的引用进行操作,避免了Java堆和Native堆的来回复制

2. 虚拟机中的对象

2.1 对象的创建的过程(不包括数组对象和Class对象)

  1. 虚拟机遇到一个字节码new指令时,首先检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号引用是否被加载,解析,初始化过,如果没有,就要完成这三个阶段。
  2. 为新生对象分配内存,对象所需内存在类加载过程就能确定。此时有两种分配方式,选择取决于Java堆是否规整(由垃圾收集器是否有空间压缩能力决定)。
    第一种:指针碰撞,使用过的内存和未用的中间有一个指针,分配就是指针向空闲空间挪动一个对象的同等距离。(Serial,ParNew)
    第二种:空闲列表,维护一个列表,记录那些内存块可用。(CMS)
    还有一个要考虑的问题,分配内存时并不是线程安全的。
    所以两种方法,一种是采用CAS配上失败重试的方式保证更新操作的原子性。第二种是预选分配一个小空间,叫做本地线程分配缓冲(TLAB),哪个线程要分配,就在哪个线程的TLAB中分配。
  3. 分配到的内存空间(不包括对象头)初始化为零值(准备阶段),也可在TLAB分配时一起进行。
  4. 把对象是哪个实例,如何找到类的元数据(Mark Word),GC分代年龄放进对象头。
  5. 对java程序来说,还差一步。new指令后要接着执行<init>()方法,按照我们的意愿初始化对象。

2.2 对象的内存布局

对象有三部分组成,对象头,实例数据,对齐填充。

  1. 对象头包含的第一个信息,是对象自身的运行时数据,哈希码、GC分代年龄01,锁状态00。
  2. 类型指针,即指向它的类型元数据指针,虚拟机会通过这个确定该对象是哪个类的实例。

2.3 对象的访问定位

程序通过java栈中的reference访问(How两种)堆上的具体对象。

  1. 句柄访问
    ![[uTools_1687753398504 1.png]]
    堆中有句柄池,一个对象两个指针,一个实例数据指针–>堆中实例池的实例数据,另一个类型指针–>方法区的类型数据
    移动对象时只要改变实例指针,reference本身不要变。
  2. 直接指针访问
    ![[uTools_1687753421115.png]]
    reference直接存的就是对象地址,在堆里放的对象的地址和类型指针–>方法区的类型数据。
    省了一次指针定位的时间。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值