Java内存区域(一)

简介: Java内存区域

概述

本篇文章是根据学习《深入理解Java虚拟机》书籍及其聆听尚硅谷宋红康老师讲解 ,最终自己按照自己的理解总结而出


图片引用: https://imlql.cn/post/a7ad3cab.html && 尚硅谷教育


对于c++选手来说, 内存管理是一项基本功,因为c++没有自带的管理技术, 所以c++开发人员需要自己对实现的所有代码进行内存管理。 虽然说Java实现了一套自己的内存管理机制, 这让Java程序员可以全心投入到需求开发中去, 不需要对内存做太多了的了解。 但是问题也正是出现在这里, 因为不知道虚拟机是怎么使用内存的,所以出了问题也是无从下手,不知道具体哪里出了问题。 所以这些都是我们Java程序员需要了解和掌握的内存管理技术的原因。


之前我们学习了类加载器子系统的知识 ,知道了一个Class文件是如何一步步被加载到虚拟机内存中的开始 ,到卸载出内存位为止, 他的整个生命周期以及每个周期所做的事等等….


既然一个类已经被加载到内存了 ,那么下一步就是查看内存如何管理这些了


88527b8d8a492dc3bb64dfac00575a21_image-20230926181353338.png


运行时数据区(Runtime Data Area)

当我们通过前面的:类的加载 –> 验证 –> 准备 –> 解析 –> 初始化,这几个阶段完成后,就会用到执行引擎对我们的类进行使用,同时执行引擎将会使用到我们运行时数据区


虚拟机在执行Java文件的时候会把他所管理的内存划分为若干个不同的数据区域, 这些区域有各自的用途 , 会随着虚拟机进程的启动而创建 或者是 随着用户线程的启动和结束而建立和销毁。


我们通过磁盘或者网络IO得到的数据,都需要先加载到内存中,然后CPU从内存中获取数据进行读取,也就是说内存充当了CPU和磁盘之间的桥梁


最新的JDK 8中对之前的运行时数据区相关内容有着不同的划分, 具体参考阿里的JDK 8 运行时数据区图


0127c74966f321bc729c7163e1967217_image-20230926182609272.png


前面我们提到这些区域有各自的用途 , 会随着虚拟机进程的启动而创建 或者是 随着用户线程的启动和结束而建立和销毁。 随着虚拟机进程这点我可以理解,因为内存区域都是由Java虚拟机管理的 但是为什么会随着用户的线程呢?


线程的相关内容

线程是一个程序里的运行单元。JVM允许一个应用有多个线程并行的执行 。 在JVM内部, 每个线程都是与本地的线程直接映射


在我们Java的程序启动一个线程时, 操作系统同时也会创建一个本地线程,本地线程一旦初始化成功就会开始执行run()方法


一些JVM系统线程

虚拟机线程:这种线程的操作是需要JVM达到安全点才会出现。这些操作必须在不同的线程中发生的原因是他们都需要JVM达到安全点,这样堆才不会变化。这种线程的执行类型括”stop-the-world”的垃圾收集,线程栈收集,线程挂起以及偏向锁撤销

周期任务线程:这种线程是时间周期事件的体现(比如中断),他们一般用于周期性操作的调度执行

GC线程:这种线程对在JVM里不同种类的垃圾收集行为提供了支持

编译线程:这种线程在运行时会将字节码编译成到本地代码

信号调度线程:这种线程接收信号并发送给JVM,在它内部通过调用适当的方法进行处理

以上的5个线程不包括main方法所在的线程以及通过main方法创建的线程, 这些都是Java程序启动之后 ,后台启动的一些线程。



程序计数器(Program Counter Register)

概述: 程序计数器占用的是一块很小的空间, 它相当于当前线程所执行的字节码的行号指示器。 可以理解为和计算机组成原理中的程序计数器一个概念(但是具体的执行功能还是不同的,知识有大致的相同), 都是通过这个计数器的值来选取下一条需要执行字节码指令。 同时程序计数器的生命周期正在执行的线程的生命周期相同


