JVM-2.Java内存区域与内存溢出异常
运行时数据区域
- 程序计数器(线程私有)
- Java虚拟机栈(线程私有)
- 本地方法栈(线程私有,执行本地方法)
- Java堆:所有的对象实例及数组都在堆上分配。基本采用分代回收,新生代(Eden/From Survivor/To Survivor),老年代。
- 方法区:存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。Non-Heap非堆。HotSpot称之为永久代(也有回收,主要针对常量和对类的卸载。但效果一般,因为回收条件苛刻。)
- 运行时常量池:本是方法区的一部分,已经独立移出。用于存放编译期生成的各种字面量和符号引用。
- 直接内存:不是虚拟机运行时数据区的一部分,也不是虚拟机规范中定义的内存区域。JDK1.4 NIO 使用Native函数库直接分配堆外内存。
对象探秘
对象创建
- 为新生对象分配内存,有两种方式可用。受到不同收集器的影响。
- 指针碰撞(Bump the pointer)。堆中内存规整,用指针划分使用过的和未使用的两块空间,分配内存就是指针向未使用空间移动。
- 空闲列表(Free List)。虚拟机需要维护一个列表,记录哪些内存块是可用的,分配空间时从列表中找到一块足够大的空间划分给新对象,同时更新列表。
- Serial、ParNew带等Compact过程:指针碰撞;CMS等基于Mark-Sweep:空闲列表。
- 并发情况下争用同一地址创建对象,如何解决?
- 虚拟机采用CAS+失败重试来保证更新操作的原子性。
- 方案二:本地线程分配缓冲(Thread local allocation buffer, TLAB). 每个线程预先在堆中拿出一小块私有,缓冲区分配满了,再同步锁定,分配新TLAB. 通过-XX:+/-UseTLAB设定。
- 为新生对象分配内存,有两种方式可用。受到不同收集器的影响。
对象的内存布局
- 对象头(Header,Mark Word)
- 存储对象自身的运行时数据,如哈希码,GC份代年龄,锁状态标志,线程持有的锁,偏向线程ID,偏向时间戳等。
- 有一部分虚拟机实现,会在对象头中存储类型指针。与对象的访问定位有关。
- 实例数据(Instance data)
- 父类+子类的各种类型的字段内容。存储顺序受到VM分配策略和字段在源码中定义顺序影响。
- 对其填充(Padding)
- HotSpot虚拟机要求对象起始地址必须是8字节的整数倍。
- 对象头(Header,Mark Word)
- 对象的访问定位
- 句柄访问:包含了对象实例数据和类型数据各自的具体地址信息。
- 直接指针访问:实例对象中存储类型数据指针。
异常
- OutOfMemoryError
- 堆溢出
- 虚拟机栈和本地方法栈溢出
- 方法区和运行时常量池溢出
- 本机直接内存溢出