【Java】数组的内存图

一、Java的内存分配

每款软件在运行的时候,都是需要占用一块内存区域的。Java也不例外,在运行的时候,虚拟机也会占用一块内存空间。

image-20240403170220115

只不过为了更好的利用这块空间,虚拟机把它分成了五个部分,每个部分都有其各自的作用。

image-20240403171143942

其中有一块空间需要单独说,那就是方法区。

在JDK7以前,方法区跟堆空间它们两个是连在一起的,在真实的物理内存当中也是一块连续的空间,但是这种设计方式并不是很好。

image-20240403170838621

到了JDK8的时候,就改进了这种设计,取消了方法区,新增了一块元空间的区域,把它跟堆空间分开了,把原来方法区要做的很多的事情都进行拆分,有的功能放到了堆当中,有的功能放到了堆中,有的功能放到了元空间中,而现在加载字节码文件的功能在JDK8以后就归属于元空间了。

image-20240403170257736

但是它具体叫什么名字不重要,重要的是它加载完后,代码该如何运行。为了方便大家理解,我们暂时将这块区域仍叫做方法区。

我们要知道程序在内存当中怎么运行的,首先我们就需要知道这五块内存空间它各自的作用。

  • 栈:方法运行时使用的内存,比如main方法运行,进入方法栈中执行

  • 堆:存储对象或者数组,new来创建的,都存储在堆内存。

    new出来的东西都是在堆当中,由于数组也是new出来的,所以数组就是存储在堆当中的。

  • 方法区:存储可以运行的class文件

    当我们一个类,要开始运行的时候,它都会会把这个类的字节码文件(也就是那个class文件)加载到方法区中,临时存储。

  • 本地方法栈:JVM在使用操作系统功能的时候使用,和我们开发无关

  • 寄存器:给CPU使用,和我们开发无关


二、栈

栈:方法运行时使用的内存,其中程序的主入口main方法在开始执行的时候就会进到栈里,当main方法中的代码执行完毕后,main方法就会从栈中出去。


三、堆

堆内存只要记住一句话就行了:只要看见 new 关键字,就是在堆里开辟了一个空间。

在堆中开辟空间它会有地址值,它表示在内存当中的位置。

image-20240403173142262

而每一块小空间它的位置都是不一样的。

image-20240403173212986

四、最简单的代码内存

先来看一个最简单的代码,我们现在只需要关注栈和堆,但由于这块代码没有用到new关键字,所以只需要关注栈空间即可。

程序在最开始运行的时候,程序的主入口main方法就会进入到栈里。

image-20240403173550688

然后从第一行开始逐行往下来执行里面的代码。

image-20240403174515715

首先执行到 int a = 10;,此时在这里定义了一个变量,它的名字就叫 a ,然后给这个变量做了一个类型的限定,这块空间以后只能存int类型的整数。

image-20240403174529138

然后再把10放到这块小空间里。

image-20240403174620639

此时需要定义第二个变量 b,其实就是将刚刚的动作重复了一下。再来开辟一个空间,给它起个名字叫做 b。给它做一个类型的限定:int。

image-20240403174655083

然后再把10放入到这块空间里

image-20240403174708796

再往下,执行到第三行代码,第三行是一个变量c,也做一个类型的限定:int。

但是它里面的值是 a + b,所以它会先把变量 a 和 变量 b 里的值拿出来进行相加,得到一个 20,然后再把20赋值给变量 c

最后就是打印变量 c 中的值。它就是先找到变量c中记录的值,然后再把20打印在控制台当中。

image-20240403174739299

五、数组中的内存

image-20240403211104723

由于这次有new关键字,所以我们即需要考虑栈,还需要考虑堆。

image-20240403205757176

首先程序开始运行,main方法需要加载到栈中。

image-20240403205816394

然后开始执行第一行代码:定义一个数组。但这行代码其实是由左右这两部分来组成的。

image-20240403205841810

所以我们先来看等号左边。等号的左边就是在栈中定义了这样的一个变量,变量的名字叫做arr,类型限定 int[]。那就表示,当前的arr可以记录int类型数组的地址值。

image-20240403205922804

然后再次执行到等号的右边。

image-20240403205937107

等号的右边因为有new关键字,所以它会在堆中开辟一块小空间。因为长度为2,所以它会有0和1两个索引。位置上所对应的元素就是0,因为数组是int类型的,它里面的默认初始化值就是0。

image-20240403210009516

由于数组中存储的数据有很多很多,没办法将所有数据都存在arr变量中,所以Java在设计的时候就会把所有的数据放在另外一块空间里,而arr记录的就是另外一块空间的地址值。

在堆里面的空间它是有地址值的,它会通过中间的等号运算符,将这个小空间的地址赋值给左边的变量arr。

image-20240403210031907

arr通过地址值也可以找到右边堆里的小空间。

image-20240403210046526