作用:


JVM中 程序计数器用于追踪线程执行的位置。它保存了当前线程正在执行的字节码指令的地址或索引。当线程被调度执行时,程序计数器指示了下一条要执行的指令的位置。在线程切换时,程序计数器的值会被保存和恢复,以确保线程能够从正确的位置继续执行。


程序计数器也是程序控制流的指示器,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。


任何时间一个线程都只有一个方法在执行,也就是所谓的当前方法。程序计数器会存储当前线程正在执行的Java方法的JVM指令地址;或者,如果是在执行native方法,则是未指定值(undefned)。


bd1a62bba00dade9ac97d3254c3794b3_image-20230926185231721.png


PC寄存器用来存储指向下一条指令的地址,也即将要执行的指令代码。由执行引擎读取下一条指令,并执行该指令。


beb1726344c414cf6f500ba6cb1739d1_image-20230926185420246.png


程序计数器区域 是唯一一个在《Java虚拟机规范》中没有规定任何OneOfMemoryError情况的区域


Java虚拟机栈(Java Virtual Machine Stack)

与上面的程序计数器一样, 都是线程私有的 ,随着线程的创建和销毁 而 生 & 死 。


虚拟机栈描述的是Java方法执行的线程内存模型


每个方法被执行的时候,Java虚拟机都 会同步创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态连接、方法出口等信 息。每一个方法被调用直至执行完毕的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。


内存中的栈与堆区分


首先栈是运行时的单位,而堆是存储的单位。

栈解决程序的运行问题,即程序如何执行,或者说如何处理数据。堆解决的是数据存储的问题,即数据怎么放,放哪里

因为按照c++中的内存布局结构, 人们好像都会将其划分为栈内存 和 堆内存, 但是实际的内存布局结构却比这更加复杂。


虚拟机栈的作用:

主管Java程序的运行,它保存方法的局部变量(8 种基本数据类型、对象的引用地址)、部分结果,并参与方法的调用和返回。

局部变量,它是相比于成员变量来说的(或属性)

基本数据类型变量 VS 引用类型变量(类、数组、接口)

baddf69e7d369e9cfb973013352de7be_image-20230926190940172.png


每个线程在创建时都会创建一个虚拟机栈,其内部保存一个个的栈帧(Stack Frame),对应着一次次的Java方法调用,栈是线程私有的


栈帧是一个内存区块,是一个数据集,维系着方法执行过程中的各种数据信息。


虚拟机栈的特点:

栈是一种快速有效的分配存储方式,访问速度仅次于程序计数器。


JVM直接对Java栈的操作只有两个:


每个方法执行,伴随着进栈(入栈、压栈)

执行结束后的出栈工作

对于栈来说不存在垃圾回收问题


栈不需要GC,但是可能存在OOM

9e0b22f89f4b34933b6197189a36c42f_image-20230926191123060.png

栈运行原理

JVM直接对Java栈的操作只有两个,就是对栈帧的压栈和出栈,遵循先进后出(后进先出)原则

在一条活动线程中,一个时间点上,只会有一个活动的栈帧。即只有当前正在执行的方法的栈帧(栈顶栈帧)是有效的。这个栈帧被称为当前栈帧(Current Frame),与当前栈帧相对应的方法就是当前方法(Current Method),定义这个方法的类就是当前类(Current Class)

执行引擎运行的所有字节码指令只针对当前栈帧进行操作。

如果在该方法中调用了其他方法,对应的新的栈帧会被创建出来,放在栈的顶端,成为新的当前帧。

注意:


不同线程中所包含的栈帧是不允许存在相互引用的,即不可能在一个栈帧之中引用另外一个线程的栈帧。


如果当前方法调用了其他方法,方法返回之际,当前栈帧会传回此方法的执行结果给前一个栈帧,接着,虚拟机会丢弃当前栈帧,使得前一个栈帧重新成为当前栈帧。


