第二章 Java内存区域与内存溢出异常
运行时数据区域
程序计数器(线程私有)
可以看作当前线程所执行的字节码行号指示器。如果线程正在执行一个java方法,计数器记录的是正在执行的虚拟机字节码指令的地址;如果是Native方法,计数器值为空(Undefind)。此内存区域是唯一一个在Java虚拟机中没有规定任何OutOfMemoryError情况的区域
Java虚拟机栈(线程私有)
生命周期与线程相同,虚拟机栈描述的是Java方法执行的内存模型;每个方法在执行的同时都会创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息,每一个方法从调用直至完成,就是一个栈帧入栈到出栈的过程。
局部变量表将被创建在栈内存中
Java虚拟机规范中,规定了两种异常状况
1 线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverFlowError(方法中无限递归)
2 虚拟机动态扩展无法申请到足够的内存,抛出OutOfMemoryError
本地方法栈(线程私有)
与虚拟机栈作用相似,虚拟机栈为虚拟机执行Java方法(也就是字节码服务),本地方法栈则为使用到的Native方法服务
异常规范与虚拟机栈一样
Java堆(线程共享)
对于大多数应用来说,Java堆(Java Heap)是java虚拟机所管理的内存中最大的一块
所有的对象实例以及数组都要在堆上分配,但是随着JIT编译器的发展与逃逸分析技术的逐渐成熟,栈上分配、标量替换优化技术将会导致一些微妙的变化发生,不那么“绝对”
Java堆可以处于物理上不连续的内存空间中,只要逻辑上是连续的即可,就像磁盘空间一样。扩展可通过-Xmx和-Xms控制,没有内存完成实例分配,堆也无法扩展,抛出OutOfMemoryError
方法区(线程共享)
用于存储被虚拟机加载的类信息、常理、静态变量、即时编译器编译后的代码等数据,有一个别名叫Non-Heap(非堆),目的是与Java堆区分开来
对于习惯HotSpot虚拟机上开发、部署程序的开发者来说愿意把方法区称为“永久代”,本质两者并不等价,仅仅HotSpot虚拟机设计团队选择把GC分代收集扩展至方法区,或者说使用永久代来实现方法区而已。1.7的HotSpot中,已经把原本放在永久代的字符串常量池移出
1.8中已经用Metaspace(元数据区)完全替代了方法区,元数据不在JVM中,使用本地内存,默认情况下受操作系统内存限制
Java虚拟机规范中,规定无法满足内存分配需求时,抛出OutOfMemoryError
运行时常量池
是方法区的一部分。Class文件除了类的版本、方法、字段、接口等描述信息外,还有一项信息是常量池。用于存放编译期生成的各种字面量和符号的引用,这部分内容将在类加载后进入方法区的运行时常量池存放
常量池无法再申请到内存时,抛出OutOfMemoryError
直接内存
并不是虚拟机运行时数据区的一部分,也不是Java虚拟机规范中定义的内存区域。这部分内存也被频繁地使用,也会导致OutOfMemoryError
1.4加入了NIO,引入了一种基于通道(Channel)与缓冲区(Buffer)的I/O方式,可以使用Native函数库直接分配堆外内存,然后通过一个存储在Java堆中的DirectByteBuffer对象作为这块内存的引用进行操作,显著提高性能,避免了Java堆和Native堆中来回复制数据
对象的创建
指针碰撞:java堆中内存规整
空闲列表:java堆中内存不规整
java堆中内存是否规整和垃圾收集器是否带有压缩整理功能决定
对象的内存布局
对象头:Mark word 和类型指针
实例数据:对象真正存储的有效信息,代码中定义各种类型的字段内容
对齐填充:不是必然存在的,起着占位符的作用,对象的大小必须是8字节的整数倍
对象的访问定位
句柄:稳定的地址,垃圾回收只会改变柄中的实例数据指针,reference不需要修改
直接指针:访问更快