博客:https://www.emanjusaka.com

博客园:https://www.cnblogs.com/emanjusaka

公众号:emanjusaka的编程栈

by emanjusaka from https://www.emanjusaka.com/archives/java-heap-stack-distribution-feature

本文为原创文章,可能会更新知识点以及修正文中的一些错误,全文转载请保留原文地址,避免产生因未即时修正导致的误导。

经常有人把 Java 内存区域笼统地划分为堆内存(Heap)和栈内存(Stack),这种划分方式直接继承自传统的 C、C++程序的内存布局结构,在 Java 语言就显得有些粗糙了,实际的内存区域划分是要更复杂一下。如下所示:

方法区、堆是由所有线程共享的数据区。虚拟机栈、本地方法栈和程序计数器是线程隔离的数据区。

我们最关注的、与对象内存分配关系最密切的区域是“堆”和“栈”两块。其中“栈”通常就是指这里的虚拟机栈,更多情况下只是指虚拟机栈中局部变量表部分。下面我们详细分析一下堆内存和栈内存的数据分布。

问题:哪些数据放在栈上,哪些数据放在堆上?

如果你擅长 Java 这种内存自动管理的语言,这个问题很好回答。

栈上的数据:

  • 基本数据类型(booleanbytecharshortintfloatlongdouble

  • 对象引用(reference 类型,它并不等同于对象本身,可能是一个指向对象起始地址的应用指针,也可能是指向一个代表对象的句柄或者其他与此对象相关的位置)

  • returnAddress 类型(指向了一条字节码指令的地址)

堆上数据:

  • 普通对象:各种类的实例。

  • 数组:数组是一种特殊类型的对象,可以存储多个相同类型的元素。

  • 基本类型包装器对象:Java 提供了一些基本类型的包装器类:如 Integer、Double、Character 等。

栈和堆内存上的数据特点

我们先来分析下程序中的栈和堆,然后总结出它们的特点。

Stack

栈的数据结构特点是先进后出。由于这个特点,非常适合记录程序的函数调用,也称为函数调用栈。函数调用栈从下到上增长,每当函数执行时,就会在栈顶部分分配一块连续的内存,称为帧。这个帧存储了当前函数的通用寄存器和当前函数的局部变量的上下文信息。下面给出一个简单的 Java 函数调用,我们分析一下这个过程:

 public class StackExample {
     public static void main(String[] args) {
         int result = add(3, 5);
         System.out.println("结果是: " + result);
    }
 ​
     public static int add(int a, int b) {
         return a + b;
    }
 } 在这个例子中,`main`函数调用了`add`函数。当`main`函数开始执行时,会在栈内存中为`main`函数分配一块空间,包括局部变量`result`和参数`args`。然后,`main`函数调用`add`函数,此时会在栈内存中为`add`函数分配另一块空间,包括局部变量`a`、`b`和返回地址。当`add`函数执行完毕后,其占用的栈空间会被释放,控制权返回给`main`函数。最后,`main`函数执行完毕,整个程序结束。 通常情况下,它需要连续的内存空间,这意味着程序在调用下一个函数之前必须知道下一个函数需要多少内存空间。但是程序是怎样知道的呢? 答案是编译器为我们完成了这一切。当编译代码时,函数是一个最小的编译单位。每当编译器遇到一个函数时,它就知道当前函数使用寄存器和局部变量所需的空间。 因此,无法在编译时确定大小或可以更改大小的数据是不能安全地放置在栈上的。 ### Heap 有些数据不能安全地放在栈上,所以最好放在堆上,比如下面的ArrayList: ```java
 import java.util.ArrayList;
 public class VariableLengthArrayExample {
     public static void main(String[] args) {
         // 创建一个空的 ArrayList
         ArrayList<Integer> arrayList = new ArrayList<>();
         // 向 ArrayList 中添加元素
         arrayList.add(1);
         arrayList.add(2);
         arrayList.add(3);
         // 输出 ArrayList 的大小
         System.out.println("Size of the ArrayList: " + arrayList.size());
         // 访问 ArrayList 中的元素
         for (int i = 0; i < arrayList.size(); i++) {
             System.out.println("Element at index " + i + ": " + arrayList.get(i));
        }
         // 删除 ArrayList 中的一个元素
         arrayList.remove(1);
         // 再次输出 ArrayList 的大小和内容
         System.out.println("Size of the ArrayList after removal: " + arrayList.size());
         for (int i = 0; i < arrayList.size(); i++) {
             System.out.println("Element at index " + i + ": " + arrayList.get(i));
        }
    }
 }

当创建一个ArrayList 时,程序需要动态的分配内存。如果数组的实际使用量超过了这个容量,程序会分配一个更大的内存块,将现有元素复制到其中,添加新元素,然后释放旧内存。此过程允许数组根据需要动态调整大小。请求系统调用并找到新的内存然后一一复制的过程是非常低效的。所以这里最好的做法是提前预留需要的空间。

另外,需要跨栈引用的内存也需要放在堆上,这很好理解,因为一旦一个栈帧被回收,其内部的局部变量也会被回收,所以在不同的调用栈中共享数据只能使用堆。

总结

  • 栈上存储的数据是静态的,大小固定,生命周期固定,线程隔离不能跨栈引用。

  • 堆上存储的数据是动态的、不固定大小、不固定生命周期、线程共享可以跨栈引用。

参考资料

  • 《深入理解 Java 虚拟机(第 3 版)》——周志明

谦学于心,谷纳万物,静思致远,共筑收获之旅!

原文地址: https://www.emanjusaka.com/archives/java-heap-stack-distribution-feature

微信公众号:emanjusaka的编程栈

Java 中堆内存和栈内存上的数据分布和特点的更多相关文章

  1. Java中堆(heap)和栈(stack)的区别

    简单的说: Java把内存划分成两种:一种是栈内存,一种是堆内存. 在函数中定义的一些基本类型的变量和对象的引用变量都在函数的栈内存中分配. 当在一段代码块定义一个变量时,Java就在栈中为这个变量分 ...

  2. Java中堆内存和栈内存详解2

    Java中堆内存和栈内存详解   Java把内存分成两种,一种叫做栈内存,一种叫做堆内存 在函数中定义的一些基本类型的变量和对象的引用变量都是在函数的栈内存中分配.当在一段代码块中定义一个变量时,ja ...

  3. Java SE之Java中堆内存和栈内存[转/摘]

    [转/摘]1-3Java中堆内存和栈内存 注解:内存(Memory)即 内存储器,主存,其作用是用于暂时存放CPU中的运算数据,以及与硬盘等外部存储器(辅存)交换的数据. Java中把内存分为两种:栈 ...

  4. 浅析JAVA中堆内存与栈内存的区别

    Java把内存划分成两种:一种是栈内存,一种是堆内存. 一.栈内存 存放基本类型的变量,对象的引用和方法调用,遵循先入后出的原则.     栈内存在函数中定义的“一些基本类型的变量和对象的引用变量”都 ...

  5. 熟悉java堆内存和栈内存和mysql的insert语句中含有id的处理

    java的堆内存和栈内存有什么区别呢? 如果mysql数据库表的id是递增的,如果没有插入id,则id自增,如果插入id,则插入什么就显示什么.

  6. JAVA内存管理之堆内存和栈内存

    我们常常做的是将Java内存区域简单的划分为两种:堆内存和栈内存.这种划分比较粗粒度,这种划分是着眼于我们最关注的.与对象内存分配密切相关的两类内存域.其中栈内存指的是虚拟机栈,堆内存指的是java堆 ...

  7. 浅析JS中的堆内存与栈内存

    最近跟着组里的大佬面试碰到这么一个问题, Q:说说var.let.const的区别 A:balabalabalabla... Q:const定义的值能改么? A:你逗我?不能吧 不知道各位看官怎么想? ...

  8. Java堆空间Vs栈内存

    之前我写了几篇有关Java垃圾收集的文章之后,我收到了很多电子邮件,请求解释Java堆空间,Java栈内存,Java中的内存分配以及它们之间的区别. 您可能在Java,Java EE书籍和教程中看到很 ...

  9. JS中的堆内存与栈内存

    在js引擎中对变量的存储主要有两种位置,堆内存和栈内存. 和java中对内存的处理类似,栈内存主要用于存储各种基本类型的变量,包括Boolean.Number.String.Undefined.Nul ...

  10. java堆内存和栈内存的处理

    前段时间学习二叉树在处理删除操作的时候遇到一个头疼的问题:删除节点的时候明明已经置null了可树上该节点依旧存在,还必须执行node.father.left = null;才可以删除node节点,寻找 ...

随机推荐

  1. spring cloud 使用nacos 作为配置中心

    概要 nacos 可以作为服务注册发现中心,也可以作为配置中心,作为配置中心的时候,系统的配置可以做到自动刷新,即当配置服务器的数据发生更改时,客户端的配置会进行自动的更新. 实现步骤 1.修改mav ...

  2. window下cmd显示乱码

    前情 最近在维护一些老项目,本地开发环境跑不起来,需要根据cmd中的报错来解决一些环境问题 坑位 在解决环境错误的时候,cmd命令行日志打印出来的是一堆乱码,导致看不清具体是什么错误 Why? cmd ...

  3. node-sass安装问题

    前情 最近在开发一个小程序项目,为了开发速度,部分页面使用原有H5,但原有H5需要对小程序做一定兼容适配,发现原有H5项目是个很古老项目. 坑位 在项目启动前,需要执行npm install安装项目依 ...

  4. Modbus新手教程

    REDISANT 提供互联网与物联网开发测试套件 # 互联网与中间件: Redis Assistant ZooKeeper Assistant Kafka Assistant RocketMQ Ass ...

  5. Gitlab误删用户导致项目丢失莫慌

    Gitlab让小朋友不小心把离职员工的账号给删了,可是离职员工有好几个项目都是他是owner,造成Gitlab上项目全部丢失. 遇到这种情况,莫慌. 一般,本地都有完整的Git备份,离职员工走了,肯定 ...

  6. 【XML】学习笔记第三章-namesapce

    目录 命名空间 命名空间概述 命名空间语法 命名空间的声明 命名空间作用域 对命名空间的使用 元素对命名空间的使用 属性对命名空间的使用 DTD对命名空间的支持 命名空间 命名空间概述 标记中出现了同 ...

  7. 【Vue】vue项目搭建、ES6的简单使用(大觅)

    目录 项目搭建与基本配置 项目搭建 安装淘宝NPM镜像 cnpm 安装webpack 新建项目 运行项目 运行时出现的一些问题和解决方案 框架安装 安装UI框架iView 引入UI框架iView 引入 ...

  8. Spring Validation 校验

    概述 在 Web 应用中,客户端提交数据之前都会进行数据的校验,比如用户注册时填写的邮箱地址是否符合规范.用户名长度的限制等等,不过这并不意味着服务端的代码可以免去数据验证的工作,用户也可能使用 HT ...

  9. initMySQLPool

    package com.be.edge.asset.source; import io.vertx.core.AbstractVerticle; import io.vertx.core.Promis ...

  10. [转]xmanager和xshell什么关系 xmanager怎么使用

    xmanager是一款小巧实用且运行于Windows系统上的X服务器软件,可以帮助用户快速连接并访问Unix/Linux服务器.那xmanager和xshell什么关系,xmanager怎么使用,本文 ...