JVM的幾點性能優化

HotSpot,家喻戶曉的JVM,我們的Java和Scala程序就運行在它上面。年復一年,一次又一次的迭代,經過無數工程師的不斷優化,現在它的代碼執行的速度和效率已經逼近本地編譯的代碼了。

它的核心是一個JIT(Just-In-Time)編譯器。JIT只有一個目的,就是為了提升你代碼的執行速度,這也是HotSpot能如此流行和成功的重要因素。

JIT編譯器都做了什么?

你的代碼在執行的時候,JVM會收集它運行的相關數據。一旦收集到了足夠的數據,證明某個方法是熱點(默認是1萬次調用),JIT就會介入進來,將“運行緩慢的”平臺獨立的的字節碼轉化成本地編譯的,優化瘦身后的版本。

有些優化是顯而易見的:比如簡單方法內聯,刪除無用代碼,將庫函數調用替換成本地方法等。不過JIT編譯的威力遠不止此。下面列舉了它的一些非常有意思的優化:

分而治之

你是不是經常會這樣寫代碼:

StringBuilder sb = new StringBuilder("Ingredients: ");for (int i = 0; i < ingredients.length; i++) {if (i > 0) {sb.append(", ");}sb.append(ingredients[i]);
}return sb.toString(); 

或者這樣:

boolean nemoFound = false;for (int i = 0; i < fish.length; i++) {String curFish = fish[i];if (!nemoFound) {if (curFish.equals("Nemo")) {System.out.println("Nemo! There you are!");nemoFound = true;continue;}}if (nemoFound) {System.out.println("We already found Nemo!");} else {System.out.println("We still haven't found Nemo : (");}
} 

這兩個例子的共同之處是,循環體里先是處理這個事情,過一段時間又處理另外一件。編譯器可以識別出這些情況,它可以將循環拆分成不同的分支,或者將幾次迭代單獨剝離。

我們來說下第一個例子。if(i>0)第一次的時候是false,后面就一直是true。為什么要每次都判斷這個呢?編譯器會對它進行優化,就好像你是這樣寫的一樣:

StringBuilder sb = new StringBuilder("Ingredients: "); 
if (ingredients.length > 0) {sb.append(ingredients[0]);for (int i = 1; i < ingredients.length; i++) {sb.append(", ");sb.append(ingredients[i]);}
}return sb.toString();

這樣寫的話,多余的if(i > 0)被去掉了,盡管也帶來了一些代碼重復(兩處append),不過性能上得到了提升。

邊界條件優化

檢查空指針是很常見的一個操作。有時候null是一個有效的值(比如,表明缺少某個值,或者出現錯誤),有時候檢查空指針是為了代碼能正常運行。

有些檢查是永遠不會失敗的(在這里null代表失敗)。這里有一個典型的場景:

public static String l33tify(String phrase) {
if (phrase == null) {
throw new IllegalArgumentException("phrase must not be null");
}
return phrase.replace('e', '3');
}

如果你代碼寫得好的話,沒有傳null值給l33tify方法,這個判斷永遠不會失敗。

在多次執行這段代碼并且一直沒有進入到if語句之后,JIT編譯器會認為這個檢查很多可能是多余的。然后它會重新編譯這個方法,把這個檢查去掉,最后代碼看起來就像是這樣的:

public static String l33tify(String phrase) {
return phrase.replace('e', '3');
}

這能顯著的提升性能,而且在很多時候這么優化是沒有問題的。

那萬一這個樂觀的假設實際上是錯了呢?

JVM現在執行的已經是本地代碼了,空引用可不會引起NullPointerException,而是真正的嚴重的內存訪問沖突,JVM是個低級生物,它會去處理這個段錯誤,然后恢復執行沒有優化過的代碼——這個編譯器可再也不敢認為它是多余的了:它會重新編譯代碼,這下空指針的檢查又回來了。

虛方法內聯

JVM的JIT編譯器和其它靜態編譯器的最大不同就是,JIT編譯器有運行時的動態數據,它可以基于這些數據進行決策。

