淺談java內存模型

 不同的平臺,內存模型是不一樣的,但是jvm的內存模型規範是統一的。其實java的多線程併發問題最終都會反映在java的內存模型上,所謂線程安全無非是要控制多個線程對某個資源的有序訪問或修改。總結java的內存模型,要解決兩個主要的問題:可見性和有序性
我們都知道計算機有高速緩存的存在,處理器並不是每次處理數據都是取內存的。JVM定義了自己的內存模型,屏蔽了底層平臺內存管理細節,對於java開發人員,要清楚在jvm內存模型的基礎上,如果解決多線程的可見性和有序性。
       那麼,何謂可見性? 多個線程之間是不能互相傳遞數據通信的,它們之間的溝通只能通過共享變量來進行。Java內存模型(JMM)規定了jvm有主內存,主內存是多個線程共享的。當new一個對象的時候,也是被分配在主內存中,每個線程都有自己的工作內存,工作內存存儲了主存的某些對象的副本,當然線程的工作內存大小是有限制的。當線程操作某個對象時,執行順序如下:
 (1) 從主存複製變量到當前工作內存 (read and load)
 (2) 執行代碼,改變共享變量值 (use and assign)
 (3) 用工作內存數據刷新主存相關內容 (store and write)

JVM規範定義了線程對主存的操作指令:read,load,use,assign,store,write。當一個共享變量在多個線程的工作內存中都有副本時,如果一個線程修改了這個共享變量,那麼其他線程應該能夠看到這個被修改後的值,這就是多線程的可見性問題。
        那麼,什麼是有序性呢 ?線程在引用變量時不能直接從主內存中引用,如果線程工作內存中沒有該變量,則會從主內存中拷貝一個副本到工作內存中,這個過程爲read-load,完成後線程會引用該副本。當同一線程再度引用該字段時,有可能重新從主存中獲取變量副本(read-load-use),也有可能直接引用原來的副本 (use),也就是說 read,load,use順序可以由JVM實現系統決定。
        線程不能直接爲主存中中字段賦值,它會將值指定給工作內存中的變量副本(assign),完成後這個變量副本會同步到主存儲區(store- write),至於何時同步過去,根據JVM實現系統決定.有該字段,則會從主內存中將該字段賦值到工作內存中,這個過程爲read-load,完成後線程會引用該變量副本,當同一線程多次重複對字段賦值時,比如:

 

Java代碼 複製代碼
  1. for(int i=0;i<10;i++)   
  2.  a++;  

 

 for(int i=0;i<10;i++)  a++;

 


線程有可能只對工作內存中的副本進行賦值,只到最後一次賦值後才同步到主存儲區,所以assign,store,weite順序可以由JVM實現系統決定。假設有一個共享變量x,線程a執行x=x+1。從上面的描述中可以知道x=x+1並不是一個原子操作,它的執行過程如下:
1 從主存中讀取變量x副本到工作內存
2 給x加1
3 將x加1後的值寫回主

如果另外一個線程b執行x=x-1,執行過程如下:
1 從主存中讀取變量x副本到工作內存
2 給x減1
3 將x減1後的值寫回主存

那麼顯然,最終的x的值是不可靠的。假設x現在爲10,線程a加1,線程b減1,從表面上看,似乎最終x還是爲10,但是多線程情況下會有這種情況發生:
1:線程a從主存讀取x副本到工作內存,工作內存中x值爲10
2:線程b從主存讀取x副本到工作內存,工作內存中x值爲10
3:線程a將工作內存中x加1,工作內存中x值爲11
4:線程a將x提交主存中,主存中x爲11
5:線程b將工作內存中x值減1,工作內存中x值爲9
6:線程b將x提交到中主存中,主存中x爲9

同樣,x有可能爲11,如果x是一個銀行賬戶,線程a存款,線程b扣款,顯然這樣是有嚴重問題的,要解決這個問題,必須保證線程a和線程b是有序執行的,並且每個線程執行的加1或減1是一個原子操作。看看下面代碼:

 

