在这一块的学习时, 我们容易陷入一个误区,就是一上来就直接搜索运行时数据区, 网上有些文章对虚拟机规范和HOTSPOT实现没有区分开,导致有时候大家看的两篇文章解释尽不一样. 自己也容易糊涂. 所以本篇特地将两个拆开讲. 且尽量以官方文档为准
我们可以把jvm规范理解成接口. 就是要这些东西, 然后不同的虚拟机厂商有不同的实现方案. 如方法区,hotspot 用了1.7及以前用了永久代, 1.8及以后用了元数据区. 别的虚拟机如JRocket,J9 都没有永久代的概念.
如上参考pdf中及下图所示,主要有六大数据区域:
程序计数器为线程私有的,每个线程都有自己独立的程序计数器
如果当前线程执行的是Java方法,则程序计数器中是当前执行虚所机字节码指令的地址,如果正在执行是native方法,这个计数器的值是空的
我们假设有以下场景. 此时有两个线程A,B正在执行.
CPU执行线程A 的 Ia1指令时, Ta 的程序计数器存的是Ia1指令的地址, 执行完指令Ia1后,转而执行线程B的Ib1指令, 再回到线程A时, 从程序计数器中取出上次执行到了Ia1, 然后继续往下执行.
java虚拟机栈也是线程私有的, 该线程每调用一个方法,都用创建一个栈帧(Frame).栈帧中有局部变量表,操作数栈,动态链接,方法出口等信息.
开发中遇到和虚拟机栈相关的问题:
我们在idea的debug界面也可以看到关于栈和栈帧相关界面,如下图所示:
1:栈帧列表
2:可以切换不同的线程,看对应的栈帧
3:当前栈帧中的用到的变量
所以线程共享的一块区域,几乎所以有java对象都在堆里面进行分配,这里要注意以下几个问题
运行时常量池是方法区的一部分,与之对应是.class文件中的静态常量信息,如下图所示:
在class文件加载的链接步骤中的解析阶段,会把静态的常量池和运行时常量池关联起来,把符号引用变成直接引用.
方法区也是被线程所共享的,其实是从堆里面划出来的一片区域(这里不要钻是从哪个代里面划出来的, 如上据说,JVM规范并没有规定分代的,由各个实际的虚机机去实现的,可自己去看怎么划分)
里面存放的有:已被虚拟机加载的类信息, 常量,静态变量,即时编译器编译后的代码缓存
这个和上面的java虚拟机栈没太大的差别, 在jvm规范层面,把本地方法栈描述为在java调用其他语言写的方法时创建,在HotSpot实现层面, 直接把本地方法栈和虚拟机栈合二为一. (所以说规范和实现要分开学习)
总的来说, 方法区是接口, 永久代和元空间是实现
在HotSpot中,1.7及以前的版本以永久代做为方法区的实现, 1.8及以后版本的jdk以MetaSpace做方法区的实现.
永久代在堆里面, MetaSpace直接使用了直接(本地)内存.
相应的1.8及以后. 移除了永久代,以MetaSpace做方法区实现,常量池中的字符串常量池,直接留在了堆中. 其他的如类信息,静态信息留在了MetaSpace中,也跟着去了直接(本地)内存
本文以JVM规范为主,部分区域给出了HotSpot的实现,先学规范,再学实现,两者切记一定要分开学,不然就学着学着就混乱了!