一、Java内存分配的重要性
Java内存分配的重要性在于它直接影响着程序的性能、可伸缩性和稳定性。以下是一些关键方面:
- 性能: 内存分配对程序的性能有着直接影响。不合理的内存分配可能导致频繁的垃圾收集(GC)和内存碎片化,从而降低应用程序的性能。适当的内存分配可以减少垃圾收集的频率,提高程序的响应速度和吞吐量。
- 垃圾收集(GC):Java程序通过垃圾收集器来管理内存。频繁的垃圾收集会导致应用程序的停顿时间增加,从而影响用户体验和系统的可用性。优化内存分配可以减少垃圾收集的次数和停顿时间。
- 内存泄露: 不正确的内存管理可能导致内存泄漏,即程序在不再需要使用的内存时无法释放它们。内存泄漏会导致内存消耗过多,最终导致系统崩溃或变慢。正确的内存分配和释放可以避免内存泄漏的发生。
- 可伸缩性:优化的内存分配可以提高应用程序的可伸缩性。通过减少内存占用和提高内存利用率,可以更好地支持大规模并发和高负载情况,从而提高系统的可伸缩性。
- 稳定性:不合理的内存分配可能导致应用程序崩溃或异常,从而影响系统的稳定性。通过合理的内存管理,可以减少由于内存相关的错误而导致的系统崩溃或异常,提高系统的稳定性和可靠性。
因此,Java内存分配的重要性在于它直接影响着程序的性能、可伸缩性和稳定性,是开发高性能、高可用性Java应用程序的关键因素之一。
二、Java内存分配
Java中的jvm会像应用程序一样占据一块内存空间
为了更好的利用这块空间,虚拟机把它分成了五个部分,每个部分都有自己的作用
分别是:栈、堆、方法区、本地方法栈、寄存器
1、栈:
- 栈是线程私有的,用于存储基本数据类型变量和对象的引用。
- 每个线程都有自己的栈,用于存储方法调用、局部变量以及操作数栈等。
- 栈中的数据是线程安全的,因为每个线程都有独立的栈空间。
- 方法运行时使用的内存,比如main方法运行,进入方法栈中执行
2、堆(Heap):
- 堆是用于存储对象实例的内存区域,是Java中所有线程共享的部分。
- 在堆中分配的内存由垃圾收集器管理,用于存储所有通过 new 关键字创建的对象。
- Java堆是Java虚拟机管理的最大的一块内存,也是垃圾收集器管理的主要区域之一。
3、方法区(Method Area):
- 存储可以运行的class文件
- 方法区也称为永久代,是存储类的结构信息、常量、静态变量、即时编译器编译后的代码等数据的区域。
- 在JDK8之前,方法区是使用永久代实现的;在JDK8及之后的版本中,永久代被元空间(Metaspace)取代。
- 方法区与堆一样,是所有线程共享的区域。
4、本地方法栈(Native Method Stack):
- 本地方法栈与栈类似,但是它是为Java虚拟机调用本地方法服务的。
- 本地方法栈中存储的是本地方法(即用C/C++等编写的)的信息。
5、寄存器(Registers):
- 寄存器并不是Java虚拟机规范中定义的内存区域,而是位于CPU内部的高速存储器。
- 寄存器用于存储Java虚拟机中的局部变量、操作数栈等数据,它们在执行Java字节码时被使用。
其中方法区,在jdk7以前是和堆空间连在一起的,它们在物理内存中也是一块连续的空间
这样的设计方法有点不好,以至于从jdk8开始,取消方法区,新增元空间。把原来方法区的多种功能进行拆分,有的功能放到了堆中,有的功能放到了元空间中。
在正常开发中,栈和堆是一直使用的,所以介绍堆和栈
栈执行的顺序是先入后出,即开始就会将程序的主入口main方法加入栈中,直到程序运行完毕才会出栈。
堆内存中,主要存储的是new出对象的地址值
三、内存
1、示例
首先看一下简单代码的内存:
我们只需要关心栈和堆就行了,但是在这个代码中,只需要看栈就行了(没有涉及到new关键字)
当程序刚开始运行的时候,main方法就会进入栈底
从第一行开始,逐行进行运行代码
首先执行 int a = 10;此时栈中就会开辟一块小空间定义一个变量,它的名字就为a,还会给该变量a进行一个类型限定为int,然后将10放入小空间中。
int b = 10同理
int c = a+b;它首先也会开辟一块空间,然后将a和b的值拿出来进行相加,得到一个20,再把20赋值给变量c
System.out.println(c);先找到c变量的值,然后再打印变量c的值
内存图如下:
2、数组内存
了解完简单内存解析,接下来就看数组的内存
从数组的定义、获取、赋值方面进行解析
定义:
由于出现了new关键字,此时就要考虑栈和堆两个空间
首先程序开始运行,main方法加入栈中
首先执行第一行 int [] arr = new int [2];定义一个数组
这个代码是由左右两个部分组成的,int [] arr 进入栈中,右边 int[]进入堆中
因为长度为2,所有由0,1两个索引,索引所对应的值都是0,因为数组是int类型的,初始值为0
堆空间中的数组是有地址值的,它会将这个地址值赋值给栈空间中的左边的数组变量int[]arr,左边也需要通过这个地址值来找到右边的数组。
变量里面存的都是真实的数字,而数组变量存的是地址值,因为数组中有大量数据,没有办法将它一次存在arr这个变量中。所以Java在设计的时候,他就将这些数据放在另外一个空间中(堆)通过地址值将他们联系起来
左边为栈,右边为堆:
获取:
所以当我们运行第二行代码,打印数组的时候,所打印的是数组的地址值。
所以我们需要通过数组名加索引的方式进行取值(arr[0]),在内存中它是通过arr找到右边堆中的空间,然后再通过索引进行获取值
赋值:
其实赋值和获取类似,arr[0]=11;就是将11赋值给arr的0索引。首先通过地址值找到堆内存中的小空间,然后再把11赋值给0索引,原来的值就会给覆盖了,此时数组中存储的就是新的元素了。
两个数组的内存:
int [] arr2 = {33,44,55};
虽然这个数组创建中没有new关键字,但是我们要知道,这个是简化的书写方式,完整的书写方式也会有new关键字
此时堆内存中,又会开辟一块新的空间,与之前创建的是相互独立的,两者之间没有任何影响
完整内存图:
总结:
1、只要是new出来的一定实在堆里面开辟了一个 小空间
2、如果new了多次,那么再堆里会有多个小空间,每个空间都有各自的数据
四、两个数组指向同一个空间的内存图
先看一段代码:
两个数组指向同一个空间,当arr2修改值时,arr1也会被修改
由于arr1中保存的是,堆空间中的地址值,当我们将arr1赋值给arr2的时候,相当于把地址值赋值给了arr2,此时arr1和arr2的地址值是一样的,所以他们通过地址值在堆空间中找到的是同一块空间,修改的值也是一样的值,所以才会出现上述情况。