Java代碼 複製代碼
  1. public class Account {   
  2.   
  3.     private int balance;   
  4.   
  5.     public Account(int balance) {   
  6.         this.balance = balance;   
  7.     }   
  8.   
  9.     public int getBalance() {   
  10.         return balance;   
  11.     }   
  12.   
  13.     public void add(int num) {   
  14.         balance = balance + num;   
  15.     }   
  16.   
  17.     public void withdraw(int num) {   
  18.         balance = balance - num;   
  19.     }   
  20.   
  21.     public static void main(String[] args) throws InterruptedException {   
  22.         Account account = new Account(1000);   
  23.         Thread a = new Thread(new AddThread(account, 20), "add");   
  24.         Thread b = new Thread(new WithdrawThread(account, 20), "withdraw");   
  25.         a.start();   
  26.         b.start();   
  27.         a.join();   
  28.         b.join();   
  29.         System.out.println(account.getBalance());   
  30.     }   
  31.   
  32.     static class AddThread implements Runnable {   
  33.         Account account;   
  34.         int     amount;   
  35.   
  36.         public AddThread(Account account, int amount) {   
  37.             this.account = account;   
  38.             this.amount = amount;   
  39.         }   
  40.   
  41.         public void run() {   
  42.             for (int i = 0; i < 200000; i++) {   
  43.                 account.add(amount);   
  44.             }   
  45.         }   
  46.     }   
  47.   
  48.     static class WithdrawThread implements Runnable {   
  49.         Account account;   
  50.         int     amount;   
  51.   
  52.         public WithdrawThread(Account account, int amount) {   
  53.             this.account = account;   
  54.             this.amount = amount;   
  55.         }   
  56.   
  57.         public void run() {   
  58.             for (int i = 0; i < 100000; i++) {   
  59.                 account.withdraw(amount);   
  60.             }   
  61.         }   
  62.     }   
  63. }  

 

public class Account {    private int balance;    public Account(int balance) {        this.balance = balance;    }    public int getBalance() {        return balance;    }    public void add(int num) {        balance = balance + num;    }    public void withdraw(int num) {        balance = balance - num;    }    public static void main(String[] args) throws InterruptedException {        Account account = new Account(1000);        Thread a = new Thread(new AddThread(account, 20), "add");        Thread b = new Thread(new WithdrawThread(account, 20), "withdraw");        a.start();        b.start();        a.join();        b.join();        System.out.println(account.getBalance());    }    static class AddThread implements Runnable {        Account account;        int     amount;        public AddThread(Account account, int amount) {            this.account = account;            this.amount = amount;        }        public void run() {            for (int i = 0; i < 200000; i++) {                account.add(amount);            }        }    }    static class WithdrawThread implements Runnable {        Account account;        int     amount;        public WithdrawThread(Account account, int amount) {            this.account = account;            this.amount = amount;        }        public void run() {            for (int i = 0; i < 100000; i++) {                account.withdraw(amount);            }        }    }}

 


第一次執行結果爲10200,第二次執行結果爲1060,每次執行的結果都是不確定的,因爲線程的執行順序是不可預見的。這是java同步產生的根源,synchronized關鍵字保證了多個線程對於同步塊是互斥的,synchronized作爲一種同步手段,解決java多線程的執行有序性和內存可見性,而volatile關鍵字之解決多線程的內存可見性問題。後面將會詳細介紹。



synchronized關鍵字
        上面說了,java用synchronized關鍵字做爲多線程併發環境的執行有序性的保證手段之一。當一段代碼會修改共享變量,這一段代碼成爲互斥區或臨界區,爲了保證共享變量的正確性,synchronized標示了臨界區。典型的用法如下:

 

Java代碼 複製代碼 收藏代碼
  1. synchronized(鎖){   
  2.      臨界區代碼   
  3. }   

 

synchronized(鎖){     臨界區代碼} 

 


爲了保證銀行賬戶的安全,可以操作賬戶的方法如下:

 

Java代碼 複製代碼 收藏代碼
  1. public synchronized void add(int num) {   
  2.      balance = balance + num;   
  3. }   
  4. public synchronized void withdraw(int num) {   
  5.      balance = balance - num;   
  6. }  

 

public synchronized void add(int num) {     balance = balance + num;}public synchronized void withdraw(int num) {     balance = balance - num;}

 


剛纔不是說了synchronized的用法是這樣的嗎:

 

Java代碼 複製代碼 收藏代碼
  1. synchronized(鎖){   
  2. 臨界區代碼   
  3. }  

 

synchronized(鎖){臨界區代碼}

 


那麼對於public synchronized void add(int num)這種情況,意味着什麼呢?其實這種情況,鎖就是這個方法所在的對象。同理,如果方法是public  static synchronized void add(int num),那麼鎖就是這個方法所在的class。
        理論上,每個對象都可以做爲鎖,但一個對象做爲鎖時,應該被多個線程共享,這樣才顯得有意義,在併發環境下,一個沒有共享的對象作爲鎖是沒有意義的。假如有這樣的代碼:

 

