最近想將java基礎的一些東西都整理整理,寫(xiě)下來(lái),這是對知識的總結,也是一種樂(lè )趣。已經(jīng)擬好了提綱,大概分為這幾個(gè)主題: java線(xiàn)程安全,java垃圾收集,java并發(fā)包詳細介紹,java profile和jvm性能調優(yōu) 。慢慢寫(xiě)吧。本人jameswxx原創(chuàng )文章,轉載請注明出處,我費了很多心血,多謝了。關(guān)于java線(xiàn)程安全,網(wǎng)上有很多資料,我只想從自己的角度總結對這方面的考慮,有時(shí)候寫(xiě)東西是很痛苦的,知道一些東西,但想用文字說(shuō)清楚,卻不是那么容易。我認為要認識java線(xiàn)程安全,必須了解兩個(gè)主要的點(diǎn):java的內存模型,java的線(xiàn)程同步機制。特別是內存模型,java的線(xiàn)程同步機制很大程度上都是基于內存模型而設定的。后面我還會(huì )寫(xiě)java并發(fā)包的文章,詳細總結如何利用java并發(fā)包編寫(xiě)高效安全的多線(xiàn)程并發(fā)程序。暫時(shí)寫(xiě)得比較倉促,后面會(huì )慢慢補充完善。
淺談java內存模型
不同的平臺,內存模型是不一樣的,但是jvm的內存模型規范是統一的。其實(shí)java的多線(xiàn)程并發(fā)問(wèn)題最終都會(huì )反映在java的內存模型上,所謂線(xiàn)程安全無(wú)非是要控制多個(gè)線(xiàn)程對某個(gè)資源的有序訪(fǎng)問(wèn)或修改??偨Yjava的內存模型,要解決兩個(gè)主要的問(wèn)題:可見(jiàn)性和有序性。我們都知道計算機有高速緩存的存在,處理器并不是每次處理數據都是取內存的。JVM定義了自己的內存模型,屏蔽了底層平臺內存管理細節,對于java開(kāi)發(fā)人員,要清楚在jvm內存模型的基礎上,如果解決多線(xiàn)程的可見(jiàn)性和有序性。
那么,何謂可見(jiàn)性? 多個(gè)線(xiàn)程之間是不能互相傳遞數據通信的,它們之間的溝通只能通過(guò)共享變量來(lái)進(jìn)行。Java內存模型(JMM)規定了jvm有主內存,主內存是多個(gè)線(xiàn)程共享的。當new一個(gè)對象的時(shí)候,也是被分配在主內存中,每個(gè)線(xiàn)程都有自己的工作內存,工作內存存儲了主存的某些對象的副本,當然線(xiàn)程的工作內存大小是有限制的。當線(xiàn)程操作某個(gè)對象時(shí),執行順序如下:
(1) 從主存復制變量到當前工作內存 (read and load)
(2) 執行代碼,改變共享變量值 (use and assign)
(3) 用工作內存數據刷新主存相關(guān)內容 (store and write)
JVM規范定義了線(xiàn)程對主存的操作指令:read,load,use,assign,store,write。當一個(gè)共享變量在多個(gè)線(xiàn)程的工作內存中都有副本時(shí),如果一個(gè)線(xiàn)程修改了這個(gè)共享變量,那么其他線(xiàn)程應該能夠看到這個(gè)被修改后的值,這就是多線(xiàn)程的可見(jiàn)性問(wèn)題。
那么,什么是有序性呢 ?線(xiàn)程在引用變量時(shí)不能直接從主內存中引用,如果線(xiàn)程工作內存中沒(méi)有該變量,則會(huì )從主內存中拷貝一個(gè)副本到工作內存中,這個(gè)過(guò)程為read-load,完成后線(xiàn)程會(huì )引用該副本。當同一線(xiàn)程再度引用該字段時(shí),有可能重新從主存中獲取變量副本(read-load-use),也有可能直接引用原來(lái)的副本(use),也就是說(shuō) read,load,use順序可以由JVM實(shí)現系統決定。
線(xiàn)程不能直接為主存中中字段賦值,它會(huì )將值指定給工作內存中的變量副本(assign),完成后這個(gè)變量副本會(huì )同步到主存儲區(store-write),至于何時(shí)同步過(guò)去,根據JVM實(shí)現系統決定.有該字段,則會(huì )從主內存中將該字段賦值到工作內存中,這個(gè)過(guò)程為read-load,完成后線(xiàn)程會(huì )引用該變量副本,當同一線(xiàn)程多次重復對字段賦值時(shí),比如:
Java代碼
for(int i=0;i<10;i++)
a++;
線(xiàn)程有可能只對工作內存中的副本進(jìn)行賦值,只到最后一次賦值后才同步到主存儲區,所以assign,store,weite順序可以由JVM實(shí)現系統決定。假設有一個(gè)共享變量x,線(xiàn)程a執行x=x+1。從上面的描述中可以知道x=x+1并不是一個(gè)原子操作,它的執行過(guò)程如下:
1 從主存中讀取變量x副本到工作內存
2 給x加1
3 將x加1后的值寫(xiě)回主 存
如果另外一個(gè)線(xiàn)程b執行x=x-1,執行過(guò)程如下:
1 從主存中讀取變量x副本到工作內存
2 給x減1
3 將x減1后的值寫(xiě)回主存
那么顯然,最終的x的值是不可靠的。假設x現在為10,線(xiàn)程a加1,線(xiàn)程b減1,從表面上看,似乎最終x還是為10,但是多線(xiàn)程情況下會(huì )有這種情況發(fā)生:
1:線(xiàn)程a從主存讀取x副本到工作內存,工作內存中x值為10
2:線(xiàn)程b從主存讀取x副本到工作內存,工作內存中x值為10
3:線(xiàn)程a將工作內存中x加1,工作內存中x值為11
4:線(xiàn)程a將x提交主存中,主存中x為11
5:線(xiàn)程b將工作內存中x值減1,工作內存中x值為9
6:線(xiàn)程b將x提交到中主存中,主存中x為9
同樣,x有可能為11,如果x是一個(gè)銀行賬戶(hù),線(xiàn)程a存款,線(xiàn)程b扣款,顯然這樣是有嚴重問(wèn)題的,要解決這個(gè)問(wèn)題,必須保證線(xiàn)程a和線(xiàn)程b是有序執行的,并且每個(gè)線(xiàn)程執行的加1或減1是一個(gè)原子操作??纯聪旅娲a:
Java代碼
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,每次執行的結果都是不確定的,因為線(xiàn)程的執行順序是不可預見(jiàn)的。這是java同步產(chǎn)生的根源,synchronized關(guān)鍵字保證了多個(gè)線(xiàn)程對于同步塊是互斥的,synchronized作為一種同步手段,解決java多線(xiàn)程的執行有序性和內存可見(jiàn)性,而volatile關(guān)鍵字之解決多線(xiàn)程的內存可見(jiàn)性問(wèn)題。后面將會(huì )詳細介紹。
synchronized關(guān)鍵字
上面說(shuō)了,java用synchronized關(guān)鍵字做為多線(xiàn)程并發(fā)環(huán)境的執行有序性的保證手段之一。當一段代碼會(huì )修改共享變量,這一段代碼成為互斥區或臨界區,為了保證共享變量的正確性,synchronized標示了臨界區。典型的用法如下:
Java代碼
synchronized(鎖){
臨界區代碼
}
為了保證銀行賬戶(hù)的安全,可以操作賬戶(hù)的方法如下:
Java代碼
public synchronized void add(int num) {
balance = balance + num;
}
public synchronized void withdraw(int num) {
balance = balance - num;
}
剛才不是說(shuō)了synchronized的用法是這樣的嗎:
Java代碼
synchronized(鎖){
臨界區代碼
}
那么對于public synchronized void add(int num)這種情況,意味著(zhù)什么呢?其實(shí)這種情況,鎖就是這個(gè)方法所在的對象。同理,如果方法是public static synchronized void add(int num),那么鎖就是這個(gè)方法所在的class。
理論上,每個(gè)對象都可以做為鎖,但一個(gè)對象做為鎖時(shí),應該被多個(gè)線(xiàn)程共享,這樣才顯得有意義,在并發(fā)環(huán)境下,一個(gè)沒(méi)有共享的對象作為鎖是沒(méi)有意義的。假如有這樣的代碼:
Java代碼
public class ThreadTest{
public void test(){
Object lock=new Object();
synchronized (lock){
//do something
}
}
}
lock變量作為一個(gè)鎖存在根本沒(méi)有意義,因為它根本不是共享對象,每個(gè)線(xiàn)程進(jìn)來(lái)都會(huì )執行Object lock=new Object();每個(gè)線(xiàn)程都有自己的lock,根本不存在鎖競爭。
每個(gè)鎖對象都有兩個(gè)隊列,一個(gè)是就緒隊列,一個(gè)是阻塞隊列,就緒隊列存儲了將要獲得鎖的線(xiàn)程,阻塞隊列存儲了被阻塞的線(xiàn)程,當一個(gè)被線(xiàn)程被喚醒(notify)后,才會(huì )進(jìn)入到就緒隊列,等待cpu的調度。當一開(kāi)始線(xiàn)程a第一次執行account.add方法時(shí),jvm會(huì )檢查鎖對象account的就緒隊列是否已經(jīng)有線(xiàn)程在等待,如果有則表明account的鎖已經(jīng)被占用了,由于是第一次運行,account的就緒隊列為空,所以線(xiàn)程a獲得了鎖,執行account.add方法。如果恰好在這個(gè)時(shí)候,線(xiàn)程b要執行account.withdraw方法,因為線(xiàn)程a已經(jīng)獲得了鎖還沒(méi)有釋放,所以線(xiàn)程b要進(jìn)入account的就緒隊列,等到得到鎖后才可以執行。
一個(gè)線(xiàn)程執行臨界區代碼過(guò)程如下:
1 獲得同步鎖
2 清空工作內存
3 從主存拷貝變量副本到工作內存
4 對這些變量計算
5 將變量從工作內存寫(xiě)回到主存
6 釋放鎖
可見(jiàn),synchronized既保證了多線(xiàn)程的并發(fā)有序性,又保證了多線(xiàn)程的內存可見(jiàn)性。
生產(chǎn)者/消費者模式
生產(chǎn)者/消費者模式其實(shí)是一種很經(jīng)典的線(xiàn)程同步模型,很多時(shí)候,并不是光保證多個(gè)線(xiàn)程對某共享資源操作的互斥性就夠了,往往多個(gè)線(xiàn)程之間都是有協(xié)作的。
假設有這樣一種情況,有一個(gè)桌子,桌子上面有一個(gè)盤(pán)子,盤(pán)子里只能放一顆雞蛋,A專(zhuān)門(mén)往盤(pán)子里放雞蛋,如果盤(pán)子里有雞蛋,則一直等到盤(pán)子里沒(méi)雞蛋,B專(zhuān)門(mén)從盤(pán)子里拿雞蛋,如果盤(pán)子里沒(méi)雞蛋,則等待直到盤(pán)子里有雞蛋。其實(shí)盤(pán)子就是一個(gè)互斥區,每次往盤(pán)子放雞蛋應該都是互斥的,A的等待其實(shí)就是主動(dòng)放棄鎖,B等待時(shí)還要提醒A放雞蛋。
如何讓線(xiàn)程主動(dòng)釋放鎖
很簡(jiǎn)單,調用鎖的wait()方法就好。wait方法是從Object來(lái)的,所以任意對象都有這個(gè)方法??催@個(gè)代碼片段:
Java代碼
Object lock=new Object();//聲明了一個(gè)對象作為鎖
synchronized (lock) {
balance = balance - num;
//這里放棄了同步鎖,好不容易得到,又放棄了
lock.wait();
}
如果一個(gè)線(xiàn)程獲得了鎖lock,進(jìn)入了同步塊,執行lock.wait(),那么這個(gè)線(xiàn)程會(huì )進(jìn)入到lock的阻塞隊列。如果調用lock.notify()則會(huì )通知阻塞隊列的某個(gè)線(xiàn)程進(jìn)入就緒隊列。
聲明一個(gè)盤(pán)子,只能放一個(gè)雞蛋
Java代碼
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();// 清空盤(pán)子
notify();// 喚醒阻塞隊列的某線(xiàn)程到就緒隊列
System.out.println("拿到雞蛋");
return egg;
}
public synchronized void putEgg(Object egg) {
if (eggs.size() > 0) {
try {
wait();
} catch (InterruptedException e) {
}
}
eggs.add(egg);// 往盤(pán)子里放雞蛋
notify();// 喚醒阻塞隊列的某線(xiàn)程到就緒隊列
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代碼
放入雞蛋
拿到雞蛋
放入雞蛋
拿到雞蛋
放入雞蛋
拿到雞蛋
放入雞蛋
拿到雞蛋
放入雞蛋
拿到雞蛋
測試結束
聲明一個(gè)Plate對象為plate,被線(xiàn)程A和線(xiàn)程B共享,A專(zhuān)門(mén)放雞蛋,B專(zhuān)門(mén)拿雞蛋。假設
1 開(kāi)始,A調用plate.putEgg方法,此時(shí)eggs.size()為0,因此順利將雞蛋放到盤(pán)子,還執行了notify()方法,喚醒鎖的阻塞隊列的線(xiàn)程,此時(shí)阻塞隊列還沒(méi)有線(xiàn)程。
2 又有一個(gè)A線(xiàn)程對象調用plate.putEgg方法,此時(shí)eggs.size()不為0,調用wait()方法,自己進(jìn)入了鎖對象的阻塞隊列。
3 此時(shí),來(lái)了一個(gè)B線(xiàn)程對象,調用plate.getEgg方法,eggs.size()不為0,順利的拿到了一個(gè)雞蛋,還執行了notify()方法,喚醒鎖的阻塞隊列的線(xiàn)程,此時(shí)阻塞隊列有一個(gè)A線(xiàn)程對象,喚醒后,它進(jìn)入到就緒隊列,就緒隊列也就它一個(gè),因此馬上得到鎖,開(kāi)始往盤(pán)子里放雞蛋,此時(shí)盤(pán)子是空的,因此放雞蛋成功。
4 假設接著(zhù)來(lái)了線(xiàn)程A,就重復2;假設來(lái)料線(xiàn)程B,就重復3。
整個(gè)過(guò)程都保證了放雞蛋,拿雞蛋,放雞蛋,拿雞蛋。
volatile關(guān)鍵字
volatile是java提供的一種同步手段,只不過(guò)它是輕量級的同步,為什么這么說(shuō),因為volatile只能保證多線(xiàn)程的內存可見(jiàn)性,不能保證多線(xiàn)程的執行有序性。而最徹底的同步要保證有序性和可見(jiàn)性,例如synchronized。任何被volatile修飾的變量,都不拷貝副本到工作內存,任何修改都及時(shí)寫(xiě)在主存。因此對于Valatile修飾的變量的修改,所有線(xiàn)程馬上就能看到,但是volatile不能保證對變量的修改是有序的。什么意思呢?假如有這樣的代碼:
Java代碼
public class VolatileTest{
public volatile int a;
public void add(int count){
a=a+count;
}
}
當一個(gè)VolatileTest對象被多個(gè)線(xiàn)程共享,a的值不一定是正確的,因為a=a+count包含了好幾步操作,而此時(shí)多個(gè)線(xiàn)程的執行是無(wú)序的,因為沒(méi)有任何機制來(lái)保證多個(gè)線(xiàn)程的執行有序性和原子性。volatile存在的意義是,任何線(xiàn)程對a的修改,都會(huì )馬上被其他線(xiàn)程讀取到,因為直接操作主存,沒(méi)有線(xiàn)程對工作內存和主存的同步。所以,volatile的使用場(chǎng)景是有限的,在有限的一些情形下可以使用 volatile 變量替代鎖。要使 volatile 變量提供理想的線(xiàn)程安全,必須同時(shí)滿(mǎn)足下面兩個(gè)條件:
1)對變量的寫(xiě)操作不依賴(lài)于當前值。
2)該變量沒(méi)有包含在具有其他變量的不變式中
volatile只保證了可見(jiàn)性,所以Volatile適合直接賦值的場(chǎng)景,如
Java代碼
public class VolatileTest{
public volatile int a;
public void setA(int a){
this.a=a;
}
}
在沒(méi)有volatile聲明時(shí),多線(xiàn)程環(huán)境下,a的最終值不一定是正確的,因為this.a=a;涉及到給a賦值和將a同步回主存的步驟,這個(gè)順序可能被打亂。如果用volatile聲明了,讀取主存副本到工作內存和同步a到主存的步驟,相當于是一個(gè)原子操作。所以簡(jiǎn)單來(lái)說(shuō),volatile適合這種場(chǎng)景:一個(gè)變量被多個(gè)線(xiàn)程共享,線(xiàn)程直接給這個(gè)變量賦值。這是一種很簡(jiǎn)單的同步場(chǎng)景,這時(shí)候使用volatile的開(kāi)銷(xiāo)將會(huì )非常小。
可能 很多人都覺(jué)得莫名其妙,說(shuō)JVM的內存模型,怎么會(huì )扯到cpu上去呢?在此,我認為很有必要闡述下,免 得很多人看得不明不白的。先拋開(kāi)java虛擬機不談,我們都知道,現在的計算機,cpu在計算的時(shí)候,并不總是從內存讀取數據,它的數據讀取順序優(yōu)先級 是:寄存器-高速緩存-內存。線(xiàn)程耗費的是CPU,線(xiàn)程計算的時(shí)候,原始的數據來(lái)自?xún)却?,在計算過(guò)程中,有些數據可能被頻繁讀取,這些數據被存儲在寄存器 和高速緩存中,當線(xiàn)程計算完后,這些緩存的數據在適當的時(shí)候應該寫(xiě)回內存。當個(gè)多個(gè)線(xiàn)程同時(shí)讀寫(xiě)某個(gè)內存數據時(shí),就會(huì )產(chǎn)生多線(xiàn)程并發(fā)問(wèn)題,涉及到三個(gè)特 性:原子性,有序性,可見(jiàn)性。在《線(xiàn)程安全總結》這篇文章中,為了理解方便,我把原子性和有序性統一叫做“多線(xiàn)程執行有序性”。支持多線(xiàn)程的平臺都會(huì )面臨 這種問(wèn)題,運行在多線(xiàn)程平臺上支持多線(xiàn)程的語(yǔ)言應該提供解決該問(wèn)題的方案。
那么,我們看看JVM,JVM是一個(gè)虛擬 的計算機,它也會(huì )面臨多線(xiàn)程并發(fā)問(wèn)題,java程序運行在java虛擬機平臺上,java程序員不可能直接去控制底層線(xiàn)程對寄存器高速緩存內存之間的同 步,那么java從語(yǔ)法層面,應該給開(kāi)發(fā)人員提供一種解決方案,這個(gè)方案就是諸如 synchronized, volatile,鎖機制(如同步塊,就緒隊 列,阻塞隊列)等等。這些方案只是語(yǔ)法層面的,但我們要從本質(zhì)上去理解它,不能僅僅知道一個(gè) synchronized 可以保證同步就完了。 在這里我說(shuō)的是jvm的內存模型,是動(dòng)態(tài)的,面向多線(xiàn)程并發(fā)的,沿襲JSL的“working memory”的說(shuō)法,只是不想牽扯到太多底層細節,因為 《線(xiàn)程安全總結》這篇文章意在說(shuō)明怎樣從語(yǔ)法層面去理解java的線(xiàn)程同步,知道各個(gè)關(guān)鍵字的使用場(chǎng) 景。
今天有人問(wèn)我,那 java的線(xiàn)程不是有棧嗎?難道棧不是工作內存嗎?工作內存這四個(gè)字得放到具體的場(chǎng)景 中描述,方能體現它具體的意義,在描述JVM的線(xiàn)程同步時(shí),工作內存指的是寄存器和告訴緩存的抽象描述,具體請自行參閱JLS。上面講的都是動(dòng)態(tài)的內存模 型,甚至已經(jīng)超越了JVM的范圍,那么JVM的內存靜態(tài)存儲是怎么劃分的?今天還有人問(wèn)我,jvm的內存模型不是有eden區嗎?也不見(jiàn)你提起。我跟他 說(shuō),這是兩個(gè)角度去看的,甚至是兩個(gè)不同的范圍,動(dòng)態(tài)的線(xiàn)程同步的內存模型,涵蓋了cpu,寄存器,高速緩存,內存;JVM的靜態(tài)內存儲模型只是一種對內 存的物理劃分而已,它只局限在內存,而且只局限在JVM的內存。那些什么線(xiàn)程棧,eden區都僅僅在JVM內存。
說(shuō)說(shuō)JVM的線(xiàn)程棧和有個(gè)朋友反復跟我糾結的eden區吧。JVM的內存,被劃分了很多的區域:
1.程序計數器
每一個(gè)Java線(xiàn)程都有一個(gè)程序計數器來(lái)用于保存程序執行到當前方法的哪一個(gè)指令。
2.線(xiàn) 程棧
線(xiàn)程的每個(gè)方法被執行的時(shí)候,都會(huì )同時(shí)創(chuàng )建一個(gè)幀(Frame)用 于存儲本地變量表、操作棧、動(dòng)態(tài)鏈接、方法出入口等信息。每一個(gè)方法的調用至完成,就意味著(zhù)一個(gè)幀在VM棧中的入棧至出棧的過(guò)程。如果線(xiàn)程請求的棧深度大 于虛擬機所允許的深度,將拋出StackOverflowError異常;如果VM??梢詣?dòng)態(tài)擴展(VM Spec中允許固定長(cháng)度的VM棧),當擴展時(shí)無(wú)法申請到足夠內存則拋出OutOfMemoryError異常。
3.本地方法 棧
4.堆
每個(gè)線(xiàn)程的棧都是該線(xiàn)程私有的,堆則是所有線(xiàn)程共享 的。當我們new一個(gè)對象時(shí),該對象就被分配到了堆中。但是堆,并不是一個(gè)簡(jiǎn)單的概念,堆區又劃分了很多區域,為什么堆劃分成這么多區域,這是為了JVM 的內存垃圾收集,似乎越扯越遠了,扯到垃圾收集了,現在的jvm的gc都是按代收集,堆區大致被分為三大塊:新生代,舊生代,持久代(虛擬的);新生代又 分為eden區,s0區,s1區。新建一個(gè)對象時(shí),基本小的對象,生命周期短的對象都會(huì )放在新生代的eden區中,eden區滿(mǎn)時(shí),有一個(gè)小范圍的 gc(minor gc),整個(gè)新生代滿(mǎn)時(shí),會(huì )有一個(gè)大范圍的gc(major gc),將新生代里的部分對象轉到舊生代里。
5. 方法區
其實(shí)就是永久代(Permanent Generation),方法區中存放了每個(gè)Class的結構信息,包括常量池、字段描述、方法描述等等。VM Space描述中對這個(gè)區域的限制非常寬松,除了和Java堆一樣不需要連續的內存,也可以選擇固定大小或者可擴展外,甚至可以選擇不實(shí)現垃圾收集。相對 來(lái)說(shuō),垃圾收集行為在這個(gè)區域是相對比較少發(fā)生的,但并不是某些描述那樣永久代不會(huì )發(fā)生GC(至 少對當前主流的商業(yè)JVM實(shí)現來(lái)說(shuō)是如此),這里的GC主要是對常量池的回收和對類(lèi)的卸載,雖然回收的“成績(jì)”一般也比較差強人意,尤其是類(lèi)卸載,條件相 當苛刻。
6.常量池
Class文件中除 了有類(lèi)的版本、字段、方法、接口等描述等信息外,還有一項信息是常量表(constant_pool table),用于存放編譯期已可知的常量,這部分內容將在類(lèi)加載后進(jìn)入方法區(永久代)存放。但是Java語(yǔ)言并不要求常量一定只有編譯期預置入 Class的常量表的內容才能進(jìn)入方法區常量池,運行期間也可將新內容放入常量池(最典型的String.intern()方法)。
關(guān)于垃圾收集,在此不多說(shuō),流到垃圾收集那一章再詳細說(shuō)吧。關(guān)于java的同步,其實(shí)還有基于 CPU原語(yǔ)的比較并交換的非阻塞算法(CAS),不過(guò)這個(gè)在java的并發(fā)包里已經(jīng)實(shí)現了很多,因此關(guān)于這點(diǎn),就留到j(luò )ava并發(fā)包那一章介紹吧。后面我 會(huì )專(zhuān)門(mén)寫(xiě)一篇文章,JVM內存與垃圾收集。