美文网首页
[JVM] JVM内存结构浅析

[JVM] JVM内存结构浅析

作者: Colors_boy | 来源:发表于2020-11-16 18:12 被阅读0次

JVM内存布局规定了Java在运行过程中内存申请、分配、管理的策略,保证了JVM高效稳定运行。

经典的JVM内存布局如图:

JVM内存机构.png

线程公用:HeapMetaspace

线程私有:Native Method Stacks、Program Counter Register、JVM Stacks

1. Heap(堆区)

Heap区是OOM(内存溢出)故障最主要的发源地,它存储着几乎所有的实例对象,堆由垃圾收集器自动回收,堆区由各子线程共享使用。堆的内存既可以固定大小,也可以在运行是动态地调整,通过以下参数设定初始值和最大值,如:-Xms256M -Xmx1024M,其中 -X 表示他是JVM运行参数,ms是memory start的简称,mx是memory max的简称,分别代表最小堆容量和最大堆容量。通常情况下,服务器在运行过程中,堆空间不断的扩容和收缩,势必形成不必要的系统压力,所以在线上生产环境中,JVM 的 ms 和 mx 设置成一样大小,避免在GC后调整堆大小时带来的压力。

  • 堆分为两块:分别为新生代和老年代。对象产生之初在新生代,步入暮年之时在老年代,老年代也接纳在新生代无法容纳的超大对象。新生代=1个Eden区 + 2个survivor区。绝大部分对象在Eden区生成,当Eden区填满的时候,会触发YGC(Young Garbage Colleciton)。垃圾回收的时候,在Eden区实现清楚策略,没有被引用的对象则直接回收。依然存活的对象会被移送到Survivor区。

  • Survivor区又分为 S0 和 S1 两块内存空间。每次 YGC 的时候,它们将存活的对象赋值到未使用的那块空间,然后将当前正在使用的那块空间完全清空,交换两块空间的使用状态。如果YGC要移送的对象大于Survivor容量的上限,则直接移交老年代。

简要GC流程图.png
  • 假如一些没有进取心的对象以为可以一直在新生代的Survivor区交换来交换去,那就错了。每个对象都有一个计数器,每次 YGC 都会 +1。-XX:MaxTenuringThreshold参数配置能配置计数器的值达到某个阈值后,对象从新生代晋升到老年代。如果该参数配置为1,那么直接从Eden区移送到老年代。默认值为15,即在Survivor区交换14次后晋升至老年代。

  • 如果在 Survivor 区无法放下,或者超大对象的阈值超过上限,则尝试在老年代分配,如果老年代也放不下,则会出发FGC(Full Garbage Collection),如果依然无法放下,则会抛出OOM。

2. Metaspace(元空间)

在JDK1.8中,因永久代(Perm)区大小固定,很难进行调优,并且FGC的时候会移动类元信息,已经被淘汰。代替它的便是元空间。区别于永久代,元空间在本地内存中分配。在JDK8里,Perm区中的所有内容中字符串常量移至堆内存,其他内容包括类元信息、字段、静态属性、方法、常量等都移动至元空间内。

3. JVM Stack(虚拟机栈)

JVM中的虚拟机栈是描述 Java 方法执行的内存区域,它是==线程私有==的。栈中的元素用于支持虚拟机进行方法调用,每个方法从开始调用到执行完成的过程,就是栈帧从入栈到出栈的过程。在活动线程中只有位于栈顶的帧才是有效的,称为当前栈帧。正在执行的方法称为当前方法,栈帧是方法运行的基本结构。在执行引擎运行时 所有指令都只能针对当前栈帧进行操作。

虚拟机栈.png
  • 操作栈:

操作栈是一个初始值为空的桶式结构结构栈。

public int simpleMethod() {
    int x = 11;
    int y = 12;
    int z = x + y;
    
    return z;
}

详细的字节码操作顺序:

public simpleMethod();
descriptor: ()I
flags: ACC_PUBLIC
Code:
 stack = 2 , locals = 4, args_Size = 1 //最大栈深度为2,局部变量个数为4
  BIPUSH 11     // 常量11压入操作栈
  ISTORE_1      // 保存到局部变量表 slot_1 中(第一处)
  
  BIPUSH 12     // 常量12压入操作栈
  ISTORE_2      // 保存到局部变量表 slot_2 中
  
  ILOAD_1       //把局部变量表的 slot_1 元素(int x)压入操作栈
  ILOAD_2       //把局部变量表的 slot_2 元素(int y)压入操作栈
  IADD          // 把上方的两个数都取出来,在CPU里加一下,并压回操作栈的栈顶
  ISTORE_3      // 把栈顶的结果存储到局部变量表 slot_3 中
  
  ILOAD_3 
  IRETURN       // 返回栈顶元素值

  • 方法返回地址:

方法退出的过程相当于退出当前栈帧,退出可能有三种方式:

  • 返回值压入上层调用枝帧。

  • 异常信息抛给能够处理的枪帧。

  • PC 数器指向方法调用后的下一条指令。

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

本地方法栈( Native Method Stack)在JVM内存布局中也是线程对象私有的,但是虚拟机栈“主内”,而本地方法栈“主外”。这个“内外”是针对JVM来说的,本地方法栈为Native方法服务。线程开始调用本地方法时,会进入一个不再受JVM约束的世界。本地方法可以通过 JNI ( Java Native Int rface )来访问虚拟机运行时的数据区 ,甚至可以调用寄存器,具有和 JVM 相同的能力和权限。当大量本地方法出现时势必会削弱JVM对系统的控制力,因为它的出错信息都比较黑盒。对于内存不足的情况 本地方法栈还是会抛出 native heap OutOfMemory。

5. Program Counter Register(程序计数寄存器)

在程序计数寄存器( Program Counter Register, PC )中, Register 的命名源于
CPU 的寄存器,CPU 只有把数据装载到寄存器才能够运行。寄存器存储指令相关的
现场信息,由于 CPU 时间片轮限制,众多线程在并发执行过程中,任何一个确定的
时刻,一个处理器或者多核处理器中的一个内核,只会执行某个线程中的一条指令。
这样必然导致经常中断或恢复,如何保证分毫无差呢?每个线程在创建后,都会产生
自己的程序计数器和栈帧,程序计数器用来存放执行指令的偏移量和行号指示器等,
线程执行或恢复都要依赖程序计数器。程序计数器在各个线程之间互不影响,此区域
也不会发生内存溢出异常


总结:

最后,从线程共享的角度来看,堆和元空间是所有线程共享的,而虚拟机栈、本地方法栈、程序计数器是线程内部私有的,从这个角度看一个 Java 内存结构

Java内存和线程.png

注明:笔记出自书本《码出高效》

相关文章

  • [JVM] JVM内存结构浅析

    JVM内存布局规定了Java在运行过程中内存申请、分配、管理的策略,保证了JVM高效稳定运行。 经典的JVM内存布...

  • JVM内存结构和Java内存模型

    最近看到两个比较容易混淆的概念:JVM内存结构和Java内存模型 JVM内存结构JVM内存结构或者说内存模型指的是...

  • JVM内存结构浅析

    1 概述 具有内存动态分配和自动回收的特点 2 运行时内存 2.1 程序计数器(Program Counter R...

  • JVM(七):JVM内存结构

    JVM(七):JVM内存结构 在前几节的文章我们多次讲到 Class 对象需要分配入 JVM 内存,并在 JVM ...

  • JVM

    简介 Jvm 系列一:Java类的加载机制Jvm系列二:JVM内存结构 --内存泄漏与内存溢出Jvm系列三:GC算...

  • JVM 内存结构解析

    1. JVM内存结构 (1) JDK1.7的JVM内存结构 JVM内存结构主要有三大块:堆内存、方法区和栈。 堆内...

  • JVM学习笔记

    一、JVM的结构图 1.1 Java内存结构 JVM内存结构主要有三大块:堆内存、方法区和栈。堆内存是JVM中最大...

  • JVM-02

    JVM内存结构

  • Java大佬之学习历程(一)

    基础篇 JVM: ①JVM内存结构: 堆、栈、方法区、直接内存、堆和栈区别, ②JVM参数及...

  • 对象实例化过程的内存分配

    JVM(Java Virtual Machine)Java虚拟机。 在学习jvm内存结构的时候,了解jvm的内存管...

网友评论

      本文标题:[JVM] JVM内存结构浅析

      本文链接:https://www.haomeiwen.com/subject/zbyzbktx.html