方法內聯是編譯器一個常見的優化,編譯器將方法調用替換成實際調用的代碼,以避免一次調用的開銷。不過當碰到虛方法調用(動態分發)的話情況就需要點小技巧了。

先看下這段代碼 :

public class Main {
public static void perform(Song s) {
s.sing();
}
}public interface Song { void sing(); }public class GangnamStyle implements Song {
@Override
public void sing() {
System.out.println("Oppan gangnam style!");
}
}public class Baby implements Song {
@Override
public void sing() {
System.out.println("And I was like baby, baby, baby, oh");
}
}

perform方法可能會被調用了無數次,每次都會調用sing方法。方法調用的開銷當然是很大的,尤其像這種,因為它需要根據運行時s的類型來動態選擇具體執行的代碼。在這里,方法內聯看真來像是遙不可及的夢想,對吧?

當然不是了。在多次執行perform方法后,編譯器會根據它收集的數據發現,95%的調用對象都是GangnamStyle實例。這樣的話,JIT編譯器會很樂觀將虛方法的調用優化掉。也就是說,編譯器會直接生成本地代碼 ,對應的Java實現大概是這樣的:

public static void perform(Song s) {
if (s fastnativeinstanceof GangnamStyle) {
System.out.println("Oppan gangnam style!");
} else {
s.sing();
}
}

由于這個優化取決于運行時信息,它可以優化掉大部分的sing方法調用,盡管這個方法是多態的。

JIT編譯器還有很多很有意思的技巧,這只是介紹了其中的幾點,讓你能感覺到我們代碼在執行的時候,JVM在底層都做了些什么優化。

我能幫助JIT做些什么優化嗎

JIT編譯器是針對一般人的編譯器;它是用來優化正常寫出的代碼的,它會去分析日常標準寫法中的一些模式。不要刻意寫代碼去幫助JIT編譯器進行優化就是對它最好的幫助 ——就正常寫你自己的代碼就好了。

譯注:JIT還有許多很多意思的優化,這里只是列舉出了幾點。當然了,你也不用太在意它,就像文中最后說的,正常寫好自己的代碼就好了。




HotSpot,家喻戶曉的JVM,我們的Java和Scala程序就運行在它上面。年復一年,一次又一次的迭代,經過無數工程師的不斷優化,現在它的代碼執行的速度和效率已經逼近本地編譯的代碼了。

它的核心是一個JIT(Just-In-Time)編譯器。JIT只有一個目的,就是為了提升你代碼的執行速度,這也是HotSpot能如此流行和成功的重要因素。

JIT編譯器都做了什么?

你的代碼在執行的時候,JVM會收集它運行的相關數據。一旦收集到了足夠的數據,證明某個方法是熱點(默認是1萬次調用),JIT就會介入進來,將“運行緩慢的”平臺獨立的的字節碼轉化成本地編譯的,優化瘦身后的版本。

有些優化是顯而易見的:比如簡單方法內聯,刪除無用代碼,將庫函數調用替換成本地方法等。不過JIT編譯的威力遠不止此。下面列舉了它的一些非常有意思的優化:

分而治之

你是不是經常會這樣寫代碼:

StringBuilder sb = new StringBuilder("Ingredients: ");for (int i = 0; i < ingredients.length; i++) {if (i > 0) {sb.append(", ");}sb.append(ingredients[i]);
}return sb.toString(); 

或者這樣:

boolean nemoFound = false;for (int i = 0; i < fish.length; i++) {String curFish = fish[i];if (!nemoFound) {if (curFish.equals("Nemo")) {System.out.println("Nemo! There you are!");nemoFound = true;continue;}}if (nemoFound) {System.out.println("We already found Nemo!");} else {System.out.println("We still haven't found Nemo : (");}
} 

這兩個例子的共同之處是,循環體里先是處理這個事情,過一段時間又處理另外一件。編譯器可以識別出這些情況,它可以將循環拆分成不同的分支,或者將幾次迭代單獨剝離。

我們來說下第一個例子。if(i>0)第一次的時候是false,后面就一直是true。為什么要每次都判斷這個呢?編譯器會對它進行優化,就好像你是這樣寫的一樣:

