JVM从入门到精通(三):热加载的实现原理,Java内存模型,缓存行,指令重排,合并写技术等
上節回顧:類加載機制
雙親委派機制
parent只是一個成員變量,不是繼承關系。
上節課的遺留問題
parent是怎么指定的?
手動指定parent:
雙親委派機制可以被打破嗎?
雙親委派機制是在ClassLoader類里的LoadClass()方法已經寫死的,你只需重寫FingClass()方法就可以了。那怎么打破它呢?
熱加載的實現原理
Tomcat把整個ClassLoader全部干掉,再用自己實現的ClassLoader把新修改過的類的Class重新Load一遍。
正確版本:將一個class加載兩次
package com.mashibing.jvm.c2_classloader;import com.mashibing.jvm.Hello;import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream;public class T012_ClassReloading2 {private static class MyLoader extends ClassLoader {@Overridepublic Class<?> loadClass(String name) throws ClassNotFoundException {File f = new File("C:/work/ijprojects/JVM/out/production/JVM/" + name.replace(".", "/").concat(".class"));if(!f.exists()) return super.loadClass(name);try {InputStream is = new FileInputStream(f);byte[] b = new byte[is.available()];is.read(b);return defineClass(name, b, 0, b.length);} catch (IOException e) {e.printStackTrace();}return super.loadClass(name);}}public static void main(String[] args) throws Exception {MyLoader m = new MyLoader();Class clazz = m.loadClass("com.mashibing.jvm.Hello");m = new MyLoader();Class clazzNew = m.loadClass("com.mashibing.jvm.Hello");System.out.println(clazz == clazzNew);} }無效版本:使用的仍然是加載過的類
package com.mashibing.jvm.c2_classloader;public class T011_ClassReloading1 {public static void main(String[] args) throws Exception {T006_MSBClassLoader msbClassLoader = new T006_MSBClassLoader();Class clazz = msbClassLoader.loadClass("com.mashibing.jvm.Hello");msbClassLoader = null;System.out.println(clazz.hashCode());msbClassLoader = null;msbClassLoader = new T006_MSBClassLoader();Class clazz1 = msbClassLoader.loadClass("com.mashibing.jvm.Hello");System.out.println(clazz1.hashCode());System.out.println(clazz == clazz1);} }Linking
證明默認值的存在
輸出結果為2. 如果交換10 11行,輸出結果為3
New 一個對象的過程
- 先分配內存
- 再賦默認值
- 再賦初始值
面試題:DCL(Double Check Lock)單例要不要加Volitile?
需要。因為有指令重排的問題。
初始化一半的時候,單例已經存在了,處于半初始化的狀態。此時另外一個線程來了,直接把半初始化的對象取走了,出現值的問題。
下圖4、7可能會發生重排
官網解釋:
new: Create new object
dup: 復制操作數堆棧上的頂部值,并將復制的值壓入操作數堆棧。
invoke special: 調用實例方法; 對超類、私有和實例初始化方法調用的特殊處理
astore_<n>: 將引用存儲到局部變量中。操作數堆棧頂部的objectref的類型必須是returnAddress或reference類型。它從操作數堆棧中彈出,并將處的局部變量的值設置為objectref。
JMM(Java Memory Model)
硬件層的并發優化基礎知識
不同層級的速度差別有多大?
產生數據的不一致問題:兩個CPU之間的緩存怎么保持一致?
老的CPU,總線鎖:效率偏低
新的CPU:各種各樣的一致性協議,例如,intel的MESI協議
4種狀態的含義:我改過、只有我在用、別人也在讀、失效
現在,CPU的數據一致性實現是通過 緩存一致性協議(MESI…)+總線鎖 共同實現的。
緩存行 CacheLine
讀取緩存以cacheline為基本單位,多數的實現為64kB(64字節,512位)
偽共享:位于同一緩存行的兩個不同數據被兩個不同CPU鎖定,產生相互影響的問題。
示例1:兩個線程修改同一個數組中的值。耗時1255
package com.mashibing.juc.c_028_FalseSharing;import java.util.Random;public class T01_CacheLinePadding {private static class T {public volatile long x = 0L;}public static T[] arr = new T[2];static {arr[0] = new T();arr[1] = new T();}public static void main(String[] args) throws Exception {Thread t1 = new Thread(()->{for (long i = 0; i < 1000_0000L; i++) {arr[0].x = i;}});Thread t2 = new Thread(()->{for (long i = 0; i < 1000_0000L; i++) {arr[1].x = i;}});final long start = System.nanoTime();t1.start();t2.start();t1.join();t2.join();System.out.println((System.nanoTime() - start)/100_0000);} }示例2:使用7個long類型的數字(7個*8字節=56字節)將緩存行填起來,加上變量x本身占用的空間,使兩個T對象恰巧不在統一緩存行。耗時443
通過緩存行對齊后,效率提升了。
開源的disruptor就考慮到了這個問題。
CPU的亂序執行
CPU為了提高效率,會在一條指令執行過程中(比如去內存讀數據,慢100倍),去同時執行另一條指令。前提是兩條指令沒有依賴關系。
合并寫技術
寫操作也可以合并
只有4個字節,一次填6個的話,要等下一次把剩下的2個填滿。如果一次填4個,就正好填滿。
亂序執行的證明
如何保證特定情況下不亂序?
Volitile
有序性保證:CPU 級別的內存屏障,我們以 intel CPU 為例:
volitile 變量,它的內存的前后都加了屏障,
下節課,我們講 Volitile 的有序性在硬件級別是如何保證的、在JVM級別是如何規范的。
總結
以上是生活随笔為你收集整理的JVM从入门到精通(三):热加载的实现原理,Java内存模型,缓存行,指令重排,合并写技术等的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: MySQL调优(二):数据类型和sche
- 下一篇: 多线程与高并发(四):LockSuppo