轉載請註明原創出處,謝謝! 最近沒有什麼實戰,準備把JVM知識梳理一遍,先以開發人員的交流來談談jvm這塊的知識以及重要性,依稀記得2、3年前用solr的時候老是經常oom,提到oom大家應該都不陌生,那個時候也並沒有從根本解決oom,由於對jvm不熟悉,只是去百度,到處都是配置jvm參數的,那個時 ...
轉載請註明原創出處,謝謝!
最近沒有什麼實戰,準備把JVM知識梳理一遍,先以開發人員的交流來談談jvm這塊的知識以及重要性,依稀記得2、3年前用solr的時候老是經常oom,提到oom大家應該都不陌生,那個時候也並沒有從根本解決oom,由於對jvm不熟悉,只是去百度,到處都是配置jvm參數的,那個時候啥不懂,直接粘貼,但是並沒有解決問題,通過這個就告訴我們作為開發人員也需要對jvm很熟悉才行,問題來了,很多人會說我的代碼並沒有出現oom啊,不需要關註啊,因為不理解不知道重要性,可以回頭看看的我的JVM菜鳥進階高手之路一到九篇系列,可能很多人說還沒有到那麼高級,不需要理解,我也告訴你也是不對的,且聽我慢慢道來。
談到jvm首先需要談的是,JAVA虛擬機規範,這個就類似jdbc規範一樣,定義了一些規範,oracle有oracle的實現,mysql有mysql的實現,JAVA虛擬機規範也一樣,java虛擬機有很多,IBM、Apache Harmony等,每個都有些細節不一樣,但是大體符合JAVA虛擬機規範的,由於Oracle收購SUN之後,Oralce主要有JRockit和Hotspot虛擬機了,後來將其進行整合了,不然維護兩套麻煩,就和原來的struts1和struts2一樣,Oralce主要是以Hotspot來的,把JRockit裡面的一些優點也慢慢加入到其中。目前市面上Hotspot占用率是最高的,一般說到JAVA虛擬機基本都是Hotspot虛擬機。JAVA8虛擬機規範地址:http://docs.oracle.com/javase/specs/jvms/se8/html/index.html,按照道理應該去閱讀閱讀的,雖然java語言與java虛擬機有密切的關係,但是兩者是完全不同的內容,像Scala、Clojure、Groovy等語言都是跑在JAVA虛擬機上面的,可以產生各種各樣的跨平臺語言,除了語言特性不一樣,他們可以共用JAVA虛擬機帶來的跨平臺性、垃圾回收器、以及即使編譯(看到這裡都應該明白這些的JAVA虛擬機擁有的不是java語言規範定義的,java語言規範地址:http://docs.oracle.com/javase/specs/jls/se8/html/index.html),稍微解釋下為什麼用JAVA虛擬機就可以做到跨平臺呢?依稀記得當時剛剛學習java的時候有句口號“一次編譯,到處運行。”Java程式理想上,並不理會真正執行哪個平臺,只要知道如何執行於JVM就可以了,至於JVM實際上如何與底層平臺溝通,那是JVM自己的事。由於JVM實際上相當於Java程式的操作系統,JVM就負責了Java程式的各種資源管理。
我們要記住兩點:
- JVM就是Java程式的操作系統,JVM的可執行文件就是.class文件。
- Java虛擬機屏蔽了操作系統之間的差異,但是不同的系統使用的虛擬機不同。
與其他語言相比,Java程式能夠做到“編譯一次,到處運行”,可見它的跨平臺性非常強。其實JVM就是在操作系統層面有抽象了一層虛擬機,這樣的好處可以屏蔽底層細節,有每個具體的平臺的虛擬機實現即可,但是對外提供的是一致的(比如windows需要安裝windows版的jdk,linux需要安裝linux版的jdk就是這個原因,jvm虛擬機幫我們屏蔽到了底層的細節)。
一直有一個疑惑,Oracle的jdk和OpenJDK到底有什麼關係呢?
Oracle/Sun JDK與OpenJDK的區別和聯繫如下: - OpenJDK原是SunMicrosystems公司為Java平臺構建的Java開發環境(JDK)的開源版本,完全自由,開放源碼。Sun Microsystems公司在2006年的JavaOne大會上稱將對Java開放源代碼,於2009年4月15日正式發佈OpenJDK。甲骨文在 2010 年收購SunMicrosystem之後接管了這個項目。
- oracle/Sun JDK裡面包含的JVM是HotSpotVM,HotSpot VM只有非常非常少量的功能沒有在OpenJDK里,那部分在Oracle內部的代碼庫里。這些私有部分都不涉及JVM的核心功能。所以說,Oracle/Sun JDK與OpenJDK其實使用的是同一個代碼庫。
- 從一個Oracle內部員工的角度來看,當他要構建OracleJDK時,他同樣需要先從http://hg.openjdk.java.NET簽出OpenJDK,然後從Oracle內部的代碼庫簽出私有的部分,放在OpenJDK代碼下的一個特定目錄里,然後構建。值得註意的是,Oracle JDK只發佈二進位安裝包,而OpenJDK只發佈源碼。
知道關係之後,其實很多就釋然了,其實阿裡的jdk就是基於OpenJDK定製的,所以看看OpenJDK對理解JVM很有幫助的,OpenJDK的github地址如下:https://github.com/dmlloyd/openjdk,既然都看見了OpenJDK的源碼,那麼是否有興趣編譯編譯。
用final可以提高性能,為什麼呢?
依稀記得以前老師說,用final可以提高性能,為什麼呢?由於類的載入機制,關於一個*.class如何載入進來,如何一系列的操作後續會進行介紹,由於類的載入機制會提到一些熱替換,熱載入,以及閱讀tomcat源碼的時候可以瞭解到他是怎麼處理載入的,由於final常量在準備階段就初始化了,而並不是在初始化結點處理的,所以可以提高程式相應效率。
申明為final的情況:
- 不需要重新賦值的變數,包括類屬性、局部變數。
- 對象參數前面加final,表示不允許修改引用的指向。
- 類的方法不可以被重寫。
由於final關鍵字,在併發鎖的時候,不可變的一些情況鎖是無效的
比如鎖一個Integer、Double、String等是不行的,你說這些你對JVM不瞭解能行嗎?
JAVA虛擬機對各各數據類型的表示,所以引入了關於,原碼,補碼,反碼等概念,為什麼需要補碼呢?
補碼的好處:
- 使用補碼可以沒有任何歧義的表示0。
補碼可以很好的參與二進位的運算,補碼相加符號位參與運算, 這樣就簡單很多了。那就可以明白為什麼數據溢出的概念了,經常看到
byte a=(byte)(127+1); System.out.println(a);
如果不瞭解JVM怎麼能懂,還有兩個值相同的Integer型值進行==比較時:
Integer a=125;
Integer b=125;
Integer d=300;
Integer c=300;
System.out.println(c==d);
System.out.println(a==b);
運行結果為false、true?為什麼呢? 查看源碼: 這兒的IntegerCache有一個靜態的Integer數組,在類載入時就將-128 到 127 的Integer對象創建了,並保存在cache數組中, 一旦程式調用valueOf 方法,如果i的值是在-128 到 127 之間就直接在cache緩存數組中去取Integer對象,超出 -128 ~ +127 範圍的數字就要即時構建新的Integer對象,可以通過JVM參數 -XX:AutoBoxCacheMax來進行調整。 那麼== 和equals的區別,== 是進行地址及值比較,無法對==操作符進行重載,而對於equals方法,Integer裡面的equals方法 重寫了Object的equals方法,所以,相同類型的包裝類對象直接的值比較全部使用equals方法比較,並且能用基本數據類型 就應該用基本數據類型,這些你不瞭解JVM你那裡知道呢?
根據IEEE754定義的浮點數的格式,所以涉及到錢的小數類型必須使用BigDecimal,禁止使用float和double,為什麼呢?
不懂JVM可以?後續會講。
慎用Object的clone方法拷貝對象,深拷貝,淺拷貝。
你不瞭解jvm模型咋知道呢?還有Object的finalize你不瞭解JVM怎麼理解呢?
對於String的一些操作,String的文章最多。+運算符號
String str = "start";
for(int i=0; i<100; i++){
str = str + "hello";
}
反編譯出的位元組碼文件顯示每次迴圈都會new出一個StringBuilder對象,然後進行append操作,最後通過toString方法返回String對象,造成記憶體資源浪費。
其他
HashMap、ArrayList、StringBuilder等的擴容機制,會浪費空間以及性能,特別在併發情況下麵可能會死鎖,後續分享hashMap的cpu 100%情況,所以集合初始化時,儘量指定集合初始值大小,你不瞭解jvm怎麼可以呢?還有很多框架,netty等對外記憶體(堆外空間),多線程相關ThreadLocal等,還有鎖在java虛擬機中的實現優化,你不瞭解怎麼可以呢?
今天僅僅是開場白,後續會有系列基礎知識文章出來。大家一起進步。
個人公眾號