JVM内存模型以及数组的内存分布
JVM内存模型
JVM内存区域分为5块,分别是:JVM栈 本地方法栈 堆 方法区 程序计数器
1.JVM栈(stack)
JVM栈用于描述方法调用
其原理是,在方法调用时,将包含方法信息(局部变量/指令/返回值)的 “栈帧” 打入栈中,方法结束时再将 “栈帧” 推出栈。每个方法都有一个独立的 “栈帧”。利用栈 “先进后出” 的特点可以控制方法的执行顺序,被调用的方法先于调用的方法,尤其是 mian 方法,它是第一个方法最先入栈,所以它在栈底,并且最后出栈。
同时这里也牵扯到了局部变量的生命周期,局部变量是方法体中定义的变量,那么局部变量也存储在JVM栈中的**方法“栈帧”**中,方法调用或者方法栈帧入栈时局部变量生效,方法结束或方法栈帧出栈时局部变量失效。
2.本地方法栈
本地方法栈的在作用上和JVM栈是一样的,但是对使用的对象不同。
本地方法是指在Java中被关键字native
修饰,由Java程序调用的C/C++语言的功能实现,即非Java代码实现的方法,同时也不具有方法体。
本地方法的目的是可以对操作系统或底层进行操作,这是C/C++的特点之一,而Java对底层不敏感也不可控。
3.堆内存(heap)
堆内存是JVM最主要的内存模型,占用最多的空间,主要为new关键字创建的对象开辟空间,这是因为Java是面向对象语言。特别的,栈中的引用变量就指向堆内存中的地址。
4.方法区
方法区同样和面向对象有关
5.程序计数器
JVM的二进制字节码文件是 ‘逐行’ 解释执行的,程序计数器用于指示逐行的过程。
这五个内存模型中JVM栈和堆内存最为重要,JVM栈决定程序执行的顺序,堆内存决定了程序的存储。
以下给出 方法 - 栈 的内存模型例子
public static void main(String[] args) {
int a = 1;
test();
test3();
}
public static void test() {
int a = 10;
test2();
}
public static void test2() {
int a = 100;
}
public static void test3() {
int a = 1000;
}
对应的内存情况如下,执行test()语句时:
执行test3()语句时
方法test3()的栈帧需要等到方法test()结束时才入栈,即test()出栈后执行
也可以使用IDEA的DEBUG模式可以查看程序执行时当前的栈,如下勾选frames
(栈帧):
可以看到 main()
test()
test2()
的栈帧
以及 test3()
的栈帧
栈和堆内容上的区别
1.存储类型:栈存储局部变量,包含普通变量和引用变量,引用变量指向堆上的对象。堆存储对象。
2.默认值:栈中存储的是局部变量,而局部变量是没有默认值的。堆中按类型有不同的默认值,如下:
byte:0
short:0
int:0
long:0L
float:0.0F
double:0.0
char:\u0000
boolean:false
引用数据类型类型:null 表示引用没有指向任何对象,无法对对象进行操作,不可用
3.生命周期:
栈上的局部变量生命周期与方法“同生共死”
堆上对象的生命周期从对象的创建到对象的销毁,对象的创建,目前使用new关键字创建,对象的销毁,当方法结束,引用销毁,对象没有被任何引用指向时,对象成为“垃圾(对象)”,由Java全自动回收对象开辟的空间,这个过程称为GC(garbage collection)。
GC是面向对象编程语言对于解决内存泄漏的一种选择,不同于C++的手动回收如free。GC的优点有全自动,易于程序员使用,降低编程门槛,缺点有不确定性和不可控性,程序员最多只能通过System.gc()
通知系统需要进行垃圾回收。
数组的内存模型
动态初始化
首先数组是引用数据类型,那么在栈中存储数组的引用,在堆中存储数组对象的内容。
如果直接打印引用,Java会打印出数组对象在堆中的地址,如下:
public void testArray4() {
int[] arr = new int[3];
System.out.println(arr);
}
/*
结果:[I@256216b3
[ 表示这是一维数组,[[ 则是二维数组
I 表示这是int类型
@ 表示后面的十六进制数为地址
*/
在JVM内存模型中表示为:
静态初始化
同动态初始化差不多,不过元素取值不是默认值
注意:虽然静态初始化元素取值最终不是默认值,但是静态初始化时,元素取值最开始一定是默认值,这与对象的构造函数有关。不过是静态还是动态初始化,都一定有赋值默认值的过程。
两个引用指向同一个对象
int[] arr = new int[3];
int[] arr2 = arr; //如果数据类型不同,则不能赋值
System.out.println(arr == arr2); //结果为true
定有赋值默认值的过程。
两个引用指向同一个对象
int[] arr = new int[3];
int[] arr2 = arr; //如果数据类型不同,则不能赋值
System.out.println(arr == arr2); //结果为true
在JVM内存中,两个引用指向同一个数组,其中有关引用改变数组,则另一个引用使用时也会发生相应的改变。