问题:在java中,一维数组和二维数组在数据量一样的情况下,开辟的内存大小是怎样的?
一、尝试阶段:
1、代码一:
public class OneArrayMemory{
public static void main(String[] args){
int num1 = 1024*1024*2;
int[] arr1 = new int[num1];
for(int i = 0;i < arr1.length;i++){
arr1[i] = i;
}
//获得占用内存总数,并将单位转换为MB
long memory1 = Runtime.getRuntime().totalMemory()/1024/1024;
System.out.println("用一维数组存储占用内存总量为:"+memory1+"MB");
int nums2 = 1024*1024;
int[][] arr2 = new int[nums2][2];
for(int i = 0;i < arr2.length;i++){
arr2[i][0] = i;
arr2[i][1] = i;
}
//获得占用内存总数,并将单位转换为MB
long memory2 = Runtime.getRuntime().totalMemory()/1024/1024;
System.out.println("用二维数组存储占用内存总量为:"+memory2+"MB");
}
}
2、运行结果:
用一维数组存储占用内存总量为:123MB
用二维数组存储占用内存总量为:123MB
3、结果有悖于常识,百思不得解。后来查阅了资料,发现了了问题所在。下面补充几个知识点:
最近在网上看到一些人讨论到java.lang.Runtime类中的freeMemory(),totalMemory(),maxMemory ()这几个方法的一些问题,很多人感到很疑惑,为什么,在java程序刚刚启动起来的时候freeMemory()这个方法返回的只有一两兆字节,而随着 java程序往前运行,创建了不少的对象,freeMemory()这个方法的返回有时候不但没有减少,反而会增加。这些人对freeMemory()这 个方法的意义应该有一些误解,他们认为这个方法返回的是操作系统的剩余可用内存,其实根本就不是这样的。这三个方法反映的都是java这个进程的内存情 况,跟操作系统的内存根本没有关系。下面结合totalMemory(),maxMemory()一起来解释。
1)maxMemory()这个方法返回的是java虚拟机(这个进程)能构从操作系统那里挖到的最大的内存,以字节为单位,如果在运行java程序的时 候,没有添加-Xmx参数,那么就是64兆,也就是说maxMemory()返回的大约是64*1024*1024字节,这是java虚拟机默认情况下能 从操作系统那里挖到的最大的内存。如果添加了-Xmx参数,将以这个参数后面的值为准,例如java -cp ClassPath -Xmx512m ClassName,那么最大内存就是512*1024*0124字节。
2)totalMemory()这个方法返回的是java虚拟机现在已经从操作系统那里挖过来的内存大小,也就是java虚拟机这个进程当时所占用的所有 内存。如果在运行java的时候没有添加-Xms参数,那么,在java程序运行的过程的,内存总是慢慢的从操作系统那里挖的,基本上是用多少挖多少,直 挖到maxMemory()为止,所以totalMemory()是慢慢增大的。如果用了-Xms参数,程序在启动的时候就会无条件的从操作系统中挖- Xms后面定义的内存数,然后在这些内存用的差不多的时候,再去挖。
3)freeMemory()是什么呢,刚才讲到如果在运行java的时候没有添加-Xms参数,那么,在java程序运行的过程的,内存总是慢慢的从操 作系统那里挖的,基本上是用多少挖多少,但是java虚拟机100%的情况下是会稍微多挖一点的,这些挖过来而又没有用上的内存,实际上就是 freeMemory(),所以freeMemory()的值一般情况下都是很小的,但是如果你在运行java程序的时候使用了-Xms,这个时候因为程 序在启动的时候就会无条件的从操作系统中挖-Xms后面定义的内存数,这个时候,挖过来的内存可能大部分没用上,所以这个时候freeMemory()可 能会有些大。
结果异常的根源:totalMemory() 减去freeMemory()才是真正给数组开辟的内存大小!!!
4、修改代码:
public class OneArrayMemory{
public static void main(String[] args){
int num1 = 1024*1024*2;
int[] arr1 = new int[num1];
for(int i = 0;i < arr1.length;i++){
arr1[i] = i;
}
//获得占用内存总数,并将单位转换为MB
long memory1 = Runtime.getRuntime().totalMemory()/1024/1024 - Runtime.getRuntime().freeMemory() / 1024 / 1024;
//long memory1 = Runtime.getRuntime().totalMemory()/1024/1024;
System.out.println("用一维数组存储占用内存总量为:"+memory1+"MB");
int nums2 = 1024*1024;
int[][] arr2 = new int[nums2][2];
for(int i = 0;i < arr2.length;i++){
arr2[i][0] = i;
arr2[i][1] = i;
}
//获得占用内存总数,并将单位转换为MB
long memory2 = Runtime.getRuntime().totalMemory()/1024/1024 - Runtime.getRuntime().freeMemory() / 1024 / 1024;
//long memory2 = Runtime.getRuntime().totalMemory()/1024/1024;
System.out.println("用二维数组存储占用内存总量为:"+memory2+"MB");
}
}
第二次运行结果:
用一维数组存储占用内存总量为:10MB
用二维数组存储占用内存总量为:37MB
5、代码三:
import java.util.Arrays;
public class OneArrayMemory {
public static void main(String[] args) {
long startTime1 = System.currentTimeMillis(); // 获取开始时间
int num1 = 1024 * 1024 * 2;
int[] arr1 = new int[num1];
Arrays.fill(arr1, 1);
// 获得占用内存总数,并将单位转换成MB
long memory1 = Runtime.getRuntime().totalMemory() / 1024 / 1024 - Runtime.getRuntime().freeMemory() / 1024 / 1024;
System.out.println("用一维数组存储占用内存总量为:" + memory1 + "MB");
long endTime1 = System.currentTimeMillis(); // 获取结束时间
System.out.println("程序运行时间: " + (endTime1 - startTime1) + "ms");
long startTime2 = System.currentTimeMillis(); // 获取开始时间
int num2 = 1024 * 1024;
int[][] arr2 = new int[num2][2];
for (int[] i : arr2) {
Arrays.fill(i, 1);
}
// 获得占用内存总数,并将单位转换成MB
long memory2 = Runtime.getRuntime().totalMemory() / 1024 / 1024 - Runtime.getRuntime().freeMemory() / 1024 / 1024;
System.out.println("用二维数组存储占用内存总量为:" + memory2 + "MB");
long endTime2 = System.currentTimeMillis(); // 获取结束时间
System.out.println("程序运行时间: " + (endTime2 - startTime2) + "ms");
}
}
运行结果:
用一维数组存储占用内存总量为:10MB
程序运行时间: 12ms
用二维数组存储占用内存总量为:38MB
程序运行时间: 115ms
二、结论:数据量相同的情况下,二维数组比一维数组需要开辟更大的内存空间。
三、分析
1、一个完整的Java程序运行过程会涉及以下内存区域:
寄存器:JVM内部虚拟寄存器,存取速度非常快,程序不可控制。
栈:保存局部变量的值,包括:1.用来保存基本数据类型的值;2.保存类的实例,即堆区对象的引用(指针)。也可以用来保存加载方法时的帧。
堆:用来存放动态产生的数据,比如new出来的对象。注意创建出来的对象只包含属于各自的成员变量,并不包括成员方法。因为同一个类的对象拥有各自的成员变量,存储在各自的堆中,但是他们共享该类的方法,并不是每创建一个对象就把成员方法复制一次。
常量池:JVM为每个已加载的类型维护一个常量池,常量池就是这个类型用到的常量的一个有序集合。包括直接常量(基本类型,String)和对其他类型、方法、字段的符号引用(1)。池中的数据和数组一样通过索引访问。由于常量池包含了一个类型所有的对其他类型、方法、字段的符号引用,所以常量池在Java的动态链接中起了核心作用。常量池存在于堆中。
代码段:用来存放从硬盘上读取的源程序代码。
数据段:用来存放static定义的静态成员。
Java中有两种类型的数组:
- 基本数据类型数组;
- 对象数组;
2、当一个对象使用关键字“new”创建时,会在堆上分配内存空间,然后返回对象的引用,这对数组来说也是一样的,因为数组也是一个对象;
1)一维数组
int[] arr = new int[3];
在以上代码中,arr变量存放了数组对象的引用;如果你创建了空间大小为10的整形数组,情况是一样的,一个数组对象所占的空间在堆上被分配,然后返回其引用;
2)二维数组
那么二维数组是如何存储的呢?事实上,在Java中只有一维数组,二维数组是一个存放了数组的数组,如下代码及示意图:
int[ ][ ] arr = new int[3][ ];
arr[0] = new int[3];
arr[1] = new int[5];
arr[2] = new int[4];
对于多维数组来说,道理是一样的;
由此可见,数据量相同的情况下,开辟多维数组会产生更大的开销。
3)趣事:对于java二维数组建议 Int[][] arr=new Int[2][100] 而不要使用int[][] arr=new int[100][2],因为后者会产生更多的开销。