HotSpot虛擬機中,對象在內存中存儲的佈局能夠分爲
對象頭
,實例數據
,對齊填充
三個區域。本文所說環境均爲HotSpot虛擬機。即輸入java -version
返回的虛擬機版本:html
java version "1.8.0_111" Java(TM) SE Runtime Environment (build 1.8.0_111-b14) Java HotSpot(TM) 64-Bit Server VM (build 25.111-b14, mixed mode)
在jvm中,任何對象都是8個字節爲粒度進行對齊的,這是對象內存佈局的第一個規則。java
若是調用new Object(),因爲Object類並無其餘沒有其餘可存儲的成員,那麼僅僅使用堆中的8個字節來保存兩個字的頭部便可。
除了上面所說的8個字節的頭部(關於對象頭,在下面會有詳細解釋),類屬性緊隨其後。屬性一般根據其大小來排列。例如,整型(int)以4個字節爲單位對齊,長整型(long)以8個字節爲單位對齊。這裏是出於性能考慮而這麼設計的:一般狀況下,若是數據以4字節爲單位對齊,那麼從內存中讀4字節的數據並寫入處處理器的4字節寄存器是性價比更高的。web
爲了節省內存,Sun VM並無按照屬性聲明時的順序來進行內存佈局。實際上,屬性在內存中按照下面的順序來組織:數據庫
雙精度型(doubles)和長整型(longs)數組
整型(ints)和浮點型(floats)安全
短整型(shorts)和字符型(chars)多線程
布爾型(booleans)和字節型(bytes)併發
引用類型(references)dom
內存使用率會經過這個機制獲得優化。例如,以下聲明一個類:jvm
class MyClass { byte a; int c; boolean d; long e; Object f; }
若是JVM並無打亂屬性的聲明順序,其對象內存佈局將會是下面這個樣子:
[HEADER: 8 bytes] 8 [a: 1 byte ] 9 [padding: 3 bytes] 12 [c: 4 bytes] 16 [d: 1 byte ] 17 [padding: 7 bytes] 24 [e: 8 bytes] 32 [f: 4 bytes] 36 [padding: 4 bytes] 40
此時,用於佔位的14個字節是浪費的,這個對象一共使用了40個字節的內存空間。可是,若是用上面的規則對這些對象從新排序,其內存結果會變成下面這個樣子:
[HEADER: 8 bytes] 8 [e: 8 bytes] 16 [c: 4 bytes] 20 [a: 1 byte ] 21 [d: 1 byte ] 22 [padding: 2 bytes] 24 [f: 4 bytes] 28 [padding: 4 bytes] 32
此次,用於佔位的只有6個字節,這個對象使用了32個字節的內存空間。
規則2:類屬性按照以下優先級進行排列:長整型和雙精度類型;整型和浮點型;字符和短整型;字節類型和布爾類型,最後是引用類型。這些屬性都按照各自的單位對齊。
如今咱們知道如何計算一個繼承了Object的類的實例的內存大小了。下面這個例子用來作下練習: java.lang.Boolean。這是其內存佈局:
[HEADER: 8 bytes] 8 [value: 1 byte ] 9 [padding: 7 bytes] 16
Boolean類的實例佔用16個字節的內存!驚訝吧?(別忘了最後用來佔位的7個字節)。
規則3:不一樣類繼承關係中的成員不能混合排列。首先按照規則2處理父類中的成員,接着纔是子類的成員。
舉例以下:
class A { long a; int b; int c; } class B extends A { long d; }
類B的實例在內存中的存儲以下:
[HEADER: 8 bytes] 8 [a: 8 bytes] 16 [b: 4 bytes] 20 [c: 4 bytes] 24 [d: 8 bytes] 32
若是父類中的成員的大小沒法知足4個字節這個基本單位,那麼下一條規則就會起做用:
規則4:當父類中最後一個成員和子類第一個成員的間隔若是不夠4個字節的話,就必須擴展到4個字節的基本單位。
class A { byte a; } class B { byte b; } [HEADER: 8 bytes] 8 [a: 1 byte ] 9 [padding: 3 bytes] 12 [b: 1 byte ] 13 [padding: 3 bytes] 16
注意到成員a被擴充了3個字節以保證和成員b之間的間隔是4個字節。這個空間不能被類B使用,所以被浪費了。
規則5:若是子類第一個成員是一個雙精度或者長整型,而且父類並無用完8個字節,JVM會破壞規則2,按照整形(int),短整型(short),字節型(byte),引用類型(reference)的順序,向未填滿的空間填充。
class A { byte a; } class B { long b; short c; byte d; } [HEADER: 8 bytes] 8 [a: 1 byte ] 9 [padding: 3 bytes] 12 [c: 2 bytes] 14 [d: 1 byte ] 15 [padding: 1 byte ] 16 [b: 8 bytes] 24
在第12字節處,類A「結束」的地方,JVM沒有遵照規則2,而是在長整型以前插入一個短整型和一個字節型成員,這樣能夠避免浪費3個字節。
數組有一個額外的頭部成員,用來存放「長度」變量。數組元素以及數組自己,跟其餘常規對象一樣,都須要遵照8個字節的邊界規則。
下面是一個有3個元素的字節數組的內存佈局:
[HEADER: 12 bytes] 12 [[0]: 1 byte ] 13 [[1]: 1 byte ] 14 [[2]: 1 byte ] 15 [padding: 1 byte ] 16
下面是一個有3個元素的長整型數字的內存佈局:
[HEADER: 12 bytes] 12 [padding: 4 bytes] 16 [[0]: 8 bytes] 24 [[1]: 8 bytes] 32 [[2]: 8 bytes] 40
非靜態內部類(Non-static inner classes)有一個額外的「隱藏」成員,這個成員是一個指向外部類的引用變量。這個成員是一個普通引用,所以遵照引用內存佈局的規則。內部類所以有4個字節的額外開銷。
對象頭主要包含兩部分信息,第一部分用於存儲對象自身運行時數據,如哈希碼,GC分代年齡(能夠查看上一篇關於java內存回收分析的文章),鎖狀態標誌,線程持有鎖,偏向線程ID,偏向時間戳等。
若是對象是數組類型,則虛擬機用3個字寬存儲對象頭,若是是非數組類型,則用2個字寬存儲對象頭。下圖是一個32位虛擬機mark部分佔用內存分佈狀況
此圖來源:http://blog.csdn.net/zhoufanyang_china/article/details/54601311
另外一部分是klass類型指針,即對象指向它的類元數據的指針,虛擬機經過這個指針來肯定這個對象是哪一個類的實例,具體結構參考下圖。
在32位系統下,存放Class指針的空間大小是4字節,MarkWord是4字節,對象頭爲8字節。 在64位系統下,存放Class指針的空間大小是8字節,MarkWord是8字節,對象頭爲16字節。 64位開啓指針壓縮的狀況下,存放Class指針的空間大小是4字節,MarkWord是8字節,對象頭爲12字節。 數組長度4字節+數組對象頭8字節(對象引用4字節(未開啓指針壓縮的64位爲8字節)+數組markword爲4字節(64位未開啓指針壓縮的爲8字節))+對齊4=16字節。 靜態屬性不算在對象大小內。
對象實際數據,大下爲實際數據的大小
按8字節對齊,參照上面內存佈局部分
synchronized到底鎖的是對象仍是代碼片斷?
例:
package com.startclan.thread; /** * Created by wongloong on 17-5-20. */ public class TestSync { public synchronized void test() { System.out.println("test1 start"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("test1 end"); } public synchronized void test2() { System.out.println("test2 start"); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("test2 end"); } }
package com.startclan.thread; /** * Created by wongloong on 17-5-20. */ public class TestSyncStatic { public static synchronized void test() { System.out.println("test1 start"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("test1 end"); } public static synchronized void test2() { System.out.println("test2 start"); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("test2 end"); } }
package com.startclan; import com.startclan.thread.TestSync; import com.startclan.thread.TestSyncStatic; import org.junit.Test; /** * Created by wongloong on 17-5-18. */ public class TestWithThread { @Test public void testThread1() throws Exception { final TestSync t1 = new TestSync(); /** * 測試synchronized同步非static代碼塊 * 此處會先執行test方法而後執行test2方法,說明synchronized在同步非static方法時, * 只能同步同一對象的同一實例進行同步 */ new Thread(new Runnable() { @Override public void run() { t1.test(); } }).start(); new Thread(new Runnable() { @Override public void run() { t1.test2(); } }).start(); Thread.sleep(4000); } @Test public void testThread2() throws Exception { final TestSync t1 = new TestSync(); final TestSync t2 = new TestSync(); /** * 測試synchronized同步非static代碼塊 * t1 t2不一樣對象, * 不能同步方法 */ new Thread(new Runnable() { @Override public void run() { t1.test(); } }).start(); new Thread(new Runnable() { @Override public void run() { t2.test2(); } }).start(); Thread.sleep(4000); } @Test public void testThread3() throws Exception { final TestSyncStatic tss1 = new TestSyncStatic(); final TestSyncStatic tss2 = new TestSyncStatic(); /** * 測試synchronized 同步 static代碼塊 * 因爲method1和method2都屬於靜態同步方法, * 因此調用的時候須要獲取同一個類上monitor(每一個類只對應一個class對象), * 因此也只能順序的執行。 */ new Thread(new Runnable() { @Override public void run() { tss1.test(); } }).start(); new Thread(new Runnable() { @Override public void run() { tss2.test2(); } }).start(); Thread.sleep(4000); } }
此時輸出結果爲:
------------------------1------------------------- test1 start test1 end test2 start test2 end -----------------------2------------------------- test1 start test2 start test2 end test1 end -----------------------3------------------------- test1 start test1 end test2 start test2 end
結論:
synchronized(this)以及非static的synchronized方法,只能防止多個線程同時執行同一個實例的同步代碼段(在第一段測試代碼中,分別new了三個Mythread類,因此並不會執行同步)
synchronized(xx.class)及static的synchronized方法,能夠防止多個線程同時執行同一個對象的多個實例同步的代碼段
synchronize原理
每個對象頭信息都包含一個鎖定狀態,能夠看上面的mark word的圖解。當線程進入對象中,嘗試獲取鎖的全部權,若是爲鎖的值爲0,則該線程進入,並設置爲1,該線程爲鎖的擁有者。若是線程已經佔用該鎖,只是從新進入,而且鎖值+1.當線程退出時則-1.若是其餘線程訪問這個對象實例,則改線程堵塞。直到鎖值爲0的時候,在從新嘗試取得鎖的全部權。
用volatile修飾的變量,線程在每次使用變量的時候,都會讀取變量修改後的最新的值而不是線程內的變量副本。volatile很容易被誤用,用來進行原子性操做。
volatile保證此變量對全部的線程的可見性,當一個線程修改了這個變量的值,volatile 保證了新值能當即同步到主內存,以及每次使用前當即從主內存刷新。但普通變量作不到這點,普通變量的值在線程間傳遞均須要經過主內存來完成。
volatile禁止指令重排序優化。
在jvm內存分析中,能夠知道2棧(虛擬機棧,本地方法棧)及程序計數器是線程私有的。也就是每個線程運行時都有一個「2棧」,這2棧中保存了線程運行時的變量信息。先線程訪問某一個對象值的時候,首先經過對象引用找到對應在堆(公有)內存變量的值,而後把堆內存變量的具體值加載到線程本地中,創建一個變量副本,以後線程就再也不和堆內存中的變量值有關係,而是直接修改線程本地的變量副本的值,在修改完成後的某一時刻(線程退出前),再把線程中變量副本的值回寫到對象在堆內存中的變量。這樣堆中對象的值就會發生變化。對於volatile修飾的變量,jvm只保證從主內存加載到線程工做內存的值是最新的。並不能保證同步。
例如假如線程1,線程2 在進行read,load 操做中,發現主內存中count的值都是5,那麼都會加載這個最新的值
在線程1堆count進行修改以後,會write到主內存中,主內存中的count變量就會變爲6
線程2因爲已經進行read,load操做,在進行運算以後,也會更新主內存count的變量值爲6
致使兩個線程及時用volatile關鍵字修改以後,仍是會存在併發的狀況。
擴展閱讀:http://www.infoq.com/cn/articles/ftf-java-volatile
一、sleep()
使當前線程(即調用該方法的線程)暫停執行一段時間,讓其餘線程有機會繼續執行,但它並不釋放對象鎖。也就是說若是有synchronized同步快,其餘線程仍然不能訪問共享數據。注意該方法要捕捉異常。
例若有兩個線程同時執行(沒有synchronized)一個線程優先級爲MAX_PRIORITY,另外一個爲MIN_PRIORITY,若是沒有Sleep()方法,只有高優先級的線程執行完畢後,低優先級的線程纔可以執行;可是高優先級的線程sleep(500)後,低優先級就有機會執行了。
總之,sleep()可使低優先級的線程獲得執行的機會,固然也可讓同優先級、高優先級的線程有執行的機會。
二、join()
join()方法使調用該方法的線程在此以前執行完畢,也就是等待該方法的線程執行完畢後再往下繼續執行。注意該方法也須要捕捉異常。
三、yield()
該方法與sleep()相似,只是不能由用戶指定暫停多長時間,而且yield()方法只能讓同優先級的線程有執行的機會。yield也不會放棄鎖。
四、wait()和notify()、notifyAll()
這三個方法用於協調多個線程對共享數據的存取,因此必須在synchronized語句塊內使用。synchronized關鍵字用於保護共享數據,阻止其餘線程對共享數據的存取,可是這樣程序的流程就很不靈活了,如何才能在當前線程還沒退出synchronized數據塊時讓其餘線程也有機會訪問共享數據呢?此時就用這三個方法來靈活控制。
wait()能夠設置wait時間,若是設置了時間就不用notify。到時間後會自動喚醒繼續執行。
wait()方法使當前線程暫停執行並釋放對象鎖標示,讓其餘線程能夠進入synchronized數據塊,當前線程被放入對象等待池中。當調用notify()方法後,將從對象的等待池中移走一個任意的線程並放到鎖標誌等待池中,只有鎖標誌等待池中線程可以獲取鎖標誌;若是鎖標誌等待池中沒有線程,則notify()不起做用。
notifyAll()則從對象等待池中移走全部等待那個對象的線程並放到鎖標誌等待池中。
@Test public void testThreadAndYield() throws Exception { Thread producer = new Thread(new Runnable() { @Override public void run() { testMethod("producer", 1000); } }); Thread consumer = new Thread(new Runnable() { @Override public void run() { testMethod("consumer", 10); } }); producer.start(); consumer.start(); producer.join(); } public synchronized void testMethod(String name, int waitTime) { System.out.println(Thread.currentThread().getId()); try { wait(waitTime); //wait(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("test method name is " + name); }
定義:線程A 須要判斷一個變量的狀態,而後根據這個變量的狀態來執行某個操做。在執行這個操做以前,這個變量的狀態可能會被其餘線程修改
eg:簡單的單例模式
public class LazyInitRace { private ExpensiveObject instance=null; public ExpensiveObject getInstance(){ if(instance==null){ instance=new ExpensiveObject(); } return instance; } }
在LazyInitRace 中包含了一個競態條件,它可能會破壞這個類的正確性。假定線程A和線程B 同時執行getInstance 方法。A 看到instance 爲空,所以A建立一個新的ExpensiveObject實例。B 一樣須要判斷instance 是否爲空。此時的instance是否爲空,要取決於不可預測的時序,包括線程的調度方式,以及A 須要花多長時間來初始化ExpensiveObject並設置instance。若是當B檢查時,instance爲空,那麼在兩次調用getInstance 時可能會獲得不一樣的對象。
經過wait()釋放鎖,及notify()來喚醒組合進行共享數據
package com.startclan.thread; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.LinkedList; import java.util.Queue; /** * Created by wongloong on 17-5-22. */ public class TestShare { public static void main(String args[]) throws InterruptedException { final Queue sharedQ = new LinkedList(); Thread producer = new Producer(sharedQ); Thread consumer = new Consumer(sharedQ); producer.start(); consumer.start(); Thread.sleep(10000); } } class Producer extends Thread { private static final Logger logger = LoggerFactory.getLogger(Producer.class); private final Queue sharedQ; public Producer(Queue sharedQ) { super("Producer"); this.sharedQ = sharedQ; } @Override public void run() { for (int i = 0; i < 4; i++) { synchronized (sharedQ) { //waiting condition - wait until Queue is not empty while (sharedQ.size() >= 1) { try { logger.info("Queue is full, waiting"); sharedQ.wait(); } catch (InterruptedException ex) { ex.printStackTrace(); } } logger.info("producing : " + i); sharedQ.add(i); sharedQ.notify(); } } } } class Consumer extends Thread { private static final Logger logger = LoggerFactory.getLogger(Consumer.class); private final Queue sharedQ; public Consumer(Queue sharedQ) { super("Consumer"); this.sharedQ = sharedQ; } @Override public void run() { while (true) { synchronized (sharedQ) { //waiting condition - wait until Queue is not empty while (sharedQ.size() == 0) { try { logger.info("Queue is empty, waiting"); sharedQ.wait(); } catch (InterruptedException ex) { ex.printStackTrace(); } } int number = (int) sharedQ.poll(); logger.info("consuming : " + number); sharedQ.notify(); //termination condition if (number == 3) { break; } } } } }
這是個設計相關的問題,一個很明顯的緣由是JAVA提供的鎖是對象級的而不是線程級的,每一個對象都有鎖,經過線程得到。若是線程須要等待某些鎖那麼調用對象中的wait()方法就有意義了。若是wait()方法定義在Thread類中,線程正在等待的是哪一個鎖就不明顯了。簡單的說,因爲wait,notify和notifyAll都是鎖級別的操做,因此把他們定義在Object類中由於鎖屬於對象。
ThreadLocal是Java裏一種特殊的變量。每一個線程都有一個ThreadLocal就是每一個線程都擁有了本身獨立的一個變量,競爭條件被完全消除了。它是爲建立代價高昂的對象獲取線程安全的好方法,好比你能夠用ThreadLocal讓SimpleDateFormat變成線程安全的,由於那個類建立代價高昂且每次調用都須要建立不一樣的實例因此不值得在局部範圍使用它,若是爲每一個線程提供一個本身獨有的變量拷貝,將大大提升效率。首先,經過複用減小了代價高昂的對象的建立個數。其次,你在沒有使用高代價的同步或者不變性的狀況下得到了線程安全。線程局部變量的另外一個不錯的例子是ThreadLocalRandom類,它在多線程環境中減小了建立代價高昂的Random對象的個數
翻譯成中文名爲線程局部變量。功能簡單,就是爲每個使用該變量的線程都提供一個變量值的副本。
每一個線程的變量副本是存儲在哪裏的?
看一下ThreadLocal的set源碼
//ThreadLocal源文件
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else createMap(t, value);//第一次設置值的時候,若是沒有map則建立一個map
}
//建立一個Map,而且設置Thread的threadLocals指向新建立的ThreadLocalMap,而且以當前的ThreadLocal做爲key初始。看一下Thread.threadlocals是什麼?
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
//Thread源文件
/* ThreadLocal values pertaining to this thread. This map is maintained * by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
經過源碼的分析,能夠知道,變量最終是存儲在Thread中的。而不是ThreadLocal中。一個線程中能夠存在多個ThreadLocal,他們以ThreadLocal爲key造成的map存儲在線程中。
知道了ThreadLocal的原理,如今來看一下ThreadLocal的初始化,設置默認值
public static final ThreadLocal<Integer> local = new ThreadLocal<Integer>() {
@Override
protected Integer initialValue() {
return 0;
}
};
在上面提到過,每一個thread中都存在一個map, map的類型是ThreadLocal.ThreadLocalMap. Map中的key爲一個threadlocal實例. 這個Map的確使用了弱引用,不過弱引用只是針對key. 每一個key都弱引用指向threadlocal. 當把threadlocal實例置爲null之後,沒有任何強引用指向threadlocal實例,因此threadlocal將會被gc回收. 可是,咱們的value卻不能回收,由於存在一條從current thread鏈接過來的強引用. 只有當前thread結束之後, current thread就不會存在棧中,強引用斷開, Current Thread, Map, value將所有被GC回收.
因此得出一個結論就是隻要這個線程對象被gc回收,就不會出現內存泄露,但在threadLocal設爲null和線程結束這段時間不會被回收的,就發生了咱們認爲的內存泄露。其實這是一個對概念理解的不一致,也沒什麼好爭論的。最要命的是線程對象不被回收的狀況,這就發生了真正意義上的內存泄露。好比使用線程池的時候,線程結束是不會銷燬的,會再次使用的。就可能出現內存泄露。
java中的Semaphore是一種新的同步類,它是一個計數信號。從概念上講,從概念上講,信號量維護了一個許可集合。若有必要,在許可可用前會阻塞每個 acquire(),而後再獲取該許可。每一個 release()添加一個許可,從而可能釋放一個正在阻塞的獲取者。可是,不使用實際的許可對象,Semaphore只對可用許可的號碼進行計數,並採起相應的行動。信號量經常用於多線程的代碼中,好比數據庫鏈接池。
import java.util.concurrent.Semaphore;
public class SemaphoreTest {
Semaphore binary = new Semaphore(1);
public static void main(String args[]) {
final SemaphoreTest test = new SemaphoreTest();
new Thread(){
@Override
public void run(){
test.mutualExclusion();
}
}.start();
new Thread(){
@Override
public void run(){
test.mutualExclusion();
}
}.start();
}
private void mutualExclusion() {
try {
binary.acquire();
//mutual exclusive region
System.out.println(Thread.currentThread().getName() + " inside mutual exclusive region");
Thread.sleep(1000);
} catch (InterruptedException i.e.) {
ie.printStackTrace();
} finally {
binary.release();
System.out.println(Thread.currentThread().getName() + " outside of mutual exclusive region");
}
}
}
Output:
Thread-0 inside mutual exclusive region
Thread-0 outside of mutual exclusive region
Thread-1 inside mutual exclusive region
Thread-1 outside of mutual exclusive region
Read more: http://javarevisited.blogspot.com/2012/05/counting-semaphore-example-in-java-5.html#ixzz4htCgzO4Z
兩個方法均可以向線程池提交任務,execute()方法的返回類型是void,它定義在Executor接口中, 而submit()方法能夠返回持有計算結果的Future對象,它定義在ExecutorService接口中,它擴展了Executor接口,其它線程池類像ThreadPoolExecutor和ScheduledThreadPoolExecutor都有這些方法。
參考資料
http://hw1287789687.iteye.com/blog/2007134
http://javarevisited.blogspot.tw/2013/12/inter-thread-communication-in-java-wait-notify-example.html#axzz4hnY25Gh7
http://blog.csdn.net/lhqj1992/article/details/52451136
http://javarevisited.blogspot.tw/2012/05/counting-semaphore-example-in-java-5.html#axzz4hnY25Gh7
http://www.importnew.com/12773.html