JVM——栈和堆概述,以及有什么区别?
方法栈
方法栈并不是某一个 JVM 的内存空间,而是我们描述方法被调用过程的一个逻辑概念。
在同一个线程内,T1()调用T2():
- T1()先开始,T2()后开始;
- T2()先结束,T1()后结束。
堆和栈概述
从英文单词角度来说
- 栈:stack
- 堆:heap
从数据结构角度来说
- 栈和堆一样:都是先进后出,后进先出的数据结构
从 JVM 内存空间结构角度来说
- 栈:通常指 Java 方法栈,存放方法每一次执行时生成的栈帧。
- 堆:JVM 中存放对象的内存空间。包括新生代、老年代、永久代等组成部分。
栈帧
栈帧存储的数据
方法在本次执行过程中所用到的局部变量、动态链接、方法出口等信息。栈帧中主要保存3 类数据:
本地变量(Local Variables):输入参数和输出参数以及方法内的变量。
栈操作(Operand Stack):记录出栈、入栈的操作。
栈帧数据(Frame Data):包括类文件、方法等等。
栈帧的结构
- 局部变量表:方法执行时的参数、方法体内声明的局部变量
- 操作数栈:存储中间运算结果,是一个临时存储空间
- 帧数据区:保存访问常量池指针,异常处理表
栈帧工作机制
当一个方法 A 被调用时就产生了一个栈帧 F1,并被压入到栈中,
A 方法又调用了 B 方法,于是产生栈帧 F2 也被压入栈,
B 方法又调用了 C 方法,于是产生栈帧 F3 也被压入栈,
……
C 方法执行完毕后,弹出 F3 栈帧;
B 方法执行完毕后,弹出 F2 栈帧;
A 方法执行完毕后,弹出 F1栈帧;
……
遵循“先进后出”或者“后进先出”原则。
图示在一个栈中有两个栈帧:
栈帧 2 是最先被调用的方法,先入栈,
然后方法 2 又调用了方法 1,栈帧 1 处于栈顶的位置,
栈帧 2 处于栈底,执行完毕后,依次弹出栈帧 1 和栈帧 2,
线程结束,栈释放。
每执行一个方法都会产生一个栈帧,保存到栈的顶部,顶部栈就是当前方法,该方法执行完毕后会自动将此栈帧出栈。
典型案例
请预测下面代码打印的结果:34
int n = 10;
n += (n++) + (++n);
System.out.println(n);
实际执行结果:32
使用 javap 命令查看字节码文件内容:
D:\record-video-original\day03\code>javap -c Demo03JavaStackExample.class
Compiled from "Demo03JavaStackExample.java"
public class Demo03JavaStackExample{
public Demo03JavaStackExample();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>: ()V
4: returnpublic static void main(java.lang.String[]);
Code:
0: bipush 10
2: istore_1
3: iload_1
4: iload_1
5: iinc 1, 1
8: iinc 1, 1
11: iload_1
12: iadd
13: iadd
14: istore_1
15: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
18: iload_1
19: invokevirtual #3 // Method java/io/PrintStream.println:(I)V
22: return
}
内存执行过程分析:
栈溢出异常
异常名称
java.lang.StackOverflowError
异常产生的原因
下面的例子是一个没有退出机制的递归:
public class StackOverFlowTest {public static void main(String[] args) {methodInvokeToDie();}public static void methodInvokeToDie() {methodInvokeToDie();}}
抛出的异常信息:
Exception in thread "main" java.lang.StackOverflowError at com.atguigu.jvm.test.StackOverFlowTest.methodInvokeToDie(StackOverFlowTest.java:10) at com.atguigu.jvm.test.StackOverFlowTest.methodInvokeToDie(StackOverFlowTest.java:10) at com.atguigu.jvm.test.StackOverFlowTest.methodInvokeToDie(StackOverFlowTest.java:10) at com.atguigu.jvm.test.StackOverFlowTest.methodInvokeToDie(StackOverFlowTest.java:10) at com.atguigu.jvm.test.StackOverFlowTest.methodInvokeToDie(StackOverFlowTest.java:10)
原因总结:方法每一次调用都会在栈空间中申请一个栈帧,来保存本次方法执行时所需要用到的数据。但是一个没有退出机制的递归调用,会不断申请新的空间,而又不释放空间,这样迟早会把当前线程在栈内存中自己的空间耗尽。
栈空间的线程私有验证
提出问题
某一个线程抛出『栈溢出异常』,会导致其他线程也崩溃吗?从以往的经验中我们判断应该是不会,下面通过代码来实际验证一下。
代码
new Thread(()->{while(true) {try {TimeUnit.SECONDS.sleep(2);System.out.println(Thread.currentThread().getName() + " working");} catch (InterruptedException e) {e.printStackTrace();}}
}, "thread-01").start();new Thread(()->{while(true) {try {TimeUnit.SECONDS.sleep(2);// 递归调用一个没有退出机制的递归方法methodInvokeToDie();System.out.println(Thread.currentThread().getName() + " working");} catch (InterruptedException e) {e.printStackTrace();}}
}, "thread-02").start();new Thread(()->{while(true) {try {TimeUnit.SECONDS.sleep(2);System.out.println(Thread.currentThread().getName() + " working");} catch (InterruptedException e) {e.printStackTrace();}}
}, "thread-03").start();
结论:02 线程抛异常终止后,01 和 03 线程仍然能够继续正常运行,说明 02 抛异常并没有影响到 01 和 03,说明线程对栈内存空间的使用方式是彼此隔离的。每个线程都是在自己独享的空间内运行,反过来也可以说,这个空间是当前线程私有的。
堆空间
堆空间工作机制
- 新创建的对象会被放在Eden区
- 当Eden区中已使用的空间达到一定比例,会触发Minor GC
- 每一次在Minor GC中没有被清理掉的对象就成了幸存者
- 幸存者对象会被转移到幸存者区
- 幸存者区分成from区和to区
- from区快满的时候,会将仍然在使用的对象转移到to区
- 然后from和to这两个指针彼此交换位置
口诀:复制必交换,谁空谁为to
- 如果一个对象,经历15次GC仍然幸存,那么它将会被转移到老年代
- 如果幸存者区已经满了,即使某个对象尚不到15岁,仍然会被移动到老年代
- 最终效果:
- Eden区主要是生命周期很短的对象来来往往
- 老年代主要是生命周期很长的对象,例如:IOC容器对象、线程池对象、数据库连接池对象等等
- 幸存者区作为二者之间的过渡地带
- 关于永久代:
- 从理论上来说属于堆
- 从具体实现上来说不属于堆
永久代在各个JDK版本之间的演变
永久代 | 常量池 | |
---|---|---|
≤JDK1.6 | 有 | 在方法区 |
=JDK1.7 | 有,但开始逐步“去永久代” | 在堆 |
≥JDK1.8 | 无 | 在元空间 |
方法区、元空间、永久代之间关系
堆、栈、方法区之间关系
堆溢出异常
异常名称
java.lang.OutOfMemoryError,也往往简称为 OOM。
异常信息
- Java heap space:针对新生代、老年代整体进行Full GC后,内存空间还是放不下新产生的对象
- PermGen space:方法区中加载的类太多了(典型情况是框架创建的动态类太多,导致方法区溢出)
我们可以参考下面的控制台日志打印:
[GC (Allocation Failure) 4478364K->4479044K(5161984K), 4.3454766 secs] [Full GC (Ergonomics) 4479044K->3862071K(5416448K), 39.3706285 secs] [Full GC (Ergonomics) 4410423K->4410422K(5416448K), 27.7039534 secs] [Full GC (Ergonomics) 4629575K->4621239K(5416448K), 24.9298221 secs] [Full GC (Allocation Failure) 4621239K->4621186K(5416448K), 29.0616791 secs] Exception in thread "main" java.lang.OutOfMemoryError: Java heap space at java.util.Arrays.copyOf(Arrays.java:3210) at java.util.Arrays.copyOf(Arrays.java:3181) at java.util.ArrayList.grow(ArrayList.java:261) at java.util.ArrayList.ensureExplicitCapacity(ArrayList.java:235) at java.util.ArrayList.ensureCapacityInternal(ArrayList.java:227) at java.util.ArrayList.add(ArrayList.java:458) at com.atguigu.jvm.test.JavaHeapTest.main(JavaHeapTest.java:16)
小练习
测试代码
查看下面程序在每个步骤中内存的状态:
public class Review {// 静态变量,类变量public static Review review = new Review();public void showMessage() {// 局部变量Review reviewLocal = new Review();}// 程序入口public static void main(String[] args) {// 局部变量Review reviewMain = new Review();// 通过局部变量调用对象的方法reviewMain.showMessage();// 手动 GCSystem.gc();}
}
各状态分析
栈和堆的区别
堆和栈的区别主要体现在以下几个方面。
- 内存分配方式
栈(stack)和堆(heap)都是内存中的一段区域,但它们的内存分配方式是不同的。栈是由程序自动创建和释放的,通常用于存储函数调用时的临时变量、函数的返回地址等信息。而堆则是由程序员手动申请和释放的,通常用于存储程序中需要动态分配的内存(如动态数组、对象等)。
- 内存管理方式
栈的内存分配是按照“后进先出”的原则进行的,即最后一个进入栈的变量最先被释放。因此,栈中的内存管理是由系统自动完成的,程序员不需要过多考虑内存的分配和释放问题。堆的内存管理则需要程序员自行负责,使用完毕后必须手动释放,否则会导致内存泄漏或其他问题。
- 内存大小
栈的容量较小,一般只有几百KB到几MB的空间,具体容量由操作系统和编译器决定。相对而言,堆用于存储较大的数据结构,大小一般比栈要大得多,可以动态扩展内存空间。但是,因为堆需要手动管理内存,如果不及时释放,会导致内存泄漏,进而影响系统性能。
- 访问速度
因为栈的内存分配是系统自动完成的,所以访问速度相对堆更快。栈中的数据直接存放在系统内存中,而访问堆中的数据需要通过指针进行间接访问,会造成一定的时间损耗。此外,在多线程环境下,由于栈的线程独享,所以不会发生竞争问题。而堆则需要考虑多线程并发访问时的同步和互斥机制。
- 应用场景
栈适合用于存储局部变量和函数调用,主要用于内存的临时分配;而堆适合用于存储需要动态分配和管理的数据结构,如动态数组、字符串、对象等。在实际开发中,应该根据具体的应用场景选择合适的内存分配方式。
相关文章:

JVM——栈和堆概述,以及有什么区别?
方法栈 方法栈并不是某一个 JVM 的内存空间,而是我们描述方法被调用过程的一个逻辑概念。 在同一个线程内,T1()调用T2(): T1()先开始,T2()后开始;T2()先结束,T1()后结束。 堆和栈概述 从英文单词角度来…...

恒盛策略:沪指冲高回落跌0.26%,酿酒、汽车等板块走弱,燃气股拉升
10日早盘,两市股指盘中冲高回落,半日成交约4200亿元,北向资金净卖出超20亿元。 到午间收盘,沪指跌0.26%报3235.9点,深成指跌0.54%,创业板指跌0.28%;两市算计成交4202亿元,北向资金净…...

Mongodb 常用操作
// 查询 user_id 是否存在 db.getCollection("t_mongo_user").find({"user_id" : { $exists: true }}) // 查询 user_id 10 的记录 db.getCollection("t_mongo_user").find({"user_id" : 10}) // 排序 -1,按照 _id 倒…...

【python】-【】
文章目录 转义字符和原字符二进制与字符编码标识符和保留字变量的定义和使用变量字符串列表for 一、print会输出①数字②字符串(必须加引号)③含有运算符的表达式(例如 31 其中3,1是操作数,是运算符)&#…...

基于Elman神经网络的电力负荷预测
1 案例背景 1.1 Elman神经网络概述 根据神经网络运行过程中的信息流向,可将神经网络可分为前馈式和反馈式两种基本类型。前馈式网络通过引入隐藏层以及非线性转移函数可以实现复杂的非线性映射功能。但前馈式网络的输出仅由当前输人和权矩阵决定,而与网络先前的输出结果无关。…...
LeetCode 0088. 合并两个有序数组
【LetMeFly】88.合并两个有序数组:O(m 1) O(1)的做法 力扣题目链接:https://leetcode.cn/problems/merge-sorted-array/ 给你两个按 非递减顺序 排列的整数数组 nums1 和 nums2,另有两个整数 m 和 n ,分别表示 nums1 和 nums2…...

定义行业新标准?谷歌:折叠屏手机可承受20万次折叠
根据Patreon账户上的消息,Android专家Mishaal Rahman透露,谷歌计划推出新的硬件质量标准,以满足可折叠手机市场的需求。Android原始设备制造商(OEM)将需要完成谷歌提供的问卷调查,并提交样品设备进行严格审…...
在vscode中配置C/C++环境GCC on Linux
https://code.visualstudio.com/docs/cpp/config-linux 官方文档 准备工作 为了能够在vs code中编译运行C/C程序,需要下载: Visual Studio Code C扩展插件,cuda,,, 对于该扩展插件,打开vs c…...
windows执行完LoadLibrary()后,可以删除源动态库文件,函数不会锁库文件
windows执行完LoadLibrary()后,可以删除源动态库文件,函数不会锁库文件。 #include <iostream> #include <Windows.h>int main() {char path[MAX_PATH]{};GetCurrentDirectoryA(sizeof(path), path);HMODULE lib LoadLibraryA("testd…...

ios 知识
IOS 类文件.h和.m中interface的区别 大家都知道我们在创建类文件时会发现: #import <UIKit/UIKit.h>interface ViewController : UIViewControllerend和 #import "ViewController.h"interface ViewController ()end那么他们之间有何区别呢&#x…...
8 | 美国航班数据分析
"在现代快节奏的生活中,航空旅行已经成为人们出行的重要方式之一。然而,航班的准时性一直以来都是旅客和航空公司关注的焦点。无论是商务出差还是休闲度假,乘客们都希望能够在既定的时间内安全、准时地到达目的地。而对于航空公司而言,准点运营不仅关乎乘客体验,还涉…...
app.use(express.json()) 使用
Express内置的中间件 自 Express 4.16.0 版本开始,Express 内置了 3 个常用的中间件,极大的提高了 Express 项目的开发效率和体验 express.static 快速托管静态资源的内置中间件,例如: HTML 文件、图片、CSS 样式等(无…...

基于PyTorch的图像识别
前言 图像识别是计算机视觉领域的一个重要方向,具有广泛的应用场景,如医学影像诊断、智能驾驶、安防监控等。在本项目中,我们将使用PyTorch来开发一个基于卷积神经网络的图像识别模型,用来识别图像中的物体。下面是要识别的四种物…...

js合并数组对象(将数组中具有相同属性对象合并到一起,组成一个新的数组)
一、根据数组对象中某一key值,合并相同key值的字段,到同一个数组对象中,组成新的数组 1.原数组: var array [{ id: 1, name: Alice },{ id: 2, name: Bob },{ id: 1, age: 25 },{ id: 3, name: Charlie, age: 30 } ];2.合并后数…...

Spring BeanPostProcessor 接口的作用和使用
BeanPostProcessor 接口是 Spring 框架中的一个扩展接口,用于在 Spring 容器实例化、配置和初始化 bean 的过程中提供自定义的扩展点。通过实现这个接口,您可以在 bean 实例创建的不同生命周期阶段插入自己的逻辑,从而实现对 bean 行为的定制…...
Android 13 Hotseat定制化修改——006 hotseat图标禁止移动到Launcher中
目录 一.背景 二.方案 三.具体实践 一.背景 客户定制需要修改让hotseat中的icon不要移动到Launcher中,所以需要进行定制 二.方案 原生的Hotseat与Launcher是可以相互移动的,然后现在的需求是Hotseat中的图标只能在Hotseat中移动,所以需要做下限制 思路:在事件拦截的地…...

RabbitMQ 发布确认机制
发布确认模式是避免消息由生产者到RabbitMQ消息丢失的一种手段 发布确认模式 原理说明实现方式开启confirm(确认)模式阻塞确认异步确认 总结 原理说明 生产者通过调用channel.confirmSelect方法将信道设置为confirm模式,之后RabbitMQ会返回Co…...
微信小程序使用rich-text解析富文本字符串的时候,遇到image标签图片很大超过屏幕
场景: 使用uniapp开发微信小程序,解析富文本文章需求 用到的组件: u-view2.0的u-parse uniapp提供的rich-text 以上两种组件都是解析富文本的作用,一般用于富文本解析场景,比如解析文章内容,商品详情&am…...

使用IIS服务器部署Flask python Web项目
参考文章 ""D:\Program Files (x86)\Python310\python310.exe"|"D:\Program Files (x86)\Python310\lib\site-packages\wfastcgi.py"" can now be used as a FastCGI script processor参考文章 请求路径填写*,模块选择FastCgiModule&…...

sentinel核心流程源码解析
sentinel的处理槽(ProcessorSlot) 可以说,sentinel实现的各种功能就是由各处理槽完成的 ,ProcessorSlot定义了四个方法: 当进入该处理槽时触发该方法 处理完 entry方法之后触发该方法 退出该处理槽时触发该方法 exit方法处理完成时触发该方法 sentinel的…...

Linux 文件类型,目录与路径,文件与目录管理
文件类型 后面的字符表示文件类型标志 普通文件:-(纯文本文件,二进制文件,数据格式文件) 如文本文件、图片、程序文件等。 目录文件:d(directory) 用来存放其他文件或子目录。 设备…...
React Native 开发环境搭建(全平台详解)
React Native 开发环境搭建(全平台详解) 在开始使用 React Native 开发移动应用之前,正确设置开发环境是至关重要的一步。本文将为你提供一份全面的指南,涵盖 macOS 和 Windows 平台的配置步骤,如何在 Android 和 iOS…...
在四层代理中还原真实客户端ngx_stream_realip_module
一、模块原理与价值 PROXY Protocol 回溯 第三方负载均衡(如 HAProxy、AWS NLB、阿里 SLB)发起上游连接时,将真实客户端 IP/Port 写入 PROXY Protocol v1/v2 头。Stream 层接收到头部后,ngx_stream_realip_module 从中提取原始信息…...

Mac软件卸载指南,简单易懂!
刚和Adobe分手,它却总在Library里给你写"回忆录"?卸载的Final Cut Pro像电子幽灵般阴魂不散?总是会有残留文件,别慌!这份Mac软件卸载指南,将用最硬核的方式教你"数字分手术"࿰…...

HBuilderX安装(uni-app和小程序开发)
下载HBuilderX 访问官方网站:https://www.dcloud.io/hbuilderx.html 根据您的操作系统选择合适版本: Windows版(推荐下载标准版) Windows系统安装步骤 运行安装程序: 双击下载的.exe安装文件 如果出现安全提示&…...
CRMEB 框架中 PHP 上传扩展开发:涵盖本地上传及阿里云 OSS、腾讯云 COS、七牛云
目前已有本地上传、阿里云OSS上传、腾讯云COS上传、七牛云上传扩展 扩展入口文件 文件目录 crmeb\services\upload\Upload.php namespace crmeb\services\upload;use crmeb\basic\BaseManager; use think\facade\Config;/*** Class Upload* package crmeb\services\upload* …...

IT供电系统绝缘监测及故障定位解决方案
随着新能源的快速发展,光伏电站、储能系统及充电设备已广泛应用于现代能源网络。在光伏领域,IT供电系统凭借其持续供电性好、安全性高等优势成为光伏首选,但在长期运行中,例如老化、潮湿、隐裂、机械损伤等问题会影响光伏板绝缘层…...
爬虫基础学习day2
# 爬虫设计领域 工商:企查查、天眼查短视频:抖音、快手、西瓜 ---> 飞瓜电商:京东、淘宝、聚美优品、亚马逊 ---> 分析店铺经营决策标题、排名航空:抓取所有航空公司价格 ---> 去哪儿自媒体:采集自媒体数据进…...

在WSL2的Ubuntu镜像中安装Docker
Docker官网链接: https://docs.docker.com/engine/install/ubuntu/ 1、运行以下命令卸载所有冲突的软件包: for pkg in docker.io docker-doc docker-compose docker-compose-v2 podman-docker containerd runc; do sudo apt-get remove $pkg; done2、设置Docker…...
Mobile ALOHA全身模仿学习
一、题目 Mobile ALOHA:通过低成本全身远程操作学习双手移动操作 传统模仿学习(Imitation Learning)缺点:聚焦与桌面操作,缺乏通用任务所需的移动性和灵活性 本论文优点:(1)在ALOHA…...