Java生態(tài)
1. Java 源碼編譯
語法糖(Syntactic sugar):這種語法對語言的功能并沒有影響榄笙,但是更方便程序員使用柳刮。通常來說使用語法糖能夠增加程序的可讀性,從而減少程序代碼出錯的機會. E.g.?用a[i]表示*(a+i),用a[i][j]表示*(*(a+i)+j),?從面向過程到面向?qū)ο笠彩且环N語法糖
2. 解析執(zhí)行
Java編譯器:將Java源文件(.java文件)編譯成字節(jié)碼文件(.class文件咳秉,是特殊的二進制文件婉支,二進制字節(jié)碼文件),這種字節(jié)碼就是JVM的“機器語言”澜建。javac.exe可以簡單看成是Java編譯器向挖。
Java解釋器:是JVM的一部分。Java解釋器用來解釋執(zhí)行Java編譯器編譯后的程序霎奢。java.exe可以簡單看成是Java解釋器户誓。
JVM:一種能夠運行Java字節(jié)碼(Java?bytecode)的虛擬機。
JDK=JRE + javac compiler+monitor tool.? ?tomcat need jdk in unix for compile.
Tomcat can be run as a daemon using the jsvc tool from the commons-daemon project. Source tarballs for jsvc are included with the Tomcat binaries, and need to be compiled. Building jsvc requires a C ANSI compiler (such as GCC), GNU Autoconf, and a JDK.
字節(jié)碼:字節(jié)碼是已經(jīng)經(jīng)過編譯幕侠,但與特定機器碼無關(guān)帝美,需要解釋器轉(zhuǎn)譯后才能成為機器碼的中間代碼。
Java字節(jié)碼:是Java虛擬機執(zhí)行的一種指令格式晤硕。
解釋器:是一種電腦程序悼潭,能夠把高級編程語言一行一行直接翻譯運行。解釋器不會一次把整個程序翻譯出來舞箍,只像一位“中間人”舰褪,每次運行程序時都要先轉(zhuǎn)成另一種語言再作運行,因此解釋器的程序運行速度比較緩慢疏橄。它每翻譯一行程序敘述就立刻運行占拍,然后再翻譯下一行,再運行捎迫,如此不停地進行下去晃酒。它會先將源碼翻譯成另一種語言,以供多次運行而無需再經(jīng)編譯窄绒。其制成品無需依賴編譯器而運行贝次,程序運行速度比較快。
即時編譯(Just-in-time compilation: JIT):又叫實時編譯彰导、及時編譯蛔翅。是指一種在運行時期把字節(jié)碼編譯成原生機器碼的技術(shù)敲茄,一句一句翻譯源代碼,但是會將翻譯過的代碼緩存起來以降低性能耗損山析。這項技術(shù)是被用來改善虛擬機的性能的堰燎。
JIT編譯器是JRE的一部分。原本的Java程序都是要經(jīng)過解釋執(zhí)行的盖腿,其執(zhí)行速度肯定比可執(zhí)行的二進制字節(jié)碼程序慢爽待。為了提高執(zhí)行速度,引入了JIT翩腐。在運行時鸟款,JIT會把翻譯過來的機器碼保存起來,以備下次使用茂卦。而如果JIT對每條字節(jié)碼都進行編譯何什,則會負擔過重,所以等龙,JIT只會對經(jīng)常執(zhí)行的字節(jié)碼進行編譯处渣,如循環(huán),高頻度使用的方法等蛛砰。它會以整個方法為單位罐栈,一次性將整個方法的字節(jié)碼編譯為本地機器碼,然后直接運行編譯后的機器碼泥畅。
1.1.1類的加載時機
Java類的加載是動態(tài)的荠诬,它并不會一次性將所有類全部加載后再運行,而是保證程序運行的基礎類(像是基類)完全加載到jvm中位仁,至于其他類柑贞,則在需要的時候才加載。這當然就是為了節(jié)省內(nèi)存開銷聂抢。
虛擬機規(guī)范則是嚴格規(guī)定了有且只有5種情況必須立即對類進行“初始化”(class文件加載到JVM中):
????a. 創(chuàng)建類的實例(new 的方式)钧嘶。訪問某個類或接口的靜態(tài)變量,或者對該靜態(tài)變量賦值琳疏,調(diào)用類的靜態(tài)方法
????b. 反射
????c. 初始化某個類的子類有决,則其父類也會被初始化
????d. Java虛擬機啟動時被標明為啟動類的類,直接使用java.exe命令來運行某個主類(包含main方法的那個類)
????e. 當使用JDK1.7的動態(tài)語言支持時
1.1.2如何將類加載到jvm
class文件是通過類的加載器裝載到jvm中的類加載器(ClassLoader)是Java語言的一項創(chuàng)新空盼,也是Java流行的一個重要原因
加載類的開放性
在類加載的第一階段“加載”過程中疮薇,需要通過一個類的全限定名來獲取定義此類的二進制字節(jié)流,完成這個動作的代碼塊就是類加載器我注。這一動作是放在Java虛擬機外部去實現(xiàn)的,以便讓應用程序自己決定如何獲取所需的類迟隅。
虛擬機規(guī)范并沒有指明二進制字節(jié)流要從一個Class文件獲取但骨,或者說根本沒有指明從哪里獲取励七、怎樣獲取。這種開放使得Java在很多領(lǐng)域得到充分運用奔缠,例如:
????????從ZIP包中讀取掠抬,這很常見,成為JAR校哎,EAR两波,WAR格式的基礎
????????從網(wǎng)絡中獲取,最典型的應用就是Applet
????????運行時計算生成闷哆,最典型的是動態(tài)代理技術(shù)腰奋,在java.lang.reflect.Proxy中,就是用了ProxyGenerator.generateProxyClass來為特定接口生成形式為“*$Proxy”的代理類的二進制字節(jié)流
? ? ? ? 由其他文件生成抱怔,最典型的JSP應用劣坊,由JSP文件生成對應的Class類
從java虛擬機角度來講,只存在兩種不同的類加載器:
? ? ? ?(1)一種是啟動類加載器屈留,由C++語言實現(xiàn)的局冰,屬于虛擬機的一部分;
? ? ? ?(2)一種是所有的其他類加載器灌危,這些都是由Java實現(xiàn)的康二,獨立于虛擬機外部,繼承自java.lang.ClassLoader勇蝙;
從開發(fā)人員角度來講沫勿,?Java默認有三種類加載器:
各個加載器的工作責任:
1)Bootstrap ClassLoader:負責加載$JAVA_HOME中jre/lib/rt.jar里所有的class,這個加載器很特殊浅蚪,它不是Java類藕帜,因此它不需要被別人加載,它嵌套在Java虛擬機內(nèi)核里面惜傲,也就是JVM啟動的時候Bootstrap就已經(jīng)啟動洽故,它是用C++寫的二進制代碼(不是字節(jié)碼),它可以去加載別的類盗誊,不是ClassLoader子類
2)Extension ClassLoader:負責加載java平臺中擴展功能的一些jar包时甚,包括$JAVA_HOME中jre/lib/*.jar或-Djava.ext.dirs指定目錄下的jar包
3)App ClassLoader:負責記載classpath中指定的jar包及目錄中class
工作過程:
1、當AppClassLoader加載一個class時哈踱,它首先不會自己去嘗試加載這個類荒适,而是把類加載請求委派給父類加載器ExtClassLoader去完成。
2开镣、當ExtClassLoader加載一個class時刀诬,它首先也不會自己去嘗試加載這個類,而是把類加載請求委派給BootStrapClassLoader去完成邪财。
3陕壹、如果BootStrapClassLoader加載失斨视(例如在$JAVA_HOME/jre/lib里未查找到該class),會使用ExtClassLoader來嘗試加載糠馆;
4嘶伟、若ExtClassLoader也加載失敗,則會使用AppClassLoader來加載
5又碌、如果AppClassLoader也加載失敗九昧,則會報出異常ClassNotFoundException
其實這就是所謂的雙親委派模型 (parent delegation model)。簡單來說:如果一個類加載器收到了類加載的請求毕匀,它首先不會自己去嘗試加載這個類铸鹰,而是把請求委托給父加載器去完成,依次向上期揪。(https://juejin.im/post/5b3cc84ee51d4519873f08da)
好處:防止內(nèi)存中出現(xiàn)多份同樣的字節(jié)碼(安全性角度)
????特別說明:
類加載器在成功加載某個類之后掉奄,會把得到的?java.lang.Class類的實例緩存起來。下次再請求加載該類的時候凤薛,類加載器會直接使用緩存的類的實例姓建,而不會嘗試再次加載。
雙親委派模型在JDK1.2中引入缤苫,但不是強制性的速兔。在一定條件下,為了完成某些操作活玲,可以“破壞”模型涣狗。
? ? 1.重新loadClass方法
? ? 2.利用線程上下文加載器(Thread Context ClassLoader)。這個類加載器可以通過java.lang.Thread類的 setContextClassLoaser()方法進行設置舒憾,如果創(chuàng)建線程時還未設置镀钓,它將會從父線程中繼承 一個,如果在應用程序的全局范圍內(nèi)都沒有設置過的話镀迂,那這個類加載器默認就是應用程序 類加載器丁溅。
????????例如JNDI服務,但是當JNDI要對資源進行集中化管理時探遵,他需要調(diào)用其他公司實現(xiàn)并部署在應用程序的classpath下的JNDI接口窟赏,因為這些代碼是需要我們開發(fā)者自己來實現(xiàn)的,這時啟動類加載器是無法識別這些類的箱季,于是乎出現(xiàn)了一種線程上下文加載器(Thread Context ClassLoader)解幽,JNDI服務可以調(diào)用該加載器去加載所需要的代碼唬渗,就是通過父類加載器去請求子類加載器來實現(xiàn)的
? ? 3.為了實現(xiàn)熱插拔,熱部署核无,模塊化辖所,意思是添加一個功能或減去一個功能不用重啟,只需要把這模塊連同類加載器一起換掉就實現(xiàn)了代碼的熱替換。
那能不能自己寫個類叫java.lang.System?
答案:通常不可以最疆,但可以采取另類方法達到這個需求。
解釋:為了不讓我們寫System類蚤告,類加載采用委托機制,這樣可以保證爸爸們優(yōu)先服爷,爸爸們能找到的類杜恰,兒子就沒有機會加載。而System類是Bootstrap加載器加載的仍源,就算自己重寫心褐,也總是使用Java系統(tǒng)提供的System,自己寫的System類根本沒有機會得到加載笼踩。
但是逗爹,我們可以自己定義一個類加載器來達到這個目的,為了避免雙親委托機制嚎于,這個類加載器也必須是特殊的掘而。由于系統(tǒng)自帶的三個類加載器都加載特定目錄下的類,如果我們自己的類加載器放在一個特殊的目錄于购,那么系統(tǒng)的加載器就無法加載袍睡,也就是最終還是由我們自己的加載器加載。
https://www.ibm.com/developerworks/cn/java/j-lo-classloader/
https://www.zhihu.com/question/46719811
1.1.3類加載詳細過程
加載器加載到jvm中肋僧,接下來分為幾個步驟:
1) 加載斑胜,查找并加載類的二進制數(shù)據(jù),在Java堆中也創(chuàng)建一個java.lang.Class類的對象嫌吠。
2) 連接止潘,連接又包含三塊內(nèi)容:驗證、準備辫诅、初始化凭戴。? 1)驗證,文件格式泥栖、元數(shù)據(jù)簇宽、字節(jié)碼、符號引用驗證吧享;? 2)準備魏割,為類的靜態(tài)變量分配內(nèi)存,并將其初始化為默認值钢颂; 3)解析钞它,把類中的符號引用轉(zhuǎn)換為直接引用
3) 初始化,為類的靜態(tài)變量賦予正確的初始值。
https://juejin.im/post/5b3cc84ee51d4519873f08da
https://www.mrsssswan.club/2018/06/30/jvm-start1/
1.1.4 JIT即時編譯器 (Just-in-time compiler)
Just in time編譯遭垛,也叫做運行時編譯尼桶,不同于 C / C++ 語言直接被翻譯成機器指令,javac把java的源文件翻譯成了class文件锯仪,而class文件中全都是Java字節(jié)碼泵督。那么,JVM在加載了這些class文件以后庶喜,針對這些字節(jié)碼小腊,逐條取出,逐條執(zhí)行久窟,這種方法就是解釋執(zhí)行(interprete)秩冈。
還有一種,就是compile, 把這些Java字節(jié)碼重新編譯優(yōu)化斥扛,生成機器碼入问,讓CPU直接執(zhí)行。這樣編出來的代碼效率會更高稀颁。通常芬失,我們不必把所有的Java方法都編譯成機器碼,只需要把調(diào)用最頻繁峻村,占據(jù)CPU時間最長的方法找出來將其編譯成機器碼麸折。這種調(diào)用最頻繁的Java方法就是我們常說的熱點方法
因為編譯也是要花費時間的,我們一般對熱點代碼做compile粘昨,非熱點代碼直接interprete就好了垢啼。
熱點代碼解釋:一、多次調(diào)用的方法张肾。二芭析、多次執(zhí)行的循環(huán)體
使用熱點探測來檢測是否為熱點代碼,熱點探測有兩種方式:采樣, 與計數(shù)器
目前HotSpot使用的是計數(shù)器的方式吞瞪,它為每個方法準備了兩類計數(shù)器:
? ? ? ? 1) 方法調(diào)用計數(shù)器(Invocation Counter)
? ? ? ? 2) 回邊計數(shù)器(Back EdgeCounter)馁启。
在確定虛擬機運行參數(shù)的前提下,這兩個計數(shù)器都有一個確定的閾值芍秆,當計數(shù)器超過閾值溢出了惯疙,就會觸發(fā)JIT編譯。
https://www.ibm.com/developerworks/cn/java/j-lo-just-in-time/
https://zhuanlan.zhihu.com/p/28476709
1.2 JVM的內(nèi)存模型
方法區(qū):?線程共享妖啥,用于儲存已被虛擬機加載的類信息霉颠、常量、靜態(tài)變量荆虱,即編譯器編譯后的代碼.?JDK 8的HotSpot JVM現(xiàn)在使用的是本地內(nèi)存來表示類的元數(shù)據(jù)蒿偎,這個區(qū)域就叫做元空間(meta space).?元空間的特點:充分利用了Java語言規(guī)范中的好處:類及相關(guān)的元數(shù)據(jù)的生命周期與類加載器的一致; 每個加載器有專門的存儲空間;? 只進行線性分配.不會單獨回收某個類; 省掉了GC掃描及壓縮的時間. 元空間里的對象的位置是固定的, 提高Full GC的性能朽们,在Full GC期間,Metadata到Metadata pointers之間不需要掃描了诉位,別小看這幾納秒時間
?http://www.reibang.com/p/7b88aa16c2f6
堆:?是JVM中最大的一塊區(qū)域骑脱,線程共享,此區(qū)唯一的目的就是存放對象實例 (方法區(qū)中的類實例化之后)苍糠,幾乎所有對象實例都在這里分配叁丧,但是隨著JIT編譯器及逃逸分析技術(shù)的發(fā)展,棧上分配岳瞭、標量替換優(yōu)化技術(shù)將會導致一些微妙的變化歹袁,所有對象都分配在堆上也漸漸變的不是那么絕對.
針對于jdk1.7之后:常量池位于堆中. 常量池存儲的是:
? ? ? ?a)? ?字面量(Literal):文本字符串等---->用雙引號引起來的字符串字面量都會進這里面
? ? ? ?b)? ?符號引用(Symbolic References) - 類和接口的全限定名(Full Qualified Name) - 字段的名稱和描述符(Descriptor) - 方法的名稱和描述符
字符串常量池只存儲引用,不存儲內(nèi)容
虛擬機棧/棧內(nèi)存
?見題目for string.intern()? https://zhuanlan.zhihu.com/p/39536807
虛擬機棧:即我們平時經(jīng)常說的棧內(nèi)存寝优,也是線程私有,是Java方法執(zhí)行時的內(nèi)存模型枫耳,類中的每個方法在執(zhí)行時都會創(chuàng)建一個棧幀(frame)用于儲存以下內(nèi)容:
? ??棧幀(frame):
? ?局部變量表:32位變量槽乏矾,存放了編譯期可知的各種基本數(shù)據(jù)類型的值、對象引用迁杨、returnAddress類型钻心。Stack memory only contains?local primitive variables?and?reference?variables?to objects in heap?space.
? ??操作數(shù)棧:基于棧的執(zhí)行引擎,虛擬機把操作數(shù)棧作為它的工作區(qū)铅协,大多數(shù)指令都要從這里彈出數(shù)據(jù)捷沸、執(zhí)行運算,然后把結(jié)果壓回操作數(shù)棧狐史。
????動態(tài)連接:每個棧幀都包含一個指向運行時常量池(方法區(qū)的一部分)中該棧幀所屬方法的引用痒给。持有這個引用是為了支持方法調(diào)用過程中的動態(tài)連接。Class文件的常量池中有大量的符號引用骏全,字節(jié)碼中的方法調(diào)用指令就以常量池中指向方法的符號引用為參數(shù)苍柏。這些符號引用一部分會在類加載階段或第一次使用的時候轉(zhuǎn)化為直接引用,這種轉(zhuǎn)化稱為靜態(tài)解析姜贡。另一部分將在每一次的運行期間轉(zhuǎn)化為直接應用试吁,這部分稱為動態(tài)連接。
????方法出口:返回方法被調(diào)用的位置楼咳,恢復上層方法的局部變量和操作數(shù)棧熄捍,如果無返回值,則把它壓入調(diào)用者的操作數(shù)棧母怜。
frameis used to store data and partial results, as well as to perform dynamic linking, return values for methods, and dispatch exceptions.
Each frame has its own array of local variables (§2.6.1), its own operand stack (§2.6.2), and a reference to the run-time constant pool (§2.5.5) of the class of the current method.
本地方法棧:線程私有余耽,與虛擬機棧類似,為native方法服務糙申。
程序計數(shù)器:一塊較小的內(nèi)存空間宾添,可以看作當前線程所執(zhí)行的字節(jié)碼行號指示器船惨。
????????程序計數(shù)器是線程私有,各線程之間互不影響
????????如果正在執(zhí)行java方法缕陕,計數(shù)器記錄的是正在執(zhí)行的虛擬機字節(jié)碼指令地址
????????如果執(zhí)行native方法粱锐,這個計數(shù)器為null
????????程序計數(shù)器也是在Java虛擬機規(guī)范中唯一沒有規(guī)定任何OutOfMemoryError異常情況的區(qū)域
舉例說明
宏觀簡述一下例子中的工作流程:
1、通過java.exe運行Java3yTest.class扛邑,隨后被加載到JVM中怜浅,元空間存儲著類的信息(包括類的名稱、方法信息蔬崩、字段信息..)恶座。
2、然后JVM找到Java3yTest的主函數(shù)入口(main)沥阳,為main函數(shù)創(chuàng)建棧幀跨琳,開始執(zhí)行main函數(shù)
3、main函數(shù)的第一條命令是Java3y java3y = new Java3y();就是讓JVM創(chuàng)建一個Java3y對象桐罕,但是這時候方法區(qū)中沒有Java3y類的信息脉让,所以JVM馬上加載Java3y類,把Java3y類的類型信息放到方法區(qū)中(元空間)
4功炮、加載完Java3y類之后溅潜,Java虛擬機做的第一件事情就是在堆區(qū)中為一個新的Java3y實例分配內(nèi)存, 然后調(diào)用構(gòu)造函數(shù)初始化Java3y實例,這個Java3y實例持有著指向方法區(qū)的Java3y類的類型信息(其中包含有方法表薪伏,java動態(tài)綁定的底層實現(xiàn))的引用
5滚澜、當使用java3y.setName("Java3y");的時候,JVM根據(jù)java3y引用找到Java3y對象嫁怀,然后根據(jù)Java3y對象持有的引用定位到方法區(qū)中Java3y類的類型信息的方法表设捐,獲得setName()函數(shù)的字節(jié)碼的地址
6、為setName()函數(shù)創(chuàng)建棧幀塘淑,開始運行setName()函數(shù)
從微觀上其實還做了很多東西挡育,正如上面所說的類加載過程(加載-->連接(驗證,準備朴爬,解析)-->初始化)即寒,在類加載完之后jvm為其分配內(nèi)存(分配內(nèi)存中也做了非常多的事)。由于這些步驟并不是一步一步往下走召噩,會有很多的“混沌bootstrap”的過程母赵,所以很難描述清楚。擴展閱讀(先有Class對象還是先有Object):?https://www.zhihu.com/question/30301819
https://www.journaldev.com/4098/java-heap-space-vs-stack-memory
1.7.1JVM垃圾回收
判斷對象死去有兩種方法:
? ? 1) 引用計數(shù)法-->這種難以解決對象之間的循環(huán)引用的問題
? ? 2) 可達性分析算法-->主流的JVM采用的是這種方式
回收對象的方法:?
????????標記-清除算法:(Mark-Sweep)算法具滴,如它的名字一樣凹嘲,算法分為“標記”和“清除”兩個階段:首先標記出所有需要回收的對象,在標記完成后統(tǒng)一回收掉所有被標記的對象构韵。
????????復制算法:?(Copying)的收集算法周蹭,它將可用內(nèi)存按容量劃分為大小相等的兩塊趋艘,每次只使用其中的一塊。當這一塊的內(nèi)存用完了凶朗,就將還存活著的對象復制到另外一塊上面瓷胧,然后再把已使用過的內(nèi)存空間一次清理掉
????????標記-壓縮算法:標記過程仍然與“標記-清除”算法一樣,但后續(xù)步驟不是直接對可回收對象進行清理棚愤,而是讓所有存活的對象都向一端移動搓萧,然后直接清理掉端邊界以外的內(nèi)存
????????分代收集算法 (一般GC常用方法。?其實就是組合上面的算法宛畦,不同的區(qū)域使用不同的算法)
無論是可達性分析算法瘸洛,還是垃圾回收算法,JVM使用的都是準確式GC(Type accurate GC.?給定某個位置上的某塊數(shù)據(jù), 知道其是不是指針次和。 與之相對的是保守與半保守式GC, 其實現(xiàn)較為簡單)反肋。JVM是使用一組稱為OopMap (object reference)的數(shù)據(jù)結(jié)構(gòu),來存儲所有的對象引用(這樣就不用遍歷整個內(nèi)存去查找了踏施,空間換時間)囚玫。 并且不會將所有的指令都生成OopMap,只會在安全點(SafePoint)上生成OopMap读规,在安全區(qū)域(Safe Region)上開始GC。
http://www.cnblogs.com/strinkbug/p/6376525.html
https://my.oschina.net/u/1757225/blog/1583822
在OopMap的協(xié)助下燃少,HotSpot可以快速且準確地完成GC Roots枚舉(可達性分析)束亏。上面所講的垃圾收集算法只能算是方法論,落地實現(xiàn)的是垃圾收集器:圖中兩個收集器之間有連線阵具,則說明它們可以配合使用.
Serial收集器碍遍,串行收集器是最古老,最穩(wěn)定以及效率高的收集器阳液,但可能會產(chǎn)生較長的停頓怕敬,只使用一個線程去回收。
ParNew收集器帘皿,ParNew收集器其實就是Serial收集器的多線程版本东跪。
Parallel收集器,Parallel Scavenge收集器類似ParNew收集器鹰溜,Parallel收集器更關(guān)注系統(tǒng)的吞吐量虽填。
Parallel Old收集器,Parallel Old是Parallel Scavenge收集器的老年代版本曹动,使用多線程“標記-整理”算法
CMS收集器斋日,CMS(Concurrent Mark Sweep)收集器是一種以獲取最短回收停頓時間為目標的收集器。它需要消耗額外的CPU和內(nèi)存資源墓陈,在CPU和內(nèi)存資源緊張恶守,CPU較少時第献,會加重系統(tǒng)負擔。CMS無法處理浮動垃圾兔港。CMS的“標記-清除”算法庸毫,會導致大量空間碎片的產(chǎn)生。
G1收集器押框,G1 (Garbage-First)是一款面向服務器的垃圾收集器,主要針對配備多顆處理器及大容量內(nèi)存的機器.?以極高概率滿足GC停頓時間要求的同時,還具備高吞吐量性能特征岔绸。G1 is planned as the long term replacement for the Concurrent Mark-Sweep Collector (CMS). Comparing G1 with CMS, there are differences that make G1 a better solution. One difference is that G1 is a compacting collector.?Also, G1 offers more predictable garbage collection pauses than the CMS collector, and allows users to specify desired pause targets
? ??The older garbage collectors (serial, parallel, CMS) all structure the heap into three sections: young generation, old generation, and permanent generation of a fixed memory size.? ??
The G1 collector takes a different approach.
The heap is partitioned into a set of equal-sized heap regions, each a contiguous range of virtual memory. Certain region sets are assigned the same roles (eden, survivor, old) as in the older collectors, but there is not a fixed size for them. This provides greater flexibility in memory usage.
JVM 調(diào)試
32 JVM 優(yōu)于 64JVM
DirectMemory
JVM除了堆內(nèi)存之外橡伞,就只有棧內(nèi)存和DirectMemory了盒揉。棧空間每個線程是固定的兑徘,線程數(shù)也沒可能多到可以占用這么多內(nèi)存的程序刚盈,所以懷疑的目標就在DirectMemory上了。
DirectMemory是java nio引入的挂脑,直接以native的方式分配內(nèi)存藕漱,不受jvm管理。這種方式是為了提高網(wǎng)絡和文件IO的效率崭闲,避免多余的內(nèi)存拷貝而出現(xiàn)的肋联。DirectMemory占用的大小沒有直接的工具或者API可以查看,不過這個在Bits類中是有兩個字段存儲了最大大小和已分配大小的刁俭,使用反射可以拿到這個數(shù)據(jù)橄仍。
Class<?>?c?=?Class.forName("java.nio.Bits");
Field?maxMemory?=?c.getDeclaredField("maxMemory");
maxMemory.setAccessible(true);
Field?reservedMemory?=?c.getDeclaredField("reservedMemory");
reservedMemory.setAccessible(true);
Long?maxMemoryValue?=?(Long)maxMemory.get(null);
Long?reservedMemoryValue?=?(Long)reservedMemory.get(null);
原來,DirectMemory 的默認大小是64M牍戚,而JDK6之前和JDK6的某些版本的SUN JVM侮繁,存在一個BUG,在用-Xmx設定堆空間大小的時候如孝,也設置了DirectMemory的大小宪哩。加入設置了-Xmx2048m,那么jvm最終可分配的內(nèi)存大小為4G多一些第晰,是預期的兩倍锁孟。
解決方式是設置jvm參數(shù)-XX:MaxDirectMemorySize=128m,指定DirectMemory的大小茁瘦。
getRuntime.exec()
占用資源很多罗岖,盡量避免使用。其調(diào)用時會先clone一個和現(xiàn)在虛擬機一樣環(huán)境變量的進程腹躁。用這個新進程去執(zhí)行外部命令
異步改消息隊列
參考資料
https://www.oracle.com/webfolder/technetwork/tutorials/obe/java/G1GettingStarted/index.html
https://www.journaldev.com/2856/java-jvm-memory-model-memory-management-in-java#memory-management-in-java-8211-java-garbage-collection
http://www.reibang.com/p/63fe09fe1a60