先上图:
本文根据的类:
public class Add { public int add(){ int a = 1; int b = 2; int c = (a + b) * 100; return c; } public static void main(String[] args) { Add a = new Add(); int result = a.add(); System.out.println(result); } }
一、虚拟机栈
方法按照栈的LIFO原则压入,每一个方法会产生一个栈帧,每一个栈帧都有四个部分:局部变量表、操作数栈、方法出口和动态链接。
局部变量表,存储方法内的局部变量,数据的赋值等操作通过操作数栈来执行。当add方法执行完成之后,会去找mian方法的局部变量表,找到其中的result变量。
二、本地方法栈
调用本地C或者C++方法,也就是java中的native方法。为虚拟机使用的native方法服务,执行每个本地方法的时候,都会创建一个栈帧用于存储局部变量表、操作数栈、方法出口和动态链接等信息。
三、程序计数器
指向当前线程所执行的字节码指令的行号。什么是字节码指定的行号?如图是一个main方法通过反编译后的.class文件(反编译操作:打开类的Add.class文件,在该目录下执行反汇编命令并将文件写进一个txt文件中:javap -c Add.class >Add.txt)
四、方法区
存放类信息(比如Add.class类)、常量、静态变量、即时编译器编译后的代码等数据。
多个堆中的对象只指向一个Add.class类,
五、堆
存放对象(比如new的对象)和运行时常量池。
# 在main方法中,有一行是这样的------------> Add a = new Add(),new Add()是在堆中创建一个对象,通过main方法栈帧中的局部变量表中的a,指向堆中的对象Add。
# 我们的物理内存会为我们的JVM分配内存,当这个内存达到一个垃圾回收的临界点的时候就会进行垃圾回收GC。
当我们的Eden(伊甸园)区满的时候,就会触发minor gc,将无用的对象(gc roots)清理掉,而还在使用的对象会进入到from(S0)中,并且分代年龄+1(age=1,每一次触发minor gc,分代年龄都会+1);而当我们的from区满的时候,会将from中的所有对象移到to区,并且to区变成from区,而原来的from区变成to区(为什么?后面会给出答案);当分代年龄达到一次次数时(默认是15),对象就会进入到老年区(担保机制);最后当我们的老年代满的时候,就会触发full gc,java应用程序的其他所有线程都被挂起,除了垃圾收集帮助器之外(也就是常说的Stop-The-World机制,简称STW)。
# 为什么需要两个Survivor区?并且Survivor from区跟Survivor to区在minor gc的时候会互相转换?
当我们Eden满的时候,会将需要的对象存入from区,此时的from区里面的对象可能有空闲的内存,就好比一个衣服没叠就放在行李箱,浪费了很多空间。而每一次进行minor gc的时候,from区就会将对象存入to区,这个时候的内存是按顺序存放的,就好比把没叠衣服的行李箱里面的衣服一个一个拿出来叠好放在另一个行李箱。这个时候的to区就会变成from区,原来的from区就会变成to区。
你会问为什么要这样?
因为full gc会消耗很多时间,而增加了两个Survivor区有利于减少full gc的次数,提供性能。