运行时数据区-基础
- 为什么学习运行时数据区
- Java内存区域(运行时数据区域)和内存模型(JMM) 区别
- 组成部分(jdk1.7 / jdk1.8)
- 从线程隔离性分类
- 与类加载的关系
- 每个区域的功能
- 参考文章
为什么学习运行时数据区
- 熟悉jvm如何管理内存
- 便于排查内存泄露和内存溢出相关的问题
Java内存区域(运行时数据区域)和内存模型(JMM) 区别
- 内存区域是指 Jvm 运行时将数据分区域存储,
强调对内存空间的划分
。 - 内存模型(Java Memory Model,简称 JMM )是
定义了线程和主内存之间的抽象关系,即 JMM 定义了 JVM 在计算机内存(RAM)中的工作方式
,如果我们要想深入了解Java并发编程,就要先理解好Java内存模型。
组成部分(jdk1.7 / jdk1.8)
- Java 虚拟机在执行 Java 程序的过程中会把它管理的内存划分成若干个不同的数据区域。
- JVM规范中规定了五个部分:
堆,方法区,虚拟机栈,本地方法栈,程序计数器
- jdk7 和 jdk8 的区别
虚拟机栈,本地方法栈,程序计数器
基本没变化方法区
是在虚拟机规范里面被定义的,不同的虚拟机对这个定义的实现不同,在HotSpot 虚拟机中在 jdk1.7 版本之前的方法区实现叫永久代(PermGen space)(是堆的一部分)
,jdk1.8 之后叫做元空间(Metaspace)(是本地内存的一部分)
。堆
在1.7 中被划分为新生代,老年代,永久代
, 1.8 中被划分为新生代,老年代(默认比例1:2)
。新生代
中进一步划分为Eden,S0(SurivorFrom),S1(SurivorTo)(默认比例 8:1:1)
- 为什么方法区会从 堆 中移除到 堆外
- 在 HotSpot JVM 中,永久代中用于存放类和方法的元数据以及常量池,比如Class和Method。每当一个类初次被加载的时候,它的元数据都会放到永久代中。但永久代是有大小限制的,因此如果加载的类太多,很有可能导致永久代内存溢出,即万恶的 java.lang.OutOfMemoryError: PermGen ,由于 PermGen 内存经常会溢出,因此 JVM 的开发者希望这一块内存可以更灵活地被管理,不要再经常出现这样的 OOM
- 移除 PermGen 可以促进 HotSpot JVM 与 JRockit VM 的融合,因为 JRockit 没有永久代。
- 根据上面的各种原因,PermGen 最终被移除,方法区移至 Metaspace,字符串常量移至 Java Heap。
从线程隔离性分类
线程共享(所有线程共享)
:堆,方法区线程私有(每个线程都有一套)
:虚拟机栈,本地方法栈,程序计数器(与线程的生命周期一致)
与类加载的关系
每个区域的功能
- 程序计数器
- 记录 JVM 字节码指令地址,如果是执行 native 方法,则是未指定值(undefined)
- 是程序控制流的指示器,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成
- 是唯一一个在 JVM 规范中没有规定任何
OutOfMemoryError
情况的区域
- 虚拟机栈
- 每个线程在创建的时候都会创建一个虚拟机栈,其内部保存一个个的栈帧(Stack Frame),对应着一次次 Java 方法调用,是线程私有的,生命周期和线程一致。
- 虚拟机栈是对程序运行,方法调用的实现,每个方法执行,伴随着入栈(进栈/压栈),方法执行结束出栈
- 栈中可能出现的异常
- Java 虚拟机规范允许 Java虚拟机栈的大小是动态的或者是固定不变的
- 如果采用固定大小的 Java 虚拟机栈,那每个线程的 Java 虚拟机栈容量可以在线程创建的时候独立选定。如果线程请求分配的栈容量超过 Java 虚拟机栈允许的最大容量,Java 虚拟机将会抛出一个 StackOverflowError 异常
- 如果 Java 虚拟机栈可以动态扩展,并且在尝试扩展的时候无法申请到足够的内存,或者在创建新的线程时没有足够的内存去创建对应的虚拟机栈,那 Java 虚拟机将会抛出一个OutOfMemoryError异常
- 栈帧的内部结构
- 局部变量表(Local Variables)
- 操作数栈(Operand Stack)(或称为表达式栈)
- 动态链接(Dynamic Linking):指向运行时常量池的方法引用
- 方法返回地址(Return Address):方法正常退出或异常退出的地址一些附加信息
- 本地方法栈
- Java 虚拟机栈用于管理 Java 方法的调用,而本地方法栈用于管理本地方法的调用
- 与操作系统交互
- 方法区
- 用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
- Java 虚拟机规范把方法区描述为堆的一个逻辑部分,但是它却有一个别名叫做 Non-Heap(非堆),目的应该是与 Java 堆区分开来。
- 堆
栈是运行时的单位,而堆是存储的单位。栈解决程序的运行问题,即程序如何执行,或者说如何处理数据。堆解决的是数据存储的问题,即数据怎么放、放在哪。
- 此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例以及数据都在这里分配内存。为了进行高效的垃圾回收,虚拟机把堆内存逻辑上划分成三块区域(分代的唯一理由就是优化 GC 性能)
参考文章
- https://javaguide.cn/java/jvm/memory-area.html#%E8%BF%90%E8%A1%8C%E6%97%B6%E6%95%B0%E6%8D%AE%E5%8C%BA%E5%9F%9F
- https://pdai.tech/md/java/jvm/java-jvm-struct.html
- https://www.51cto.com/article/704419.html
- https://cnblogs.com/czwbig/p/11127124.html