那么对象除了各种实际数据外,就是各种函数方法了(函数方法的内存表示,网上很多博客都描述的语焉不详,甚至错误)。函数方法可以分为两个部分来看:一方面是整体逻辑流程,这个是所有实例对象所共有的,故保存在方法区(而不是某些博客所说的,不是具体实现,所以内存中不存在。代码都压入内存了,你和我说执行逻辑不存在?)。另一方面是数据(属性,变量这种),这个即使是同一个实例对象不同调用时也是不一样的,故运行时保存在栈(具体保存在虚拟机栈,还是本地方法栈,取决于方法是否为本地方法,即native方法。这部分网上说明较多)。
针对第二点,我举个实际例子。
如StudentManager对象中有Student stu = new Student("ming");,那么在内存中是存在两个对象的:StudentManger实例对象,Student实例对象(其传入构造方法的参数为"ming")。而在StudentManager实例对象中有一个Student类型的stu引用变量,其值指向了刚才说的Student实例对象(其传入构造方法的参数为"ming")。那么再深入一些,为什么StudentManager实例对象中的stu引用变量要强调是Student类型的,因为JVM要在堆中为StudentManager实例对象分配明确大小的内存啊,所以JVM要知道实例对象中各个引用变量需要分配的内存大小。那么stu引用变量是如何指向Student实例对象(其传入构造方法的参数为"ming")的?这个问题的答案涉及到句柄的概念,这里简单立即为指针指向即可。
数组是如何确定内存大小的。
那么数组在内存中的表现是怎样的呢?其实和之前的思路还是一样的。引用变量指向实际值。
二维数组的话,第一层数组中保存的是一维数组的引用变量。其实如果学习过C语言,并且学得还行的话,这些概念都很好理解的。
关于对象中的变量与函数方法中的变量区别及缘由:众所周知,Java有对内存与栈内存,两者都有着保存数据的职责。堆的优势可以动态分配内存大小,也正由于动态性,所以速度较慢。而栈由于其特殊的数据结构-栈,所以速度较快。一般而言,对象中的变量的生命周期比对象中函数方法的变量的生命周期更长(至少前者不少于后者)。当然还有一些别的原因,最终对象中的变量保存在堆中,而函数方法的变量放在栈中。
补充一下,Java的内存分配策略分为静态存储,栈式存储,堆式存储。后两者本文都有提到,说一下静态存储。静态存储就是编译时确定每个数据目标在运行时的存储需求,保存在堆内对应对象中。
针对虚拟机栈(本地方法不在此讨论),简单说明一下(因为后面用得到)。
先上个图
虚拟机栈属于JVM中线程私有的部分,即每个线程都有属于自己的虚拟机栈(Stack)。而虚拟机栈是由一个个虚拟机栈帧组成的,虚拟机栈帧(Stack Frame)可以理解为一次方法调用的整体逻辑流程(Java方法执行的内存模型)。而虚拟机栈是由局部变量表(Local Variable Table),操作栈(Operand Stack),动态连接(Dynamic Linking),返回地址(Reture Address)等组成。简单说明一下,局部变量表就是用于保存方法的局部变量(生命周期与方法一致。注意基本数据类型与对象的不同,如果是对象,则该局部变量为一个引用变量,指向堆内存中对应对象),操作栈用于实现各种加减乘除的操作等(如iadd,iload等),动态链接(这个解释比较麻烦,详见《深入理解Java虚拟机》p243),返回地址(用于在退出栈帧时,恢复上层栈帧的执行状态。说白了就是A方法中调用B方法,B方法执行结束后,如何确保回到A方法调用B方法的位置与状态,毕竟一个线程就一个虚拟机栈)。
到了这一步,就满足了接下来学习的基本要求了。如果希望有更为深入的理解,可以坐等我之后有关JVM的博客,或者查看我的相关笔记,或者查询相关资料(如百度,《深入理解Java虚拟机》等。
Java对象头的组成(不同状态下的不同组成)说了这么多,JVM是如何支持Java锁呢?