参考资料
本文参考:《深入理解Java虚拟机》作者 周志明 知识产权归作者所有
走近java
- java组成部分:java语言、各平台虚拟机、Class文件结构、java api 类库、第三方类库
- Jre包括jvm和api
- java 未来展望:模块化、混合语言、多核并行、丰富语法、64位虚拟机
自动内存管理机制
PC程序计数器
PC:程序计数器,用于记录和控制下一条需要执行的字节码的位置,分支、循环、跳转、异常处理、线程恢复都需要依赖PC完成。
PC 是每个线程私有的,如果执行的是java方法,该值是字节码指令的地址,如果是native方法,该值是Undefined
Java虚拟机栈
JVM Stack 线程私有,用于存放栈帧 Frame 包括局部变量表、操作数栈、动态链接、方法出口等信息,如果线程请求的栈深度大于虚拟机所允许的深度,将抛出*Error异常,如果Stack可以动态扩展,扩展到无法申请到足够的内存时,抛出OutOfMemory'Error异常。-Xss 设置栈容量
Java堆
Heap 堆是线程共享 用于分配对象实例 -Xms10m 初始大小 -Xmx20m 最大大小 也包括线程私有的分配缓冲区 TLAB
方法区 Method Area 线程共享 用于存储被JVM加载的类信息、常量、静态变量、JIT后的代码,又叫做非堆 Non-Heap,永久代,-XX:PermSize=64M JVM初始分配的非堆内存,-XX:MetaspaceSize,class metadata的初始空间配额,以bytes为单位,jdk1.7,符号引用(Symbols)转移到了native heap;字面量(interned strings)转移到了java heap;类的静态变量(class statics)转移到了java heap;在JDK8中,classe metadata(the virtual machines internal presentation of Java class),被存储在叫做Metaspace的native memory。
运行时常量池
常量池是方法区的一部分,由Class文件解析后完成。
DirectMemory 或 Native Memory
JDK1.4引入的NIO可以使用Native函数直接分配堆外内存,然后通过DirectByteBuffer对象对这块内存直接操作,避免了在Heap和NativeHeap直接来回复制数据,在设置-Xmx参数时要注意留出NativeMemory空间。
JDK1.6的intern方法把首次出现的字符串实例复制到永久代中,并返回已复制的实例引用
JDK1.7的intern方法不会再复制实例、而只是在常量池中记录首次出现的实例引用,因此intern返回的引用和调用者是同一个引用。
创建对象分配内存方式:
- 指针碰撞,移动已使用内存和空闲内存之间的指针,前提是已使用和空闲内存比较规整。
- 空闲列表,记录可用内存块及大小。
- 内存分配的同步问题:使用CAS + 失败重试 保证内存分配的原子性,或利用TLAB,现在TLAB上分配,使用完分配新的TLAB时才需要同步
- 分配完成后内存空间全部初始化为零值
对象的内存布局
布局:对象头Header、实例数据Instance Data 对齐填充 Padding 疑问:为什么JVM对象地址要8字节对齐?
对象头包括MarkWord和类型指针,MarkWord 32bit or 64bit 包括哈希码、GC分代年龄、锁状态标记、线程持有的锁、偏向线程ID、偏向时间戳
类型指针是非必须的、以来虚拟机实现、如果是Java数组、对象头还包括数组长度。
实例数据:父类变量在子类变量之前。CompactFields = true 子类较窄变量会插到父类变量的空隙之间。
对象的访问定位
- 使用句柄访问:在Java堆中划分出一块内存做句柄池 reference中存储的是对象的句柄地址,而句柄中包含了对象实例地址和类型数据地址
- 使用对象指针访问:reference中存储的是对象地址,需要考虑的是对象布局中如何放置访问类型数据的指针
各种区域溢出现象
Java堆溢出 -XX:+HeapDumpOnOutOfMemoryError ava.lang.OutOfMemoryError: Java heap space
使用Eclipse Memory Analyzer 进行分析,内存泄露 查看泄露对象到GC Roots的引用链
Stack溢出 stack length unable to create new native thread
方法区和运行时常量池溢出 提示PermGen space
DirectMemory溢出时明显的特征是dump文件中看不见明显的异常、文件很小,请检查是否有NIO调用。