Java代碼 複製代碼 收藏代碼
  1. public class ThreadTest{   
  2.   public void test(){   
  3.      Object lock=new Object();   
  4.      synchronized (lock){   
  5.         //do something   
  6.      }   
  7.   }   
  8. }  

 

public class ThreadTest{  public void test(){     Object lock=new Object();     synchronized (lock){        //do something     }  }}

 


lock變量作爲一個鎖存在根本沒有意義,因爲它根本不是共享對象,每個線程進來都會執行Object lock=new Object();每個線程都有自己的lock,根本不存在鎖競爭。
        每個鎖對象都有兩個隊列,一個是就緒隊列,一個是阻塞隊列,就緒隊列存儲了將要獲得鎖的線程,阻塞隊列存儲了被阻塞的線程,當一個被線程被喚醒 (notify)後,纔會進入到就緒隊列,等待cpu的調度。當一開始線程a第一次執行account.add方法時,jvm會檢查鎖對象account 的就緒隊列是否已經有線程在等待,如果有則表明account的鎖已經被佔用了,由於是第一次運行,account的就緒隊列爲空,所以線程a獲得了鎖,執行account.add方法。如果恰好在這個時候,線程b要執行account.withdraw方法,因爲線程a已經獲得了鎖還沒有釋放,所以線程 b要進入account的就緒隊列,等到得到鎖後纔可以執行。
一個線程執行臨界區代碼過程如下:
1 獲得同步鎖
2 清空工作內存
3 從主存拷貝變量副本到工作內存
4 對這些變量計算
5 將變量從工作內存寫回到主存
6 釋放鎖
可見,synchronized既保證了多線程的併發有序性,又保證了多線程的內存可見性。


生產者/消費者模式
        生產者/消費者模式其實是一種很經典的線程同步模型,很多時候,並不是光保證多個線程對某共享資源操作的互斥性就夠了,往往多個線程之間都是有協作的。
        假設有這樣一種情況,有一個桌子,桌子上面有一個盤子,盤子裏只能放一顆雞蛋,A專門往盤子裏放雞蛋,如果盤子裏有雞蛋,則一直等到盤子裏沒雞蛋,B專門從盤子裏拿雞蛋,如果盤子裏沒雞蛋,則等待直到盤子裏有雞蛋。其實盤子就是一個互斥區,每次往盤子放雞蛋應該都是互斥的,A的等待其實就是主動放棄鎖,B 等待時還要提醒A放雞蛋。
如何讓線程主動釋放鎖
很簡單,調用鎖的wait()方法就好。wait方法是從Object來的,所以任意對象都有這個方法。看這個代碼片段:

 

Java代碼 複製代碼 收藏代碼
  1. Object lock=new Object();//聲明瞭一個對象作爲鎖   
  2.    synchronized (lock) {   
  3.        balance = balance - num;   
  4.        //這裏放棄了同步鎖,好不容易得到,又放棄了   
  5.        lock.wait();   
  6. }  

 

Object lock=new Object();//聲明瞭一個對象作爲鎖   synchronized (lock) {       balance = balance - num;       //這裏放棄了同步鎖,好不容易得到,又放棄了       lock.wait();}

 


如果一個線程獲得了鎖lock,進入了同步塊,執行lock.wait(),那麼這個線程會進入到lock的阻塞隊列。如果調用 lock.notify()則會通知阻塞隊列的某個線程進入就緒隊列。
聲明一個盤子,只能放一個雞蛋

 

Java代碼 複製代碼 收藏代碼
  1. import java.util.ArrayList;   
  2. import java.util.List;   
  3.   
  4. public class Plate {   
  5.   
  6.     List<Object> eggs = new ArrayList<Object>();   
  7.   
  8.     public synchronized Object getEgg() {   
  9.         if (eggs.size() == 0) {   
  10.             try {   
  11.                 wait();   
  12.             } catch (InterruptedException e) {   
  13.             }   
  14.         }   
  15.   
  16.         Object egg = eggs.get(0);   
  17.         eggs.clear();// 清空盤子   
  18.         notify();// 喚醒阻塞隊列的某線程到就緒隊列   
  19.         System.out.println("拿到雞蛋");   
  20.         return egg;   
  21.     }   
  22.   
  23.     public synchronized void putEgg(Object egg) {   
  24.         if (eggs.size() > 0) {   
  25.             try {   
  26.                 wait();   
  27.             } catch (InterruptedException e) {   
  28.             }   
  29.         }   
  30.         eggs.add(egg);// 往盤子裏放雞蛋   
  31.         notify();// 喚醒阻塞隊列的某線程到就緒隊列   
  32.         System.out.println("放入雞蛋");   
  33.     }   
  34.        
  35.     static class AddThread extends Thread{   
  36.         private Plate plate;   
  37.         private Object egg=new Object();   
  38.         public AddThread(Plate plate){   
  39.             this.plate=plate;   
  40.         }   
  41.            
  42.         public void run(){   
  43.             for(int i=0;i<5;i++){   
  44.                 plate.putEgg(egg);   
  45.             }   
  46.         }   
  47.     }   
  48.        
  49.     static class GetThread extends Thread{   
  50.         private Plate plate;   
  51.         public GetThread(Plate plate){   
  52.             this.plate=plate;   
  53.         }   
  54.            
  55.         public void run(){   
  56.             for(int i=0;i<5;i++){   
  57.                 plate.getEgg();   
  58.             }   
  59.         }   
  60.     }   
  61.        
  62.     public static void main(String args[]){   
  63.         try {   
  64.             Plate plate=new Plate();   
  65.             Thread add=new Thread(new AddThread(plate));   
  66.             Thread get=new Thread(new GetThread(plate));   
  67.             add.start();   
  68.             get.start();   
  69.             add.join();   
  70.             get.join();   
  71.         } catch (InterruptedException e) {   
  72.             e.printStackTrace();   
  73.         }   
  74.         System.out.println("測試結束");   
  75.     }   
  76. }  

 

import java.util.ArrayList;import java.util.List;public class Plate {    List<Object> eggs = new ArrayList<Object>();    public synchronized Object getEgg() {        if (eggs.size() == 0) {            try {                wait();            } catch (InterruptedException e) {            }        }        Object egg = eggs.get(0);        eggs.clear();// 清空盤子        notify();// 喚醒阻塞隊列的某線程到就緒隊列        System.out.println("拿到雞蛋");        return egg;    }    public synchronized void putEgg(Object egg) {        if (eggs.size() > 0) {            try {                wait();            } catch (InterruptedException e) {            }        }        eggs.add(egg);// 往盤子裏放雞蛋        notify();// 喚醒阻塞隊列的某線程到就緒隊列        System.out.println("放入雞蛋");    }        static class AddThread extends Thread{        private Plate plate;        private Object egg=new Object();        public AddThread(Plate plate){            this.plate=plate;        }                public void run(){            for(int i=0;i<5;i++){                plate.putEgg(egg);            }        }    }        static class GetThread extends Thread{        private Plate plate;        public GetThread(Plate plate){            this.plate=plate;        }                public void run(){            for(int i=0;i<5;i++){                plate.getEgg();            }        }    }        public static void main(String args[]){        try {            Plate plate=new Plate();            Thread add=new Thread(new AddThread(plate));            Thread get=new Thread(new GetThread(plate));            add.start();            get.start();            add.join();            get.join();        } catch (InterruptedException e) {            e.printStackTrace();        }        System.out.println("測試結束");    }}

  執行結果:

 

Html代碼 複製代碼 收藏代碼
  1. 放入雞蛋   
  2. 拿到雞蛋   
  3. 放入雞蛋   
  4. 拿到雞蛋   
  5. 放入雞蛋   
  6. 拿到雞蛋   
  7. 放入雞蛋   
  8. 拿到雞蛋   
  9. 放入雞蛋   
  10. 拿到雞蛋   
  11. 測試結束  

 

放入雞蛋拿到雞蛋放入雞蛋拿到雞蛋放入雞蛋拿到雞蛋放入雞蛋拿到雞蛋放入雞蛋拿到雞蛋測試結束

 

 


聲明一個Plate對象爲plate,被線程A和線程B共享,A專門放雞蛋,B專門拿雞蛋。假設
1 開始,A調用plate.putEgg方法,此時eggs.size()爲0,因此順利將雞蛋放到盤子,還執行了notify()方法,喚醒鎖的阻塞隊列的線程,此時阻塞隊列還沒有線程。
2 又有一個A線程對象調用plate.putEgg方法,此時eggs.size()不爲0,調用wait()方法,自己進入了鎖對象的阻塞隊列。
3 此時,來了一個B線程對象,調用plate.getEgg方法,eggs.size()不爲0,順利的拿到了一個雞蛋,還執行了notify()方法,喚醒鎖的阻塞隊列的線程,此時阻塞隊列有一個A線程對象,喚醒後,它進入到就緒隊列,就緒隊列也就它一個,因此馬上得到鎖,開始往盤子裏放雞蛋,此時盤子是空的,因此放雞蛋成功。
4 假設接着來了線程A,就重複2;假設來料線程B,就重複3。

整個過程都保證了放雞蛋,拿雞蛋,放雞蛋,拿雞蛋。



volatile關鍵字
       volatile是java提供的一種同步手段,只不過它是輕量級的同步,爲什麼這麼說,因爲volatile只能保證多線程的內存可見性,不能保證多線程的執行有序性。而最徹底的同步要保證有序性和可見性,例如synchronized。任何被volatile修飾的變量,都不拷貝副本到工作內存,任何修改都及時寫在主存。因此對於Valatile修飾的變量的修改,所有線程馬上就能看到,但是volatile不能保證對變量的修改是有序的。什麼意思呢?假如有這樣的代碼:

 

Java代碼 複製代碼 收藏代碼
  1. public class VolatileTest{   
  2.   public volatile int a;   
  3.   public void add(int count){   
  4.        a=a+count;   
  5.   }   
  6. }  

 

public class VolatileTest{  public volatile int a;  public void add(int count){       a=a+count;  }}

 


        當一個VolatileTest對象被多個線程共享,a的值不一定是正確的,因爲a=a+count包含了好幾步操作,而此時多個線程的執行是無序的,因爲沒有任何機制來保證多個線程的執行有序性和原子性。volatile存在的意義是,任何線程對a的修改,都會馬上被其他線程讀取到,因爲直接操作主存,沒有線程對工作內存和主存的同步。所以,volatile的使用場景是有限的,在有限的一些情形下可以使用 volatile 變量替代鎖。要使 volatile 變量提供理想的線程安全,必須同時滿足下面兩個條件:
1)對變量的寫操作不依賴於當前值。
2)該變量沒有包含在具有其他變量的不變式中

volatile只保證了可見性,所以Volatile適合直接賦值的場景,如

 

Java代碼 複製代碼 收藏代碼
  1. public class VolatileTest{   
  2.   public volatile int a;   
  3.   public void setA(int a){   
  4.       this.a=a;   
  5.   }   
  6. }  

 

public class VolatileTest{  public volatile int a;  public void setA(int a){      this.a=a;  }}

 


在沒有volatile聲明時,多線程環境下,a的最終值不一定是正確的,因爲this.a=a;涉及到給a賦值和將a同步回主存的步驟,這個順序可能被打亂。如果用volatile聲明瞭,讀取主存副本到工作內存和同步a到主存的步驟,相當於是一個原子操作。所以簡單來說,volatile適合這種場景:一個變量被多個線程共享,線程直接給這個變量賦值。這是一種很簡單的同步場景,這時候使用volatile的開銷將會非常小。站內很多人都問我,所謂線程的“工作內存”到底是個什麼東西?有的人認爲是線程的棧,其實這種理解是不正確的。看看JLS(java語言規範)對線程工作內存的描述,線程的working memory只是cpu的寄存器和高速緩存的抽象描述。

 