StringBuilder sb = new StringBuilder("Ingredients: "); 
if (ingredients.length > 0) {sb.append(ingredients[0]);for (int i = 1; i < ingredients.length; i++) {sb.append(", ");sb.append(ingredients[i]);}
}return sb.toString();

這樣寫的話,多余的if(i > 0)被去掉了,盡管也帶來了一些代碼重復(兩處append),不過性能上得到了提升。

邊界條件優化

檢查空指針是很常見的一個操作。有時候null是一個有效的值(比如,表明缺少某個值,或者出現錯誤),有時候檢查空指針是為了代碼能正常運行。

有些檢查是永遠不會失敗的(在這里null代表失敗)。這里有一個典型的場景:

public static String l33tify(String phrase) {
if (phrase == null) {
throw new IllegalArgumentException("phrase must not be null");
}
return phrase.replace('e', '3');
}

如果你代碼寫得好的話,沒有傳null值給l33tify方法,這個判斷永遠不會失敗。

在多次執行這段代碼并且一直沒有進入到if語句之后,JIT編譯器會認為這個檢查很多可能是多余的。然后它會重新編譯這個方法,把這個檢查去掉,最后代碼看起來就像是這樣的:

public static String l33tify(String phrase) {
return phrase.replace('e', '3');
}

這能顯著的提升性能,而且在很多時候這么優化是沒有問題的。

那萬一這個樂觀的假設實際上是錯了呢?

JVM現在執行的已經是本地代碼了,空引用可不會引起NullPointerException,而是真正的嚴重的內存訪問沖突,JVM是個低級生物,它會去處理這個段錯誤,然后恢復執行沒有優化過的代碼——這個編譯器可再也不敢認為它是多余的了:它會重新編譯代碼,這下空指針的檢查又回來了。

虛方法內聯

JVM的JIT編譯器和其它靜態編譯器的最大不同就是,JIT編譯器有運行時的動態數據,它可以基于這些數據進行決策。

方法內聯是編譯器一個常見的優化,編譯器將方法調用替換成實際調用的代碼,以避免一次調用的開銷。不過當碰到虛方法調用(動態分發)的話情況就需要點小技巧了。

先看下這段代碼 :

public class Main {
public static void perform(Song s) {
s.sing();
}
}public interface Song { void sing(); }public class GangnamStyle implements Song {
@Override
public void sing() {
System.out.println("Oppan gangnam style!");
}
}public class Baby implements Song {
@Override
public void sing() {
System.out.println("And I was like baby, baby, baby, oh");
}
}

perform方法可能會被調用了無數次,每次都會調用sing方法。方法調用的開銷當然是很大的,尤其像這種,因為它需要根據運行時s的類型來動態選擇具體執行的代碼。在這里,方法內聯看真來像是遙不可及的夢想,對吧?

當然不是了。在多次執行perform方法后,編譯器會根據它收集的數據發現,95%的調用對象都是GangnamStyle實例。這樣的話,JIT編譯器會很樂觀將虛方法的調用優化掉。也就是說,編譯器會直接生成本地代碼 ,對應的Java實現大概是這樣的:

public static void perform(Song s) {
if (s fastnativeinstanceof GangnamStyle) {
System.out.println("Oppan gangnam style!");
} else {
s.sing();
}
}

由于這個優化取決于運行時信息,它可以優化掉大部分的sing方法調用,盡管這個方法是多態的。

JIT編譯器還有很多很有意思的技巧,這只是介紹了其中的幾點,讓你能感覺到我們代碼在執行的時候,JVM在底層都做了些什么優化。

我能幫助JIT做些什么優化嗎

JIT編譯器是針對一般人的編譯器;它是用來優化正常寫出的代碼的,它會去分析日常標準寫法中的一些模式。不要刻意寫代碼去幫助JIT編譯器進行優化就是對它最好的幫助 ——就正常寫你自己的代碼就好了。

譯注:JIT還有許多很多意思的優化,這里只是列舉出了幾點。當然了,你也不用太在意它,就像文中最后說的,正常寫好自己的代碼就好了。



原創文章轉載請注明出處:JVM的幾點性能優化

英文原文鏈接



