面试必问的JVM知识-JVM特性学习

运行时数据区Java虚拟机在执行Java程序的过程中会将其管理的内存划分为若干个不同的区域 , 这些区域有各自的用途 , 以及创建和销毁的时间 , 有的区域随着虚拟机进程的启动而一直存在 , 有些区域则是依赖用户线程的启动和结束而建立和销毁 。 Java虚拟机所管理的内存包括下面几个区域 。
面试必问的JVM知识-JVM特性学习文章插图
内存划分
其中阴影部分的为所有线程共享的数据区 , 非阴影部分的为线程隔离的数据区 。
程序计数器程序计数器(Program Counter Register)是一块较小的内存空间 , 它可以看作是当前线程所执行的 字节码的行号指示器 , 字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令 , 它是程序控制流的指示器 , 分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成 。
在任何一个时间点 , 一个处理器只会执行一条线程中的指令 。 为了线程切换后能回到正确的位置 , 每条线程都需要有一个独立的程序计数器 , 各线程之间的计数器互不影响 。 所以说程序计数器这部分的内存空间是每个线程私有的 。
还有要注意:当线程正在执行的是一个Java方法 , 这个计数器记录的是正在执行的虚拟机字节码指令的地址;当正在执行的是本地(Native)方法 , 这个计数器值则应为空(Undefined) 。 并且这块内存区域不会出现OutOfMemoryError 。
Java虚拟机栈Java虚拟机栈(Java Virtual Machine Stack)为线程私有的 , 它的生命周期与线程相同 。 每个方法被执行的时候 , Java虚拟机都会同步创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态连接、方法出口等信息 。 每一个方法被调用直至执行完毕的过程 , 就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程 。
面试必问的JVM知识-JVM特性学习文章插图
Java虚拟机栈
局部变量表存放了编译期可知的各种Java虚拟机基本数据类型(boolean、byte、char、short、int、 float、long、double)、对象引用 。
局部变量表所需要的内存在编译期间就完成了分配 , 在方法运行期间不会改变局部变量表的大小 。
如果线程请求的栈深度大于虚拟机所允许的深度 , 将抛出StackOverflowError异常;如果Java虚拟机栈容量可以动态扩展 , 当栈扩展时无法申请到足够的内存会抛出OutOfMemoryError异常 。
本地方法栈本地方法栈为虚拟机使用到的本地(Native)方法服务 。 其作用于Java虚拟机栈相似 。 当然 , 本地方法栈也会在栈深度溢出或者栈扩展失败时分别抛出StackOverflowError和OutOfMemoryError异常
Java堆Java堆是被所有线程共享的一块内存区域 , 在虚拟机启动时创建 。 此内存区域的唯一目的就是存放对象实例 , Java世界里"几乎"所有的对象实例都在这里分配内存 。 当然随着技术发展 , 对象实例也有可能在其他地方分配 。
将Java堆细分的目的只是为了更好地回收内存 , 或者更快地分配内存 。
Java堆可以处于物理上不连续的内存空间中 , 但在逻辑上它应该被视为连续的 。 但对于大对象(典型的如数组对象) , 多数虚拟机实现出于实现简单、存储高效的考虑 , 很可能会要求连续的内存空间 。
如果在Java堆中没有内存完成实例分配 , 并且堆也无法再扩展时 , Java虚拟机将会抛出OutOfMemoryError异常 。
面试必问的JVM知识-JVM特性学习文章插图

上图为Java堆中的结构 。
关于对象在堆中的分配原则:
对象优先在Eden区分配
大对象直接进入老年代
长期存活的对象将进入老年代
方法区方法区(Method Area)与Java堆一样 , 是各个线程共享的内存区域 , 它用于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据 。
JDK 7的HotSpot , 已经把原本放在永久代的字符串常量池、静态变量等移出 , 而到了 JDK 8 , 终于完全废弃了永久代的概念 , 改用与JRockit、J9一样在本地内存中实现的元空间(Metaspace)来代替 , 把JDK 7中永久代还剩余的内容(主要是类型信息)全部移到元空间中 。
注意上面这段话 , JDK8已经没有永久代了 , 而是使用了元空间 。 JDK7时 , 很多人喜欢将方法区和永久代混为一谈 。
垃圾收集行为在这个区域的确是比较少出现的 , 但并非数据进入了方法区就如永久代的名字一样"永久"存在了 。 这区域的内存回收目标主要是针对常量池的回收和对类型的卸载 。 如果方法区无法满足新的内存分配需求时 , 将抛出OutOfMemoryError异常 。