1.前言
这是最近面试腾讯日常实习的一个面试题,自己答得不是很好,故重新分析下。
题目:java的内存模型有了解过吗?说说new 一个对象,并调用方法返回一个值,这之中jvm做了什么,栈桢变化是怎么样的?
知识点主要是jvm的内存模型、对象加载过程、虚拟机执行引擎,自己虽然也啃过《JVM高级特性与最佳实践》这本书,内存模型和对象加载过程答得还行,栈桢变化的细节没答好,故本篇侧重于栈桢变化的分析,其他两个问题直接给出答案。
1.1 java内存模型
java内存模型分为线程公有和线程私有,线程公有包括gc堆和方法区,线程私有包括操作计数器、虚拟机栈、本地方法栈,如下图。
1.2 new一个对象,jvm都做了什么
其中,类加载包括加载、验证、准备、解析、初始化,不是本篇的重点,这里不再展开,有兴趣的同学可以看看《JVM高级特性与最佳实践》第九章。
2.分析
接下来主要分析new一个对象并调用方法,栈桢变化。
简单分析代码如下,声明成员变量主要分析init
方法赋值顺序:
public class StackObserver {
static class A {
int a1 = 1;
int a2 = 2;
int a3;
int a4;
public A() {
a3 = 3;
}
{
a4 = 4;
}
int b(){
return 1;
}
}
public static void main(String[] args) {
new A().b();
}
}
通过IDE debug简单分析下栈桢变化,new的时候init
方法压入栈桢,执行b方法时init
出栈,b()
压入栈桢。如下图:
init方法
init
方法是对象初始化的过程,包括成员变量赋值、对象代码块即{}
、构造方法拼接起来的初始化方法,拼接过程是由jvm帮我们做的。对应还有一个class init
方法,是在类加载阶段执行,包括类变量的赋值、类静态代码块的执行。成员变量和全局变量是如何传递进方法中的?
这个问题之前学习jvm的时候真的没有仔细思考过,果然细节决定成败。
首先我们先思考下实例方法中,this
变量是怎样传递进方法的作用域中的。修改下代码,如下:
public class StackObserver {
static int GLOBAL = 4;
static class A {
...
int b(){
return a1;
}
int c() {
// 返回全局变量
return GLOBAL;
}
}
public static void main(String[] args) {
// = new A().b();
A a = new A();
a.b();
new A().c();
}
}
this
变量的传递我们需要先了解下局部变量表,它属于栈桢的一部分,用于传递变量,存储单位为Slot
、32位,存储类型包括java基本数据类型,即int float boolean char之类的,用1Slot存储,但double long用2Slot存储,reference即对象引用,用1Slot存储。
在每个方法执行时,方法栈桢都会附带一个局部变量表,大小为jvm自动计算,其中,Slot是可以复用的(减少不必要的内存占用)。
回到原问题,我们可以猜测,成员变量的传递是通过this
引用传递的,类变量是通过类引用变量传递的。通过查阅资料可知,在a调用b方法时,会将自身引用——this
作为第0位Slot传递给b方法栈桢的局部变量表。
那么我们验证下猜测,通过javac编译源代码得到class文件。
// 以下为编译的class文件,而非java文件
public class StackObserver {
static int GLOBAL = 4;
public StackObserver() {
}
public static void main(String[] args) {
StackObserver.A a = new StackObserver.A();
a.b();
(new StackObserver.A()).c();
}
static class A {
....
int b() {
return this.a1; // 猜测正确
}
int c() {
return StackObserver.GLOBAL; // 猜测正确
}
}
}
- return语句执行时,栈桢的变化
将返回值压入上层栈桢,这里的情景下上层栈桢是main(),如果有声明局部变量赋值的话,返回值会被记录在局部变量表中;如没有,会被舍弃掉。引用《JVM高级特性与最佳实践》第八章的一段话。
方法退出
方法退出的过程实际上就等同于把当前栈帧出栈,因此退出时可 能执行的操作有:恢复上层方法的局部变量表和操作数栈,把返回值 (如果有的话)压入调用者栈帧的操作数栈中,调整PC计数器的值以 指向方法调用指令后面的一条指令等。
3.总结
以上就是我个人对该问题的全部分析和总结啦,如果还没了解过jvm的,强烈建议阅读《JVM高级特性与最佳实践》
这本书,电子版pdf可以私我,还是希望大家支持正版。
面试的时候我们也不必把所有细节都一一列举出来,如上题,只要把涉及的知识点java内存布局、对象加载过程、虚拟机执行引擎(栈桢部分)简单说下,然后选一个方向展开(这里可以是面试官的二次提问,也可以的个人延伸),我在面试时由于对栈桢这部分细节掌握不好,就被问傻了。。。
最后,分析问题时一定要自己好好思考。结论不是最重要的,思考的过程才是