本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/448991.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/448991.shtml
英文地址,請注明出處:http://en.pswp.cn/news/448991.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

IDEA配置 及 快捷鍵

出處&#xff1a; https://www.cnblogs.com/hero123/p/10120552.html 快捷鍵&#xff1a; 格式化代碼 CtrlaltL 后退Ctrlalt <- 格式化代碼快捷鍵&#xff1a;Ctrl Alt L 刪除整行&#xff1a;CtrlX 實現類 ctrl alt CtrlN 查找類 CtrlShiftN 查找文件 CTRLSHIFTALTN 查找…

LeetCode Decode Ways

123123轉載于:https://www.cnblogs.com/ZHONGZHENHUA/p/10854545.html

SpringBoot 之集成 Spring AOP

前些天發現了一個巨牛的人工智能學習網站&#xff0c;通俗易懂&#xff0c;風趣幽默&#xff0c;忍不住分享一下給大家。點擊跳轉到教程。 在開始之前&#xff0c;我們先把需要的jar包添加到工程里。新增Maven依賴如下&#xff1a; <dependency><groupId>org.spri…

9件事把你從消極情緒中解救出來

也許你很難相信&#xff0c;但是情緒可以通過重復形成習慣。消極情緒甚至可以變成某種嵌入你每日生活的東西。 如何將它們趕跑? 你發現你不斷地埋怨世界和自己?你可以輕易地生氣并且對人變得刻薄?那憤怒又是否成為你對事情本能的回應了?如果你對所述問題中的一個回答了“是…

數據庫主鍵自增插入顯示值

版權聲明&#xff1a;本文為博主原創文章&#xff0c;未經博主同意不得轉載。 https://blog.csdn.net/nwsuaf2009012882/article/details/32703597 SQL Server 2008 數據庫主鍵自增插入顯示值 前幾天在工作的時候遇到在刪除數據庫中表的數據的時候。刪除之后&#xff0c;又一次…

解決: This application has no explicit mapping for /error, so you are seeing this as a fallback.

前些天發現了一個巨牛的人工智能學習網站&#xff0c;通俗易懂&#xff0c;風趣幽默&#xff0c;忍不住分享一下給大家。點擊跳轉到教程。 報錯如題&#xff0c;出現這個異常說明了跳轉頁面的url無對應的值. 原因1: Application啟動類的位置不對.要將Application類放在最外側…

Selenium自動化獲取WebSocket信息

性能日志 ChromeDriver支持性能日志記錄&#xff0c;您可以從中獲取域“時間軸”&#xff0c;“網絡”和“頁面”的事件&#xff0c;以及指定跟蹤類別的跟蹤數據。啟用性能日志 默認情況下不啟用性能日志記錄。因此&#xff0c;在創建新會話時&#xff0c;您必須啟用它。 Desir…

零負債之人的10個習慣

無論你是已下定決心要于今年實現零負債&#xff0c;還是距離這個目標的實現有很長的路要走&#xff0c;能受到啟發總是好事。 看看你認識的已經過上“無債一身輕”生活的人──朋友、家人、同事或是你認為可能與其他無負債之人具有類似品質的人。 下文為無負債之人的10個共同…

《App后臺開發運維與架構實踐》第3章 App后臺核心技術

2019獨角獸企業重金招聘Python工程師標準>>> 3.1 用戶驗證方案 3.1.1 使用HTTPS協議 HTTPS協議是“HTTP協議”和“SSL/TLS”的組合。SSL&#xff08;Secure Sockets Layer&#xff09;&#xff0c;即安全套接層&#xff0c;是為了解決因HTTP協議是明文而導致傳輸內容…

IntelliJ IDEA 配置 JDK

前些天發現了一個巨牛的人工智能學習網站&#xff0c;通俗易懂&#xff0c;風趣幽默&#xff0c;忍不住分享一下給大家。點擊跳轉到教程。 提前安裝jdk&#xff0c;配置環境變量 一、配置jdk 1、依次點開File -->Project Structure&#xff0c;點擊左側標簽頁&#xff0c…

xml編輯無提示?這么破!

