JAVA內存管理

被問到有關Java內存管理的知識,因此要蒐集整理一下了。開始以前,咱們要明白一點,咱們所使用的變量就是一塊一塊的內存空間!!java

1、內存管理原理:

在java中,有java程序、虛擬機、操做系統三個層次,其中java程序與虛擬機交互,而虛擬機與操做系統間交互!這就保證了java程序的平臺無關性!下面咱們從程序運行前,程序運行中、程序運行內存溢出三個階段來講一下內存管理原理!
一、程序運行前:JVM向操做系統請求必定的內存空間,稱爲初始內存空間!程序執行過程當中所需的內存都是由java虛擬機從這片內存空間中劃分的。
二、程序運行中:java程序一直向java虛擬機申請內存,當程序所須要的內存空間超出初始內存空間時,java虛擬機會再次向操做系統申請更多的內存供程序使用!
三、內存溢出:程序接着運行,當java虛擬機已申請的內存達到了規定的最大內存空間,但程序還須要更多的內存,這時會出現內存溢出的錯誤!
至此能夠看出,Java 程序所使用的內存是由 Java 虛擬機進行管理、分配的。Java 虛擬機規定了 Java 程序的初始內存空間和最大內存空間,開發者只須要關心 Java 虛擬機是如何管理內存空間的,而不用關心某一種操做系統是如何管理內存的。  
 
2、 RUNTIME 類的使用:
 
Java 給咱們提供了Runtime 類獲得JVM 內存的信息
 方法名稱  參數 做用  返回值 
 getRuntime   無  獲取 Runtime 對象   Runtime 對象 
 totalMemory   無  獲取 JVM 分配給程序的內存數量   long:內存數量 
 freeMemory  無  獲取 當前可用的內存數量   long:內存數量 
 maxMemory   無  獲取 JVM 能夠申請到的最大內存數量  long:內存數量 
   
 
3、內存空間邏輯劃分:
 
JVM 會把申請的內存從邏輯上劃分爲三個區域,即:方法區、堆與棧。 
方法區方法區默認最大容量爲64M,Java虛擬機會將加載的java類存入方法區,保存類的結構(屬性與方法),類靜態成員等內容。
堆:默認最大容量爲64M,堆存放對象持有的數據,同時保持對原類的引用。能夠簡單的理解爲對象屬性的值保存在堆中,對象調用的方法保存在方法區。
:棧默認最大容量爲1M,在程序運行時,每當遇到方法調用時,Java虛擬機就會在棧中劃分一塊內存稱爲棧幀(Stack frame),棧幀中的內存供局部變量(包括基本類型與引用類型)使用,當方法調用結束後,Java虛擬機會收回此棧幀佔用的內存。 
 
4、java數據類型
 
 
一、基本數據類型:沒封裝指針的變量。
聲明此類型變量,只會在棧中分配一塊內存空間。
 
二、引用類型:就是底層封裝指針的數據類型。
他們在內存中分配兩塊空間,第一塊內存分配在棧中,只存放別的內存地址,不存放具體數值,咱們也把它叫指針類型的變量,第二塊內存分配在堆中,存放的是具體數值,如對象屬性值等。
 
三、下面咱們從一個例子來看一看:
public class Student { 

  String stuId; 

  String stuName; 

  int stuAge; 

} 

 


public class TestStudent { 

  public static void main(String[] args) { 

    Student s = new Student(); 

    String name = new String("雲鶴");  

    int a = 10; 

    char b = 'm'; 

    s.stuId = "6363"; 

    s.stuName = "劉德華"; 

    s.stuAge = 25; 

  } 

}

(1)類固然是存放在方法區裏面的。數組

(2) Student s = new Student(); 
這行代碼就建立了兩塊內存空間,第一個在棧中,名字叫s,它就至關於指針類型的變量,咱們看到它並不存放學生的姓名、年齡等具體的數值,而是存放堆中第二塊內存的地址,第二塊才存放具體的數值,如學生的編號、姓名、年齡等信息。
 
(3) int a = 10; 
這是 基本數據類型 變量,具體的值就存放在棧中,並無只指針的概念!
 
下圖就是本例的內存佈置圖:
 
此外咱們還要知道 Student s = new Student(); 包括了聲明和建立,即:
聲明: Student s;
建立:s = new Student();
其中聲明只是在棧中聲明一個空間,但尚未具體的值,聲明後的狀況以下圖所示:
建立後的狀況以下圖所示:
 
 
(4) 引用類型中的數組也封裝了指針,即使是基本數據類型的數組也封裝了指針,數組也是引用類型。好比代碼int[] arr = new int[]{3, 6, 12, 9, 66, 31};以下圖所示:
 
 5、java值傳參與引用傳參
 
