深入浅出Java虚拟机(JVM)-JVM内存区域

文章基于学习《深入理解 Java 虚拟机:JVM 高级特性与最佳实践》进行总结。

1、引言

对于Java开发者而言,在虚拟机自动内存管理机制下,不再需要为每个对象创建去写配对的delete/free代码,由于虚拟机自动内存管理,从而减少了内存泄漏和内存溢出问题。不过,也正是将内存控制权限交由Java虚拟机,一旦出现内存泄漏或内存溢出问题,如果不了解虚拟机是如何使用内存的,排查问题时将无参下手。

本文将介绍Java虚拟机内存的各个区域,这些区域的作用、服务对象以及其中可能产生的问题。

2、运行时数据区域

Java虚拟机在执行Java程序时将它管理的内存划分为多个区域。这些区域有各自的用途,以及创建和销毁时间,如图:

2.1、程序计数器

程序计数器是一块较小的内存区域,它的作用可以看作是线程所执行的字节码的行号指示器。通过程序计数器的值可以知道当前线程执行到哪条指令了,需要执行的下一条字节码指令是什么。由于Java虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式实现的。因此每个线程都会有一个自己独立的计数器来记录程序执行位置。

  1. 程序执行的是Java方法,这个计算器记录的是正在执行的虚拟机字节码指令地址;
  2. 程序执行的是Native方法,这个计算器记的值则为空。

注: 程序计数器是内存区域唯一一个在Java虚拟机规范中没有规定任何OoutOfMemoryError的区域。

2.2、虚拟机栈

Java虚拟机也是线程私有的,它的生命周期与线程相同。每一个方法被执行时都会创建一个栈帧用于存放局部变量表、操作数栈、动态链接、方法出口等信息。

虚拟机栈可能发生的异常:

  1. 如果线程请求的栈深度大于虚拟机允许的最大深度,将抛出StackOverflowError异常;
  2. 如果虚拟机可以动态扩展,当无法申请到足够的内存时将会抛出OutOfMemoryError异常。

2.3、本地方法栈

本地方法栈与虚拟机栈非常相似,其区别是虚拟机栈为Java方法服务,本地方法栈为Native方法服务。本地方法栈的方法可以使用其他语言的方法。本地方法栈也会抛出StackOverflowError异常和OutOfMemoryError异常。

2.4、堆

堆是Java虚拟机所管理内存中最大的一块区域,是所有线程共享的一块区域,随虚拟机启动而创建。此内存区域唯一的目的是存放对象实例,所有的对象实例和数组都在堆上分配内存空间。

堆是垃圾收集器主要工作区域,因此又叫GC堆。堆又可以细分为新生代和老年代,新生代又可以分为Eden和From Survivor 和 To Survivor。

堆在逻辑上是连续的,但在物理上可以是不连续的空间。堆的大小可以固定也可以是可扩展的,通过-Xmx-Xms。如果堆没有足够的内存分配给对象实例,且无法再扩容,则会抛出OutOfMemoryError异常。

2.5、方法区

方法区是各个线程共享的内存区域,主要用于存放加载的类信息、常量、静态变量以及即时编译器编译的代码等数据。方法区又被叫"永久代",因为HotSpot虚拟机将GC回收扩展到方法区,现在也改为元空间划分到直接内存区域。

2.5.1、运行时常量池

运行时常量池是方法区重要的一部分,类中的常量在类加载后被存放在常量池中。运行时常量池会受到方法区内存的现在,当常量池无法申请到足够内存时会抛出OutOfMemoryError异常。

2.6、直接内存

除了虚拟机运行时数据区外,还有一部分内存也被频繁使用,而且也可能导致OutOfMemoryError异常出现,就是直接内存。NIO引入的基于Channel和缓冲区的I/O方式,它可以直接操作Native函数库直接分配堆外内存。可以避免在Java堆和Native堆中来回复制数据,可以提升性能。直接内存虽然不受Java堆大小限制,但会受本机总内存的大小和处理器寻址空间的限制。内存不足时,动态扩展也会导致OutOfMemoryError异常。另外,我们在根据实际内存设置-Xmx时,不要忽略了直接内存。

3、对象访问

在虚拟机栈的局部变量表中存放各种基本数据类型和对象引用(reference类型)和returnAddress类型,

在方法中出现 Object obj = new Object();, Objcet obj 这部分语义将会定义到虚拟机栈的局部变量表中,作为reference类型数据出现。而 new Object这部分语义将反映到Java堆中。在Java堆中包含两部分数据,实例数据值和能查询到此对象类型的数据(如对象类型、父类、实现的接口、方法等)地址信息,这些类信息存放在方法区中。

在Java虚拟机,对象常见的访问有两种方式:

  • 句柄访问
  • 直接指针访问

句柄访问:Java堆中划分一块内存作为句柄池,句柄池存放的是对象的句柄地址,而句柄包含对象实例数据和类型数据的访问地址信息。

优势 :引用中存储的是稳定 的句柄地址,在对象被移动(垃圾收集时移动对象是非常普遍的行为)时只会改变句柄中实例数据指针 ,而引用本身不需要修改。

直接指针访问:Java堆对象中包含了对象类型访问地址和对象实例数据。

优势 :速度更 ,节省了一次指针定位的时间开销。由于对象的访问在Java中非常频繁,因此这类开销积少成多后也是非常可观的执行成本。HotSpot 中采用的就是这种方式。

相关推荐
Code成立8 小时前
《深入理解Java虚拟机:JVM高级特性与最佳实践(第3版)》第2章 Java内存区域与内存溢出异常
java·jvm·jvm内存模型·jvm内存区域
南汐以墨10 小时前
探秘JVM内部
java·jvm
云闲不收17 小时前
垃圾回收——三色标记法(golang使用)
jvm·算法·golang
云之兕21 小时前
Java内存模型详解:堆、栈、方法区
java·开发语言·jvm
bing_1581 天前
JVM 垃圾回收器是如何判断一个对象是否要回收?
jvm·垃圾回收机制·java调优
陳長生.1 天前
JAVA EE_多线程-初阶(二)
java·开发语言·jvm·java-ee
快来卷java1 天前
JVM虚拟机篇(五):深入理解Java类加载器与类加载机制
java·jvm·mysql
程序猿chen1 天前
《JVM考古现场(十六):太初奇点——从普朗克常量到宇宙弦的编译风暴》
jvm·git·后端·程序人生·金融·java-ee·量子计算
Excuse_lighttime2 天前
JAVA阻塞队列
java·开发语言·jvm