1. 程式人生 > >Java中一維陣列和二維陣列儲存佔用記憶體大小問題

Java中一維陣列和二維陣列儲存佔用記憶體大小問題

問題:在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],因為後者會產生更多的開銷。