JVM内存区域划分

简介: JVM内存区域划分

学习Java虚拟机我们需要先从Java虚拟机运行时的数据区开始学习,了解Java虚拟机在运行时各个数据区域的划分以及各自的职能职责是什么,这将为我们了解Java代码在Java虚拟机中如何加载打下坚实的基础。

一、JVM运行时数据区的组成

Java虚拟机运行数据区主要包括所有线程共享数据区和线程隔离数据区。线程共享数据区指的是所有线程共享的数据区,主要包括方法区和Java堆。线程隔离的数据区指的是本线程私有的数据区,主要包括Java虚拟机栈、本地方法栈和程序计数器。

网络异常,图片无法展示
|

Java虚拟机运行时数据区

1.方法区

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

2.Java堆

对于绝大多数应用来说,这块区域是 JVM 所管理的内存中最大的一块。线程共享,主要是存放对象实例和数组。内部会划分出多个线程私有的分配缓冲区(Thread Local Allocation Buffer, TLAB)。可以位于物理上不连续的空间,但是逻辑上要连续。

3.Java虚拟机栈

线程私有,生命周期和线程一致。描述的是 Java 方法执行的内存模型:每个方法在执行时都会床创建一个栈帧(Stack Frame)用于存储局部变量表操作数栈动态链接方法出口等信息。每一个方法从调用直至执行结束,就对应着一个栈帧从虚拟机栈中入栈到出栈的过程。

4.本地方法栈

区别于 Java 虚拟机栈的是,Java 虚拟机栈为虚拟机执行 Java 方法(也就是字节码)服务,而本地方法栈则为虚拟机使用到的 Native 方法服务。也会有 StackOverflowError 和 OutOfMemoryError 异常。

5.程序计数器

内存空间小,线程私有。字节码解释器工作是就是通过改变这个计数器的值来选取下一条需要执行指令的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖计数器完成

6.运行时常量池

属于方法区一部分,用于存放编译期生成的各种字面量和符号引用。编译器和运行期(String 的 intern() )都可以将常量放入池中。内存有限,无法申请时抛出 OutOfMemoryError。

7.直接内存

非虚拟机运行时数据区的部分

二、HotSpot 虚拟机对象探秘

深入理解HotSpot 虚拟机在Java堆中对象是如何分配、布局和访问的全过程

1.对象如何创建

  • 虚拟机遇到 new 指令时,首先检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已经被加载、解析和初始化过。如果没有,执行相应的类加载。
  • 类加载检查通过之后,为新对象分配内存(内存大小在类加载完成后便可确认)。在堆的空闲内存中划分一块区域(Java堆中的内存如果是规整的就采用“指针碰撞”的方式分配对象的内存空间,Java肚中的内存空间是否规整由其采用的垃圾收集器是否带有压缩整理的功能有关。如果Java队中的内存不连续交错排列,Java虚拟机就会维护一个空闲列表,空闲列表主要记录哪一块内存块是可用的,再给新对象分配内存空间时从列表中寻找一块足够大的空间,更新列表上的记录。Serial、ParNew等带compact过程的收集器,系统采用的是指针碰撞,而CMS这种基于Mark-Sweep算法的收集器,采用空闲列表的形式初始化对象)。
  • 前面讲的每个线程在堆中都会有私有的分配缓冲区(TLAB),这样可以很大程度避免在并发情况下频繁创建对象造成的线程不安全。
  • 内存空间分配完成后会初始化为 0(不包括对象头),接下来就是填充对象头,把对象是哪个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的 GC 分代年龄等信息存入对象头。
  • 执行 new 指令后执行 init 方法后才算一份真正可用的对象创建完成。(这时只是完成了虚拟机层面的对象创建,而在Java代码层面对象才刚刚开始,执行完new指令后还需要接着执行init();初始化方法,执行完init()方法后,一个Java对象才算完全的产生出来)

2.对象的内存布局

在HotSpot虚拟机中,对象在内存中存储的布局主要分为3块区域:对象头、实例数据、和对齐填充。

对象头(Header)

对象头(数组对象除外)包含两部分:

  • 第一部分用于存储对象自身的运行时数据,如哈希码、GC 分代年龄、锁状态标志、线程持有的锁、偏向线程 ID、偏向时间戳等,32 位虚拟机占 32 bit,64 位虚拟机占 64 bit。官方称为 ‘Mark Word’。

网络异常,图片无法展示
|

  • 第二部分是类型指针,即对象指向它的类的元数据指针,虚拟机通过这个指针确定这个对象是哪个类的实例。

另外,如果是 Java 数组,对象头中还必须有一块用于记录数组长度的数据,因为普通对象可以通过 Java 对象元数据确定大小,而数组对象不可以。

