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,垃圾收集器)执行垃圾回收的重点区域。



目录
相关文章
|
12月前
|
安全 Java 应用服务中间件
Spring Boot + Java 21:内存减少 60%,启动速度提高 30% — 零代码
通过调整三个JVM和Spring Boot配置开关,无需重写代码即可显著优化Java应用性能:内存减少60%,启动速度提升30%。适用于所有在JVM上运行API的生产团队,低成本实现高效能。
1266 3
|
存储 缓存 Java
Java数组全解析:一维、多维与内存模型
本文深入解析Java数组的内存布局与操作技巧,涵盖一维及多维数组的声明、初始化、内存模型,以及数组常见陷阱和性能优化。通过图文结合的方式帮助开发者彻底理解数组本质,并提供Arrays工具类的实用方法与面试高频问题解析,助你掌握数组核心知识,避免常见错误。
|
存储 缓存 Java
【高薪程序员必看】万字长文拆解Java并发编程!(5):深入理解JMM:Java内存模型的三大特性与volatile底层原理
JMM,Java Memory Model,Java内存模型,定义了主内存,工作内存,确保Java在不同平台上的正确运行主内存Main Memory:所有线程共享的内存区域,所有的变量都存储在主存中工作内存Working Memory:每个线程拥有自己的工作内存,用于保存变量的副本.线程执行过程中先将主内存中的变量读到工作内存中,对变量进行操作之后再将变量写入主内存,jvm概念说明主内存所有线程共享的内存区域,存储原始变量(堆内存中的对象实例和静态变量)工作内存。
427 0
|
11月前
|
Java 大数据 Go
从混沌到秩序:Java共享内存模型如何通过显式约束驯服并发?
并发编程旨在混乱中建立秩序。本文对比Java共享内存模型与Golang消息传递模型,剖析显式同步与隐式因果的哲学差异,揭示happens-before等机制如何保障内存可见性与数据一致性,展现两大范式的深层分野。(238字)
380 4
|
11月前
|
存储 缓存 Java
【深入浅出】揭秘Java内存模型(JMM):并发编程的基石
本文深入解析Java内存模型(JMM),揭示synchronized与volatile的底层原理,剖析主内存与工作内存、可见性、有序性等核心概念,助你理解并发编程三大难题及Happens-Before、内存屏障等解决方案,掌握多线程编程基石。
|
缓存 监控 Kubernetes
Java虚拟机内存溢出(Java Heap Space)问题处理方案
综上所述, 解决Java Heap Space溢出需从多角度综合施策; 包括但不限于配置调整、代码审查与优化以及系统设计层面改进; 同样也不能忽视运行期监控与预警设置之重要性; 及早发现潜在风险点并采取相应补救手段至关重要.
1122 17
|
Java 物联网 数据处理
Java Solon v3.2.0 史上最强性能优化版本发布 并发能力提升 700% 内存占用节省 50%
Java Solon v3.2.0 是一款性能卓越的后端开发框架,新版本并发性能提升700%,内存占用节省50%。本文将从核心特性(如事件驱动模型与内存优化)、技术方案示例(Web应用搭建与数据库集成)到实际应用案例(电商平台与物联网平台)全面解析其优势与使用方法。通过简单代码示例和真实场景展示,帮助开发者快速掌握并应用于项目中,大幅提升系统性能与资源利用率。
387 6
Java Solon v3.2.0 史上最强性能优化版本发布 并发能力提升 700% 内存占用节省 50%
|
监控 Kubernetes Java
最新技术栈驱动的 Java 绿色计算与性能优化实操指南涵盖内存优化与能效提升实战技巧
本文介绍了基于Java 24+技术栈的绿色计算与性能优化实操指南。主要内容包括:1)JVM调优,如分代ZGC配置和结构化并发优化;2)代码级优化,包括向量API加速数据处理和零拷贝I/O;3)容器化环境优化,如K8s资源匹配和节能模式配置;4)监控分析工具使用。通过实践表明,这些优化能显著提升性能(响应时间降低40-60%)同时降低资源消耗(内存减少30-50%,CPU降低20-40%)和能耗(服务器功耗减少15-35%)。建议采用渐进式优化策略。
706 2
|
缓存 监控 Cloud Native
Java Solon v3.2.0 高并发与低内存实战指南之解决方案优化
本文深入解析了Java Solon v3.2.0框架的实战应用,聚焦高并发与低内存消耗场景。通过响应式编程、云原生支持、内存优化等特性,结合API网关、数据库操作及分布式缓存实例,展示其在秒杀系统中的性能优势。文章还提供了Docker部署、监控方案及实际效果数据,助力开发者构建高效稳定的应用系统。代码示例详尽,适合希望提升系统性能的Java开发者参考。
703 4
Java Solon v3.2.0 高并发与低内存实战指南之解决方案优化