目录
目录一、运行时数据区(详述)1.1 程序计数器1.2 本地方法栈1.3 虚拟机栈1.3.1 什么是虚拟机栈1.3.2 栈桢1.3.2.1 局部变量表1.3.2.2 操作数栈1.3.2.2.1 栈式指令集架构 VS 寄存器指令集架构1.3.2.3 动态链接1.3.2.4 返回地址1.4 方法区1.4.1 组成部分1.5 堆
一、运行时数据区(详述)
在程序的运行过程中,为了支持程序的运行JVM中定义了各种不同的运行时区域.有些区域是虚拟机启动时便存在,当虚拟机销毁时退出. 有些区域是随着线程的创建而创建,随着线程的结束而销毁。
本章就运行时数据区中的五个部分进行阐述:堆、虚拟机栈、本地方法栈、程序计数器、方法区。
1.1 程序计数器
特点:
-
线程安全
每个线程都有自己的程序计数器空间
唯一不会OOM的运行时数据区且不需要垃圾回收
作用:
线程在执行过程中,需要在不同线程之间来回切换。在线程执行过程中,程序计数器需要记录线程的指令地址,当某线程从丢失CPU使用权到再次获得使用权时,为继续执行剩余指令提供依据。
1.2 本地方法栈
在java的运行过程中,我们经常会调用到C的本地方法来完成功能实现. 针对C/C++代码实现的方法(也就是这些native修饰的方法)调用过程.需要有一个本地方法栈来保存方法的执行操作逻辑和操作数据.本地方法栈就是这样的一个角色。
1.3 虚拟机栈
1.3.1 什么是虚拟机栈
虚拟机栈是一种标准的栈数据结构,当每个线程被创建起之后,都会创建其独立的虚拟机栈空间,每个虚拟机栈都会有独立的虚拟机栈。
当线程需要执行某个方法时,方法以栈桢(Frame)形式压入虚拟机栈中,当方法执行完毕之后再从虚拟机栈中弹出。
这样的一种栈结构非常适合方法之间的调用执行。
1.3.2 栈桢
通过上面的描述栈桢可以理解为线程执行某个方法,方法的运行时空间。栈桢的生命周期随方法的调用而发起,随方法的执行完毕而销毁。
栈桢的数据结构如下:
虚拟机栈中就是由各个栈桢所组成,因此研究栈桢中的结构是非常有必要的。
在栈桢中有包含如下四个部分:
局部变量表
操作数栈
动态链接
方法返回地址
下面对这四个部分进行详述:
1.3.2.1 局部变量表
在运行时常量池中的变量,栈桢中并没有直接使用,运行时常量池中的变量会加载到栈桢中的局部变量表中去操作使用。
这里也有个面试常问的点:
在一个简单的成员方法中,只有两个变量。问?在栈桢中局部变量表中有几个变量?
同样的问题,如果是在静态方法中呢?
文末见答案吧!
1.3.2.2 操作数栈
操作数栈也是一种栈式数据结构,在方法执行过程中,会根据方法中的指令(如:iload、istore、iconst)将局部变量表中的数据加载到操作数栈中,然后根据操作类型指令(iadd...)去除栈顶对应几个元素进行操作。
可以看出,JVM栈桢中使用的就是这种零地址指令,是通过不断的存入、去除操作数栈中栈顶对应几个元素进行操作,这中间过程中并不涉及到任何变量地址。
这种就称为栈式指令集架构,与之对应的还有寄存器指令集架构(Davlik),而Davlik指令集架构中引入了地址。
1.3.2.2.1 栈式指令集架构 VS 寄存器指令集架构
-
可移植性
栈式指令集架构不需要地址,在大多机器上都适配,可移植性较好;
寄存器指令集架构有一地址、二地址等,需要寄存器的支持,可移植性不算好。
-
占用空间大小
栈式指令集架构占用空间更少
-
工作效率
因为栈式指令集架构没有地址,因此数据之间的操作需要借助操作数栈,效率较低;
寄存器指令集架构中指令包含地址,可以更加高效执行复杂的操作逻辑。
1.3.2.3 动态链接
Class字节码文件中包含常量池部分,常量池中存放的是变量的符号引用、方法的符号引用。当类加载器将Class字节码加载到运行时数据区中,这些常量池中的数据会放入到方法区中的运行时常量池中。
而栈桢中含有指向运行时常量池的引用就是动态链接。
1.3.2.4 返回地址
一个方法有两种方式结束运行:
方法执行完毕正常退出
方法异常退出
方法不论以何种方式结束运行,最终都要有一个返回地址。
1.4 方法区
方法区属于堆的逻辑组成部分,但是不同的厂商具体的实现方式不同,哪怕是相同厂商不同的JDK版本可能实现也有所区别。
比如HotSpot虚拟机中在1.7版本之前,方法区是通过Perm Space实现的,在1.8版本之后是通过元空间实现的。
方法区的生命周期同JVM一致,方法区是线程共享的。
1.4.1 组成部分
-
Kclass文件(类结构信息)
这里也有一个面试常问的点:类的static变量存储在哪?
答:在JDK1.6 静态变量存储在Kclass末尾,在JDK1.7开始,静态变量存储在Class对象中,即存储在堆中
-
运行时常量池
Class字节码文件中包含常量池信息,当Class文件被ClassLoader加载到JVM时,这些常量池信息会被加载到运行时常量池信息中。
其中StringTable比较特殊,在1.6版本之前,串池存在于运行时常量池中,在1.7之后,StringTable被拿到了堆中
ClassLoader
JIT即时编译代码
这里描述一下Kclass与对象的Class对象之间的关系:
1.5 堆
背景:堆是JVM中空间占比最大的一部分。生命周期同JVM,且是线程环境下共享;
“几乎”所有的对象都分配在堆中(下文会讲解到TLAB与栈上分配);
堆是垃圾回收关注的重点对象。
堆的组成部分:
老年代
-
新生代
Eden
S0
S1
其中Eden:S0:S1 = 8:1:1,为何是这样呢,这其实是根据大量实验得出的最好配比。
重要JVM参数:
- -Xmx:堆最大空间
- -Xms:堆的初始化空间
TODO