实例数据(Instance Data)

程序代码中所定义的各种类型的字段内容(包含父类继承下来的和子类中定义的)。

对齐填充(Padding)

对齐填充并不是必然需要,主要是占位,保证对象大小是某个字节的整数倍

3.对象的访问定位

使用对象时,通过栈上的 reference 数据来操作堆上的具体对象。

  • 通过句柄访问

Java 堆中会分配一块内存作为句柄池。reference 存储的是句柄地址,而句柄中包含对象实例数据和类型数据各自的具体地址信息。详情见图。

网络异常,图片无法展示
|

\

  • 通过直接指针访问

reference 中直接存储对象地址

网络异常,图片无法展示
|

比较:使用句柄的最大好处是 reference 中存储的是稳定的句柄地址,在对象移动(GC)是只改变实例数据指针地址,reference 自身不需要修改。直接指针访问的最大好处是速度快,节省了一次指针定位的时间开销。如果是对象频繁 GC 那么句柄方法好,如果是对象频繁访问则直接指针访问好。虚拟机Hotspot在对象访问的实现上,采用了直接指针访问的方式

三、内存溢出(OutOfMemoryError)异常

内容待填


相关文章
|
5天前
|
缓存 Java
《JVM由浅入深学习九】 2024-01-15》JVM由简入深学习提升分(生产项目内存飙升分析)
《JVM由浅入深学习九】 2024-01-15》JVM由简入深学习提升分(生产项目内存飙升分析)
10 0
|
9天前
|
存储 Java C++
Java虚拟机(JVM)在执行Java程序时,会将其管理的内存划分为几个不同的区域
【6月更文挑战第24天】Java JVM管理内存分7区:程序计数器记录线程执行位置;虚拟机栈处理方法调用,每个线程有独立栈;本地方法栈服务native方法;Java堆存储所有对象实例,垃圾回收管理;方法区(在Java 8后变为元空间)存储类信息;运行时常量池存储常量;直接内存不属于JVM规范,通过`java.nio`手动管理,不受GC直接影响。
18 5
|
7天前
|
存储 Java 对象存储
jvm内存模型剖析
当线程cpu时间片执行完后,线程进入休眠状态,当再次唤醒时,通过程序计数器确定指令执行到哪一行,然后继续往下执行。
18 1
|
8天前
|
存储 Java C++
Java虚拟机(JVM)管理内存划分为多个区域:程序计数器记录线程执行位置;虚拟机栈存储线程私有数据
Java虚拟机(JVM)管理内存划分为多个区域:程序计数器记录线程执行位置;虚拟机栈存储线程私有数据,如局部变量和操作数;本地方法栈支持native方法;堆存放所有线程的对象实例,由垃圾回收管理;方法区(在Java 8后变为元空间)存储类信息和常量;运行时常量池是方法区一部分,保存符号引用和常量;直接内存非JVM规范定义,手动管理,通过Buffer类使用。Java 8后,永久代被元空间取代,G1成为默认GC。
21 2
|
13天前
|
监控 算法 Java
Java虚拟机(JVM)使用多种垃圾回收算法来管理内存,以确保程序运行时不会因为内存不足而崩溃。
【6月更文挑战第20天】Java JVM运用多种GC算法,如标记-清除、复制、标记-压缩、分代收集、增量收集、并行收集和并发标记,以自动化内存管理,防止因内存耗尽导致的程序崩溃。这些算法各有优劣,适应不同的性能和资源需求。垃圾回收旨在避免手动内存管理,简化编程。当遇到内存泄漏,可以借助VisualVM、JConsole或MAT等工具监测内存、生成堆转储,分析引用链并定位泄漏源,从而解决问题。
24 4
|
5天前
|
存储 缓存 算法
详解JVM内存优化技术:压缩指针
详解JVM内存优化技术:压缩指针
|
5天前
|
Java UED 开发者
JVM逃逸分析原理解析:优化Java程序性能和内存利用效率
JVM逃逸分析原理解析:优化Java程序性能和内存利用效率
|
5天前
|
存储 缓存 监控
深入解析JVM内存分配优化技术:TLAB
深入解析JVM内存分配优化技术:TLAB
|
6天前
|
存储 安全 Java
深入理解Java内存模型(JMM)与虚拟机的内存结构(JVM)
深入理解Java内存模型(JMM)与虚拟机的内存结构(JVM)
|
6天前
|
存储 算法 安全
JVM-内存划分-垃圾回收器-回收算法-双亲委派-三色标记
JVM-内存划分-垃圾回收器-回收算法-双亲委派-三色标记