JVM内存模型和内存溢出

文章目录

    • 概念
    • 抽象模型
      • 模型
      • 通信的实现
    • JVM内存
      • 程序计数器
      • 虚拟机栈
      • 本地方法栈
      • 方法区
        • 运行时常量池
      • 直接内存
    • JAVA对象
      • 创建
      • 布局
      • 访问
    • OutOfMemoryError异常
    • 内存相关参数
    • 硬件内存模型
    • 参考

概念

  • JMM,Java Memory Model,定义JVM在计算机内存(RAM)中的工作方式。
  • JVM是整个计算机的虚拟模型,所以JMM隶属于JVM。

抽象模型

模型

  • 线程之间的共享变量储存在主内存中。
  • 每个线程都有一个私有的本地内存,储存了该线程读写共享变量的副本。
  • 本地内存是一个抽象概念,涵盖缓存、写缓冲区、寄存器、其他硬件和编译器优化

JVM内存模型和内存溢出_第1张图片

通信的实现

  1. 首先,线程A把本地内存A中更新过的共享变量刷新到主内存中去。
  2. 然后,线程B到主内存中去读取线程A之前已更新过的共享变量。

JVM内存模型和内存溢出_第2张图片

JVM内存

  • 分为线程共享和线程隔离的区域。
    JVM内存模型和内存溢出_第3张图片

程序计数器

  • 可看做当前线程所执行的字节码的行号指示器。
    • 线程执行java方法时:虚拟机字节码指令的地址。
    • 线程执行native方法时:空(undefined)。
  • 唯一没有规定任何OutOfMemoryError的区域。

虚拟机栈

  • 描述java方法执行的内存模型:每个方法执行时创建栈帧,存储局部变量表、操作数栈、动态链接、方法出口。
  • 局部变量表存储基本数据类型、引用类型,其中64位的long和double占用2个局部变量空间。
  • 异常:
    • StackOverflowError:线程请求的栈深度大于虚拟机所允许的深度。
    • OutOfMemoryError:虚拟机栈扩展时无法申请到足够的内存。

本地方法栈

  • 类似java虚拟机栈,但是使用native方法服务。
  • Sun的HotSpot 虚拟机将本地方法栈和虚拟机栈合并

  • 存储对象实例和数组(JIT编译器的优化使得对象并不一定存储在堆上)。
  • OutOfMemoryError:堆中没有内存完成实例的分配,并且堆无法扩展。

方法区

  • 存储被虚拟机加载的类信息、常量、静态变量、即时编译器编译的代码等。
    • JIT:将热点代码编译成平台相关的机器码。
  • 内存回收的目标:常量池的回收、类型的卸载。
  • OutOfMemoryError:方法区无法满足内存分配需求。
运行时常量池
  • 方法区的一部分,存放编译期生成的的字面量和符号引用,或者运行期间产生的常量(String.intern()方法)。

直接内存

  • 不是虚拟机运行时数据区的一部分,可以通过NIO分配和操作这一堆外内存。
  • OutOfMemoryError:各个内存区域的总和大于物理内存的限制。

JAVA对象

  • 基于HotSpot虚拟机讨论,普通java对象,不包括数组、Class对象。

创建

  1. 类加载检查。检查常量池中是否有对应的类的符号引用,并且这个类是否已被加载、解析、初始化。如没有,执行类加载。
  2. 分配内存。内存规整时使用指针碰撞,内存不规整时使用空闲列表,两个以上的对象同时分配内存时,需要注意线程安全,解决方法:
    1. 同步内存分配动作:CAS和失败重试,保证更新的原子性。
    2. 每个线程预先分配本地线程分配缓冲(Thread Local Allocation Buffer, TLAB),在各自的TLAB上分配内存,只有分配新的TLAB时,才需要同步锁定。
  3. 初始化零值。除对象头之外的内存空间初始化,保证对象的实例字段不附初始值就可以使用。也可以在TLAB分配时进行。
  4. 设置对象头。包括属于的类、如何找到类的元数据、对象哈希码、对象GC分代年龄、偏向锁等。
  5. 执行init方法。按照程序员意愿初始化。

布局

  • 对象头
    • 分类
      1. MarkWord,对象自身运行时数据。哈希码、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等。
      2. Class指针。指向类元数据,数组还需要包含数组长度。
    • 大小
      1. 在32位系统下,存放Class指针的空间大小是4字节,MarkWord是4字节,对象头为8字节。
      2. 在64位系统下,存放Class指针的空间大小是8字节,MarkWord是8字节,对象头为16字节
      3. 在64位开启指针压缩的情况下 -XX:+UseCompressedOops,存放Class指针的空间大小是4字节,MarkWord是8字节,对象头为12字节。
      4. 如果对象是数组,那么额外增加4个字节
    • JVM内存模型和内存溢出_第4张图片
  • 实例数据。定义的各种类型字段,存储顺序受虚拟机分配策略和源码中的定义顺序影响,hotspot中相同宽度的字段分配到一起,父类中的变量在子类之前。
  • 对齐填充。HotSpot内存管理系统要求对象的大小必须是8字节的整数倍。

访问

  • 句柄访问。划分句柄池和实例池,reference存储对象句柄地址,句柄包含对象实例数据和类型数据的地址。
    JVM内存模型和内存溢出_第5张图片
  • 直接指针访问。reference存储对象地址,包含指向类型数据的指针。
    JVM内存模型和内存溢出_第6张图片
  • 句柄访问的优势:对象移动时只会改变句柄中的实例数据指针,reference本身不变。
  • 直接指针访问优势:节省一次指针定位开销,速度更快。HotSpot使用直接指针访问

OutOfMemoryError异常

  • 堆溢出:不停创建对象,并维持 GC Roots 到对象之间的引用避免对象被回收。
  • 栈溢出:
    • 递归过深。
    • 创建过多线程。这是由于栈的空间被每个线程瓜分。
  • 方法区和运行时常量池溢出:
    • String.intern()方法,在常量池中记录首次出现的实例的引用。
    • cglib产生大量的动态类。
  • 本机直接内存溢出:Unsafe实例分配过多内存。
  • 溢出实例。

内存相关参数

  • Xms设置进程堆内存的最小值
  • Xmx设置进程堆内存的最大值。一般来说,为了避免频繁的堆内存震荡,导致系统性能下降,Xms,Xmx设为相等。
  • Xss设置每个线程可使用的内存大小。
  • Xmn用来设置堆内新生代的大小。老生代的大小:-Xmx减去-Xmn。
  • 参考JVM优化总结。

硬件内存模型

  • CPU寄存器、CPU缓存、主存。

JVM内存模型和内存溢出_第7张图片

  • JMM和硬件的关系:交叉关系。

JVM内存模型和内存溢出_第8张图片

参考

  • 全面理解Java内存模型
  • 浅析Java内存模型与垃圾回收

你可能感兴趣的:(java,jvm,内存模型,溢出)