程序运行时,内存到底是如何进行分配的?

在 Java 编译成 class 文件的时候,会在方法 Code 属性表中的 max_locals 数据项中确定该方法需要分配的最大局部变量表的容量。

如下面示例:

public static int add(int k){

int i = 1;

int j = 2;

return i + j + k;

}

将上述代码通过 javac 命令编译成 class 文件,再通过 javap -v 命令进行反编译,结果如下:

public static int add(int);

descriptor: (I)I

flags: ACC_PUBLIC, ACC_STATIC

Code:

stack=2, locals=3, args_size=1

0: iconst_1

1: istore_1

2: iconst_2

3: istore_2

4: iload_1

5: iload_2

6: iadd

7: iload_0

8: iadd

9: ireturn

LineNumberTable:

line 8: 0

line 9: 2

line 10: 4

可以看到 locals 中定义的数就是局部变量表的长度为3。符合我们在代码中的定义。

【注意】系统不会为局部变量赋予初始值,不存在类变量那样的准备阶段(实例变量和类变量都会被赋予初始值)

操作数栈

操作数栈(Operand Stack)也常称为操作栈,它是一个后入先出栈(LIFO)。

操作数栈的最大深度也在编译的时候写入方法的 Code 属性表中的 max_stacks 数据项中,栈中的元素可以是任意 Java 数据类型,包括 long 和 double

当一个方法刚开始执行时,这个方法的操作数栈是空的,在方法执行过程中,会有各种字节码指令被压入和弹出操作数栈。例如:iadd 指令就是将操作树栈中栈顶的两个元素弹出执行加法运算,并将结果重新压回到操作数栈中。

动态链接

动态链接 主要目的为了支持方法调用过程中的动态链接。

在一个 class 文件中,一个方法要调用其他方法:需要将这些方法的符号引用转化为其所在内存地址中的直接引用,而符号引用存在于 方法区 中。

Java 虚拟机栈中,每个栈帧都包含一个指向运行时常量池中该栈所属方法的符号引用。

返回地址

当一个方法开始执行后,只有两种方式可以退出这个方法:

正常退出:方法中代码正常完成,或者遇到一个方法返回的字节码指令(如 return )并退出没有抛出任何异常。

异常退出:方法执行过程中遇到异常,并且这个异常在方法体内部没有得到处理,导致方法退出。

不管方法是 正常退出 还是 异常退出,都会返回到调用该方法的位置处。所以,虚拟机栈中的 返回地址 是用来帮助 当前方法恢复它的上层方法执行状态

值得说明的是:

当 正常退出时,调用者的 PC 计数值可以作为返回地址,栈帧中可能保存此计数值。当 异常退出时,返回地址是通过异常处理器表确定的,栈帧中一般不会保存此部分信息。

实例讲解

public class Hello {

public static int add(){

int i = 1;

int j = 2;

int result = i + j;

return result + 10;

}

}

将上述代码使用 javap 命令来查看字节码指令:

public static int add();

descriptor: ()I

flags: ACC_PUBLIC, ACC_STATIC

Code:

stack=2, locals=3, args_size=0

0: iconst_1 // 把常数 1 压入操作数栈栈顶

1: istore_0 // 把操作数栈栈顶的出栈放入局部变量表索引为 1 的位置

2: iconst_2 // 把常量 2 压缩操作数栈栈顶

3: istore_1 // 把操作数栈栈顶的出栈放入局部变量表索引为 2 的位置

4: iload_0 // 把局部变量表索引为 1 的值放入操作数栈栈顶

5: iload_1 // 把局部变量表索引为 2 的值放入操作数栈栈顶

6: iadd // 将操作数栈栈顶的和栈顶下面的一个进行加法运算后放入栈顶

7: istore_2 // 把操作数栈栈顶的出栈放入局部变量表索引为 2 的位置

8: iload_2 // 把局部变量表索引为 2 的值放入操作数栈栈顶

