东东
发布于 2022-11-09 / 73 阅读 / 0 评论 / 0 点赞

JVM内存区域

Java的内存控制权在Java虚拟机。

运行时数据区域(内存模型)

image-1667980539390
虚拟机在执行Java程序的时候会把所管理的内存进行区域划分,初步分为两类:

  1. 运行时数据区域
  2. 本地内存

按照线程是否共享进行归类

线程私有

  1. 程序计数器
  2. 虚拟机栈
  3. 本地方法栈

线程共享

  1. 元空间
  2. 直接内存

程序计数器

  1. 程序计数器是当前线程执行字节码的行号指示器
  2. 通过改变计数器的值来选取下一条要执行的字节码指令(分支、循环、异常、线程恢复都依赖此)
  3. 每个线程都有一个独立的程序计数器,在阻塞恢复的过程中确保线程执行位置的准确,各线程间互不影响,独立存储
  4. 程序计数器是唯一一个不会出现OOM的内存区域
  5. 程序计数器的生命周期随着线程创建而创建,随着线程结束而死亡

Java虚拟机栈

  1. 线程私有,生命周期同线程
  2. 大部分Java方法的调用都是通过虚拟机栈以及配合其他区域来实现(除了一些Native方法通过本地方法栈)
  3. 方法调用的数据通过栈进行传递,调用时对应栈帧压入栈中,调用结束后被弹出
  4. 虚拟机栈是由一个个栈帧组成,先进后出

栈帧

栈帧结构分为四部分

  1. 局部变量表
  2. 操作数栈
  3. 动态链接
  4. 方法返回地址

局部变量表

  1. 存放编译器可知的各种数据类型

(boolean、byte、char、short、int、float、long、double)

  1. 存放对象引用 ?

(reference 类型,它不同于对象本身,可能是一个指向对象起始地址的引用指针,也可能是指向一个代表对象的句柄或其他与此对象相关的位置)

操作数栈

  1. 存放方法执行过程中产生的中间结果
  2. 存放执行过程中产生的临时变量

动态链接

用于方法调用其他方法的场景,作用时将方法的符号引用转为调用方法的直接引用。

  1. 在Java源文件编译为字节码时,所有的变量和方法引用都做为符号引用保存在Class文件中的常量池中
  2. 需要调用其他方法时,需要将常量池中指向方法的符号引用转为在内存地址中的直接引用

方法返回地址

虚拟机栈异常情况

Java方法有两种返回方式,一种return正常返回,一种抛出异常返回,无论哪种返回,都会弹出对应的栈帧。

  • StackOverflowError:当栈空间不允许动态扩展时,当前线程请求栈的深度超过最大深度时,就会抛出错误
  • OutOfMemoryError:当栈空间允许动态扩展时,如果在扩展的时候申请不到足够的内存空间,就会OOM

本地方法栈

  1. 和虚拟机栈一样
  2. 虚拟机栈服务Java方法,本地方法栈服务Native方法
  3. 也是由栈帧组成,也会出现两种错误

  1. 虚拟机管理的最大一块内存
  2. Java中所有线程共享
  3. 虚拟机启动时创建
  4. 唯一目的就是存放对象实例,为对象实例、数组等分配内存
  5. 堆是垃圾回收器管理的主要区域,也叫GC堆

堆的划分

堆分为新生代、老年代,新生代分为Eden、Survivor、old
现在的垃圾回收期基本都是采用分代垃圾回收算法
详细的划区也是为了更好回收、分配内存
image-1667984502271

JDK8之后永久代被元空间替代,元空间使用直接内存

对象在堆中的变化

  1. 大部分情况,对象都会在Eden区进行内存分配
  2. 在一次新生代的垃圾回收后,如果对象还存活,就会进入S0或者S1区,并且对象年龄加1
  3. 当年龄增加到一定程度(默认15岁),就会晋升到老年代

堆中最容易出现OOM错误,有如下几种形式:

  1. java.lang.OutOfMemoryError: GC Overhead Limit Exceeded:当JVM花太多时间执行垃圾回收并且只能回收很少内存时,会报错
  2. java.lang.OutOfMemoryError: Java heap space:创建新对象时,如果堆中空间不足,会报错(和分配的最大堆有关,最大堆受限于物理内存,最大为1/4)

方法区

  1. 逻辑区域
  2. 线程共享
  3. 方法区存储的是被加载的类信息、字段信息、方法信息、常量等数据

方法区和元空间的理解
方法区相当于规范,可以认为是方法区
元空间相当于实现,可以认为是类,实现接口的类

元空间

  1. 元空间使用的是直接内存,受本机内存限制,出现内存溢出的概率更小
  2. 元空间存放的是类的元数据
  3. 元空间需要指定大小,否则随着越来越多的类创建,虚拟机会耗尽系统内存

运行时常量池

  1. 常量池中存放的是编译器生成的字面量和符号引用
    • 字面量包括整数、浮点数和字符串字面量
    • 符号引用包括类、字段、方法、接口等符号引用
  2. 常量池属于方法区的一部分,当常量池无法申请到内存时就会OOM

字符串常量池

  1. 存在堆中
  2. 主要目的时为了避免字符串的重复创建
  3. 更高效进行垃圾回收

直接内存