深入理解Java虚拟机阅读笔记(二.1):Java内存区域与内存溢出异常
第二部分 自动内存管理机制
第二章 Java内存区域与内存溢出异常
2.1 概述
Java的内存管理交付于虚拟机,本章介绍分区有哪些
2.2 运行时数据区
- 程序计数器(Program Counter Register):当前线程所执行的字节码的行号指示器
- 线程私有:相互独立,互不影响
- 线程执行的是Java代码时,记录正在执行的虚拟字节码指令的地址;执行Native方法(作用范围无法到达,调用c的库)时,计数器为空
- 唯一没有规定任何OutOfMemoryError的区域
- 虚拟机栈(Java Virtual Machine Stacks):Java方法执行的内存模型
- 线程私有:每个方法运行时都会创建一个栈帧(具体会在后面介绍,可以先看这个链接),用于存储操作数,函数出口…方法调用和完成的过程就是入栈和出栈的过程
- 局部变量表就是人们常称作的“栈”,其大小于编译期间分配,且运行期间不改变,存储基本数据类型,对象应用,返回地址类型(?)
- 关于异常:StackOverFlowErrow静态申请时的长度(栈太小) vs OutOfMemeryError动态扩展时的长度(使用的太多)
- 本地方法栈(Native Method Stack):Native方法执行的内存模型,以此为分界线随着线程而生而亡
- Java 堆(Java Heap):
- 所有线程共享,JVM启动时创建,所有的对象实例和数组都在堆上分配
- Garbage Collected Heap:分代收集算法
- 逻辑连续,物理不要求,可动态拓展
- 方法区(Method Area):
- 所有线程共享,“Non-Heap”
- 存储已被虚拟机加载的类信息,常量,静态变量,编译后代码
- 逻辑连续,可动态拓展,不常但需要回收
- 运行时常量池(Runtime Constant Pool):方法区的一部分
- 编译期确定的字面量,符号引用,翻译出的直接引用
- 动态性,将在类加载后进入方法区的运行时存放,如
String.intern()
- JDK8后移至堆区
2.3 虚拟机对象探秘
- 对象的创建
- 检查:指令参数能否在常量池中定位到类的符号引用,该类是否被加载/解析/初始化,如果没有,先进行类加载
- 分配内存:类加载后内存基本确定啦,即在Java堆中划分
- 方法:指针碰撞 & 空闲列表->取决于Java堆是否规整,而堆是否规整由所采用的垃圾收集器是否带有压缩整理功能
- 考虑:线程安全性,通常有两种解决方式,操作原子性&在堆中事先划分本地线程分配缓冲器(TLAB)
- 初始化+必要的设置:
- Object Header不进行设置
- 其它:全部初始化为0
<init>
方法:invokespecical
指令决定(?),一般执行new后会接着执行<init>
方法,即按照程序员的想法进行初始化
- 对象的内存
- 对象头:
- Mark Word:自身运行时的数据,如哈希码,锁状态,数组中的长度…设计成非固定的数据结构,根据对象状态复用空间
- 类型指针:确定该对象是谁的实例,指向类元数据(?),非必须
- 实例数据:
- 存储顺序受到JVM分配策略参数和定义顺序的影响
CompactFields
为True时,可以插入到空隙中
- 对齐填充:不必须,占位符
- 对象头:
- 对象的访问:
- 规定栈上的reference来指向堆中的具体对象
- 句柄访问,稳定地址 & 直接指针访问,速度更快(为什么类型数据在方法区?)
- 规定栈上的reference来指向堆中的具体对象
2.4 实战
- 目的:通过代码验证每个区域中存储的内容,遇到内存溢出快速判断溢出位置,及如何处理这些异常
- 堆溢出:
- 实践方法:
- 在虚拟机中设置堆的大小+不能动态拓展(最小==最大即可)+输出内存堆转储快照 :
-Xxm20m -Xmx20 -XX:+HeapDumpOnOutOfMemoryError
- 不断产生对象,使得堆内存溢出
- 在虚拟机中设置堆的大小+不能动态拓展(最小==最大即可)+输出内存堆转储快照 :
- 异常信息:
java heap space
- 处理方法:
- 使用映像分析工具分析转储快照,确定是内存泄漏(对象已死但没有回收)or 内存溢出(对象必须存活)
- 内存泄漏则寻找对象到GC的引用链
- 内存溢出则调整堆的大小,以及从代码上控制对象存活周期
- 实践方法:
- 栈溢出:
- HotSpot中不分虚拟机栈和本地方法栈,只由
-Xss
参数决定 - 对于单线程而言,减少栈内存or不断定义大量本地变量(局部变量)都会获得
StackOverflowError
- 对多线程而言,分配的栈越大,能承受的线程数量越小,获得
OutOfMemoryError
,此时需要依靠“减少内存”(减少最大堆,减少每个线程的栈容量)来换得更多线程
- HotSpot中不分虚拟机栈和本地方法栈,只由
- 方法区和常量池溢出(?):
- 通过
-XX:PermSize
来限制方法区大小,JDK“去永久代”后不再复制实例而是只在常量池中记录引用 - 通过产生大量常量对象或动态类,使得
OutOfMemoryError: PermGen space
永久代溢出
- 通过
- 本机直接内存溢出:Dump文件中没有明显异常且使用了NIO