Java 内存结构和内存模型

什么Java内存模型?

答:Java内存模型分别有堆、栈、方法区…

可快别这样回答了,你回答的只是内存结构!内存模型是另一种东西。

应该这样回答:

Java内存模型,简称JMM,是java虚拟机规范定义的,用来屏蔽掉java程序在各种不同的硬件和操作系统对内存的访问的差异,这样就可以实现java程序在各种不同的平台上都能达到内存访问的一致性。

注意:JVM中的堆啊、栈啊、方法区什么的,是Java虚拟机的内存结构。内存模型概念如上,千千万万不要再搞混淆了。

Java内存结构

Java虚拟机内存结构,主要就是分析 Java虚拟机(JVM) 运行时数据存储区域。Java虚拟机(JVM)的运行时数据区主要包括:堆、栈、方法区、程序计数器等。

而Java虚拟机(JVM)的优化问题主要在线程共享的数据区中:堆、方法区。
在这里插入图片描述
内存结构就是上图中内存空间这些东西,而Java内存模型,完全是另外的一个东西。

Java中的内存分配

Java程序在运行时,需要在内存中的分配空间。为了提高运算效率,就对数据进行了不同空间的划分,因为每一片区域都有特定的处理数据方式和内存管理方式。

具体划分为如下5个内存空间:

  • 栈:存放局部变量
  • 堆:存放所有new出来的东西
  • 方法区:被虚拟机加载的类信息、常量、静态常量等。
  • 程序计数器(和系统相关)
  • 本地方法栈

在这里插入图片描述

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

程序计数器是一块很小的内存空间,它是线程私有的,可以认作为当前线程的行号指示器。

一个线程的执行,是通过字节码解释器改变当前线程的计数器的值,来获取下一条需要执行的字节码指令,从而确保线程的正确执行。

在这里插入图片描述
为什么需要程序计数器 ?

我们知道对于一个处理器(如果是多核cpu那就是一核),在一个确定的时刻都只会执行一条线程中的指令,一条线程中有多个指令,为了线程切换可以恢复到正确执行位置,每个线程都需有独立的一个程序计数器,不同线程之间的程序计数器互不影响,独立存储。

**注意:**如果线程执行的是个java方法,那么计数器记录虚拟机字节码指令的地址。如果为native【底层方法】,那么计数器为空。这块内存区域是虚拟机规范中唯一没有OutOfMemoryError的区域。

2、虚拟机栈(Java Stack)

又叫Java栈,就是我们平常说的栈

虚拟机栈也是线程私有的,生命周期与程序计数器相同,就是我们平时说的栈,栈描述的是Java方法执行的内存模型。

虚拟机栈-过程详情描述

  1. 每创建一个线程,虚拟机就会为这个线程创建一个虚拟机栈
  2. 虚拟机栈表示Java方法执行的内存模型,每调用一个方法,就会生成一个栈帧(Stack Frame)用于存储方法的本地变量表、操作栈、方法出口等信息,当这个方法执行完后,就会弹出相应的栈帧。

在这里插入图片描述

如果请求的栈的深度过大,虚拟机可能会抛出StackOverflowError异常,如果虚拟机的实现中允许虚拟机栈动态扩展,当内存不足以扩展栈的时候,会抛出OutOfMemoryError异常。

栈帧(Stack Frame)

在这里插入图片描述
栈帧分为三部分:局部变量区(Local Variables)、操作数栈(Operand Stack)和帧数据区(Frame Data)。

  • 局部变量区(Loca Variables):局部变量区被组织一个一个从0开始的字数组,byte、short、char在存储前被转换为int,boolean也被转换为int,0表示false,非0表示true,long和double占据两个字长。
  • 操作数栈(Operand Stack):操作数栈也被组织为一个字数组,但不同于局部变量区,它不是通过数组下标访问的,而是能过栈的Push和Pop操作,前一个操作Push进的数据可以被下一个操作Pop出来使用。
  • **帧数据区(Frame Data)**这部分的作用主要有三部分:
    • 常量池中数据的解析
    • 方法执行完后处理方法返回,恢复调用方现场
    • 方法执行过程中抛出异常时的异常处理,存储有一个异常表,当出现异常时虚拟机查找相应的异常表看是否有对应的Catch语句,如果没有就抛出异常终止这个方法调用

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

与虚拟机栈类似,只是是执行本地方法时使用的

  • (本地方法栈则为 JVM 使用到的 Native 方法服务。)

4、方法区(Method Area)

用于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译后的代码等信息。

方法区是线程间共享的,当两个线程同时需要加载一个类型时,只有一个类会请求ClassLoader加载,另一个线程会等待。

在这里插入图片描述
小结:

  • 静态变量+常量+类信息(版本、方法、字段等)+运行时常量池存在方法区中

拓展内容 - 常量池分类

  • 常量池大体可以分为:静态常量池,运行时常量池。

静态常量池:存在于class文件中,比如经常使用的javap -verbose中,常量池总是在最前面

运行时常量池:就是在class文件被加载进了内存之后,常量池保存在了方法区中,通常说的常量池 指的是运行时常量池。所以呢,讨论的都是运行时常量池

在这里插入图片描述

5、堆(Heap)

虚拟机中用于存放对象与数组实例的地方,垃圾回收的主要区域就是这里(还可能有方法区)。

Java 中的堆是 JVM 所管理的最大的一块内存空间,主要用于存放各种类的实例对象

在 Java 中,堆被划分成两个不同的区域:新生代 ( Young )、老年代 ( Old )。

新生代又可能分为Eden区,From Survivor区和To Survivor区,主要是为了垃圾回收。所有的线程共享Java堆,在这里还可以划分线程私有的缓冲区(Thread Local Allocation Buffer,TLAB)。

在这里插入图片描述
堆的大小可以通过参数 –Xms、-Xmx 来指定。

Java堆只要求逻辑上是连续的,在物理空间上可以不连续。


什么是新生代 ?

新生代(Young Generation)主要是用来存放新生的对象。

新生代又被进一步划分为Eden区(伊甸园区)和Survivor区(幸存区,包含空间相等的S0、S1区,或者说From、To区,没有先后顺序,是Copying算法的需要)。

大多数情况下,java中新建的对象都是在新生代上分配的,通过Copying算法来进行分配内存和垃圾回收。

什么是老生代 ?

老生代(Old Generation)主要存放应用程序中生命周期长的内存对象。

在新生代中经过多次垃圾回收仍然存活的对象,会被存放到老生代中。老生代通过标记/整理算法来清理无用内存。

在这里插入图片描述

参考

本文稍作修改,感谢博主,博主CSDN主页:https://blessing.blog.csdn.net/
参考地址:https://www.cnblogs.com/blessing2022/p/16622248.html

猜你喜欢

转载自blog.csdn.net/u014641168/article/details/129866621