在學習testng這個單元測試框架時&#xff0c;如果咱們碰到了編輯測試套件xml&#xff0c;不提示的情況&#xff08;有提示方便咱們學習&#xff0c;并且testng的測試套件定義必須按照他的dtd文件約束來&#xff09;&#xff0c;咱們可以按照下面的步驟去解決這個問題。 1.檢查t…

“云棲直播”升級為“公開課”

直播平臺是面向廣大開發者的視頻學習平臺&#xff0c;幫助廣大開發者學習最新技術&#xff0c;了解最新阿里云產品以及最新技術發展趨勢&#xff0c;幫助開發者們不斷學習與成長。截止到2019年3月&#xff0c;直播共進行800余場&#xff0c;觀看人次100萬。  社區將對“云棲直…

遭銀行賬號詐騙最快最有效自救法

銀行卡或賬戶詐騙案件層出不窮&#xff0c;當匯錯款時該怎么做&#xff0c;切記以下方法&#xff1a; 一、當匯錯款或被騙匯款后&#xff0c;最快最有效的緊急自救法&#xff1a;當你把自己的錢不小心匯到了不該匯的人卡上&#xff0c;或者被騙子忽悠而把錢匯給了騙子&#xf…

SQL 判斷非空 NULL :IFNUL( ) 、COALESCE( ) 、ISNULL( ) 、NVL( )

前些天發現了一個巨牛的人工智能學習網站&#xff0c;通俗易懂&#xff0c;風趣幽默&#xff0c;忍不住分享一下給大家。點擊跳轉到教程。 1. mysql 支持&#xff1a; IFNULL&#xff08;&#xff09;、COALESCE&#xff08;&#xff09; 如 IFNULL(UnitsOnOrder, 0) 或者 CO…

navigator.geolocation的應用 - 將定位信息顯示在百度地圖上

在學習navigator.geolocation的時候&#xff0c;有一個實例是獲取坐標后顯示在谷歌地圖上。眾所周知&#xff0c;谷歌地圖國內并不能直接訪問&#xff0c;得用特殊手段&#xff0c;那我要測試的時候還要開著梯子挺麻煩的&#xff0c;想給別人用也得那個人能訪問谷歌地圖先。 地…

centos7 mysql數據庫安裝和配置

2019獨角獸企業重金招聘Python工程師標準>>> 一、系統環境 yum update升級以后的系統版本為 [rootyl-web yl]# cat /etc/redhat-release CentOS Linux release 7.1.1503 (Core) 二、mysql安裝 一般網上給出的資料都是 #yum install mysql #yum install mysql-serve…

5種聰明工作法

1、每天最多做三件事 請拿出你落落長的待辦清單&#xff0c;圈出最重要的一~三件事&#xff0c;然后給自己一天的時間&#xff0c;卯足全力解決它! 你不需要因為還有很多事要做而焦慮&#xff0c;只需要專注今天、當下、以及最重要的問題。 《與成功有約》作者史蒂芬.柯維(Step…

【Quartz】Quartz概述及入門實例

前些天發現了一個巨牛的人工智能學習網站&#xff0c;通俗易懂&#xff0c;風趣幽默&#xff0c;忍不住分享一下給大家。點擊跳轉到教程。 Quartz 在開源任務調度框架中的翹首&#xff0c;它提供了強大任務調度機制&#xff0c;難能可貴的是它同時保持了使用的簡單性。Quartz 允…

python中del語句

有一種方式可以從列表按照給定的索引而不是值來移除一個元素: 那就是 del 語句。 它不同于會返回一個值的 pop() 方法。 del 語句也可以用來從列表中移除切片或者清空整個python列表&#xff08;我們之前用過的方式是將一個空列表賦值給指定的切片&#xff09;。 例如: >>…

偷時間的孩子

從事臨床心理工作已有十三、四年(至一九九五年)&#xff0c;真的有很多話想跟父母們敞開心扉的談談。 忙碌的現代社會&#xff0c;讓我窺探到了許許多多的杰出角色&#xff0c;他們偷取家庭時間去換取自己的功成名就&#xff0c;他們的心隨著公司的企劃案四處流浪&#xff0c;孩…