Java -3 程序运行时的内存分析
Java 程序运行时的内存分析
从某种层面上讲,程序的执行过程就是内存中数据的变化过程,对Java程序运行时内存中数据变化过程的分析,有助于更清晰的理解Java程序的执行过程。
当要解释执行一个Java程序时,Java虚拟机首先要把硬盘中相应的Java类文件(*.class文件),通过类装载器装载到内存中,然后再从Java程序的入口方法main()开始执行。
JVM会把它管理的内存分为几个区域。
方法区Method Area:存放类的信息(类型信息)
- 当Java虚拟机装载一个类时,会把这个类的信息保存到方法区。
- 类型信息主要包括:类名、父类/接口列表、类修饰符、成员变量信息、成员方法信息、静态变量、常量池。
- 成员变量信息包含:变量的修饰符、变量类型、变量名称、变量初始化等。
- 成员方法信息包含:方法的修饰符、返回值、方法名、参数列表、方法字节码(方法体中的代码)等。
栈Stack:存放局部变量。
- 调用一个方法时,会在栈中为该方法分配一块内存,成为方法的“栈帧”,用以存放这个方法中定义的局部变量,当方法调用结束后,会收回这块内存。
堆Heap:存放对象的数据。
- 一个对象的数据包含:对象的属性和类型信息引用。
- 类型信息引用就是一个保存类信息内存地址的引用型变量,指向方法区中类型信息的指针。
- 对象都是再程序执行的过程中动态创建的,需要使用“new”关键字,动态申请堆中的内存,以存放新建对象的数据。
- 当一个对象不被引用时,这个对象所占用的内存会被JVM中的垃圾回收器GC(Carbage Collection)进行回收。
案例分析:
1、Java虚拟机从硬盘中读取Person.class类文件,通过类装载器把Person类装载到内存中,在方法区Method Area中就存放了Person类型信息。
2、在方法区中找到Person类型信息中的main()方法,开始执行main()方法的代码。在栈Stack中会为main方法分配一块内存:“main()方法栈帧”。
3、执行第一行代码 “int i=20” ,定义一个整型变量i ,并给 i 赋值 。因为变量i 是在main() 方法中定义的局部变量,所以变量i 存放在栈 Stack 的 “main()方法栈桢”中。
4、执行第二行代码 “Person p1=new Person(i) ”,是在内存中创建了一个新的Person对象,并调用构造方法Person(int)初始化了这个对象的属性,定义了一个引用类型的变量“p1” ,并在p1内存放了新建Person对象在内存中的地址。
4.1、首先执行的是“=” 右边的内容“new Person(i)”,通过关键字“new ”在堆中申请了一块内存,存放新建Person 对象的数据,包含 :对象的属性和类型信息引用。对象属性age 的值等于Person类型信息中保存的成员变量信息:age的初始值。图中的Person类型信息引用的值用“X”表示一个具体的内存地址。
4.2、然后通过代码 “Person(i)”调用Person类的构造方法“Person(int)”,以对新建Person对象初始化。 这时代码的执行流会跳转到Person(int) 方法中,所以会在栈Stack中创建Person(int) 的方法栈帧。
有参方法的调用有一个值传递的过程,在这个例子中,会把main() 方法中的“局部变量i 的值” 传递(赋值)给Person(int) 方法中的形参a, 所以变量a 中存放的值会等于变量i 中存放的值20。
一个方法的形参是这个方法中定义的局部变量,局部变量是存放在方法栈帧中,所以变量a 会存放在栈Stack的“Person(int)方法栈帧"中。
4.3、执行构造方法“Preson(int) ”中的代码“age=a”,讲新建Person对象中的age属性值改为形参变量a 的值。构造方法 “Preson(int)”中的代码执行完之后,会撤销Stack中的“Person(int)方法栈帧”。
4.4、表达式“new Person(int)”的值是新建Person 对象在内存中的值地址,这个值会通过 “=‘赋值’ Person p1 ”,“p1”在main()方法中定义的Person引用类型的局部变量,会存放在main()方法栈帧中。
5、“Person p2=p1” ,是在main() 方法中新建一个Person 引用类型的局部变量p2 ,并且讲p1 的值赋值给p2。堆Heap中依然只有一个Person对象。
6、“Person p3=new Person(30)” 执行过程中会新建一个Person 对象,引用类型变量p3 中存储的是这个新建Person对象的内存地址值,和变量p1、p2 的值是不同的。