轉載請注明原創出處,謝謝!
最近沒有什么實戰,准備把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,表示不允許修改引用的指向。
- 類的方法不可以被重寫。
備注: 此處有點錯誤,今天修改下,更能突出JVM的重要性了。
這個也是今天看見R大回復關於阿里代碼規范才發現的。
學習了,看看JVM重要吧,今天在此糾正下,我查看新版阿里java代碼規范也修改了,新版內容如下:
由於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虛擬機中的實現優化,你不了解怎么可以呢?
今天僅僅是開場白,后續會有系列基礎知識文章出來。大家一起進步。
個人公眾號