什么是內(nèi)存模型
-
寫(xiě)入動(dòng)作可見(jiàn)
內(nèi)存模型定義了一個(gè)充分必要條件惭适,保證其它CPU的寫(xiě)入動(dòng)作對(duì)該CPU是可見(jiàn)的护桦,而且該CPU的寫(xiě)入動(dòng)作對(duì)其它CPU也是可見(jiàn)的
- 強(qiáng)內(nèi)存模型扳埂,所有CPU在任何時(shí)候都能看到內(nèi)存中任意位置相同的值宋彼,這種完全是硬件提供的支持赫悄。
- 弱內(nèi)存模型,需要執(zhí)行一些特殊指令(就是經(jīng)程梗看到或者聽(tīng)到的盐数,memory barriers內(nèi)存屏障),刷新CPU緩存的數(shù)據(jù)到內(nèi)存中伞梯,保證這個(gè)寫(xiě)操作能夠被其它CPU可見(jiàn)玫氢,或者將CPU緩存的數(shù)據(jù)設(shè)置為無(wú)效狀態(tài),保證其它CPU的寫(xiě)操作對(duì)本CPU可見(jiàn)谜诫。通常這些內(nèi)存屏障的行為由底層實(shí)現(xiàn)漾峡,對(duì)于上層語(yǔ)言的程序員來(lái)說(shuō)是透明的(不需要太關(guān)心具體的內(nèi)存屏障如何實(shí)現(xiàn))。
- 重排序可以發(fā)生在好幾個(gè)地方:編譯器喻旷、運(yùn)行時(shí)生逸、JIT等,比如編譯器會(huì)覺(jué)得把一個(gè)變量的寫(xiě)操作放在最后會(huì)更有效率且预,編譯后槽袄,這個(gè)指令就在最后了(前提是只要不改變程序的語(yǔ)義,編譯器锋谐、執(zhí)行器就可以這樣自由的隨意優(yōu)化)遍尺,一旦編譯器對(duì)某個(gè)變量的寫(xiě)操作進(jìn)行優(yōu)化(放到最后),那么在執(zhí)行之前涮拗,另一個(gè)線程將不會(huì)看到這個(gè)執(zhí)行結(jié)果乾戏。
Class Reordering {
int x = 0, y = 0;
public void writer() {
x = 1;
y = 2;
}
public void reader() {
int r1 = y;
int r2 = x;
}
/*在writer方法中迂苛,可能發(fā)生了重排序,
*y的寫(xiě)入動(dòng)作可能發(fā)在x寫(xiě)入之前鼓择,這種情況下,
*線程B就有可能看到 x的值還是0灾部。
*/
}
在Java內(nèi)存模型中,描述了在多線程代碼中惯退,哪些行為是正確的赌髓、合法的,以及多線程之間如何進(jìn)行通信催跪,代碼中變量的讀寫(xiě)行為如何反應(yīng)到內(nèi)存锁蠕、CPU緩存的底層細(xì)節(jié)。
在Java中包含了幾個(gè)關(guān)鍵字:volatile懊蒸、final和synchronized荣倾,幫助程序員把代碼中的并發(fā)需求描述給編譯器。Java內(nèi)存模型中定義了它們的行為骑丸,確保正確同步的Java代碼在所有的處理器架構(gòu)上都能正確執(zhí)行舌仍。
synchronization 可以實(shí)現(xiàn)什么
Synchronization有多種語(yǔ)義,其中最容易理解的是互斥通危,對(duì)于一個(gè)monitor對(duì)象铸豁,只能夠被一個(gè)線程持有,意味著一旦有線程進(jìn)入了同步代碼塊菊碟,那么其它線程就不能進(jìn)入直到第一個(gè)進(jìn)入的線程退出代碼塊(這因?yàn)槎寄芾斫猓?/p>
對(duì)于兩個(gè)線程來(lái)說(shuō)节芥,在相同的monitor對(duì)象上同步是很重要的,以便正確的設(shè)置happens-before關(guān)系逆害。
final 可以影響什么
如果一個(gè)類(lèi)包含final字段头镊,且在構(gòu)造函數(shù)中初始化,那么正確的構(gòu)造一個(gè)對(duì)象后魄幕,final字段被設(shè)置后對(duì)于其它線程是可見(jiàn)的相艇。
這里所說(shuō)的正確構(gòu)造對(duì)象,意思是在對(duì)象的構(gòu)造過(guò)程中纯陨,不允許對(duì)該對(duì)象進(jìn)行引用坛芽,不然的話,可能存在其它線程在對(duì)象還沒(méi)構(gòu)造完成時(shí)就對(duì)該對(duì)象進(jìn)行訪問(wèn)队丝,造成不必要的麻煩靡馁。
JVM的內(nèi)存分區(qū)
棧
每個(gè)線程都有獨(dú)立的棧并且是相互隔離的欲鹏,
棧的大小
一個(gè)是jvm參數(shù) -XSS机久,默認(rèn)值隨著虛擬機(jī)版本以及操作系統(tǒng)影響。我們可以認(rèn)為64位linux默認(rèn)是1m的樣子赔嚎。 除了JVM設(shè)置膘盖,我們還可以在創(chuàng)建Thread的時(shí)候手工指定大小
棧的大小影響到了線程的最大數(shù)量胧弛,尤其在大流量的server中,我們很多時(shí)候的并發(fā)數(shù)受到的是線程數(shù)的限制侠畔,這時(shí)候需要了解限制在哪里
可看作:
線程數(shù) = (系統(tǒng)空閑內(nèi)存-堆內(nèi)存(-Xms, -Xmx)- perm區(qū)內(nèi)存(-XX:MaxPermSize)) / 線程棧大小(-Xss)
堆
堆的結(jié)構(gòu)
對(duì)于大多數(shù)應(yīng)用來(lái)說(shuō)结缚,Java 堆(Java Heap)是Java 虛擬機(jī)所管理的內(nèi)存中最大的一塊。Java 堆是被所有線程共享的一塊內(nèi)存區(qū)域软棺,在虛擬機(jī)啟動(dòng)時(shí)創(chuàng)建红竭。此內(nèi)存區(qū)域的唯一目的就是存放對(duì)象實(shí)例,幾乎所有的對(duì)象實(shí)例都在這里分配內(nèi)存喘落。
新對(duì)象會(huì)首先分配在Eden中(如果對(duì)象過(guò)大茵宪,比如大數(shù)組,將會(huì)直接放到老年代)瘦棋。在GC中稀火,Eden中的對(duì)象會(huì)被移動(dòng)到survivor中,直至對(duì)象滿(mǎn)足一定的年紀(jì)(定義為熬過(guò)minor GC的次數(shù))赌朋,會(huì)被移動(dòng)到老年代凰狞。
方法區(qū)
又稱(chēng)為“靜態(tài)區(qū)”,和堆一樣沛慢,被所有線程共享赡若。 其中包含所有的 class 和 static 變量
GC 垃圾收集
思考一下復(fù)制和標(biāo)記清除/整理的區(qū)別,為什么新生代要用復(fù)制团甲?因?yàn)閷?duì)新生代來(lái)講斩熊,一次垃圾收集要回收掉絕大部分對(duì)象,我們通過(guò)冗余空間的辦法來(lái)加速整理過(guò)程(不冗余空間的整理操作要做swap伐庭,而冗余只需要做move)粉渠。同時(shí)可以記錄下每個(gè)對(duì)象的『年齡』從而優(yōu)化『晉升』操作使得中年對(duì)象不被錯(cuò)誤放到老年代。而反過(guò)來(lái)老年代偏穩(wěn)定圾另,我們哪怕是用清除霸株,也不會(huì)產(chǎn)生太多的碎片,并且整理的代價(jià)也并不會(huì)太大集乔。
引用與內(nèi)存實(shí)例
首先去件,字面值及其引用
int a = 1;
int b = 1;
int b = 2;
//先創(chuàng)建名為a的引用,再去找是否已存在字面值為1的地址扰路,沒(méi)找到開(kāi)辟新地址放1.
//再創(chuàng)建b的引用變量尤溜,---棧---中已有“1”,直接指向它
//改變b時(shí)汗唱,a不會(huì)改變宫莱,因?yàn)楦淖兊氖莃的引用,而不是指向的字面值
new String()
String str1 = "Hello";
String str2 = "Hello";
System.out.println(str1 == str2);//true
String str3 = new String("Hello");
String str4 = new String("Hello");
System.out.println(str3 == str4);//false
//String 是特殊的包裝類(lèi)
//用new()來(lái)新建哩罪,會(huì)存放在---堆---中授霸,每次調(diào)用都是新的巡验。
//而創(chuàng)建引用常量還是和字面值一樣,不會(huì)新建碘耳,而去---棧---中的常量池中尋找
String str5 = str1+"World";
String str6 = "HelloWorld";
System.out.println(s5 == s6);//false
//equals判斷的是字面值是否相等显设,而==判斷的是引用是否指向同一個(gè)對(duì)象
new()出一個(gè)實(shí)例,JVM首先在堆中為其分配內(nèi)存辛辨,擁有著指向方法區(qū)
類(lèi)加載過(guò)程
參考鏈接:http://www.reibang.com/p/ace2aa692f96
從.java文件到實(shí)際加載到內(nèi)存中
JVM調(diào)用指定的ClassLoader去加載.class文件等各類(lèi)路徑捕捂、文件的類(lèi)
.java文件 -> 通過(guò)你的JDK環(huán)境相關(guān)指令編譯 -> .class文件 -> JVM初始化之后,如果有類(lèi)的執(zhí)行斗搞、調(diào)用等相關(guān)操作绞蹦,JVM就會(huì)將.class文件加載到內(nèi)存中,并開(kāi)始下面的一系列處理:(鏈接->初始化)
/* 類(lèi)加載方式:都是JVM調(diào)用ClassLoader去加載類(lèi) */
//方式一 Class.forName(String name);
public static Class<?> forName(String className) throws ClassNotFoundException {
return forName(className, true, VMStack.getCallingClassLoader());
}
public static Class<?> forName(String className, boolean shouldInitialize,
ClassLoader classLoader) throws ClassNotFoundException {
if (classLoader == null) {
classLoader = BootClassLoader.getInstance();
}
Class<?> result;
try {
result = classForName(className, shouldInitialize, classLoader);
} catch (ClassNotFoundException e) {
Throwable cause = e.getCause();
if (cause instanceof LinkageError) {
throw (LinkageError) cause;
}
throw e;
}
return result;
}
//方式二
類(lèi)的初始化之前
-
加載
- 通過(guò)一個(gè)類(lèi)全限定名(java.lang.String)來(lái)獲取定義此類(lèi)的二進(jìn)制字節(jié)流
- 將其所代表的靜態(tài)存儲(chǔ)結(jié)構(gòu) 》 方法區(qū)運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu)
- 在---堆---中生成一個(gè)代表這個(gè)類(lèi)的java.lang.Class