(1)參數根據調用後的效果不一樣,便是否改變參數的原始數值,又能夠分爲兩種: 按值傳遞的參數按引用傳遞的參數
按值傳遞的參數原始數值不改變,按引用傳遞的參數原始數值改變!這是爲何呢?其實至關簡單:
咱們知道 基本數據類型的變量存放在棧裏面,變量名處存放的就是變量的值,那麼當基本數據類型的變量做爲參數時,傳遞的就是這個值,只是把變量的值傳遞了過去, 無論對這個值如何操做,都不會改變變量的原始值。而 對引用數據類型的變量來講,變量名處存放的地址,因此引用數據類型的變量做爲傳參時, 傳遞的其實是地址,對地址處的內容進行操做,固然會改變變量的值了!
正常狀況下,咱們用數組測試TestArray類以下:
public class TestArray { 

      void change(int[] arr) { 

            for(int i=0;i<arr.length;i++)
               if(i%2==0)
                   arr[i]=1000;
            System.out.println("方法體內修改值後:" ); 
            for(int i=0;i<arr.length;i++)
                System.out.println(arr[i]);
      }

public static void main(String[] args) { 


    int[] a = {1,2,3,4}; 

    TestArray testString = new TestArray(); 

    System.out.println("方法調用前:"); 
    for(int i=0;i<a.length;i++)
        System.out.println(a[i]);
    testString.change(a); 
    System.out.println("方法調用後:"); 
    for(int i=0;i<a.length;i++)
        System.out.println(a[i]);
  } 

    }

輸出結果以下:多線程

方法調用前:
1
2
3
4
方法體內修改值後:
1000
2
1000
4
方法調用後:
1000
2
1000
4

數組實際上也是引用類型,在調用函數的過程當中改變了其值。函數

 (2)特例:String性能

 
public class TestString { 

      void change(String str) { 

            str = "吳奇隆"; 

            System.out.println("方法體內修改值後:" + str); 
    
      }

public static void main(String[] args) { 


    String name = "歌星"; 

    TestString testString = new TestString(); 

    System.out.println("方法調用前:" + name); 
    testString.change(name); 
    System.out.println("方法調用後:" + name); 

  } 

    

結果:測試

方法調用前:歌星
方法體內修改值後:吳奇隆
方法調用後:歌星

分析:ui

上例中,雖然參數String 是引用數據類型,但其值沒有發生改變,這是由於String 類是final 的,它是定長,不容許對其進行改變,而StringBuffer(多線程下使用性能優)和StringBuilder(單線程下面使用性能優)是能夠改變的。若是這裏用StringBuffer和SringBuiler替代,結果和Array的使用同樣,中間結果會被改變。
咱們看初始狀況,即String name = "歌星";這行代碼運行
完,以下圖:
 
 
 當調用方法時testString.change(name),內存變化爲:
 
 
 
在方法體內,參數str賦予一個新值,str = "吳奇隆"。由於"吳奇隆"這個String是定長,系統就會在堆中分配一塊新的內存空間37DF,這樣str指向了新的內存空間37DF,而name仍是指向36DF, 37DF的改變對它已沒影響:
 
 
最後,方法調用結束,str與37DF的內存空間消亡。Name的值依然爲歌星,並無改變。
因此String雖然是引用類型參數,但值依然不變:
 
 
 (3)沒法交換的例子:
 
public class TestChange { 

  void change(Student stu1, Student stu2) { 

    stu1.stuAge ++; 

    stu2.stuAge ++; 

    Student stu = stu1; 

    stu1 = stu2; 

    stu2 = stu; 

  } 

   

  public static void main(String[] args) { 

     

    Student z = new Student(); 

    z.stuName = "張信哲"; 

    z.stuAge = 40; 

     

    Student r = new Student(); 

    r.stuName = "任賢齊"; 

    r.stuAge = 30; 

    System.out.println("交換前z:\t"+z.stuName+"\t"+z.stuAge); 
    System.out.println("交換前r:\t"+r.stuName+"\t"+r.stuAge); 

    TestChange testChange = new TestChange(); 

    testChange.change(z, r);      

    System.out.println("交換後z:\t"+z.stuName+"\t"+z.stuAge); 
    System.out.println("交換後r:\t"+r.stuName+"\t"+r.stuAge); 

  } 
  
} 

運行結果:spa

交換前z:    張信哲    40
交換前r:    任賢齊    30
交換後z:    張信哲    41
交換後r:    任賢齊    31

 

相關文章
相關標籤/搜索