首先,内存模型图,如下:
其次,一句话概括各个区域的作用:
1:程序计数器(Program Counter Register),让虚拟机中的字节码解释器通过改变计数器的值来获取下一条代码指令,比如分支、循环、跳转、异常处理、线程恢复等;
2:Java 虚拟机栈(Java Virtual Machine Stacks),栈顶存放当前方法,里面有局部变量表,
3:本地方法栈(Native Method Stacks),本地方法栈则,是为虚拟机使用到的Native 方法服务,作用同虚拟机栈。
4:Java 堆(Java Heap)是Java 虚拟机所管理的内存中最大的一块,是被所有线程共享的一块内存区域。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。
5:方法区(Method Area)与Java 堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
一:创建一个引用类型对象的内存图
Object obj = new Object();
假设这句代码出现在方法体中,那:
1:首先包含这个方法体的类首先被加载到方法区中;
2:其次方法体本身被压栈进虚拟机栈;
3:“Object obj”这部分的语义将会反映到虚拟机栈的本地变量表中,作为一个reference 类型数据出现。
4:而“new Object()”这部分的语义将会反映到Java 堆中,形成一块存储了Object 类型所有实例数据值(Instance Data,对象中各个实例字段的数据)的结构化内存,这块内存的地址存储在虚拟机栈。另外,在Java 堆中还必须包含能查找到此对象类型数据(如对象类型、父类、实现的接口、方法等)的地址信息,这些类型数据则存储在方法区中。
以上过程,用一个更具体的例子,就是创建数组,其内存图如下:
二:创建二位数组的内存图
如下,为了简单起见,省略了方法区的描述:
三:方法调用内存图
假设是这样一段代码,
public class MethodInvoker {
public static void main(String[] args) {
int re = add(1,2);
System.out.println(re);
}
private static int add(int i, int j) {
return i + j;
}
}
其内存图是如下的,
准备动作是类和方法的信息加载到方法区,接下来,
1:main方法压栈;
2:方法执行过程中add方法压栈,然后方法执行,返回3;
3:add方法被弹栈,main方法打印3;
4:main方法弹栈;
三:自定义引用类型对象创建内存图
第一步:
第二步:
附件:关于虚拟机栈访问堆中的数据,有两种方式,如下:
由于reference 类型在Java 虚拟机规范里面只规定了一个指向对象的引用,并没有定义这个引用应该通过哪种方式去定位,以及访问到Java 堆中的对象的具体位置,因此不同虚拟机实现的对象访问方式会有所不同,主流的访问方式有两种:使用句柄和直接指针。
如果使用句柄访问方式,Java 堆中将会划分出一块内存来作为句柄池,reference中存储的就是对象的句柄地址,而句柄中包含了对象实例数据和类型数据各自的具体地址信息,如下图所示。
如果使用直接指针访问方式,Java 堆对象的布局中就必须考虑如何放置访问类型数据的相关信息,reference 中直接存储的就是对象地址,如下图所示