0%

方法调用的指令说明

以一个简单地继承来说明一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
public interface Breathable {

void breath();
}

public class Parent implements Breathable{
public int field = 0;

public int getField(){
return field;
}

private void testPrivate(){
System.out.println("父类私有方法");
}

public Parent(int field){
this.field = field;
}

public static void testStatic(){

}

public final void testFinal(){

}

public void amountField(){
field+=5;
}

public Parent(){

}

@Override
public void breath() {
System.out.println("人当然会呼吸啦");
}
}

public class Child extends Parent {
public int field = 1;


@Override
public int getField(){
return field;
}

public void test(){

}

@Override
public void amountField(){
field+=5;
}

public static void main(String[] args) {
Parent p = new Child();
// 重写的实例方法 invokevirtual
p.amountField();
// 调用静态方法 invokestatic
p.testStatic();
// 调用父类的final方法 invokevirtual
p.testFinal();

Breathable breathable = new Child();
// 实现的接口方法 invokeinterface
breathable.breath();
//invokevirtual
System.out.println(p.getField());
System.out.println(p.field);

}
}
阅读全文 »

栈顶缓存技术

指令集架构

常用的指令集架构有基于栈的栈式架构和基于寄存器的架构

基于栈的架构

java虚拟机使用的是基于栈式架构的,也就是所有操作数必须先入栈,然后根据指令中的操作码选择从栈顶弹出若干个元素进行计算后再将结果压入栈中。

而栈式架构使用的是零地址指令,指令更加紧凑,但是完成一项操作需要更多的入栈出栈指令,即需要更多的指令分派次数和内存读写次数,如进行一个加法运算,如果两个操作数都在本地变量中,那么一个加法操作就要有5次栈操作,分别是将两个操作数从本地变量入栈(2次入栈),再将两个操作数出栈用于加法操作(两次出栈),再将加法结果压入栈顶(1次入栈),而如果是基于寄存器的话,一般只需要将两个操作数存入寄存器进行加法运算后再将结果存入其中一个寄存器即可

最大的优点是可移植

缺点是执行速度会稍慢一点

基于寄存器的架构

基于寄存器是依赖寄存器进行工作的,但是寄存器由硬件直接提供,程序直接依赖这些硬件寄存器则需要受到硬件的约束

为什么要基于栈

  • JVM要设计为与平台无关的,需要保证在没有或者有很少的寄存器的机器上也要同样能正确执行java代码
  • 为了指令的紧凑性,减少class文件的大小

解决问题

基于栈的设计会造成频繁的读写,频繁的读写内存肯定会影响执行速度,所以HotSpot JVM中有一个栈顶缓存技术,将栈顶元素全部存储在物理CPU寄存器中,以此降低对内存的读写次数,提升执行引擎的执行效率

虚拟机栈

虚拟机栈(Java Virtual Machine Stack),每个线程在创建时都会创建一个虚拟机栈,内部保存一个个的栈帧(Stack Frame),对应着一次次的方法调用,生命周期与线程一致,也是线程私有的,其描述的是java方法执行的内存模型,每个方法在执行的同时都会创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息,每一个方法从调用到执行完成的过程就对应着一个栈帧在虚拟机中入栈到出栈的过程

虚拟机栈的访问速度是很快的,仅次于程序计数器,JVM对于虚拟机栈的操作只有两个,入栈(方法执行)和出栈(方法结束)

虚拟机栈并不存在垃圾回收

阅读全文 »

程序计数器

程序计数器(Program Counter Register)是用来存储当前线程指向下一条指令的地址,也就是即将要执行的指令代码,字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等功能都需要依赖这个计数器来完成。每个线程都有自己的一个程序计数器,程序计数器的生命周期与线程的一致,在任何时间同一个线程只会执行一个方法,也就是当前方法,程序计数器会存储当前线程正在执行方法的JVM指令地址,如果执行的是native方法,在指令地址是undefined

程序计数器是JVM规范中唯一一个不会出现OOM的区域,且也不存在垃圾回收

阅读全文 »

内存分配

JVM运行时数据区结构

JVM内存结构

在运行时数据区中的各个结构的生命周期是不同的,有些是随着JVM虚拟机的启动而创建,随着JVM虚拟机的退出而销毁;而有一些是随着线程的创建和结束而进行创建和销毁的。

线程间共享的:堆、堆外内存(永久代、元空间、代码缓存区)

每个线程的:程序计数器、栈、本地方法栈

阅读全文 »