1、数据区、栈帧
关注对象为计算机内存分区。
Java虚拟机定义了若干种程序运行期间会使用的运行时数据区,其中有一些会随着虚拟机的启动而创建,随着虚拟机退出而销毁。另一些则与线程一一对应,随着线程的开始和结束而创建和销毁。
pc寄存器:每一条Java虚拟机线程都有对应的pc寄存器,任意时刻,一条线程只会执行一个方法代码,如果该方法为非native方法,那么该pc寄存器就保存Java虚拟机正在执行的字节码指令地址(如iaad\isub\imul\idiv等),其容量至少能存储一个returnAddress类型数据或一个与平台相关的本地指针值。
Java虚拟机栈:每一条Java虚拟机线程都有对应的Java虚拟机栈,用于存储栈帧。与传统栈类似,用来存储局部变量和一些尚未算好的结果。另外,它在方法调用和返回也扮演重要角色。jvm优化时,可以自定义stack大小。
栈帧:frame,用来存储数据和部分过程数据的数据结构,同时也用来处理动态链接(dynamic linking)、方法返回值和异常分派(dispatch exception)。栈帧随着方法调用而创建,方法结束而销毁。栈帧存储空间由创建它的线程在Java虚拟机栈中分配,每个栈帧都有自己的局部变量表、操作数栈、指向当前方法所属类的运行时常量池的引用。
局部变量表:表大小在编译期决定,仅仅取决于Java虚拟机的实现。Java虚拟机使用局部变量表来完成方法调用时的参数传递。调用方法时,他的参数依此存储到局部变量表中0开始的连续位置上,局部变量表第0个位置一定用来存储实例方法所在对象的引用(即Java的this关键字),其他参数将会存储在从1开始的连续位置上。
操作数栈:Java虚拟机提供字节码指令从局部变量表或实例对象字段中复制常量或变量到操作数栈;也提供了指令从操作数栈中取走、操作、返回数据;在调用方法时,操作数栈用来准备调用方法的参数和接受返回结果。
动态链接:每个栈帧内部都有一个指向当前方法所属类的运行时常量池的引用,以便对当前方法的代码实现动态链接。在class文件中,一个方法调用其他方法、或访问成员变量,需要通过符号引用(symbolic reference)来表示,动态链接的作用就是将这些符号引用表示的方法转化为对实际方法的引用。
本地方法栈:若Java虚拟机需要使用传统的栈(C stack)来支持本地方法(Java调用非Java编写的方法)执行,这就是本地方法栈。
Java堆:堆是供各个线程共享的运行时内存区域,也是供所有类实例和数组对象分配内存的区域。heap中存储了被GC管理的各种对象,这些对象无需也无法显示的销毁。jvm优化时可以自定义heap大小。
方法区:方法区是堆的逻辑组成部分,它存储了一个类的结构信息,例如,运行时常量池、字段、方法数据、构造函数、普通函数的字节码内容,还包括一些在类、实例、接口初始化时用到的特殊方法。一言以蔽之,就是方法区存储class文件的结构数据。
运行时常量池:runtime constant pool,类似于广义符号表,存储对象从常量到方法或字段引用不等。
2、字节码指令
Java虚拟机指令由操作码(opcode)和操作数(operand)组成,其中操作码代表操作含义,操作数代表此操作所需参数。虚拟机中的许多指令并不包含操作数,仅一个操作码。虚拟机定义操作码为一个字节长度,指令集就被设计为不完整的指令集,即所谓非完全独立性(not orthogonal)。
指令集举例
操作码 | int | long | float | double |
---|---|---|---|---|
Tconst | iconst | lconst | fconts | dconst |
Tload | iload | lload | fload | dload |
Tstore | istore | lstore | fstore | dstore |
Tadd | iadd | ladd | fadd | dadd |
Tsub | isub | lsub | fsub | dsub |
Tmul | imul | lmul | fmul | dmul |
Tdiv | idiv | ldiv | fdiv | ddiv |
Trem | irem | lrem | frem | drem |
Tneg | ineg | lneg | fneg | dneg |
加载和存储指令说明
加载和存储指令用来将数据从局部变量表和操作数栈间来回传递。
1、将一个本地变量加载到操作数栈的指令:iload、lload、flood、load、aload及带参数指令。
2、将一个数值从操作数栈存储到局部变量表的指令:istore、lstore 、fstore、dstore、astore及带参数指令。
3、将一个常量加载到操作数栈的指令: iconst、lconst、fconst、dconst、aconst及带参数指令,bipush、sipush等。
指令其实还有很多分类:算术指令(顾名思义用来处理算数运算的指令,加减乘除、与或非异或、自增、比较等);数据类型转换指令(int转换long、float转换为double、long转换为float或double、float转换为double),类型转换抽象来说非为宽转窄(narrowing numeric conversion)、窄转宽(widening numeric conversion)。
数值类型转换可能会丢失精度,原则就是待转换数值不能超过转换后数据类型的范围。
3、总结
在实际编程时,大部分时候都不会考虑虚拟机结构方面的内容,不过了解这方面的内容能让编程人员将原来类似于黑匣子的Java虚拟机的内部结构,在碰到jvm优化时,有时会看到shell命令中会有部分堆栈内存参数的选择,就不足为奇了。
当然本篇文章内容仅仅是Java虚拟机内容的皮毛,仅作为认识Java虚拟机的引言,更多深入的内容还是得去翻专业的书籍。