【Java】【JVM】内存模型

时间:2021-09-30 01:18:18

【Java】【JVM】内存模型

程序计数器

  1. 较小内存空间
  2. 线程私有
  3. 当前线程行号指示器
  4. 执行native,则为空undefined
  5. 唯一没有规定任何OutOfMemoryError区域
  6. 虚拟机的概念模型

虚拟机栈

  1. 线程私有,描述方法执行的内存模型
  2. 存放方法运行时所需数据(局部变量表、操作数栈、动态链接、方法出口等),成为栈帧Stack Frame
    1. 局部变量表:一组变量值存储空间,基本类型存值,引用类型存地址
    2. 操作数栈:表达式求值计算
    3. 动态链接:保存指向当前方法所在类的运行时常量池,调用其他方法时直接从常量池中获取符号引用,转为直接引用使用
    4. 方法出口:return返回地址
    5. 其他附加信息
  3. 执行Java方法(字节码)服务
  4. 栈深度大于虚拟机允许,抛出*Error异常(栈深度可以理解为方法调用层级数)
  5. 扩展得不到足够内存,抛出OutOfMemoryError异常

本地方法栈

  1. 类似虚拟机栈,但执行Native方法服务
  2. 会存在*Error和OutOfMemoryError异常

堆Heap

  1. 内存最大,被所有线程共享,虚拟机启动时创建
  2. 唯一目的,存放对象实例
  3. 垃圾收集器管理的主要区域,也称GC堆(Garbage Collectied Heap)
  4. 细分:新生代、老年代;Eden空间、From Survior空间、To Survior空间
  5. 线程共享堆可划分出多个线程私有的分配缓冲区(Thread Local Allocation Buffer TLAB)
  6. 若堆中没有内存完成实例分配,且无法扩展,抛出OutOfMemoryError异常

对象创建及内存分配过程

  1. new新对象时放入新生代中Eden区域(大对象直接进入老年代)
  2. 当新生代满时,触发Minor GC,将幸存对象移入幸存第一区域
  3. 再次Minjor GC时,存活对象移入幸存第二区域
  4. 当存活对象超过多次Minjor GC时,进入老年代,默认15次
  5. 当老年代内存不足时,触发Major GC
  6. 若老年代GC后内存仍不足,会触发Full GC,再次不足则报OOM错误
  7. Full GC触发时机:System.gc调用,老年代或方法区内存不足时

TLAB机制

避免多线程操作同一地址,使用加锁机制影响分配速率。建立线程本地分配缓存区,即每个线程专有的内存分配区域。

字符串常量池

在类加载完成,经过验证,准备阶段之后在堆中生成字符串对象实例,然后将该字符串对象实例的引用值存到string pool中。

提示:JDK7之前在永久区(方法区)。

方法区Method Area

  1. 线程共享区域,存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码和运行时常量池等(类结构信息)
  2. 是堆的逻辑部分,即Non-Heap非堆
  3. 也称永久区,Java8已取消该区(改元空间,数据拆分到了Java堆和本地内存)
    1. 字面量(字符串常量)转移到堆
    2. 类的静态变量转移到堆
    3. 符号引用转移至本地内存
  4. 内存回收目标主要针对常量池的回收和堆类型的卸载
  5. 可抛出OutOfMemoryError异常
  6. 内存分配可不连续,可动态扩展
  7. 并非一直永久,只是相对,垃圾回收率低

运行时常量池 Runtime Constant Pool

  1. 方法区的一部分
  2. Class文件常量池(非运行时常量池,编译阶段就已确定)信息:Constant Pool table
  3. 存放编译器生成的各种字面量和符号引用(及这些符号翻译的直接引用),在类加载后进入方法区的运行时常量池中存放
  4. 可抛出OutOfMemoryError异常
  5. 存储java文件常量池中的符号信息
  6. 相对class常量池,具有动态性,并非只有class常量池唯一数据来源,运行时产生的常量也可以存放进运行时常量池

直接内存Direct Memory

  1. 非虚拟机运行时数据区的一部分,非规范中定义的内存区域
  2. 被频繁使用
  3. 可抛出OutOfMemoryError异常
  4. 本机直接内存的分配不受堆大小限制,受本机总内存大小及处理器寻址空间的限制
  5. 可设置-Xmx信息,忽略直接内存

参考:

  • 官方文档jdk8
  • 网络博文整理