一、幾個基本概念
GCRoots對象都有哪些
所有正在運行的線程的棧上的引用變量。所有的全局變量。所有ClassLoader。。。
1.System Class
.2.JNI Local
3.JNI Global
4.Thread Block
5.Busy Monitor
6.Java Local
7.Native Stack
8.Unfinalized
9.Unreachable
10.Java Stack Frame
11.Unknown
棧幀的解釋
Java虛擬機棧(Java Virtual Machine Stacks)是線程私有的,它的生命周期與線程相同。虛擬機棧描述的是Java方法執行的內存模型:每個方法被執行的時候都會同時創建一個棧幀(Stack Frame)用於存儲局部變量表、操作棧、動態鏈接、方法出口等信息。每一個方法被調用直至執行完成的過程,就對應着一個棧幀在虛擬機棧中從入棧到出棧的過程。
簡單地說,棧幀就是一個方法,里面有輸入輸出參數,局部變量表,返回值等信息,第一個參數一定是this
方法區說明
與Java堆一樣,是各個線程共享的內存區域,它用於存儲已被虛擬機加載的類信息、常量、靜態變量、即時編譯器編譯后的代碼等數據。
舉例:有一個HelloWorld的類如下
import java.text.SimpleDateFormat; import java.util.Date; import org.apache.log4j.Logger; public class HelloWorld { private static Logger LOGGER = Logger.getLogger(HelloWorld.class.getName()); public void sayHello(String message) { SimpleDateFormat formatter = new SimpleDateFormat("dd.MM.YYYY"); String today = formatter.format(new Date()); LOGGER.info(today + ": " + message); } }
堆區、方法區、棧區存放的東西如下:
說明:堆區存放的是對象信息,方法區存放的是類信息、常量、靜態變量、即時編譯后的代碼等數據;棧區存放的是線程、參數、變量、行號
二、JIT優化
JIT優化指的是即時編譯器優化(Just In Time)
常規優化
1、禁用System.gc
因為System.gc會觸發full GC,非常耗系統性能,所以要禁用
參數設置:
-XX:-DisableExplicitGC,禁用了System.gc()的顯示調用
2、逃逸分析與標量替換
分析對象動態作用域:當一個對象在方法中被定義后,它可能被外部方法所引用,例如作為調用參數傳遞到其他方法中,稱為方法逃逸。 甚至還有可能被外部線程訪問到,譬如賦值給類變量或可以在其他線程中訪問的實例變量,稱為線程逃逸。
棧上分配(Stack Allocation):如果確定一個對象不會逃逸出方法之外,那讓這個對象在棧上分配內存將會是一個很不錯的主意。由於HotSpot虛擬機目前的實現方式導致棧上分配實現起來比較復雜,因此在HotSpot中暫時還沒有做這項優化。
同步消除(Synchronization Elimination):線程同步本身是一個相對耗時的過程,如果逃逸分析能夠確定一個變量不會逃逸出線程,無法被其他線程訪問,那這個變量的讀寫肯定就不會有競爭,對這個變量實施的同步措施也就可以消除掉。
標量替換(Scalar Replacement):標量(Scalar)是指一個數據已經無法再分解成更小的數據來表示了,Java虛擬機中的原始數據類型(int、 long等數值類型以及reference類型等)都不能再進一步分解,它們就可以稱為標量。 相對的,如果一個數據可以繼續分解,那它就稱作聚合量(Aggregate),Java中的對象就是最典型的聚合量。 如果把一個Java對象拆散,根據程序訪問的情況,將其使用到的成員變量恢復原始類型來訪問就叫做標量替換。 如果逃逸分析證明一個對象不會被外部訪問,並且這個對象可以被拆散的話,那程序真正執行的時候將可能不創建這個對象,而改為直接創建它的若干個被這個方法使用到的成員變量來代替。
標量替換的示例:
有一個類A public class A{ public int a=1; public int b=2 } 方法getAB使用類A里面的a,b private void getAB(){ A x = new A(); x.a; x.b; } JVM在編譯的時候會直接編譯成 private void getAB(){ a = 1; b = 2; } 這就是標量替換
參數設置:
逃逸分析默認是啟用的,-XX:+DoEscapeAnalysis。后續有三種優化會進行:棧內分配,同步消除,標量替換
3、關閉偏向鎖優化
偏向鎖的概念:一把鎖被使用之后不主動釋放,保留給當前的使用者,預判等下一個進程來獲取的時候再釋放出來,
參數設置:
偏向鎖關閉: -XX:-UseBiasedLocking
-XX:+UseBiasedLocking -XX:BiasedLockingStartupDelay=0
4、指針壓縮
參數設置:
-XX:+UseCompressedOops
5、getter方法優化
指內聯函數的優化,何為內聯函數呢,即一個方法里面調用了另外一個方法,JVM在編譯的時候把被調用的方法合入到調用的方法里面,這樣就能減少棧幀的創建(因為每一個方法執行時都會創建一個棧幀),節約內存
使用示例:
方法1: private void getA(){ getB() } 方法2: private void getB(){ system.out.print("getB"); } 如果配置了getter方法的優化參數,JVM在編譯的時候會編譯成如下形式 private void getA(){ system.out.print("getB"); }
參數設置:
getter方法優化,-XX:UseFastAccessorMethods
JIT優化
1、開啟服務端模式
開啟服務端模式以后就有即時編譯器和解釋器兩種執行引擎,執行效率最高的是即時編譯器,所以我們做JIT優化的目的是盡量使代碼使用即時編譯器
參數設置:
-server
2、增加內聯函數的可能性
增加函數內聯的可能性能減少棧幀的創建,節約內存空間
參數設置:
使用final修飾函數向編譯器建議可以內聯,啟動參數不宜設置,注意只是建議,具體是否內聯看JVM決定
3、提高使用即時編譯器的可能性
小方法:寫方法時盡量不要寫得太大,讓JVM盡可能使用即時編譯器編譯代碼
在啟動項配置參數-XX:CompileThreshold=10000,使得一個方法被調用超過10000次以后使用即時編譯器編譯為機器碼
OSR編譯閾值
A、調用計數器,即方法被調用的次數,CompileThreshold,該值是指當方法被調用多少次后,就編譯為機器碼,client模式默認為1500次,server模式默認為1萬次,可以在啟動時添加-XX:CompileThreshold=10000來設置該值。
B、回邊計數器,即方法中循環執行部分代碼的執行次數,OnStackReplacePercentage,該值用於/參與計算是否觸發OSR編譯的閾值,client默認為933,sever默認為140,可以通過-XX:OnStackReplacePercentage=140來設置。
client模式下的計算規則為
CompileThreshold*OnStackReplacePercentage/100,
server模式下計算規則為
CompileThreshold*(OnStackReplacePercentage-InterpreterProfilePercentage)/100。
InterpreterProfilePercentage,默認為33。
4、降低線程優先級
Linux不能設置,需要root權限
5、熱度衰減與半衰周期
三、內存優化
1、將新對象預留在年輕代
參數設置:
-XX:TargetSurvivorRatio=90
90表示讓新生代的from區的利用率為90%,這樣新對象進來就會優先在里面
2、讓大對象進入年老代
參數設置:
-XX:PetenureSizeThreshold=1000000,1M
大小為1M的對象為大對象
3、設置對象進入年老代的年齡
參數設置:
-XX:MaxTenuringThreshold=31
表示在新生代經過31次回收以后還存活的對象移到老年代,默認值是15,設置31的目的是讓對象盡可能的在新生代就被回收,避免進入老年代觸發full GC
4、穩定的 Java 堆
參數設置:
Xmx與Xms相同
最小堆內存和最大堆內存設置為一樣的目的是避免頻繁的向操作系統申請內存占用系統資源
5、增大吞吐量提升系統性能
指設置合理的垃圾回收器
參數設置:
– X X : + U s e P a r a l l e l G C :年輕代使用多線程的收集器
–XX:+UseParallelOldGC:老年代使用多線程的垃圾收集器
–XX:ParallelGC-Threads(CPU核心數相等):設置垃圾回收時使用的線程數
6、使用非占有的垃圾回收器
參數設置:
–XX:+UseConcMarkSweepGC:使用CMS垃圾回收器
四、監控及工具
1. jps:虛擬機進程狀況工具
它的功能也和ps命令類似:可以列出正在運行的虛擬機進程,並顯示虛擬機執行主類(Main Class,main()函數所在的類)名稱以及這些進程的本地虛擬機唯一ID(Local Virtual Machine Identifier,LVMID)。
jps可以通過RMI協議查詢開啟了RMI服務的遠程虛擬機進程狀態,hostid為RMI注冊表中注冊的主機名。
簡單地說:jps可以用來查看java進程的id
參數選項:
2. jstat:虛擬機統計信息監視工具
用於監視虛擬機各種運行狀態信息的命令行工具。它可以顯示本地或者遠程虛擬機進程中的類裝載、內存、垃圾收集、JIT編譯等運行數據,在沒有GUI圖形界面,只提供了純文本控制台環境的服務器上,它將是運行期定位虛擬機性能問題的首選工具
參數選項:
3. jmap:Java內存映像工具
jmap的作用並不僅僅是為了獲取dump文件,它還可以查詢finalize執行隊列、Java堆和永久代的詳細信息,如空間使用率、當前用的是哪種收集器等。
參數選項:
4. 其他工具
jinfo:Java配置信息工具
作用是實時地查看和調整虛擬機各項參數。使用-sysprops選項把虛擬機進程System.getProperties()的內容打印出來。
jhat:虛擬機堆轉儲快照分析工具,和jmap配合使用
jhat內置了一個微型的HTTP/HTML服務器,jmap生成dump文件的分析結果后,可以在瀏覽器中查看。
HSDIS:JIT生成代碼反匯編
HSDIS是一個HotSpot虛擬機JIT編譯代碼的反匯編插件,它包含在HotSpot虛擬機的源碼之中,但沒有提供編譯后的程序。
5. 可視化工具
JConsole:Java監視與管理控制台
JConsole(Java Monitoring and Management Console)是一種基於JMX的可視化監視、管理工具。它管理部分的功能是針對JMX MBean進行管理,由於MBean可以使用代碼、中間件服務器的管理控制台或者所有符合JMX規范的軟件進行訪問。
VisualVM:多合一故障處理工具
VisualVM(All-in-One Java Troubleshooting Tool)是到目前為止隨JDK發布的功能最強大的運行監視和故障處理程序。VisualVM的還有一個很大的優點:不需要被監視的程序基於特殊Agent運行,因此它對應用程序的實際性能的影響很小,使得它可以直接應用在生產環境
JMC,Oracle Java Mission Control 是一個用於對 Java 應用程序進行管理、監視、概要分析和故障排除的工具套件。首次安裝時,Java Mission Control 包括 JMX 控制台和 Java 飛行記錄器。從 Mission Control 中可以輕松安裝更多插件
6. 即時編譯器監控工具JITWatch
安裝:
git clone git@github.com:AdoptOpenJDK/jitwatch.git
cd jitwatch
mvn clean install -DskipTests=true
運行:launchUI.bat
使用:XX:+UnlockDiagnosticVMOptions -XX:+TraceClassLoading -XX:+LogCompilation - XX:+PrintAssembly
查看結果。
五、JVM優化配置示例
服務器:8 cpu, 8G mem
e.g.
java -Xmx3550m -Xms3550m -Xss128k -XX:NewRatio=4 -XX:SurvivorRatio=4 -XX:MaxPermSize=16m -XX:MaxTenuringThreshold=0
調優方案:
-Xmx5g:設置JVM最大可用內存為5G。
-Xms5g:設置JVM初始內存為5G。此值可以設置與-Xmx相同,以避免每次垃圾回收完成后JVM重新分配內存。
-Xmn2g:設置年輕代大小為2G。整個堆內存大小 = 年輕代大小 + 年老代大小 + 持久代大小 。持久代一般固定大小為64m,所以增大年輕代后,將會減小年老代大小。此值對系統性能影響較大,Sun官方推薦配置為整個堆的3/8。
-XX:+UseParNewGC:設置年輕代為並行收集。可與CMS收集同時使用。JDK5.0以上,JVM會根據系統配置自行設置,所以無需再設置此值。
-XX:ParallelGCThreads=8:配置並行收集器的線程數,即:同時多少個線程一起進行垃圾回收。此值最好配置與處理器數目相等。
-XX:SurvivorRatio=6:設置年輕代中Eden區與Survivor區的大小比值。根據經驗設置為6,則兩個Survivor區與一個Eden區的比值為2:6,一個Survivor區占整個年輕代的1/8。
-XX:MaxTenuringThreshold=30: 設置垃圾最大年齡(次數)。如果設置為0的話,則年輕代對象不經過Survivor區直接進入年老代。對於年老代比較多的應用,可以提高效率。如果將此值設置為一個較大值,則年輕代對象會在Survivor區進行多次復制,這樣可以增加對象再年輕代的存活時間,增加在年輕代即被回收的概率。設置為30表示 一個對象如果在Survivor空間移動30次還沒有被回收就放入年老代。
-XX:+UseConcMarkSweepGC:設置年老代為並發收集。測試配置這個參數以后,參數-XX:NewRatio=4就失效了,所以,此時年輕代大小最好用-Xmn設置,因此這個參數不建議使用