Java虚拟机之内存区域,今天这篇文章来深入理解一下把

简介: Java虚拟机之内存区域,今天这篇文章来深入理解一下把深入理解Java虚拟机之Java内存区域Java 虚拟机在执行 Java 程序的时候会把它所管理的内存分为多个不同的区域,每个区域都有不同的作用,以及由各自的生命周期,有些随着虚拟机进行的 启动而存在,有些区域则依赖于用户线程的启动或结束而建立或销毁等。

Java虚拟机之内存区域,今天这篇文章来深入理解一下把

深入理解Java虚拟机之Java内存区域

QQ_20190825204159

Java 虚拟机在执行 Java 程序的时候会把它所管理的内存分为多个不同的区域,每个区域都有不同的作用,以及由各自的生命周期,有些随着虚拟机进行的 启动而存在,有些区域则依赖于用户线程的启动或结束而建立或销毁等。在《Java虚拟机规范(Java SE7版)》中规定,Java 内存分为以下一种,如图所示:

QQ_20190825205338

1、程序计数器

程序计数器(Program Counter Register)是一个内存较小的区域,它可以被看作是当前线程所执行到的字节码的行号的指示器,字节码解释器在执行下一条指令,比如分支,跳转,循环,异常处理等都需要依赖这个计数器来完成的。

由于 Java 多线程是通过不断的切换分配处理器时间片的方式来实现的,在任何一个确定的时刻,一个处理器都只会执行一条指令。也就是说,在多线程中每个线程都可以抢到 cpu 的时间片,那么别抢去的线程会立即停止下来,直到它再一次获得 cpu 的时间片。那么, Java 虚拟机是如何确保再一次获得处理器时间片的时候能够在正确的位置上继续执行指令?

Java 虚拟机就是通过当前线程的程序计数器保证的,因为保存了当前线程上次执行结束的位置,因此,程序计数器是每个线程独有的,各线程之前的程序计数器互不影响,独立存储,我们称这类内存区域为“线程私有”的内存。

如果线程正在执行 Java 方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址,如果正在执行的是 native 方法,那么这个计数器值则为空(Undefined);因为程序计数器记录的是当前线程字节码执行指令的地址,所以它的内存大小是不会随着线程的执行而发生变化,因此,该内存区域是唯一一个在 Java虚拟机规范中没有规定任何 OutOfMemeryError (内存溢出)的情况的区域。

  1. Java虚拟机栈

Java 虚拟机栈与程序计数器一样都是线程私有的,它的生命周期与线程一致,每当创建一个新的线程时都会产生一个新的的虚拟机栈,线程销毁时虚拟机栈也随即销毁。每个方法的执行都会创建一个栈帧用于存储局部变量、操作数栈、动态链表,方法的出口等信息。每个方法的执行到结束就对应着一个栈帧在虚拟机中入栈和出栈的过程。

我们在日常开发的过程的中经常会把 Java 内存分为堆内存和栈内存,这个说法是不准确的,因为 Java 内存区域的划分远比这个复杂的多,这种划分的方法只能说明程序员最关注的,与对象内存分配关系最密切的内存区域这两块,这里所说的栈其实指的是虚拟机中的局部变量表。

局部变量表中存放了编译期可知的基本数据类型(char,byte,int,boolean,short,float,long,double)、对象的引用(reference类型,指的是对象地址的引用指针或是句柄)、returnAddress类型(指向字节码指令的地址)。

局部变量表所需的内存空间在编译期完成分配,当进入一个方法时,这个方法所需要的栈帧的大小就已经确定了,在运行期间不会改变局部变量表的大小。其中64位长度的 long 和 double 类型的数据会占用两个局部变量空间(Slot),其余的数据类型只能占用1个。

在 Java虚拟机规范中,对虚拟机栈定义了两种异常:第一,如果线程请求的深度大于虚拟机栈所允许的最大深度,将抛出 StackoverflowError 异常,第二,如果虚拟机栈可以动态的扩展,当扩展时无法申请到足够的内存,则会抛出 OutOfMemoryError 异常。

2.1、运行时栈帧

栈帧是 Java 虚拟机用来进行方法的调用和方法的指定的数据结构,它是虚拟机运行时数据库的 Java 虚拟机栈的栈元素。

每一个栈帧都包含局部变量表、操作数栈、动态链接、方法返回地址和一些额外的附加信息。
__20190825202010

局部变量:

方法的参数和方法中声明的局部变量都存储在局部变量中。在 Class 编译的时候就已经确定了的局部变量表的最大容量,声明在方法的 Code 属性的max_locals 数据项中。变量槽(Slot)是局部变量表的最小单位,可以存储32位和64位的数据,64位的数据则需要两个连续的变量槽来表示。

