- 在虚拟机自动内存管理机制的帮助下, 不容易出现内存泄漏和内存溢出问题
- 不过, 也正是因为把内存控制的权力交给了Java虚拟机
- 如果不了解虚拟机是怎样使用内存的, 那么排查错误将会成为一项异常艰难的工作。
运行时数据区
Java虚拟机定义了若干种程序运行期间会使用到的运行时数据区,其中有一些会随着虚拟机启动而创建,随着虚拟机退出而销毁。还有一些则是与线程一一对应,他们的生命周期也随着线程而创建于销毁。
程序计数器
每条虚拟机线程都有自己的程序计数器。程序计数器记录着字节码行号。
由于Java虚拟机的多线程是通过线程切换并分配处理器执行时间的方式来实现的,在任何一个确定的时刻, 一个处理器( 对于多核处理器来说是一个内核) 都只会执行一条线程中的指令。 因此, 为了线程切换后能恢复到正确的执行位置, 每条线程都需要有一个独立的程序计数器, 各条线程之间计数器互不影响, 独立存储, 我们称这类内存区域为“线程私有”的内存。
@@>>>字节码指令
《深入理解java虚拟机中jdk1.7》中称作:程序计数器
《 虚拟机规范JAVA SE8》中称作:PC寄存器
英文原名:Program Counter Register
- 如果正在执行的是一个Java方法, 这个程序计数器记录的是正在执行的虚拟机字节码指令的地址
- 如果正在执行的是Native方法, 这程序计数器则为空( Undefined)
- 程序计数器中的容量至少足够可以保存一条returnAddress类型数据或者一个与平台相关的本地指令地址。(retrunAddress 指向了一条字节码指令地址)
- 此内存区域是唯一一个在Java虚拟机规范中没有规定任何OutOfMemoryError情况的区域。
Java虚拟机栈
Stack与Java vm Stack是不同的。我们java程序员所说的栈,指的是虚拟机栈(Java vm Stack),然而虚拟机是由其他语言实现,其中java vm stack可能是分配在heap(堆)上
- 虚拟机栈主要用于存储局部变量与尚未计算完毕的过程结果
- 线程创建的同时得到了虚拟机栈,栈中保存着栈帧
- 每个方法执行的同时会创建栈帧,栈帧的创建到销毁,就是其在虚拟机栈中出入栈的过程
虚拟机栈在线程请求的时候,如果超过虚拟机栈最大深度会抛出*Error
虚拟机栈支持动态扩展,在尝试扩展并无法申请到内存时会抛出OutOfMemoryError
说完程序计数器和虚拟机栈,我们总结成一张图
位于栈顶的栈帧为(当前栈)
Java堆
Java堆是被所有线程共享的一块内存区域, 在虚拟机启动时创建
此内存区域的唯一目的就是存放数组对象与类对象实例, 几乎所有的对象实例都在这里分配内存。
这一点在Java虚拟机规范中的描述是: 所有的对象实例以及数组都要在堆上分配
随着JIT编译器的发展与逃逸分析技术逐渐成熟, 栈上分配、 标量替换优化技术将会导致一些微妙的变化发生, 所有的对象都分配在堆上也渐渐变得不是那么“绝对”了。
@@>>>逃逸分析
垃圾回收期主要管理的就是堆区域
@@>>>GC
堆可以根据虚拟机参数动态扩展和调整堆大小,当堆超过了可以提供的最大容量时。则会抛出OutOfMemoryError
方法区
方法区是java堆的逻辑组成部分
存储了每一个类的结构信息,例:运行时常量池,字段和方法数据,构造函数,普通方法的字节码以及类,实例,接口初始化时的特殊方法。
@@>>>特殊方法
方法区根据不同虚拟机有不同实现,方法区只是一个逻辑存在,在Hotspot中方法区实现为“永久代”(PermGen)。
然而从JAVA8开始,永久代被移除,而增加了“元空间”(Metaspace),利用本地内存(native memory)来存储(MaxMetaspaceSize来设置其大小,默认是动态调节),极大的减少了永久代堆溢出。
运行时常量池
运行时常量池在方法区分配
保存符号引用与直接饮用,其动态性确保了不仅仅在编译期才产生保存,在运行期间也可以,最好的例子就是String.intern()
本地方法栈
当调用native本地方法时,会有一个额外的栈来服务,这就是本地方法栈
HotSpot已经把本地方法栈和虚拟机栈合并
堆外内存
JUC中DirectByteBuffer可以引用堆外内存,堆外内存不受虚拟机堆内存限制。
栈帧
在活动线程中, 只有位于栈顶的栈帧才是有效的, 称为当前栈帧 , 与这个栈帧相关联的方法称为当前方法。
执行引擎运行的所有字节码指令都只针对当前栈帧进行操作
在编译程序代码的时候, 栈帧中需要多大的局部变量表, 多深的操作数栈都已经完全确定了, 因此一个栈帧需要分配多少内存,不会受到程序运行期变量数据的影响, 而仅仅取决于具体的虚拟机实现。
局部变量表
局部变量表( Local Variable Table) 是一组变量值存储空间, 用于存放方法参数和方法内部定义的局部变量
局部变量表中数据存放在局部变量表槽中(称作变量槽,Slot),Slot在定义中可以满足boolean、 byte、 char、 short、 int、 float、 reference或returnAddress类型的数据,当然64位的虚拟机仍要补白和对齐。
- Slot 0 在非static方法中 保存的永远是 this
- Slot 1 ~ N 排列其他参数
操作数栈
操作数栈最大深度在编译器就已确定。
依赖后入先出的规则,执行字节码指令
动态连接
每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用, 持有这个引用是为了支持方法调用过程中的动态连接
返回地址
方法正常退出下,会得到字节码指令来唤起调用者栈帧,并在有返回值的情况下返回结果。
当异常退出下,会将异常指令返回并唤起调用者,不会有返回值。