2024年【JVM】JVM内存模型 内存布局_内存布局和内存模型(2),2024年最新由浅入深

img
img

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域,如图所示:
在这里插入图片描述

  • 程序计数器、虚拟机栈、本地方法栈——线程私有(随用户线程的启动/结束而创建/销毁)
  • Java堆、方法区——线程共享(随虚拟机的启动/关闭而创建/销毁)

在这里插入图片描述

1.程序计数器(Program Counter Register)

程序计数器是一块较小的内存空间,用于记录下一条要运行的指令,可以看作是当前线程所执行的字节码的行号指示器,分支,循环,跳转,异常处理,线程回复等都需要依赖这个计数器来完成。每个线程都需要一个独立的程序计数器,各个线程之中的计数器相互独立,互不影响、独立存储,是线程中私有的内存空间

Java虚拟机的多线程(JVM的并发)是通过线程切换并分配时间片执行来实现的。任何时刻,一个处理器内核都只会执行一条线程中的指令,某个线程在执行的过程中因为时间片耗尽而挂起,当它再次获取时间片时,需要从挂起的地方继续执行,程序计数器就是用来记录程序的字节码执行位置。因此,为了线程切换后能恢复到正确的执行位置,每天线程都需要有一个独立的程序计数器,所以这部分是“线程私有”的内存。

如果一个线程执行一个Java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是一个Native方法(本地方法),这个计数器的值则为空,此内存区域是唯一一个在Java的虚拟机规范中没有规定任何OutOfMemoryError异常情况的区域。

2.虚拟机栈(Java Virtual Machine Stacks)

虚拟机栈描述的是Java方法执行的内存模型:每个方法在执行时都会创建一个栈帧用来存储局部变量表,操作数栈,动态链接,方法出口等信息,参与方法的调用和返回,每一个方法从调用到执行完成的过程就对应着一个栈帧在虚拟机栈中入栈到出栈的过程

局部变量表存放了编译期可知的各种基本数据类型(boolean,byte,char,short,int,float,long,double),对象引用(reference类型即所有引用类型的父类,它不等同于对象本身,可能是一个指向对象起始地址的引用指针,也可能是指向一个代表对象的句柄或其他与此对象相关的位置)和returnAddress类型(指向了一条字节码指令的地址)。

异常状况:我们知道,一个线程拥有一个自己的栈,这个栈的大小决定了方法调用的可达深度(递归多少层次,或嵌套调用多少层其他方法,-Xss 参数可以设置虚拟机栈大小),当栈帧一直在执行入栈操作时,线程请求的栈的深度大于虚拟机栈所允许的深度时,就会发生栈溢出(StackOverflowError),一般造成这个问题的原因是程序中方法被循环调用没有退出。虚拟机栈也可以动态扩展,但当超过Java虚拟机规范中所规定的长度时,就会报OutOfMemoryError(内存溢出)异常。

  • StackOverflowError:线程请求的栈深度大于虚拟机所允许的深度,将会抛出此异常。
  • OutOfMemoryError:当可动态扩展的虚拟机栈在扩展时无法申请到足够的内存,就会抛出该异常。

3.本地方法栈(Native Method Stack)

本地方法栈和java虚拟机栈的功能相似,java虚拟机栈用于管理Java函数(字节码)的调用,而本地方法栈用于管理本地Native方法的调用,但不是由Java实现的,而是由C实现的。本地方法栈中方法使用的语言,使用方式,数据结构没有强制要求。有的虚拟机比如(HotSpot)直接就将虚拟机栈和本地方法栈合二为一。

与虚拟机栈一样,本地方法栈区域也会抛出StackOverflowError和OutOfMemoryError异常。
  参数:使用-Xss参数减少栈内存容量。

4.Java堆(Heap)

堆是JVM里最大的一块内存区域,被所有线程共享,在虚拟机启动时创建,此区域的目的就是存放对象实例和数组为所有创建的对象和数据分配内存空间,且每次分配的空间是不定长的。在Heap 中分配一定的内存来保存对象实例,实际上只是保存对象实例的属性值,属性的类型和对象本身的类型标记等,并不保存对象的方法(方法是指令,保存在Stack中)。

Java堆是垃圾收集管理的主要区域,也被称为GC堆,由于现在收集器基本都采用分代收集算法,所以Java的堆中还可以分为新生代,老年代,永久代,JDK1.8之后取消了永久代;其中新生代又划分为Eden空间,From Survivor空间,To Survivor空间。无论怎么划分都是为了更好的回收,分配,利用内存。

