一文读懂 -Java虚拟机的内存结构(图详解)

你好我是辰兮,很高兴与你分享近期学习总结,本篇是学习了哔哩哔哩上面JVM底层原理后的小结,学习完对Java内存结构有了新的见解,为了更直观的感受其模型,找了很多的图片,希望对你们理解学习有帮助。



0、序言(运行时数据区)

JVM中的堆啊、栈啊、方法区什么的,是Java虚拟机的内存结构Java程序启动后,会初始化这些内存的数据。

Java 虚拟机在执行 Java 程序的过程中会把它管理的内存划分为若干个不同的数据区域。每个区域都有各自的作用。

分析 JVM 内存结构,主要就是分析 JVM 运行时数据存储区域。JVM 的运行时数据区主要包括:方法区程序计数器等。

JVM 的优化问题主要在线程共享的数据区中:堆、方法区。

在这里插入图片描述

内存结构就是上图中内存空间这些东西,而Java内存模型,完全是另外的一个东西。


Java中的内存分配:

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

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

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

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

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

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


为什么需要程序计数器 ?

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

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


2、虚拟机栈(Java Stack)

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

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

虚拟机栈-过程详情描述

每创建一个线程,虚拟机就会为这个线程创建一个虚拟机栈
②虚拟机栈表示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)主要存放应用程序中生命周期长的内存对象。

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


在这里插入图片描述


The best investment is to invest in yourself

在这里插入图片描述

2020.05.19 记录辰兮的第67篇博客

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章