操作数栈:也成为操作栈,是一个后进先出的结构。方法执行过程中的算术运算或者调用其它方法的参数传递的时候都是在操作数栈中进行的。

动态链接:Class 文件中存放了大量的符号引用,字节码中的方法调用指令就是以常量池中指向方法的符号引用作为参数。这些符号引用一部分会类第一次加载或第一次引用的时候转化为直接引用,这种转化称为静态解析。另一部分将在每一次运行期转化为直接引用,这部分称为动态连接。

返回地址:当一个方法执行完成后的出口,有两个情况:一是正常情况退出,会将返回值传递给上一个方法的调用者,另一种是异常情况,此时是没有返回值的。

3.本地方法栈

本地方法栈和 Java 虚拟机栈的作用是差不多的,他们的唯一区别在于: Java 虚拟机栈为虚拟机执行 Java 方法(字节码)服务,而本地方法栈则为虚拟机中 Native 方法服务。在虚拟机规范中并没有明确的规定本地方法栈使用何种语言与数据结构,可有具体的虚拟机去实现它。本地方法栈和 Java 虚拟机栈一样,也会抛出 StackoverflowError 和 OutOfMemoryError 两个异常。

4.Java堆

Java 堆是 Java 内存管理区域中最大的一块,Java 堆是所有线程共享的一块内存区域,在虚拟机启动时创建。 Java 堆是用来存放对象,几乎所有的 Java 对象和数组都存放在Java堆中,都在堆中分配空间。

Java 堆是立即回收器管理的主要区域,因此有人也会把 Java 堆称为“GC堆”。从垃圾回收的角度来看,由于现在大多数的收集器都是采用分代收集算法,所以 Java 堆还可以细分为:新生代和老年代;再细致一点有可以分为Eden 空间, From Survivor 空间、To Survivor 空间等;从内存分配的角度来看,线程共享的java堆中可能划分为多个线程私有的分配缓冲区(TLAB)。

这样分配是为了更好的回收内存和创建内存等,与存放的区域无关,都是存放 Java 对象。根据Java虚拟机规范的规定,Java 堆可以是物理上不连续的内存空间,只要逻辑上连续就可以了。在实现时可以设置固定大小的空,也可以动态扩展的,不过当前流程的虚拟机都是可以动态来扩展的。如果在堆中没有足够的空间来分配实例对象,或者无法扩展堆空间,那么就会抛出 OutOfMemoryError 异常。

5.方法区

方法区和 Java 堆一样,都是线程共享的内存区域,它主要用于存储已被虚拟机加载的类信息,常量,静态变量,即时编译器编译后的代码等数据。虽然 Java 虚拟机规范把方法区描述为 Java 堆的一部分,但是它却有一个别名叫做 Non-Heap(非堆),目的是与 Java 堆分开。

Java 虚拟机规范对方法区的限制非常的宽松,除了和 Java 堆一样不需要物理上连续的内存和可以选择固定大小空间或动态扩展为,还可以选择不实现垃圾回收。相对而言,垃圾回收是比较少在这个区域中出现的,但并非进入到方法区的数据都能永久的存在的。这块区域的的内存回收目标主要是针对常量池的回收和对类型的卸载,但是回收的效果并不是非常明显,特别是对类型的卸载,条件非常的苛刻,但这块内存的回收是非常必要的。在 SUN 公司的 BUG列表中,曾经出现过若干个非常严重的 BUG ,就是由于低版本中 Hotspot 虚拟机没有对此内存区域进行回收造成的。

根据 Java 虚拟机规范的规定,当方法区无法满足内存分配的需求时,将抛出 OutOfMemoryError 异常。

6.运行时常量池

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

运行时常量池相对于 Class 文件常量池的另一个特征是具备动态性,Java 语言并不要求常量一定只有编译期才能产生,也就是并非预置入 Class 文件中的常量池的内容才可以进入方法区的运行时常量池,运行期间也可以将新的常量放入到池中,比如 String 类的 intern 方法。

既然运行时常量池是存在于方法区中的,那么在无法分配到足够的内存是也会抛出 OutOfMemoryError 异常。

最后福利来咯。有很多不了解想要学习的朋友们,小编也准备了一些学习资料,可以免费分享给大家,有视频还有面试题,面试题都整理成了pdf,需要的话可以关注我的公众号 Java周某人 就可以免费领取拉