Java堆可以处于物理上不连续,逻辑上连续的存空间,Java堆在实现时,既可以是固定大小的,也可以是可拓展的,并且主流虚拟机都是按可扩展来实现的(通过-Xmx(最大堆容量) 和 -Xms(最小堆容量)控制)。如果在堆中没有内存完成实例分配,并且堆也无法再拓展时,将会抛出 OutOfMemoryError 异常。

5.方法区(Method Area)

也被称为永久区,与Java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据,堆的一个逻辑部分,Non-Heap(非堆),其中运行时常量池就在方法区,对永久区的GC回收,主要是针对常量池的回收和对类型的卸载

Java堆是 Java代码可及的内存,是留给开发人员使用的;而非堆(Non-Heap)是JVM留给自己用的,所以方法区、JVM内部处理或优化所需的内存 (如JIT编译后的代码缓存)、每个类结构 (如运行时常量池、字段和方法数据)以及方法和构造方法的代码都在非堆内存中。

根据Java虚拟机规范的规定,当方法区无法满足内存分配的需求时,将抛出 OutOfMemoryError 异常。

5.1运行时常量池(Runtime Constant Pool)

运行时常量池时方法区的一部分,用来存放编译器生成的各种字面量(常量和字符串)和符号引用(类和接口的符号引用、字段名称和描述的符号引用、方法名称和描述的符号引用),当常量池无法再申请到内存时也会抛出 OutOfMemoryError 异常。运行时常量池相对于Class文件常量池的一个重要特征是具备动态性。

Java语言并不要求常量一定只有编译期才能产生,运行期间也可能将新的常量放入池中,比如字符串的手动入池方法intern()。

6、直接内存(Direct Memory)

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

这个我们实际中主要接触到的就是NIO,在NIO引入了一种基于通道(Channel)与缓冲区(Buffer)的I/O方式,它可以使用Native函数库直接分配堆外内存,然后通过一个存储在Java堆中的DirectByteBuffer对象作为这块内存的引用进行操作。这样能避免在Java堆和Native堆中来回复制数据,在一些场景里显著提高性能。这块内存不受Java堆大小限制,但受本机总内存限制,可以通过MaxdirectMemorySize来设置(默认与堆内存最大值一样),所以也会出现OOM异常;

二、栈和栈帧

虚拟机栈是线程私有的,每个方法在执行时都会创建一个栈帧,加入虚拟机栈,方法执行结束后,所对应的栈帧出栈。栈帧用来存储局部变量表,操作数栈,动态链接,返回地址,附加信息等,参与方法的调用和返回,每一个方法从调用到执行完成的过程就对应着一个栈帧在虚拟机栈中入栈到出栈的过程

1、栈:

又名堆栈,它是一种运算受限的线性表。其限制是仅允许在表的一端进行插入和删除运算。这一端被称为栈顶,相对地,把另一端称为栈底。其特性是先进后出。栈是线程私有的,生命周期跟线程相同,当创建一个线程时,同时会创建一个栈,栈的大小和深度都是固定的。方法参数列表中的变量,方法体中的基本数据类型的变量和引用数据类型的引用都存放在栈中,成员变量和对象本身不存放在栈中。运行时,成员函数的局部变量引用也存放在栈中。栈的变量随着变量作用域的结束而释放,栈中的变量随着方法的调用而创建,当方法执行结束后,jvm会自动释放内存,不需要GC回收栈不是全局共享的,每个线程创建一个栈,该线程只能访问其对应的栈数据。栈内存的大小是在编译期就确定了的。

2、栈帧:

一个栈中可以有多个栈帧栈帧随着方法的调用而创建,随着方法的结束而消亡。该栈帧中存储该方法中的变量,原则上各个栈帧之间的数据是不能共享的,但是在方法间调用时,jvm会将一方法的返回值赋值给调用它的栈帧中。每一个方法调用,就是一个压栈的过程,每个方法的结束就是一个出栈的过程。压栈都会将该栈帧置于栈顶,每个栈不会同时操作多个栈帧,只会操作栈顶,当栈顶操作结束时,会将该栈帧弹出,同时会释放该栈帧内存,其下一个栈帧将变为栈顶。栈内存归属于单个线程,每个线程都会有一个栈内存,其存储的变量只能在其所属线程中可见,即栈内存可以理解成线程的私有内存。

img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Go语言开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以戳这里获取

涵盖了95%以上Go语言开发知识点,真正体系化!**

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以戳这里获取

  • 28
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值