当前位置: 首页 > news >正文

讲讲 JVM 的内存结构(附上Demo讲解)

讲讲 JVM 的内存结构

  • 什么是 JVM 内存结构?
    • 线程私有
      • 程序计数器​
      • 虚拟机栈
      • 本地方法栈
    • 线程共享
      • 堆​
      • 方法区​
      • 注意
        • 永久代​
        • 元空间​
        • 运行时常量池​
        • 直接内存​
  • 代码详解

什么是 JVM 内存结构?

在这里插入图片描述

JVM内存结构分为5大区域,程序计数器、虚拟机栈、本地方法栈、堆、方法区。​

HotSpot在JDK1.8之前方法区就是永久代,永久代就是方法区。JDK1.8后删除了永久代,改为元空间,元空间在本地内存中。方法区就是元空间,元空间就是方法区。​

创建一个线程,JVM就会为其分配一个私有内存空间,其中包括PC、虚拟机栈和本地方法栈​

简单来说:

  • 堆:存放 new 出来的东西
  • 方法区:被虚拟机加载的类信息、常量、静态常量等。
  • 栈:存放局部变量
  • 程序计数器:记录指令
  • 本地方法栈:Native 方法

接着进行详细的介绍:

线程私有

程序计数器​

线程私有的,作为当前线程的行号指示器,用于记录当前虚拟机正在执行的线程指令地址。程序计数器主要有两个作用:​

  • 当前线程所执行的字节码的行号指示器,通过它实现代码的流程控制,如:顺序执行、选择、循环、异常处理。​
  • 多线程的情况下,程序计数器用于记录当前线程执行的位置,当线程被切换回来的时候能够知道它上次执行的位置。​

程序计数器是唯一一个不会出现 OutOfMemoryError 的内存区域,它的生命周期随着线程的创建而创建,随着线程的结束而死亡。​

虚拟机栈

Java 虚拟机栈是由一个个栈帧组成,而每个栈帧中都拥有:局部变量表、操作数栈、动态链接、方法出口信息。每一次函数调用都会有一个对应的栈帧被压入虚拟机栈,每一个函数调用结束后,都会有一个栈帧被弹出。​

局部变量表是用于存放方法参数和方法内的局部变量。​

每个栈帧都包含一个指向运行时常量池中该栈所属方法的符号引用,在方法调用过程中,会进行动态链接,将这个符号引用转化为直接引用。​

  • 部分符号引用在类加载阶段的时候就转化为直接引用,这种转化就是静态链接​
  • 部分符号引用在运行期间转化为直接引用,这种转化就是动态链接​

Java 虚拟机栈也是线程私有的,每个线程都有各自的 Java 虚拟机栈,而且随着线程的创建而创建,随着线程的死亡而死亡。

Java 虚拟机栈会出现两种错误:StackOverFlowErrorOutOfMemoryError。​
可以通过 -Xss 参数来指定每个线程的虚拟机栈内存大小:java -Xss2M​

本地方法栈

虚拟机栈为虚拟机执行 Java 方法服务,而本地方法栈则为虚拟机使用到的 Native 方法服务。Native 方法一般是用其它语言(C、C++等)编写的。​

本地方法被执行的时候,在本地方法栈也会创建一个栈帧,用于存放该本地方法的局部变量表、操作数栈、动态链接、出口信息。​

线程共享

堆​

堆用于存放对象实例,是垃圾收集器管理的主要区域,因此也被称作GC堆。​

堆可以细分为:新生代(Eden空间(伊甸园)、From Survivor、To Survivor空间)和老年代。​

通过 -Xms 设定程序启动时占用内存大小,通过 -Xmx 设定程序运行期间最大可占用的内存大小。如果程序运行需要占用更多的内存,超出了这个设置值,就会抛出OutOfMemory异常。​

方法区​

方法区与 Java 堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。​

对方法区进行垃圾回收的主要目标是对常量池的回收和对类的卸载。​

注意

永久代​

方法区是 JVM 的规范,而永久代PermGen 是方法区的一种实现方式,并且只有 HotSpot 有永久代。对于其他类型的虚拟机,如 JRockit 没有永久代。由于方法区主要存储类的相关信息,所以对于动态生成类的场景比较容易出现永久代的内存溢出。​

元空间​

JDK 1.8 的时候,HotSpot的永久代被彻底移除了,使用元空间替代。元空间的本质和永久代类似,都是对JVM规范中方法区的实现。

两者最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。

为什么要将永久代替换为元空间呢?​
永久代内存受限于 JVM 可用内存,而元空间使用的是直接内存,受本机可用内存的限制,虽然元空间仍旧可能溢出,但是相比永久代内存溢出的概率更小。​

运行时常量池​

运行时常量池是方法区的一部分,在类加载之后,会将编译器生成的各种字面量和符号引号放到运行时常量池。在运行期间动态生成的常量,如 String 类的 intern()方法,也会被放入运行时常量池。​

直接内存​

直接内存并不是虚拟机运行时数据区的一部分,也不是虚拟机规范中定义的内存区域,但是这部分内存也被频繁地使用,而且也可能导致 OutOfMemoryError 错误出现。​