9: bipush 10 // 把常量 10 压入操作数栈栈顶

11: iadd // 将操作数栈栈顶的和栈顶下面的一个进行加法运算后放入栈顶

12: ireturn // 结束

指令详解:

iconst 和 bipush 将常量压入操作数栈顶,区别是:当 int 取值为 -1 ~ 5 采用 iconst 指令,取值 -128 ~ 127 采用 bipush 指令。

istore 将操作数栈顶的元素放入局部变量表的某索引位置。比如 istore_5 代表将操作数栈顶元素放入局部变量表下标为 5 的位置。

iload 将局部变量表中某下标上的值加载到操作数栈顶中。比如 iload_2 代表将局部变量表索引为 2 上的值压入操作数栈顶。

iadd 代表加法运算,具体是将操作数栈最上方的两个元素进行相加操作,然后将结果重新压入栈顶。

上述的下标操作的逻辑是:在 **.java 被编译成 **.class的时候,栈帧中需要多大的局部变量表、多深的操作数栈都已经完全确定了,并且写入到了方法表的 Code 属性中

本地方法栈

本地方法栈和虚拟机栈基本相同,是针对本地(Native)方法,在开发中如果涉及 JNI 可能接触本地方法栈多一些,在有些虚拟机的实现中已经将两个合二为一了(比如:HotSpot)。

堆(Heap)

是 JVM 所管理的内存中最大的一块区域,该区域唯一目的就是存放对象实例。它是 Java 垃圾回收器(GC)管理的主要区域,有时候也叫做“GC堆”。同时是所有线程共享的内存区域,被分配在此区域的对象如果被多个线程访问,需要考虑线程安全问题。

方法区

方法区是 JVM 规范里规定的一块运行时数据区。

方法区主要是存储:

  • 已经被 JVM 加载的类信息(版本、字段、方法、接口)

  • 常量

  • 静态变量

  • 即时编译器编译后的代码

  • 数据

方法区是被各个线程共享的内存区域。

方法区与永久区:

方法区是 JVM 规范中规定的一块区域,但是并不是实际实现,切忌将规范和实现混为一谈,不同的 JVM 厂商可以有不同的版本的“方法区”实现。

例如:HotSpot 在 JDK1.7 以前使用 “永久区”(或者叫Perm区)来实现方法区,在 JDK1.8 后“永久区”就已经被移除了,取而代之的是一个叫做“元空间(metaspace)”的实现方式。

【总结】

方法区:是规范层面的东西,规定了这一个区域要存放哪些数据。

永久区或者metaspace:是对方法区的不同实现,是实现层面的东西。

异常

StackOverflowError 栈溢出异常

递归调用是造成 StackOverflowError 的一个常见场景。

public class StackOver {

private int number;

public static void main(String[] args){

StackOver so = new StackOver();

try {

so.method();

} catch(StackOverflowError e){

System.out.println(“栈容量已经溢出!”);

}

}

public void method(){

number++;

method();

}

}

每调用一次 method 方法,都会在虚拟机栈中创建出一个栈帧,因为是递归调用,method 方法并不会退出,也不会将栈帧销毁。

OutOfMemoryError 内存溢出异常

理论上,虚拟机栈、堆、方法区都有发生 OutOfMemoryError 的可能,但在实际项目中,大多发生在堆中:

public class HeapError {

public static void main(String[] args){

ArrayList list = new ArrayList();

while (true) {

list.add(new HeapError()​
);

}

}

}

最后

小编这些年深知大多数初中级Android工程师,想要提升自己,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助

因此我收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人

都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

资料⬅专栏获取
ayList list = new ArrayList();

while (true) {

list.add(new HeapError()​
);

}

}

}

最后

小编这些年深知大多数初中级Android工程师,想要提升自己,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助

因此我收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。

[外链图片转存中…(img-LnB0yWdJ-1719114354125)]一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人

都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

资料⬅专栏获取

  • 23
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值