"深入Java虚拟机:JVM中的Stack和Heap"
Java虚拟机(JVM)是一种运行Java字节码的虚拟机环境,它具有自己的内存管理机制。JVM的内存分为两个部分:Stack(栈)和Heap(堆)。
Stack(栈)是JVM的内存指令区,管理很简单,push一定长度字节的数据或者指令,Stack指针压栈相应的字节位移;pop一定字节长度数据或者指令,Stack指针弹栈。Stack的速度很快,管理很简单,并且每次操作的数据或者指令字节长度是已知的。Java基本数据类型、Java指令代码、常量都保存在Stack中。
Heap(堆)是JVM的内存数据区。Heap的管理很复杂,每次分配不定长的内存空间,专门用来保存对象的实例。在Heap中分配一定的内存来保存对象实例,实际上只是保存对象实例的属性值,属性的类型和对象本身的类型标记等,不保存对象的方法(方法是指令,保存在Stack中)。在Heap中分配一定的内存保存对象实例和对象的序列化比较类似。
在JVM中,另有一个GC进程,定期扫描Heap,它根据Stack中保存的4字节对象地址扫描Heap,定位Heap中的对象,进行一些优化(例如合并空闲内存块什么的),并且假设Heap中的没有扫描到的区域都是空闲的,统统refresh(实际上是把Stack中丢失了对象地址的无用对象清除了),这就是垃圾收集的过程。
在JVM中,我们需要搞清楚的是什么是数据以及什么是指令。然后要搞清楚对象的方法和对象的属性分别保存在哪里。方法本身是指令的操作码部分,保存在Stack中;方法内部变量作为指令的操作数部分,跟在指令的操作码之后,保存在Stack中(实际上是简单类型保存在Stack中,对象类型在Stack中保存地址,在Heap中保存值)。对象实例包括其属性值作为数据,保存在数据区Heap中。非静态的对象属性作为对象实例的一部分保存在Heap中,而对象实例通过Stack中保存的地址指针才能访问到。
因此,能否访问到对象实例以及它的非静态属性值完全取决于能否获得对象实例在Stack中的地址指针。非静态方法和静态方法的区别:非静态方法有一个和静态方法很大的不同:非静态方法有一个隐含的传入参数,该参数是JVM给它的,并且和我们怎么写代码无关,这个隐含的参数就是对象实例在Stack中的地址指针。因此非静态方法(在Stack中的指令代码)总是可以找到自己的专用数据(在Heap中的对象属性值)。当然非静态方法也获得该隐含参数,因此非静态方法在调用前,先new一个对象实例,获得Stack中的地址指针,否则JVM将无法将隐含参数传给非静态方法。
静态方法无此隐含参数,因此也不需要new对象,只要class文件被ClassLoader load进入JVM的Stack,该静态方法即可被调用。当然此时静态方法是存取不到Heap中的对象属性的。
总结一下该过程:当一个class文件被ClassLoader load进入JVM后,方法指令保存在Stack中,此时Heap区没有数据。然后程序技术器开始执行指令,如果是静态方法,直接依次执行指令代码,当然此时指令代码是不能访问Heap数据区的;如果是非静态方法,隐含参数没有值,会报错。因此在非静态方法执行前,要先new对象,在Heap中分配数据,并把Stack中的地址指针交给非静态方法,这样程序技术器依次执行指令,而指令代码此时能够访问到Heap数据区了。
静态属性和动态属性:前面提到对象实例以及动态属性都是保存在Heap中的,而Heap通过Stack中的地址指针才能够被指令(类的方法)访问到。因此可以推断出:静态属性是保存在Stack中的,而不同于动态属性保存在Heap中。正是因为静态属性被保存在Stack中,具有了全局属性。在JVM中,静态属性保存在Stack指令内存区,动态属性保存在Heap数据内存区。