异常情况(两类)

Java 虚拟机规范允许Java栈的大小是动态的或者是固定不变的。


如果采用固定大小的Java虚拟机栈,那每一个线程的Java虚拟机栈容量可以在线程创建的时候独立选定。如果线程请求分配的栈容量超过Java虚拟机栈允许的最大容量,Java虚拟机将会抛出一个StackoverflowError 异常。

如果Java虚拟机栈可以动态扩展,并且在尝试扩展的时候无法申请到足够的内存,或者在创建新的线程时没有足够的内存去创建对应的虚拟机栈,那Java虚拟机将会抛出一个 OutofMemoryError 异常。

本地方法栈(Native Method Stack)

本地方法栈和虚拟机栈所发挥的作用是非常相似的,区别就是虚拟机栈为虚拟机执行Java方法服务, 而本地方法栈则是为虚拟机使用本地方法时提供服务。


《Java虚拟机规范》对本地方法栈中使用的任何内容都没有规范。 抛出的异常和Java虚拟机中的异常是一样的。


在栈深度溢出或者栈扩展失败时分别抛出StackoverflowError 异常 和 OutofMemoryError 异常。


Java堆(Java Heap)

**Java堆是虚拟机所管理的内存中最大的一块, 被所有线程所共享, 生命周期是随着虚拟机的, 此内存的唯一目的就是存放对象实例的。 **


上述就是堆区的重点。Java中, 几乎所有的对象示例都会在这里分配内存。 (但是在《Java虚拟机规范》中 它表明 所有的对象示例以及数组都应该在堆上分配), 具体听谁的咱也不知道…


相关细节:

一个JVM实例只存在一个堆内存,堆也是Java内存管理的核心区域。

Java堆区在JVM启动的时候即被创建,其空间大小也就确定了,堆是JVM管理的最大一块内存空间,并且堆内存的大小是可以调节的。

《Java虚拟机规范》规定,堆可以处于物理上不连续的内存空间中,但在逻辑上它应该被视为连续的。

所有的线程共享Java堆,在这里还可以划分线程私有的缓冲区(Thread Local Allocation Buffer,TLAB)。

《Java虚拟机规范》中对Java堆的描述是:所有的对象实例以及数组都应当在运行时分配在堆上。(The heap is the run-time data area from which memory for all class instances and arrays is allocated)

从实际使用角度看:“几乎”所有的对象实例都在堆分配内存,但并非全部。因为还有一些对象是在栈上分配的(逃逸分析,标量替换)

数组和对象可能永远不会存储在栈上,因为栈帧中保存引用,这个引用指向对象或者数组在堆中的位置。

在方法结束后,堆中的对象不会马上被移除,仅仅在垃圾收集的时候才会被移除。

也就是触发了GC的时候,才会进行回收

如果堆中对象马上被回收,那么用户线程就会收到影响,因为有stop the word

堆,是GC(Garbage Collection,垃圾收集器)执行垃圾回收的重点区域。



