概要
- 运行时数据区域
- 对象创建、布局以及定位
- 各区域OOM分析
比较枯燥,比较干,但没办法,怼吧`
运行时内存分布图
线程私有
- 程序计数器(字节码行号指示器)
- 虚拟机栈(Java方法栈)
- 本地方法栈
线程共享
- 方法区
- 堆
区域说明
程序计数器
- 很小的一块内存空间
- 记录当前线程正在执行的字节码指令地址(分支、循环、跳转、异常处理),如果当前在执行native方法,值为空
- 唯一一个在JVM规范中没有规定OOM情况的区域
Java虚拟机栈
描述Java方法执行的内存模型:方法执行时会创建一个栈帧,用于存储局部变量表、操作数栈、动态链接、方法出口等信息。方法从调用到执行完成过程,对应一个栈帧在虚拟机栈中的入栈和出栈的过程。通常说的堆内存(heap)和栈内存(Stack),栈内存是指虚拟机栈中的局部变量表部分
局部变量表存放基本数据类型、对象引用和retrunAdress类型.所需要的内存空间在编译期就可以完成分配。
在JVM规范中,这个区域有两种异常情况:- 请求的栈深度大于虚拟机允许的深度,抛出StackflowError异常
- 当动态扩展时无法申请到足够的内存,抛出OOM异常
本地方法栈
类似Java虚拟机栈,只是适用与Native方法,也会抛出StackflowError和OOM异常。Java堆
- 在虚拟机启动时创建,时Java虚拟机所管理的内存中最大的一块。
- 所有对象实例和数组都要在堆上分配内存。
- 由于现代垃圾收集器基本都采用分代收集算法,故可以划分为老年代和新生代。
- 当堆中没有内存完成分配实例,并无法在扩展时,抛出OOM异常
方法区
- 存储已被虚拟机加载的类的信息、常量、静态变量、即时编译器编译后的代码
- JVM规范不限制是否对该区域进行垃圾回收
- 当无法满足内存分配需求时,抛出OOM
- 还包含运行时常量池
运行时常量池
Class文件出了有类的版本、字段、方法、接口等描述信息外,还有一项是常量池,用于存放编译期生成的各种字面量和符号引用,这部分内容在类加载后进入方法区的运行时常量池存放。
常量池作为方法区的一部分,无法再申请到内存时,抛出OOM直接内存
不属于虚拟机运行时数据区的一部分,也不是JVM规范中定义的内存区域,但是使用很频繁(NIO)NIO在JDK1.4引入,使用Native函数直接分配堆外内存,然后通过Java堆中的DirectByteBuffer引用、操作这块内存,避免Java堆和Native堆来回复制数据
不计入-Jvm的Xmx统计中,但是由于物理内粗的限制,会影响Java堆动态扩展,导致OOM异常
Java对象创建、布局以及定位
包括对象的创建、对象的内存布局以及JVM如何定位对象三部分
创建对象过程
分为6步
- 检查new指令参数是否能在常量池中定位到一个类的符号引用,并检查这个类是否已经被加载、解析和初始化过,如果没有,则执行类的加载过程(第7章)
- 加载完类后,为对象分配内存,两种方式:
指针碰撞
:假设Java堆中内存是绝对规整的,用过的内存在一边,空闲的在另一边,中间以一个指针作为分界点,分配空间只需要将指针向空间内存方向移动与对象大小相等的距离空闲列表
:假设Java堆中的内存是不规整的,已使用和空间的你存交错。虚拟机维护一个列表,记录内存块使用情况。分配时,从列表上找一块足够打的空间给对象实例,并更新列表。
指针碰撞
:CMS(基于Mark-Sweep)空闲列表
:使用Serial,ParNew等带Compact过程的收集器
- 将分配到的内存空间初始化为零值
- 对对象进行必要的设置,如对象是哪个类的实例,GC分代年龄等信息,存放在对象的对象头中。
- 执行对象的
init
方法.从虚拟机角度,对象已经产生。但是,从Java程序角度,对象创建才开始。在执行new指令后,执行队形的init方法,将对象按照程序员的意愿初始化。
对象的内存布局
在HotSpot中,对象在内存中的布局分为三部分
- 对象头包括两部分信息
- 对象自身运行时数据,如哈希码、GC分代年龄、锁状态等等
- 类型指针,虚拟机通过这个指针确定对象是哪个类的实例
- 实例数据
存放对象真正存储的有效信息 - 对齐填充
HotSpot VM要求对象起始地址必须是8字节整数倍,这部分占位,补齐8字节
对象的访问定位
主流的两种方式:使用句柄和直接指针
- 句柄
- 句柄包含到实例数据指针和到类型数据的指针。
- 好处是引用中存储的是稳定的句柄地址,在对象被移动时,只改变句柄中的地址,引用不需要变
- 直接指针
好处是节省了一次指针定位的开销,访问速度更快。
各区域OOM分析
- Java堆溢出
使用内存分析工具,重点确认- 是否出现了内存泄漏
- 确实是内存溢出
- 方法区和运行时常量区
- 加载的类太多?
- 代码中向常量区中加入太多常量?(如String.intern等方法活着动态生成大量class)
- 本机直接内存溢出
最明显的特征:Heap Dump文件不会看到明显异常
OOM后发现Dump文件很小,和程序中又使用了NIO,有可能是这部分原因。
书籍 :《深入理解Java虚拟机》第二章 周志明 著