1,volatile 變量的內存可見性是基於內存屏障(Memory Barrier)實現。
- 內存屏障,又稱內存柵欄,是一個 CPU 指令。
- 在程序運行時,爲了提高執行性能,編譯器和處理器會對指令進行重排序,JMM 爲了保證在不同的編譯器和 CPU 上有相同的結果,通過插入特定類型的內存屏障來禁止特定類型的編譯器重排序和處理器重排序,插入一條內存屏障會告訴編譯器和 CPU:不管什麼指令都不能和這條 Memory Barrier 指令重排序。
- 在每個 volatile 寫操作的前面插入一個 StoreStore 屏障。(禁止上面的普通寫和下面的 volatile 寫重排序)
- 在每個 volatile 寫操作的後面插入一個 StoreLoad 屏障。(防止上面的 volatile 寫與下面可能有的 volatile 讀/寫重排序)
- 在每個 volatile 讀操作的後面插入一個 LoadLoad 屏障。(禁止下面所有的普通讀操作和上面的 volatile 讀重排序)
- 在每個 volatile 讀操作的後面插入一個 LoadStore 屏障。(禁止下面所有的普通寫操作和上面的 volatile 讀重排序)
- volatile應用場景(對變量的寫操作不依賴於當前值,該變量沒有包含在具有其他變量的不變式中)
- 狀態標誌,一次性安全發佈,溫度計,安全計數(++i),雙重檢查(單例模式)
2,java中的鎖
- 在 Java 中主要2種加鎖機制:
- synchronized 關鍵字
- java.util.concurrent.Lock (Lock是一個接口,ReentrantLock是該接口一個很常用的實現)
- 這兩種機制的底層原理存在一定的差別
- synchronized 關鍵字通過一對字節碼指令 monitorenter/monitorexit 實現, 這對指令被 JVM 規範所描述
- java.util.concurrent.Lock 通過 Java 代碼搭配sun.misc.Unsafe 中的本地調用實現的
-
Java SE1.6 爲了改善性能, 使得 JVM 會根據競爭情況, 使用3 種不同的鎖機制,synchronized 關鍵字之鎖的升級(偏向鎖->輕量級鎖->重量級鎖)場景:偏向鎖(只有一個線程訪問同步塊)輕量(追求響應時間)重量(追求吞吐量),只能升級不能降級
-
對象頭:markWord(2字寬(8字節)hashcode,分代年齡、鎖信息),對象地址,對象長度(如果是數組),(偏向鎖epoch(用於檢測是否有效))
-
cas 三大問題 (ABA(解決:AtomicStampedReference)、循環長(解決:可以sleep,cpu pause)、只能保證一個共享變量(解決:AtomicReference))
3,Java內存模型(主內存與工作內存)屬於數據交互的理解
4,Java內存區域:
-
線程共享區:方法區、堆
-
線程私有區:虛擬機棧、本地方法棧、程序計數器
-
棧:爲即時調用的方法開闢空間,存儲局部變量值(基本數據類型),局部變量引用。
-
堆:存放引用類型的對象,即new出來的對象、數組值、類的非靜態成員變量值(基本數據類型)、非靜態成員變量引用。
-
方法區:存放class二進制文件。包含類信息、靜態變量,常量池(String字符串和final修飾的常量值等),類的版本號等基本信息。
4,單例模式中 voliate 是爲了禁止重排序,class靜態類的方式是爲了讓線程看不到重排序
5,類初始化順序:父(靜態變量、靜態初始化塊) > 子(靜態變量、靜態初始化塊) > 父(變量、初始化塊) > 父(構造器) > 子(變量、初始化塊) > 子(構造器)
內存區域 | 描述 | VM Option | 異常 |
---|---|---|---|
虛擬機棧 | 存放編譯器可知的各種基本類型,對象引用和returnAddress類型 | -Xss160K 每個線程的棧大小 | StackOverflowError/OutOfMemoryError |
Java堆 | 存放對象實例 | -Xms10M 最小值 -Xmx20M 最大值 |
OutOfMemory: Java heap space |
運行時常亮池 | 存放編譯期生成的字面量和符號引用,運行期也能放入常量池(string.intern())。JDK 1.7之前在方法區中,JDK 1.7及之後移至堆中 | 隨方法區或堆設置 | OutOfMemoryError |
方法區 | 存儲虛擬機加載的類信息、常亮、靜態變量、即時編譯器編譯後的代碼等數據,又稱爲永久代(Permanent Generation) | -XX:PermSize=10M 初始值 -XX:MaxPermSize=20M 最大值 |
OutOfMemoryError: PermGen space |
直接內存 | 在JDK 1.4中加入NIO類,直接分配堆外內存 | -XX:MaxDirectMemorySize=10M, 如果不指定默認與-Xmx一樣 |
OutOfMemoryError |
JVM調優基本思路
如果CPU使用率較高,GC頻繁且GC時間長,可能就需要JVM調優了(基本思路就是讓每一次GC都回收儘可能多的對象)
對於CMS來說,要合理設置年輕代和年老代的大小。該如何確定它們的大小呢?這是一個迭代的過程,可以先採用JVM的默認值,然後通過壓測分析GC日誌。
如果看年輕代的內存使用率處在高位,導致頻繁的Minor GC,而頻繁GC的效率又不高,說明對象沒那麼快能被回收,這時年輕代可以適當調大一點。
如果看年老代的內存使用率處在高位,導致頻繁的Full GC,這樣分兩種情況:如果每次Full GC後年老代的內存佔用率沒有下來,可以懷疑是內存泄漏;如果Full GC後年老代的內存佔用率下來了,說明不是內存泄漏,要考慮調大年老代。