日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

JVM的几点性能优化

發布時間:2023/12/13 编程问答 49 豆豆
生活随笔 收集整理的這篇文章主要介紹了 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的幾點性能優化

英文原文鏈接



總結

以上是生活随笔為你收集整理的JVM的几点性能优化的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。