相关文章
|
10天前
|
Java 编译器 开发者
深入理解Java内存模型(JMM)及其对并发编程的影响
【9月更文挑战第37天】在Java的世界里,内存模型是隐藏在代码背后的守护者,它默默地协调着多线程环境下的数据一致性和可见性问题。本文将揭开Java内存模型的神秘面纱,带领读者探索其对并发编程实践的深远影响。通过深入浅出的方式,我们将了解内存模型的基本概念、工作原理以及如何在实际开发中正确应用这些知识,确保程序的正确性和高效性。
|
2天前
|
缓存 算法 Java
Java中的内存管理:理解与优化
【10月更文挑战第6天】 在Java编程中,内存管理是一个至关重要的主题。本文将深入探讨Java内存模型及其垃圾回收机制,并分享一些优化内存使用的策略和最佳实践。通过掌握这些知识,您可以提高Java应用的性能和稳定性。
14 4
|
1天前
|
Java 数据挖掘 数据库连接
Java使用直接内存的好处
综上所述,Java直接内存的使用为开发者提供了一种绕过JVM堆限制、直接高效操作内存资源的途径,特别适用于高吞吐量、低延迟和大规模数据处理的场景。虽然直接内存的使用需要更精细的管理以避免内存泄漏和过度消耗系统资源,但恰当的利用能够显著提升应用的性能表现,是现代高性能Java应用不可或缺的工具之一。
6 1
|
2天前
|
存储 监控 算法
Java中的内存管理:理解Garbage Collection机制
本文将深入探讨Java编程语言中的内存管理,着重介绍垃圾回收(Garbage Collection, GC)机制。通过阐述GC的工作原理、常见算法及其在Java中的应用,帮助读者提高程序的性能和稳定性。我们将从基本原理出发,逐步深入到调优实践,为开发者提供一套系统的理解和优化Java应用中内存管理的方法。
|
6天前
|
监控 算法 Java
Java中的内存管理:理解垃圾回收机制
【10月更文挑战第2天】 在本文中,我们将深入探讨Java编程语言中的内存管理机制,特别是垃圾回收机制。我们将从基本原理、垃圾回收算法到实际应用场景全面解析,帮助你更好地理解和优化Java应用的内存使用。无论你是初学者还是有经验的开发者,这篇文章都能带给你新的启发和思考。
20 2
|
7天前
|
Java
java操作内存,简单讲解varhandle的使用
本文介绍了Java中VarHandle的使用,它是一种从JDK 9开始引入的用于高效访问对象字段的特性。文章通过示例代码展示了如何通过VarHandle操作对象的字段,包括设置和获取字段值,以及如何通过MethodHandles.lookup().findVarHandle()方法获取VarHandle实例。VarHandle提供了一种比反射更高效的内存操作方式,并且支持原子操作。
16 0
java操作内存,简单讲解varhandle的使用
|
8天前
|
存储 Java
深入理解java对象的内存布局
这篇文章深入探讨了Java对象在HotSpot虚拟机中的内存布局,包括对象头、实例数据和对齐填充三个部分,以及对象头中包含的运行时数据和类型指针等详细信息。
18 0
深入理解java对象的内存布局
|
19天前
|
存储 算法 Java
深入解析 Java 虚拟机:内存区域、类加载与垃圾回收机制
本文介绍了 JVM 的内存区域划分、类加载过程及垃圾回收机制。内存区域包括程序计数器、堆、栈和元数据区,每个区域存储不同类型的数据。类加载过程涉及加载、验证、准备、解析和初始化五个步骤。垃圾回收机制主要在堆内存进行,通过可达性分析识别垃圾对象,并采用标记-清除、复制和标记-整理等算法进行回收。此外,还介绍了 CMS 和 G1 等垃圾回收器的特点。
76 0
深入解析 Java 虚拟机:内存区域、类加载与垃圾回收机制
|
7天前
|
监控 算法 Java
Java中的内存管理:理解垃圾回收机制
本文深入探讨了Java编程语言中的内存管理,特别是其垃圾回收机制。我们将从基本原理出发,逐步解析垃圾回收的工作流程、优缺点以及如何通过编程实践优化应用性能。此外,文章还将讨论Java 11中引入的ZGC(Z Garbage Collector)这一新兴技术,帮助读者更好地理解和利用现代Java环境中的内存管理特性。
|
2月前
|
Java Docker 索引
记录一次索引未建立、继而引发一系列的问题、包含索引创建失败、虚拟机中JVM虚拟机内存满的情况
这篇文章记录了作者在分布式微服务项目中遇到的一系列问题,起因是商品服务检索接口测试失败,原因是Elasticsearch索引未找到。文章详细描述了解决过程中遇到的几个关键问题:分词器的安装、Elasticsearch内存溢出的处理,以及最终成功创建`gulimall_product`索引的步骤。作者还分享了使用Postman测试接口的经历,并强调了问题解决过程中遇到的挑战和所花费的时间。