所以说代码往下,打印arr的时候,它打印的其实就是变量所记录的地址值。

image-20240403210113505

但是这个地址值对我们来讲没有什么用,我们要用到的是数组里的数据。

所以再往下,第三行代码:通过数组名 + 索引的方式进行获取。内存中它是通过arr找到了右边堆中的空间,然后再通过0索引找到了第一个数据。

image-20240403210301546

所以在控制台中打印的就是0。

image-20240403210324730

同理,打印索引1也是一样的,先通过arr找到右边这个空间。

image-20240403210343661

通过1索引找到里面所对应的数据。在控制台中打印的就是数据0。

image-20240403210401203


六、在内存中如何给数组赋值?

其实跟刚刚是一样的。看下面的代码,arr[0] = 11 ,其实是就是将 11 赋值给 arr 的0索引。

在此之前,它也要通过arr找到右边的这块小空间。

image-20240403210719204

然后再把11赋值给0索引,此时0索引原来的元素就可以被覆盖了。

image-20240403210650414

同样的道理,将22赋值给1索引也是把原来的元素给覆盖了。此时数组里面存储的就是新的元素。

image-20240403210743014

再往下,如果现在再来获取数组里的元素。

image-20240403210803023

由于数组中的0索引和1索引都被修改了,所以现在获取的就是修改之后的元素,通过0元素找到的元素是11,控制台打印的就是11。

image-20240403210821813

通过1索引找到的就是22,所以控制台打印的就是22。

image-20240403210839037


七、两个数组的内存图

第二个数组会对第一个数组产生影响吗。

在代码中,又创建了第二个数组,虽然第二个数组里面没有new关键字,但是我们要知道,这个是简化的书写格式,它的完整书写格式里面还是有new关键字的。

image-20240403210852756

因此它同样也会在堆中开辟一个空间。

image-20240403210904035

而左边的arr2记录的就是第二个空间的地址值。此时在堆里就有两块空间了。这两块空间是互相独立的,两者之间是没有任何影响的。

所以在打印arr2的时候,打印的就是arr2里记录的地址值。

image-20240403210927689

再往下,我们在打印arr2的0索引,此时就是通过arr2来找到了右边的第2个空间。

image-20240403210940062

再去打印里面的0索引,此时在控制台里面打印的就是33

image-20240403210953882

同理,我们要打印1索引,同样通过arr2找到右边第2块空间,然后再找到1索引对应的44,此时在控制台打印的就是44。

最后一个打印2索引,同样也是过arr2找到右边第2块空间,然后再找到2索引对应的55。

image-20240403211032444


八、总结

  1. 只要是new出来的一定是在堆里面开辟了一个小空间。并且堆里开辟的空间是有地址值的。
  2. 如果new了多次,那么在堆里面有多个小空间,每个小空间中都有各自的数据。

九、两个数组指向同一个空间的内存图

在代码中定义了一个数组,在第二行代码中,并没有创建,而是把arr1赋值给了arr2,然后再在下面一顿操作。

首先main方法需要先进栈。

image-20240403213221291

然后再来往下执行第一行代码

image-20240403213233963

第一行代码就是定义一个数组,所以在栈里面就会有一个arr1。

image-20240403213243930

由于等号右边的完整格式中有new关键字,所以就在堆里面开辟了一段空间,里面要存储11 和 22。

image-20240403213301479

然后再把这块空间的地址值 0x0011 赋值给arr1,此时arr1就可以通过这个地址找到右边的空间。

image-20240403213321697

再来看这里面的第2行代码,等号的左边还是在栈里定义了一个arr2。但是你要注意,在等号的右边它是没有new关键字的,在等号的右边是arr1。它表示将arr1所记录的内容赋值给了arr2。

image-20240403213355101

来看中间这块内存,现在arr1里记录的是0x0011地址值,所以它就会把这个地址值赋值给arr2。

现在就形成了arr1和arr2都指向了同一块空间。

image-20240403213418211

接下来打印arr1[0],首先通过arr1找到 0x0011,然后找到里面的0索引,然后找到元素11。所以在控制台中打印的就是11。

image-20240403213432335

然后再来执行下面的代码 sout(arr2[0]) ,要注意,现在arr2记录的也是 0x0011,所以找的同样的也是右边的空间。找到0索引同样也是11。

image-20240403213448765

所以在控制台中这两个语句在控制台中打印的都是11。

image-20240403213501200

再往下,arr[0] = 33,相当于把33赋值给了arr2的0索引,arr2现在记录的是 0x0011,所以它找的就是右边这块空间的0索引里面的元素变成了33。

image-20240403213515729

再往下,修改完后,再通过 arr1arr2 再去访问0索引,此时打印的值应该打印的是一样的。都是33。

image-20240403213540874

结论:当两个数组指向同一个小空间时,其中一个数组对小空间中的值发生了改变,那么其他数组再次访问的时候都是修改之后的结果了。

  • 17
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值