NIO 的 Buffer 提供了 DirectBuffer,可以直接访问系统物理内存,避免堆内内存到堆外内存的数据拷贝操作,提高效率。

DirectBuffer直接分配在物理内存中,并不占用堆空间,其可申请的最大内存受操作系统限制,不受最大堆内存的限制。​

直接内存的读写操作比堆内存快,可以提升程序I/O操作的性能。通常在I/O通信过程中,会存在堆内内存到堆外内存的数据拷贝操作,对于需要频繁进行内存间数据拷贝且生命周期较短的暂存数据,都建议存储到直接内存。

在这里插入图片描述

代码详解

以下是一个简单的 Java 类,我们将在注释中说明各个内存区域的作用:

public class MemoryStructureExample {// 静态变量,存放在方法区private static int staticVar = 42;public static void main(String[] args) {// 局部变量,存放在虚拟机栈int localVar = 10;// 创建一个对象,存放在堆中MemoryStructureExample obj = new MemoryStructureExample();// 调用方法,创建一个新的栈帧obj.doSomething(localVar);// 创建一个数组,存放在堆中int[] array = new int[5];// 常量,存放在方法区final String constantString = "Hello, world!";// 本地方法调用,使用本地方法栈System.out.println("Static variable: " + staticVar);System.out.println("Constant string: " + constantString);}// 方法,存放在方法区public void doSomething(int value) {// 局部变量,存放在虚拟机栈int result = value * 2;System.out.println("Result: " + result);}
}

在这段代码中,我们可以看到:

  • staticVar 是一个静态变量,存放在方法区。
  • localVar 是一个局部变量,存放在虚拟机栈。
  • obj 是一个对象,存放在堆中。
  • array 是一个数组,也存放在堆中。
  • constantString 是一个常量,存放在方法区。
  • doSomething 方法创建了一个新的栈帧,其中的局部变量存放在虚拟机栈。

总之,这些内存区域共同构成了 Java 虚拟机的内存结构,确保 Java 程序的正常运行和优化。如果你还有其他问题,欢迎继续提问!

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • LVS集群及其它的NAT模式
  • (自用)网络编程
  • 华为配置蓝牙终端定位实验
  • Apache中使用SSI设置
  • 【网络安全科普】网络安全指南请查收
  • IDEA社区版使用Maven archetype 创建Spring boot 项目
  • 《从零开始学习Linux》——开篇
  • 高盛开源的量化金融 Python 库
  • 精通 mysqldumpslow:深度分析 MySQL 慢查询日志
  • Matlab-Simulink模型保存为图片的方法
  • Python应用爬虫下载QQ音乐歌曲!
  • html5——表单
  • SpringCloudAlibaba Nacos配置中心与服务发现
  • 为企业提升销售工作效率的工作手机管理系统
  • C/C++ list模拟
  • JS 中的深拷贝与浅拷贝
  • [译]前端离线指南(上)
  • 「面试题」如何实现一个圣杯布局?
  • 【干货分享】SpringCloud微服务架构分布式组件如何共享session对象
  • CSS进阶篇--用CSS开启硬件加速来提高网站性能
  • Java比较器对数组,集合排序
  • Making An Indicator With Pure CSS
  • miniui datagrid 的客户端分页解决方案 - CS结合
  • Node项目之评分系统(二)- 数据库设计
  • php面试题 汇集2
  • vue和cordova项目整合打包,并实现vue调用android的相机的demo
  • 动态魔术使用DBMS_SQL
  • 对象管理器(defineProperty)学习笔记
  • 将回调地狱按在地上摩擦的Promise
  • 前端路由实现-history
  • 深入 Nginx 之配置篇
  • 验证码识别技术——15分钟带你突破各种复杂不定长验证码
  • #pragam once 和 #ifndef 预编译头
  • #中国IT界的第一本漂流日记 传递IT正能量# 【分享得“IT漂友”勋章】
  • ${factoryList }后面有空格不影响
  • (11)MATLAB PCA+SVM 人脸识别
  • (11)MSP430F5529 定时器B
  • (3)选择元素——(17)练习(Exercises)
  • (C++17) std算法之执行策略 execution
  • (Matlab)基于蝙蝠算法实现电力系统经济调度
  • (代码示例)使用setTimeout来延迟加载JS脚本文件
  • (附源码)python房屋租赁管理系统 毕业设计 745613
  • (附源码)小程序 交通违法举报系统 毕业设计 242045
  • (过滤器)Filter和(监听器)listener
  • (利用IDEA+Maven)定制属于自己的jar包
  • (论文阅读26/100)Weakly-supervised learning with convolutional neural networks
  • (五)activiti-modeler 编辑器初步优化
  • (一)插入排序
  • .net web项目 调用webService
  • .NET开发人员必知的八个网站
  • 。Net下Windows服务程序开发疑惑
  • [20161101]rman备份与数据文件变化7.txt
  • [2021 蓝帽杯] One Pointer PHP
  • [7] CUDA之常量内存与纹理内存
  • [ACTF2020 新生赛]Include