目录
相关文章
|
13天前
|
存储 Java 编译器
Java内存模型(JMM)深度解析####
本文深入探讨了Java内存模型(JMM)的工作原理,旨在帮助开发者理解多线程环境下并发编程的挑战与解决方案。通过剖析JVM如何管理线程间的数据可见性、原子性和有序性问题,本文将揭示synchronized关键字背后的机制,并介绍volatile关键字和final关键字在保证变量同步与不可变性方面的作用。同时,文章还将讨论现代Java并发工具类如java.util.concurrent包中的核心组件,以及它们如何简化高效并发程序的设计。无论你是初学者还是有经验的开发者,本文都将为你提供宝贵的见解,助你在Java并发编程领域更进一步。 ####
|
24天前
|
缓存 easyexcel Java
Java EasyExcel 导出报内存溢出如何解决
大家好,我是V哥。使用EasyExcel进行大数据量导出时容易导致内存溢出,特别是在导出百万级别的数据时。以下是V哥整理的解决该问题的一些常见方法,包括分批写入、设置合适的JVM内存、减少数据对象的复杂性、关闭自动列宽设置、使用Stream导出以及选择合适的数据导出工具。此外,还介绍了使用Apache POI的SXSSFWorkbook实现百万级别数据量的导出案例,帮助大家更好地应对大数据导出的挑战。欢迎一起讨论!
139 1
|
1月前
|
缓存 算法 Java
Java中的内存管理:理解与优化
【10月更文挑战第6天】 在Java编程中,内存管理是一个至关重要的主题。本文将深入探讨Java内存模型及其垃圾回收机制,并分享一些优化内存使用的策略和最佳实践。通过掌握这些知识,您可以提高Java应用的性能和稳定性。
44 4
|
1月前
|
存储 监控 算法
Java中的内存管理:理解Garbage Collection机制
本文将深入探讨Java编程语言中的内存管理,着重介绍垃圾回收(Garbage Collection, GC)机制。通过阐述GC的工作原理、常见算法及其在Java中的应用,帮助读者提高程序的性能和稳定性。我们将从基本原理出发,逐步深入到调优实践,为开发者提供一套系统的理解和优化Java应用中内存管理的方法。
|
8天前
|
缓存 算法 Java
本文聚焦于Java内存管理与调优,介绍Java内存模型、内存泄漏检测与预防、高效字符串拼接、数据结构优化及垃圾回收机制
在现代软件开发中,性能优化至关重要。本文聚焦于Java内存管理与调优,介绍Java内存模型、内存泄漏检测与预防、高效字符串拼接、数据结构优化及垃圾回收机制。通过调整垃圾回收器参数、优化堆大小与布局、使用对象池和缓存技术,开发者可显著提升应用性能和稳定性。
29 6
|
12天前
|
存储 缓存 安全
Java内存模型(JMM):深入理解并发编程的基石####
【10月更文挑战第29天】 本文作为一篇技术性文章,旨在深入探讨Java内存模型(JMM)的核心概念、工作原理及其在并发编程中的应用。我们将从JMM的基本定义出发,逐步剖析其如何通过happens-before原则、volatile关键字、synchronized关键字等机制,解决多线程环境下的数据可见性、原子性和有序性问题。不同于常规摘要的简述方式,本摘要将直接概述文章的核心内容,为读者提供一个清晰的学习路径。 ####
33 2
|
13天前
|
存储 安全 Java
什么是 Java 的内存模型?
Java内存模型(Java Memory Model, JMM)是Java虚拟机(JVM)规范的一部分,它定义了一套规则,用于指导Java程序中变量的访问和内存交互方式。
35 1
|
19天前
|
存储 运维 Java
💻Java零基础:深入了解Java内存机制
【10月更文挑战第18天】本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!
26 1
|
1月前
|
Java
java操作内存,简单讲解varhandle的使用
本文介绍了Java中VarHandle的使用,它是一种从JDK 9开始引入的用于高效访问对象字段的特性。文章通过示例代码展示了如何通过VarHandle操作对象的字段,包括设置和获取字段值,以及如何通过MethodHandles.lookup().findVarHandle()方法获取VarHandle实例。VarHandle提供了一种比反射更高效的内存操作方式,并且支持原子操作。
34 0
java操作内存,简单讲解varhandle的使用
|
22天前
|
存储 算法 Java
Java虚拟机(JVM)的内存管理与性能优化
本文深入探讨了Java虚拟机(JVM)的内存管理机制,包括堆、栈、方法区等关键区域的功能与作用。通过分析垃圾回收算法和调优策略,旨在帮助开发者理解如何有效提升Java应用的性能。文章采用通俗易懂的语言,结合具体实例,使读者能够轻松掌握复杂的内存管理概念,并应用于实际开发中。