前言:今天学长带领大家走进JVM学习,让我们一起来学习认识虚拟机栈吧~
目录
6 栈定缓存 (Top-of-Stack Cashing)技术
由于跨平台性的设计,Java的指令都是根据栈来设计的。不同平台CPU架构不同,所以不能设计为基于寄存器的。
优点是跨平台,指令集小,编译器容易实现,缺点是性能下降,实现同样的功能需要更多的指令。
有不少Java开发人员一提到Java内存结构,就会非常粗粒度地将JVM中的内存区理解为仅有Java堆(heap)和Java栈(stack)?为什么?
栈是运行时的单位,而堆是存储的单位
即:栈解决程序的运行问题,即程序如何执行,或者说如何处理数据。堆解决的是数据存储的问题,即数据怎么放、放在哪儿。
实例:
<span style="background-color:#f8f8f8"><span style="color:#333333"><span style="color:#770088">public</span> <span style="color:#008855">void</span> <span style="color:#0000ff">methodA</span>() {
<span style="color:#008855">int</span> <span style="color:#000000">i</span> <span style="color:#981a1a">=</span> <span style="color:#116644">10</span>;
<span style="color:#008855">int</span> <span style="color:#000000">j</span> <span style="color:#981a1a">=</span> <span style="color:#116644">20</span>;
}
<span style="color:#770088">public</span> <span style="color:#008855">void</span> <span style="color:#0000ff">methodB</span>() {
<span style="color:#008855">int</span> <span style="color:#000000">k</span> <span style="color:#981a1a">=</span> <span style="color:#116644">30</span>;
<span style="color:#008855">int</span> <span style="color:#000000">m</span> <span style="color:#981a1a">=</span> <span style="color:#116644">40</span>;
}</span></span>
一个栈帧对应一个方法
面试题:开发中遇到的异常有哪些?
栈中可能出现的异常
设置栈内存大小
我们可以使用参数-Xss 选项来设置线程的最大栈空间,栈的大小直接决定了函数调用的最大可达深度。
代码测试
<span style="background-color:#f8f8f8"><span style="color:#333333"><span style="color:#aa5500">// 默认情况下打印 9810</span>
<span style="color:#aa5500">// 设置 -Xss256k</span>
<span style="color:#aa5500">// 打印 2239</span>
<span style="color:#770088">static</span> <span style="color:#008855">int</span> <span style="color:#000000">count</span> <span style="color:#981a1a">=</span> <span style="color:#116644">1</span>;
<span style="color:#770088">public</span> <span style="color:#770088">static</span> <span style="color:#008855">void</span> <span style="color:#0000ff">main</span>(<span style="color:#008855">String</span>[] <span style="color:#000000">args</span>) {
<span style="color:#000000">System</span>.<span style="color:#000000">out</span>.<span style="color:#000000">println</span>(<span style="color:#000000">count</span><span style="color:#981a1a">++</span>);
<span style="color:#000000">main</span>(<span style="color:#000000">args</span>);
}</span></span>
每个栈帧中存储着:
栈帧的局部变量表的槽位是可以重用的,如果一个局部变量过了其作用域,那么在其作用域之后申明的新的局部变量就很有可能会复用过期局部变量的槽位,从而达到节省资源的目的。
<span style="background-color:#f8f8f8"><span style="color:#333333"><span style="color:#770088">public</span> <span style="color:#008855">void</span> <span style="color:#0000ff">test</span>() {
<span style="color:#008855">int</span> <span style="color:#000000">i</span>;
<span style="color:#000000">System</span>.<span style="color:#000000">out</span>.<span style="color:#000000">println</span>(<span style="color:#000000">i</span>)
}</span></span>
这样的代码是错误的,没有赋值不能够使用。
理论
操作数栈并非采用访问索引的方式来进行数据访问的,而是只能通过标准的入栈和出栈操作来完成一次数据访问。
<span style="background-color:#f8f8f8"><span style="color:#333333"><span style="color:#770088">public</span> <span style="color:#008855">void</span> <span style="color:#0000ff">testAddOperation</span>() {
<span style="color:#008855">byte</span> <span style="color:#000000">i</span> <span style="color:#981a1a">=</span> <span style="color:#116644">15</span>;
<span style="color:#008855">int</span> <span style="color:#000000">j</span> <span style="color:#981a1a">=</span> <span style="color:#116644">8</span>;
<span style="color:#008855">int</span> <span style="color:#000000">k</span> <span style="color:#981a1a">=</span> <span style="color:#000000">i</span> <span style="color:#981a1a">+</span> <span style="color:#000000">j</span>;
}</span></span>
使用javap命令反编译class文件:javap -v 类名.class
<span style="background-color:#f8f8f8"><span style="color:#333333"> 0 bipush 15
2 istore_1
3 bipush 8
5 istore_2
6 iload_1
7 iload_2
8 iadd
9 istore_3
10 return</span></span>
代码转成字节码
<span style="background-color:#f8f8f8"><span style="color:#333333">int m = 8; -> bipush
int m = 800; -> sipush</span></span>
常见i++和++i的区别,
<span style="background-color:#f8f8f8"><span style="color:#333333">前面提过基于栈式架构的虚拟机所使用的零地址指令更增加紧凑,但完成一项操作的时候必然需要使用更多的入栈和出栈命令,这同事也就意味着需要更多的指令分派(instaruction dispatch)次数和内存读/写次数。</span></span>
由于操作数是存储在内存中的,因此频繁地执行内存读/写操作必然会影响执行速度。为了解决这个问题,HotSpot JVM的设计者们提出了栈顶缓存(Tos, Top-of-Stack Cashing)技术,将栈顶元素全部缓存在物理CPU的寄存器中,以此降低对内存的读/写次数,提升执行引擎的执行效率。
问题:为什么要有常量池?
常量池的作用,就是为了提供一些符号和常量,便于指令的识别。
在JVM中,将符号引用转换为调用方法的直接引用与方法的绑定机制相关。
对应方法的绑定机制为:早起绑定(Early Binding)和晚期绑定(Late Binding)。绑定的是一个字段、方法或者类在符号引用被替换为直接引用的过程,这仅仅发生一次。
随着高级语言的横空出世,类似与Java一样的基于面向对象的编程语言如今越来越多,尽管这类编程语言在语法风格上存在一定的差别,但是它们彼此之间始终保持着一个共性,那就是都支持封装、继承和多态等面向对象特性,既然<font color='red'>这一类的编程语言具备多态特性,那么自然也就具备早期绑定和晚期绑定两种方式。
Java中任何一个普通的方法其实都具备虚函数的特征,它们相当于C++语言中的虚函数(C++ 中需要使用关键字virtual来显式定义)。如果在Java程序中不希望某个方法拥有虚函数的特征时,则可以使用关键字final来标记这个方法。
非虚方法:
多态的使用前提:
虚拟机中提供了一下几条方法调用指令:
前四条指令固话在虚拟机内部,方法的调用执行不可认为干预,而invokedynamic指令则支持由用户确定方法版本。其中invokestatic指令和invokespecial指令调用的方法称为非虚方法,其中的(final修饰的除外)称为虚方法。
动态类型语言和静态类型语言两者的区别就在于对类型的检查是在编译期还是在运行期,满足前者就是静态类型语言,反之就是动态类型语言。
说的直白一点就是,静态类型语言是判断自身的类型信息;动态类型语言是判断变量的类型信息,变量没有类型信息,变量值才有类型信息,这是动态语言的一个重要特征。
IllegalAccessError介绍:
程序视图访问或修改一个属性或调用一个方法,这属性或方法,你没有权限访问。一般的,这个会引起编译异常。这个错误如果发生在运行时,就说明一个类发生了不兼容的改变。
举例1
在调用Son的toString()方法的时候,如Son类和Father类都没有重新,会直接调用Object类的toString方法。
举例2
对于CockerSpaniel的虚方法表来说,sayHello()、sayGoodbye()是调用自身,toString()方法是调用Dog类,其他调用Object类
当一个方法开始执行后,只有两种方式可以退出这个方法:
方法执行过程中抛出异常时的异常处理,存储在一个异常处理表,方便在发生异常的时候找到处理异常的代码。
异常表
<span style="background-color:#f8f8f8"><span style="color:#333333"> Exception table:
from to target type
0 6 9 Class java/lang/Exception</span></span>
如果从0~6的字节码位置发生Exception异常,由字节码位置9处理
栈帧中还允许携带与Java虚拟机实现相关的一些附加信息。例如,对程序调试提供支持的信息。