String str1="hello"; String str2="he"+new String("llo"); System.out.println(str1==str2);//false
一、兩個或者以上的字符串常量相加,【String str="s1"+"s2"】,在預編譯的時候「+」會被優化,至關於把兩個或者兩個以上字符串常量自動合成一個字符串常量html
二、字符串的+操做本質上是new了StringBuilder對象進行append操做,拼接後調用toString()返回String對象(可經過javap -c xxx.class查看字節碼指令)java
Code: 0: ldc #10 // String hello 2: astore_1 3: new #11 // class java/lang/StringBuilder 6: dup 7: invokespecial #12 // Method java/lang/StringBuilder."<init>":()V 10: ldc #13 // String he 12: invokevirtual #14 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 15: new #15 // class java/lang/String 18: dup 19: ldc #16 // String llo 21: invokespecial #17 // Method java/lang/String."<init>":(Ljava/lang/String;)V 24: invokevirtual #14 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 27: invokevirtual #18 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
1、GC日誌分析linux
爲了在內存溢出時排查緣由,能夠在JVM啓動時加一些參數來控制,當JVM內存出問題時能夠經過分析記錄下來的GC日誌,GC的頻率和每次GC回收了哪些內存算法
GC的日誌輸入有如下參數sql
一、-verbose:gc 能夠輔助輸出一些詳細的GC信息數組
二、-XX:+PrintGCDetails 輸出GC的詳細信息瀏覽器
三、-XX:+PrintGCApplicationStoppedTime 輸出GC形成應用程序暫停的時間安全
四、-XX:+PrintGCDateStamps 輸出GC發生的時間信息服務器
五、-XX:PrintHeapAtGC 在GC先後輸出堆中各個區域的大小多線程
六、-Xloggc:[file] 將GC信息輸出到單獨的文件
每種GC方式輸出日誌的形式不一樣,除CMS的日誌和其餘GC方式差別較大外,其他GC方式的日誌能夠抽象成以下方式
[GC [<collector>: <starting occupancy1> -> <ending occupancy1> (total size1) , <pause time1> secs]
<starting occupancy2> -> <ending occupancy2> (total size2) , <pause time2> secs] ]
說明以下
一、<collectot>GC 表示垃圾收集器的名稱
二、<starting occupancy1> 表示Young區在GC前佔用的內存
三、<ending occupancy1> 表示Young區在GC後佔用的內存
四、(total size1) 表示Young區的總內存大小
五、<pause time1> 表示Young區局部收集時JVM暫停處理的時間 secs表示單位秒
六、<starting occupancy2> 表示Heap在GC前佔用的內存
七、<ending occupancy2> 表示Heap在GC後佔用的內存
八、(total size2) 表示Heap的總內存
九、<pause time2> 表示在GC過程當中JVM暫停處理的總時間
能夠根據日誌來判斷是否存在內存泄漏的問題:
<starting occupancy1> - <ending occupancy1> 和 <starting occupancy2> - <ending occupancy2> 比較
一、若是前者差等於後者差,代表Young區GC 對象100%被回收,沒有對象進入 Old區或者Perm區
二、若是前者大於後者,那麼差值就是此次GC對象進入Old或者Perm區的大小
若是隨着時間的的延長,<ending occupancy2>的大小一直在增加,並且Full GC很頻繁,那麼極可能就是內存泄漏致使的。
2、堆快照文件分析
一、經過命令 jmap -dump:format=b,file=[filename][pid] jmap(Memory Map for Java)
來記錄下堆的內存快照,而後利用第三方工具如eclipse 插件MAT來分析整個Heap的對象關聯狀況。
若是內存耗盡可直接致使JVM退出,能夠經過參數
-XX:+HeapDumpOnOutOfMemoryError 來配置當內存耗盡時記錄下當時的內存快照
-XX:HeapDumpPath 指定內存快照文件的路徑 文件快照的名稱格式爲 java_[pid].hprof
若是是OOM,可能有兩方面的緣由
一、內存分配太小,不知足程序運行所須要的內存
二、內存泄漏(FullGC頻繁,回收後Heap佔用的內存不斷增加)
3、JVM Crash 日誌分析
TODO
垃圾收集器與內存分配策略
棧的內存隨着方法的結束和線程結束自動回收,所以Java堆和方法區是垃圾收集器所關注的內存
判斷對象是否能夠回收
一、 引用計數法:給對象中添加一個引用計數器,當有一個地方被引用時加1,引用失效減1,計數器爲0的就是能夠回收的,可是會有互相引用的狀況
二、可達性分析法 對象到一系列稱爲GC Roots的對象有沒有引用鏈相連
即便在可達性分析法中不可達的對象,也至少要經歷兩次標記過程
第一次標記:可達性分析後無與GC Roots相連的引用鏈
第二次標記:第一次標記後篩選(finalize()方法沒有被JVM調用過)後放置在F-Queue隊列中,仍無引用鏈和GC Roots相連則進行第二次標記
方法區的收集:廢棄常量和無用的類
廢棄的常量:如常量池中的字符串常量「abc」,沒有String對象引用常量池的這個「abc」常量,那麼abc就是廢棄常量能夠移除常量池
無用的類:一、該類的實例都被回收 二、加載該類的ClassLoader已被回收 三、該類的Class對象沒有在任何地方被引用,也就是沒法經過反射訪問該類的方法
垃圾收集算法
一、複製
將內存劃分爲大小相等的兩塊,每次只使用其中一塊,當其中的一塊用完了將其上面存活的對象複製到另外一塊上面,而後把使用過的內存空間一次清理掉。缺點是將可用內存縮小爲了原來的一半,對象存活率較高時不適合使用。
新生代中的對象98%都是朝生夕死的,所以新生代按照8:1:1的比例分爲了eden,survivor from 和survivor to空間,每次回收將eden和survivor from中存活的對象複製到survivor to中,不夠的話再放到old中,而後將eden,survivor from一次清除掉。
二、標記-清除
首先標記須要回收的對象,在標記完成後統一回收 問題一、效率問題:標記和清除效率都不高 二、空間問題:清除後會產生大量內存碎片,過多的話會致使之後分配大對象如數組找不到一塊連續的內存而提早觸發一次GC
三、標記-整理
首先標記須要回收的對象,而後將全部存活的對象向一側移動與將要回收的對象分隔開,而後將要回收的對象一次清理掉,適合用再老年代上。
分代收集
新生代每次垃圾回收都有大量的對象死去少許存活,只需付出少許對象的複製成本便可完成收集。採用複製算法
老年代對象存活率高,沒有額外的空間作擔保, 只能採用標記-清除或者標記-整理算法
新生代垃圾收集器:Serial、ParNew、ParallelScavenge、G1
老年代垃圾收集器:CMS、Serial Old(MSC)、Parallel Old、G1
垃圾收集器的發展,使用戶線程的停頓時間在不斷縮短,可是仍沒辦法徹底消除,所以尋找更優秀的垃圾收集器仍在繼續!
Serial收集器:單線程,採用複製算法,並且進行垃圾收集時,必須暫停JVM其餘全部的工做進程,直到它收集結束。還是Client模式下虛擬機新生代默認收集器
ParNew收集器:Serial的多線程版本,採用複製算法,其餘基本相同。是運行在server模式下的虛擬機首選的新生代收集器
Parallel Scavenge收集器:與其餘收集器關注點在縮短用戶線程停頓時間不一樣,它關注點是達到一個可控制的吞吐量,吞吐量=運行用戶代碼時間/(運行代碼時間+垃圾收集時間)如:JVM總運行100分鐘,垃圾收集1分鐘,那吞吐量=99%,若是新生代採用了此收集器,那老年代只能使用Serial Old收集器
Serial Old收集器:Serial收集器的老年代版本,一樣單線程,採用標記-整理算法,存在乎義是給Client客戶端JVM使用
Parallel Old收集器:Parallel Scavenge收集器的老年代版本,採用多線程和標記-整理算法
CMS收集器(Concurrent Mark Sweep):一種以獲取最短回收停頓時間爲目標的收集器,基於標記- 清除算法實現。(只會收集老年代和永久代,1.8後改成元空間(須要設置 CMSClassUnloadingEnabled)),不會收集年輕代
一、初始標記 標記GCRoots直接關聯的對象
二、併發標記 往下跟蹤標記全部與GCRoots有引用鏈可達的對象 (可與用戶線程同時工做),就是進行GCROOTS Tracing的過程
三、從新標記 修正併發標記期間因用戶線程運行而致使的標記變更的一部分對象
四、併發清除 清除未標記的對象 (可與用戶線程同時工做)
缺點:
一、雖然在併發階段可與用戶線程同時工做,可是會佔用CPU資源,致使應用程序變慢,總吞吐量會下降
二、沒法處理浮動垃圾,即在併發清除階段新產生的垃圾,只有留待下一次GC時再清理掉
三、使用標記-清除算法,會有大量內存碎片產生
G1收集器(Garbage-First):特色:
一、並行與併發:充分利用多CPU,多核環境的硬件優點,來縮短停頓時間,在GC期間可經過併發的方式讓Java程序繼續執行
二、分代收集:採用不一樣的算法去收集剛建立的對象,存活了一段時間的對象和熬過屢次GC的對象,以獲取更好的收集效果
三、空間整合:總體基於標記-整理算法,內部region之間採用複製算法,都不會產生內存空間碎片
四、可預測的停頓:除了追求短期停頓外,還創建了可預測停頓模型,使在M毫秒內,在垃圾收集上的時間不超過N毫秒
G1邏輯上將整個Java堆劃分爲多個大小相等的獨立區域(region)。仍保留新生代和老年代的概念,但它們之間不是物理隔離了,新生代和老年代都是一部分region的集合了。G1之因此
能創建可預測停頓模型,由於它能夠有計劃的避免在整個Java堆中進行全區域的垃圾收集,G1跟蹤各個Region裏面的垃圾堆積的價值大小(回收所得到的空間大小以及回收所需時間的最優),在後臺維護一個優先列表,每次根據容許的收集時間,優先回收價值最大的region,這也是Garbage-First的由來。這種使用region劃份內存空間以及有優先級的區域回收方式保證了G1在有限的時間內可獲取儘量高的收集效率。
在G1收集器中,Region之間的對象引用以及其餘收集器中的新生代和老年代之間的對象引用都是使用Remembered Set來避免全堆掃描的。G1中每一個Region都有一個與之對應的Remembered Set,
虛擬機發現程序在對Reference類型的數據進行寫操做時,會產生一個Write Barrier暫時中斷寫操做,檢查Reference引用的對象是否處於不一樣的Region之中(在分代的例子中就是檢查是否老年代中的對象
引用了新生代中的對象),若是是,便經過CardTable把相關引用信息記錄到被引用對象所屬的Region的Remembered Set之中。當進行內存回收時,在GC根節點的枚舉範圍中加入Remember Set便可保
證不對全堆掃描也不會有遺漏。
若是不計算維護Remembered Set的操做,G1收集器的運做大體可劃分爲如下幾個步驟:
一、初始標記
二、併發標記
三、最終標記
四、篩選回收
內存分配與回收策略
Minor GC 新生代GC:通常比較頻繁,回收速度也比較快
Major GC/Full GC 老年代GC:調用System.gc() 強制執行GC爲Full GC
(Full GC停頓時間比Minor GC高几個量級,通常爲50倍以上)
對象優先在Eden區上分配,Eden區上沒有足夠的空間分配時,觸發一次Minor GC(新生代GC),將存活的對象複製進Survivor to區,若Survivor to區沒有足夠的空間存放,則經過分配擔保機制將對象轉移到老年代中。同時,通過一次Minor GC進入到survivor to區的對象,年齡計數器設爲1,在Survivor from區的對象每通過一次Minor GC,年齡加1,當年齡增長到 -XX:MaxTenuringThreshold 設定的閥值(默認15)或者在Survivor區中有相同年齡的全部對象大小總和大於Survivor區大小的一半,那麼大於這個年齡的對象,將會被移動到老年代中。
每進行Minor GC以前,在容許擔保失敗的狀況下,JVM將查看老年代中最大可用連續空間是否大於歷次minor GC晉升到老年代的對象的平均大小,若是大於,將進行一次Minor GC;若是minor GC後老年代空間不足,則緊接着觸發Full GC,若是小於,則直接觸發Full GC。(新生代和老年代的比例默認是1:2)
(HandlePromotionFailure設置是否容許擔保失敗(默認容許),若是不容許擔保失敗,那麼每次Minor GC前JVM查看老年代中最大的連續空間是否大於新生代全部對象的大小總和,若是小於,則直接觸發Full GC)
大的對象可能直接進入老年代,避免在Eden區和兩個Survivor區之間發生大量的內存複製。典型的大對象是那種很長的字符串對象或者數組。超過 -XX:PretenureSizeThreshold參數配置的大小的對象直接在老年代分配內存。
JVM性能監控和故障處理
經過工具導出和處理分析 運行日誌、異常堆棧、GC日誌、線程快照(threaddump/javacore文件)、堆轉儲快照(headdump/hprof文件)等
jps:JVM process status tool,顯示指定系統內全部的HotSpot虛擬機進程
jstat:JVM statistics Monitoring Tool,收集HotSpot虛擬機各方面的運行數據
jinfo:Configuration Info for java,顯示虛擬機配置信息
jmap:Memory map for Java,生成虛擬機的內存轉儲快照(heapdump文件)
jhat:JVM Heap Dump Browser,用於分析heapdump文件,它會創建一個http/html服務器,讓用戶能夠在瀏覽器上查看分析結果
jstack:Stack trace for Java,顯示虛擬機的線程快照
jps:和linux中ps命令類似,可列出正在運行的虛擬機進程,並顯示虛擬機執行主類和這些進程的惟一ID(Local Virtual Machine Identifier LVMID)
格式:jps [option] [hostid(主機名)]
參數:-l 輸出主類的全路徑 -v 輸出虛擬機進程啓動是的JVM參數
jstat:用於監視虛擬機各類運行狀態信息的命令行工具,它能夠顯示本地或者遠程虛擬機進程中的類加載、內存、垃圾收集、JIT編譯等運行數據,是定位虛擬機性能問題的首選工具。
格式:jstat [ option vmid [interval] [count] ]
interval和count是查詢間隔和次數,若是忽略這兩個參數則只查詢一次
選項:-class 監視類裝載、卸載數量、總空間以及類裝載消耗時間
-gc 監視Java堆情況,包含Eden區,兩個Survivor區、老年代、永久代等的容量,已用空間,GC時間等信息。
jmap:Java內存映射工具,用於生成堆存儲快照heapdump,生成堆快照還能夠經過設置參數使在OOM異常以後自動生成堆dump文件。
jmap還可查詢finalize執行隊列、Java堆和永久代的詳細信息(空間使用率,使用哪一種收集器等)。
格式:jmap [option] vmid
option參數
-dump:format=b,file=[filename] 生成堆轉儲快照
-heap 顯示Java堆詳細信息,如使用哪一種收集器、參數配置、分代情況等。
jhat:與jmap搭配使用,分析jmap生成的堆轉儲快照文件。通常不會直接使用jhat命令分析dump文件,一是不會直接在應用服務器上分析dump文件,由於分析耗時耗資源,二是jhat分析結果比較簡陋,可用VisualVM,MAT等工具
jstack:Java堆棧跟蹤工具,用戶生成虛擬機當前時刻的線程快照,線程快照就是當前虛擬機內每一條線程正在執行的方法堆棧的集合,生成快照的主要目的是定位線程出現長時間等待的緣由,如線程間死鎖、死循環、請求外部資源(如sql)致使的長時間等待等。
線程出現停頓的時候經過jstack來查看各個線程的調用堆棧,就能夠知道沒有響應的線程到底在後臺作什麼事情或者等待什麼資源
格式:jstack [option] vmid
option參數,-F:強制輸出線程堆棧
-l:除堆棧外顯示關於鎖的附加信息
-m:可顯示調用本地方法的堆棧
JDK1.5以後的Thread類新增了getAllStackTraces()方法用戶獲取虛擬機中全部線程的StackTraceElement對象。和jstack功能相似
public static Map<Thread,StackTraceElement[]> getAllStackTraces() 返回從 Thread 到 StackTraceElement 數組的一個 Map,表明相應線程的堆棧跟蹤。 、
總結若是要定位OOM問題使用jps和jmap組合命令,先用jps或者linux的ps命令查看虛擬機進程的vmid,而後用jamp命令生成堆快照文件,最後使用工具分析dump文件定位問題。
若是要定位線程響應時間過長的問題,使用jps和jstack命令,先用jps或者linux的ps命令查看虛擬機進程的vmid,而後用jstack命令查看線程堆棧信息
JVM調優
JVM調優是經過分析GC日誌等來分析java內存和垃圾回收的狀況,來調整各內存區域內存佔比和垃圾回收策略。充分使用系統資源,減小GC停頓時間和停頓次數,因爲Full GC的停頓時間遠比Minor GC的停頓時間長,所以要控制Full GC的頻率。控制Full GC的頻率的關鍵是看應用中的絕大多數對象是否符合「朝生夕滅」的原則,即大多數的對象的生存時間都不該太長,尤爲是不能有成批量的、長時間存活的對象產生,這樣這些對象在Minor GC就會被回收,不會進入老年代,這樣才能保證老年代的穩定。好比對於十幾小時乃至一天才出現一次Full GC的系統能夠經過定時任務的方式在夜間觸發Full GC。若是FullGC次數過多多是下面的緣由:一、內存佔用高:代碼中建立了大量的對象致使內存泄漏,不能回收內存,建立新對象致使空間不足觸發fullGC二、內存佔用不高:多是顯示的調用System.gc()次數太多致使的fullGC,能夠經過添加-XX:+DisableExplicitGC來禁用JVM對顯式GC的響應