栈:
基本数据类型直接在栈中分配空间,局部变量(在方法代码段中定义的变量)也在栈中直接分配空间,当局部变量所在方法执行完成之后该空间便立刻被JVM回收
引用数据类型,用关键字new创建出来的对象所对应的引用也是在栈空间中,此时,JVM在栈空间中给对象引用分配了一个地址空间(相当于一个门牌号,通过这个门牌号就可以找到你在堆中的家),在堆空间中给该引用的对象分配一个空间,栈空间中的地址引用指向了堆空间中的对象区(通过门牌号找住址)
局部变量,无默认值
堆:
一般用来存放用关键字new出来的数据
成员变量,有默认值
通过一段程序来说明运行时JVM的内存情况(仅看部分代码即可):
package demo;
public class Student {
private String name;
private int age;
public void study() {
System.out.println("I love study!");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
package demo;
public class StudentDemo {
public static void main(String[] args) {
Student student = new Student();
System.out.println(student.getName() + ":" + student.getAge());
student.setName("John");
student.setAge(23);
System.out.println(student.getName() + ":" + student.getAge());
student.study();
Student student2 = student;
student2.setName("Jack");
student2.setAge(25);
System.out.println(student2.getName() + ":" + student2.getAge());
System.out.println(student.getName() + ":" + student.getAge());
}
}
当我们运行程序时,JVM会把Student类与StudentDemo类编译完然后加载到JVM中一个叫方法区的地方,类的成员变量与成员方法也被加载到方法区中,此时内存模型如下:
可以看到study方法右边各有一个16进制的标记,而name与age变量没有,这是因为每个对象都有各自的成员变量,而类中的成员方法却可以被每个对象所共用,为了节省内存空间,JVM为方法分配了该标记(也叫内存地址)便于每个new出来的对象查找调用,接着JVM会自动寻找main方法,在栈中为main方法申请一个空间,这个过程也叫入栈,然后执行我们Student类中第5行代码
这时候,JVM在堆空间中分配一块内存给Student对象,并为其分配一个内存地址,并把对象的成员加到堆中,如果对象的成员变量没有赋值,则JVM会为变量赋初始值,在栈中分配一块内存空间用于指向堆空间中的Student对象区的内存地址。
【即:Student s = new Student();的过程:
1)Student.class加载进内存(方法区);
2)声明一个Student类型引用s ;
3)在堆内存创建对象;
4)给对象中属性默认初始化值;
5)属性进行显示初始化;
6)构造方法进栈,对对象中的属性赋值,构造方法弹栈;
7)将对象的地址值赋值给s。】
此时内存模型如下
接着看代码第8行与第9行
程序为student对象的成员变量赋值,JVM会根据student所指向的地址在堆内存中寻找Student类的变量,并为变量赋新的值
第12行
这时student对象调用study方法,JVM在栈空间中为study方法申请了一块内存空间
study方法执行完后,立即释放栈空间,代码第14行,
student2指向1x0003(即新的一块地址),图仅供参考:
到这,main方法中所有代码执行完毕,main方法所占用的栈空间也被回收,而堆空间等待 GC 回收。