JVM 内存结构
SunRan

前言


根据上图我们发现 Java 内存结构由堆、虚拟机栈、方法区、程序计算器、本地方法栈。如果分为两种类型则是:线程共享的线程私有的

线程私有

程序计数器(PC Register)

在图中看到了很大一块程序计数器其实在现实中只是开辟了很小很小的一部分,他的核心作用是用于:记录线程执行到的位置。

由于Java虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的,在任何一个确定的时刻,一个处理器(对于多核处理器来说是一个内核)只会执行一条线程中的指令。因此,为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各条线程之间的计数器互不影响,独立存储。

  • 程序计数器中只存储当前线程执行程序的行号,一个类指针的数据结构。
    JVM线程中执行的方法有2种类型:普通Java方法和由其他语言实现的native方法。如果当前执行的是普通Java方法,则程序计数器记录的是虚拟机字节码指令的地址。如果当前执行的是native方法,则计数器的值为空(Undefined)

虚拟机栈(VM Stack)

和程序计数器一样,虚拟机栈也是线程私有的,即生命周期和线程相同。

Java虚拟机栈和线程同时创建,用于存储栈帧。每个方法在执行时都会创建一个栈帧(Stack Frame),用于存储局部变量表操作数栈、动态链接、方法出口等信息。每一个方法从调用直到执行完成的过程就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。栈帧:一个栈帧随着一个方法的调用开始而创建,这个方法调用完成而销毁。栈帧内存放者方法中的局部变量,操作数栈等数据。栈帧也叫过程活动记录,是编译器用来实现过程/函数调用的一种数据结构。

  • 栈帧结构

    • 局部变量表
      局部变量表(Local Variable Table)是一组变量值存储空间,用于存放方法参数和方法内部定义的局部变量。并且在Java编译为Class文件时,就已经确定了该方法所需要分配的局部变量表的最大容量。

    • 动态连接
      每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用,持有这个引用是为了支付方法调用过程中的动态连接Dynamic Linking)。

    • 操作数据栈
      操作数栈和局部变量表一样,在编译时期就已经确定了该方法所需要分配的局部变量表的最大容量。

    • 返回方法当一个方法开始执行后,只有2种方式可以退出这个方法 :

      • 方法返回指令 执行引擎遇到一个方法返回的字节码指令,这时候有可能会有返回值传递给上层的方法调用者,这种退出方式称为正常完成出口。
      • 异常退出 : 在方法执行过程中遇到了异常,并且没有处理这个异常,就会导致方法退出。

本地方法栈(Native Method Stack)

简单来说就是一个java调用非java代码的接口,一般是C或C++代码。至于为什么使用其他语言主要是使用更底层的语言效率更高。

线程共享

堆(Heap)

JAVA堆内存管理是影响性能主要因素之一也是虚拟机管理中心内存最大的一块,在虚拟机启时创建。此内存区域唯一的目的就是存放对象实例,Java中几乎所有的对象实例都在这里分配内存。

  • 下面通过一个图片看一下组成部分:
  • 新生代

新生成的对象首先放到年轻代Eden区,当Eden空间满了,触发Minor GC,存活下来的对象移动到Survivor0区,Survivor0区满后触发执行Minor GC,Survivor0区存活对象移动到Suvivor1区,这样保证了一段时间内总有一个survivor区为空。经过多次Minor GC仍然存活的对象移动到老年代。

结构组成:

  • 伊甸园区
  • 幸存者一区
  • 幸存者二区

💡 新生代特点:每次垃圾回收时都有大量的对象需要被回收。垃圾回收器在新生代采用的收集算法是Copying(复制)方法:它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块面对内存用完了,就将还存活的对象复制到另外一块,然后再把已使用的内存空间一次清理掉。由于新生代有每次垃圾回收时都有大量的对象需要被回收的特点,所以采用Copying算法,复制的存活对象较少,性能比较好。但是Copying算法还是有缺陷的,那就是内存的使用率只有一半。

  • 内存分配
  • 堆的大小可以通过参数 –Xms-Xmx来指定。
  • 新生代 ( Young ) 与老年代 ( Old ) 的默认比例的值为 1:2 ( 该值可以通过参数 –XX:NewRatio 来指定 )
  • 伊甸区 : 幸存一区 : 幸存二区 = 8 : 1 : 1 ( 可以通过参数 –XX:SurvivorRatio 来设定 ),
  • 老年代

老年代存储长期存活的对象,占满时会触发Major GC=Full GC,GC期间会停止所有线程等待GC完成,所以对响应要求高的应用尽量减少发生Major GC,避免响应超时。


  • 永久代
    永久代存放类的常量、静态变量等信息,是方法区的实现。对永久代的回收主要回收两部分内容:废弃常量和无用的类。

💡 注意:JDK1.8 之后,不存在永久带,即没有java.lang.OutOfMemoryError: PermGen space 这种错误。取而代之的是Meta space(元空间)。之前存储在永久带的字符串常量,静态变量移到了堆中去了,元空间不存储在堆中,而是存储在本地内存中。

方法区(Method Area)

方法区是被所有线程共享,所有字段和字节码,以及一些特殊方法如构造器,接口代码也在此定义。简单说,所有定义的方法的信息都保存在该区域,此区域属于共享区间。
静态变量 + 常量 + 类信息 + 运行时常量池存在方法区中,实例对象存在堆内存中。

  • 本文标题:JVM 内存结构
  • 本文作者:SunRan
  • 创建时间:2022-01-08 18:24:34
  • 本文链接:https://lksun.cn/2022/01/08/JVM-内存结构/
  • 版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
 评论