      可能 很多人都覺得莫名其妙,說JVM的內存模型,怎麼會扯到cpu上去呢?在此,我認爲很有必要闡述下,免得很多人看得不明不白的。先拋開java虛擬機不談,我們都知道,現在的計算機,cpu在計算的時候,並不總是從內存讀取數據,它的數據讀取順序優先級是:寄存器-高速緩存-內存。線程耗費的是CPU,線程計算的時候,原始的數據來自內存,在計算過程中,有些數據可能被頻繁讀取,這些數據被存儲在寄存器和高速緩存中,當線程計算完後,這些緩存的數據在適當的時候應該寫回內存。當個多個線程同時讀寫某個內存數據時,就會產生多線程併發問題,涉及到三個特性:原子性,有序性,可見性。在《線程安全總結》這篇文章中,爲了理解方便,我把原子性和有序性統一叫做“多線程執行有序性”。支持多線程的平臺都會面臨這種問題,運行在多線程平臺上支持多線程的語言應該提供解決該問題的方案。

 

       那麼,我們看看JVM,JVM是一個虛擬的計算機,它也會面臨多線程併發問題,java程序運行在java虛擬機平臺上,java程序員不可能直接去控制底層線程對寄存器高速緩存內存之間的同步,那麼java從語法層面,應該給開發人員提供一種解決方案,這個方案就是諸如synchronized, volatile,鎖機制(如同步塊,就緒隊列,阻塞隊列)等等。這些方案只是語法層面的,但我們要從本質上去理解它,不能僅僅知道一個 synchronized 可以保證同步就完了。  在這裏我說的是jvm的內存模型,是動態的,面向多線程併發的,沿襲JSL的“working memory”的說法,只是不想牽扯到太多底層細節,因爲 《線程安全總結》這篇文章意在說明怎樣從語法層面去理解java的線程同步,知道各個關鍵字的使用場景。

 

      今天有人問我,那java的線程不是有棧嗎?難道棧不是工作內存嗎?工作內存這四個字得放到具體的場景中描述,方能體現它具體的意義,在描述JVM的線程同步時,工作內存指的是寄存器和告訴緩存的抽象描述,具體請自行參閱JLS。上面講的都是動態的內存模型,甚至已經超越了JVM的範圍,那麼JVM的內存靜態存儲是怎麼劃分的?今天還有人問我,jvm的內存模型不是有eden區嗎?也不見你提起。我跟他說,這是兩個角度去看的,甚至是兩個不同的範圍,動態的線程同步的內存模型,涵蓋了cpu,寄存器,高速緩存,內存;JVM的靜態內存儲模型只是一種對內存的物理劃分而已,它只侷限在內存,而且只侷限在JVM的內存。那些什麼線程棧,eden區都僅僅在JVM內存。

 

      說說JVM的線程棧和有個朋友反覆跟我糾結的eden區吧。JVM的內存,被劃分了很多的區域:

1.程序計數器
每一個Java線程都有一個程序計數器來用於保存程序執行到當前方法的哪一個指令。
2.線程棧
線程的每個方法被執行的時候,都會同時創建一個幀(Frame)用於存儲本地變量表、操作棧、動態鏈接、方法出入口等信息。每一個方法的調用至完成,就意味着一個幀在VM棧中的入棧至出棧的過程。如果線程請求的棧深度大於虛擬機所允許的深度,將拋出StackOverflowError異常;如果VM棧可以動態擴展(VM Spec中允許固定長度的VM棧),當擴展時無法申請到足夠內存則拋出OutOfMemoryError異常。
3.本地方法棧
4.堆

每個線程的棧都是該線程私有的,堆則是所有線程共享的。當我們new一個對象時,該對象就被分配到了堆中。但是堆,並不是一個簡單的概念,堆區又劃分了很多區域,爲什麼堆劃分成這麼多區域,這是爲了JVM的內存垃圾收集,似乎越扯越遠了,扯到垃圾收集了,現在的jvm的gc都是按代收集,堆區大致被分爲三大塊:新生代,舊生代,持久代(虛擬的);新生代又分爲eden區,s0區,s1區。新建一個對象時,基本小的對象,生命週期短的對象都會放在新生代的eden區中,eden區滿時,有一個小範圍的gc(minor gc),整個新生代滿時,會有一個大範圍的gc(major gc),將新生代裏的部分對象轉到舊生代裏。
5.方法區
其實就是永久代(Permanent Generation),方法區中存放了每個Class的結構信息,包括常量池、字段描述、方法描述等等。VM Space描述中對這個區域的限制非常寬鬆,除了和Java堆一樣不需要連續的內存,也可以選擇固定大小或者可擴展外,甚至可以選擇不實現垃圾收集。相對來說,垃圾收集行爲在這個區域是相對比較少發生的,但並不是某些描述那樣永久代不會發生GC(至 少對當前主流的商業JVM實現來說是如此),這裏的GC主要是對常量池的回收和對類的卸載,雖然回收的“成績”一般也比較差強人意,尤其是類卸載,條件相當苛刻。
6.常量池
 Class文件中除了有類的版本、字段、方法、接口等描述等信息外,還有一項信息是常量表(constant_pool table),用於存放編譯期已可知的常量,這部分內容將在類加載後進入方法區(永久代)存放。但是Java語言並不要求常量一定只有編譯期預置入Class的常量表的內容才能進入方法區常量池,運行期間也可將新內容放入常量池(最典型的String.intern()方法)。

 

關於垃圾收集,在此不多說,流到垃圾收集那一章再詳細說吧。關於java的同步,其實還有基於CPU原語的比較並交換的非阻塞算法(CAS),不過這個在java的併發包裏已經實現了很多,因此關於這點,就留到java併發包那一章介紹吧。後面我會專門寫一篇文章,JVM內存與垃圾收集。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章