from:http://blog.csdn.net/tanxiang21/article/details/16859781
          1.語(yǔ)法糖 數(shù)字下劃線

           1  2  3  4  5  6  7  8  9
          package com.java7developer.chapter1;
          import java.util.Collection;
          import java.util.HashMap;
          public class Coin {
          int test = 123_567;
          long test1 = 100_000L;
          }
           來自CODE的代碼片
          Coin.java

          2.switch語(yǔ)句中的String

           1  2  3  4
          public void printDay(String dayOfWeek){
          case "Sunday":System.out.println("ddd");break;
          default:System.out.println("sss");break;
          }
           來自CODE的代碼片
          snippet_file_0.txt

          3.multicatch

            1   2   3   4   5   6   7   8   9  10  11  12  13  14
          public Configuration getConfig(String fileName) {
          Configuration cfg = null;
          try {
          String fileText = getFile(fileName);
          cfg = verifyConfig(parseConfig(fileText));
          } catch (FileNotFoundException | ParseException | ConfigurationException e) {
          System.err.println("Config file '" + fileName
          + "' is missing or malformed");
          } catch (IOException iox) {
          System.err.println("Error while processing file '" + fileName + "'");
          }
          return cfg;
          }
           來自CODE的代碼片
          snippet_file_0.txt

          4.final重拋

          對(duì)比上份代碼
           1  2  3  4  5  6
          try {
          String fileText = getFile(fileName);
          cfg = verifyConfig(parseConfig(fileText));
          } catch (final Exception e) {
          throw e;
          }
           來自CODE的代碼片
          snippet_file_0.txt

          5.try-with-resources(TWR) AutoCloseable

            1   2   3   4   5   6   7   8   9  10  11  12  13  14  15  16  17  18  19  20  21  22  23  24  25  26  27  28  29  30  31  32  33  34  35  36
          package com.java7developer.chapter1;
          import java.io.File;
          import java.io.FileOutputStream;
          import java.io.IOException;
          import java.io.InputStream;
          import java.io.OutputStream;
          import java.net.MalformedURLException;
          import java.net.URL;
          public class Java7ResourcesExample {
          private void run() throws IOException {
          File file = new File("foo");
          URL url = null;
          try {
          url = new URL("http://www.google.com/");
          } catch (MalformedURLException e) {
          }
          try (OutputStream out = new FileOutputStream(file);
          InputStream is = url.openStream()) {
          byte[] buf = new byte[4096];
          int len;
          while ((len = is.read(buf)) > 0) {
          out.write(buf, 0, len);
          }
          }
          }
          public static void main(String[] args) throws IOException {
          Java7ResourcesExample instance = new Java7ResourcesExample();
          instance.run();
          }
          }
           來自CODE的代碼片
          Java7ResourcesExample.java

          6.鉆石語(yǔ)法

           1
          HashMap<String, String> a = new HashMap<>();
           來自CODE的代碼片
          Java7-新特性-鉆石語(yǔ)法

          7.變參 消失的警告 @SafeVarargs

            1   2   3   4   5   6   7   8   9  10  11  12  13  14  15
          public class Coin {
          int test = 123_567;
          long test1 = 100_000L;
          @SafeVarargs
          public static <T> Collection<T> doSomething(T... entries){
          return null;
          }
          public static void main(String[] args) {
          HashMap<String, String> a = new HashMap<>();
          HashMap<String, String> b = new HashMap<>();
          doSomething(a,b);
          }
          }
          posted @ 2015-03-06 16:35 小馬歌 閱讀(241) | 評(píng)論 (0)編輯 收藏
           

          from:http://www.lifebackup.cn/timsort-java7.html

          2012年09月25日 21:57:48

          1. 為什么寫這篇文章

          這篇文章的根源是在產(chǎn)品中發(fā)現(xiàn)了一個(gè)詭異的bug:只能在產(chǎn)品環(huán)境下重現(xiàn),在我的本地開發(fā)環(huán)境無法重現(xiàn),而雙方的代碼沒有任何區(qū)別。最后用remote debug的方法找到異常所在:

          Exception in thread "main" java.lang.IllegalArgumentException: Comparison 
          method violates its general contract!

          Google了這個(gè)錯(cuò)誤,是由于Java 7內(nèi)置的新排序算法導(dǎo)致的。這才猛然想起產(chǎn)品的編譯環(huán)境最近升級(jí)到了Java 7。

          2. 結(jié)論

          在Java 6中Arrays.sort()和Collections.sort()使用的是MergeSort,而在Java 7中,內(nèi)部實(shí)現(xiàn)換成了TimSort,其對(duì)對(duì)象間比較的實(shí)現(xiàn)要求更加嚴(yán)格:

          Comparator的實(shí)現(xiàn)必須保證以下幾點(diǎn)(出自這兒):

          a). sgn(compare(x, y)) == -sgn(compare(y, x)) 
          b). (compare(x, y)>0) && (compare(y, z)>0) 意味著 compare(x, z)>0 
          c). compare(x, y)==0 意味著對(duì)于任意的z:sgn(compare(x, z))==sgn(compare(y, z)) 均成立

          而我們的代碼中,某個(gè)compare()實(shí)現(xiàn)片段是這樣的:

          public int compare(ComparatorTest o1, ComparatorTest o2) { 
              return o1.getValue() > o2.getValue() ? 1 : -1; 
          }

          這就違背了a)原則:假設(shè)X的value為1,Y的value也為1;那么compare(X, Y) ≠ –compare(Y, X) 
          PS: TimSort不僅內(nèi)置在各種JDK 7的版本,也存在于Android SDK中(盡管其并沒有使用JDK 7)。

          3. 解決方案

          3.1) 更改內(nèi)部實(shí)現(xiàn):例如對(duì)于上個(gè)例子,就需要更改為

          public int compare(ComparatorTest o1, ComparatorTest o2) { 
              return o1.getValue() == o2.getValue() ? 0 :  
                          (o1.getValue() > o2.getValue() ? 1 : -1); 
          }

          3.2) Java 7預(yù)留了一個(gè)接口以便于用戶繼續(xù)使用Java 6的排序算法:在啟動(dòng)參數(shù)中(例如eclipse.ini)添加-Djava.util.Arrays.useLegacyMergeSort=true

          3.3) 將這個(gè)IllegalArgumentException手動(dòng)捕獲住(不推薦)

          4. TimSort在Java 7中的實(shí)現(xiàn)

          那么為什么Java 7會(huì)將TimSort作為排序的默認(rèn)實(shí)現(xiàn),甚至在某種程度上犧牲它的兼容性(在stackoverflow上有大量的問題是關(guān)于這個(gè)新異常的)呢?接下來我們不妨來看一看它的實(shí)現(xiàn)。

          首先建議大家先讀一下這篇文章以簡(jiǎn)要理解TimSort的思想。

          4.1) 如果傳入的Comparator為空,則使用ComparableTimSort的sort實(shí)現(xiàn)。

           image

          4.2) 傳入的待排序數(shù)組若小于MIN_MERGE(Java實(shí)現(xiàn)中為32,Python實(shí)現(xiàn)中為64),則

          a) 從數(shù)組開始處找到一組連接升序或嚴(yán)格降序(找到后翻轉(zhuǎn))的數(shù) 
          b) Binary Sort:使用二分查找的方法將后續(xù)的數(shù)插入之前的已排序數(shù)組

          image

          4.3) 開始真正的TimSort過程:

          4.3.1) 選取minRun大小,之后待排序數(shù)組將被分成以minRun大小為區(qū)塊的一塊塊子數(shù)組

          a) 如果數(shù)組大小為2的N次冪,則返回16(MIN_MERGE / 2) 
          b) 其他情況下,逐位向右位移(即除以2),直到找到介于16和32間的一個(gè)數(shù)

          image

          4.3.2) 類似于4.2.a找到初始的一組升序數(shù)列 
          4.3.3) 若這組區(qū)塊大小小于minRun,則將后續(xù)的數(shù)補(bǔ)足(采用binary sort插入這個(gè)數(shù)組) 
          4.3.4) 為后續(xù)merge各區(qū)塊作準(zhǔn)備:記錄當(dāng)前已排序的各區(qū)塊的大小 
          4.3.5) 對(duì)當(dāng)前的各區(qū)塊進(jìn)行merge,merge會(huì)滿足以下原則(假設(shè)X,Y,Z為相鄰的三個(gè)區(qū)塊):

          a) 只對(duì)相鄰的區(qū)塊merge 
          b) 若當(dāng)前區(qū)塊數(shù)僅為2,If X<=Y,將X和Y merge 
          b) 若當(dāng)前區(qū)塊數(shù)>=3,If X<=Y+Z,將X和Y merge,直到同時(shí)滿足X>Y+Z和Y>Z

          image

          4.3.6) 重復(fù)4.3.2 ~ 4.3.5,直到將待排序數(shù)組排序完 
          4.3.7) Final Merge:如果此時(shí)還有區(qū)塊未merge,則合并它們

          image

          5. Demo

          這一節(jié)用一個(gè)具體的例子來演示整個(gè)算法的演進(jìn)過程:

          *注意*:為了演示方便,我將TimSort中的minRun直接設(shè)置為2,否則我不能用很小的數(shù)組演示。。。同時(shí)把MIN_MERGE也改成2(默認(rèn)為32),這樣避免直接進(jìn)入binary sort。

          初始數(shù)組為[7,5,1,2,6,8,10,12,4,3,9,11,13,15,16,14] 
          => 尋找連續(xù)的降序或升序序列 (4.3.2) 
          [1,5,7] [2,6,8,10,12,4,3,9,11,13,15,16,14] 
          => 入棧 (4.3.4) 
          當(dāng)前的棧區(qū)塊為[3] 
          => 進(jìn)入merge循環(huán) (4.3.5) 
          do not merge因?yàn)闂4笮H為1 
          => 尋找連續(xù)的降序或升序序列 (4.3.2) 
          [1,5,7] [2,6,8,10,12] [4,3,9,11,13,15,16,14] 
          => 入棧 (4.3.4) 
          當(dāng)前的棧區(qū)塊為[3, 5] 
          => 進(jìn)入merge循環(huán) (4.3.5) 
          merge因?yàn)閞unLen[0]<=runLen[1] 
          1) gallopRight:尋找run1的第一個(gè)元素應(yīng)當(dāng)插入run0中哪個(gè)位置(”2”應(yīng)當(dāng)插入”1”之后),然后就可以忽略之前run0的元素(都比run1的第一個(gè)元素小) 
          2) gallopLeft:尋找run0的最后一個(gè)元素應(yīng)當(dāng)插入run1中哪個(gè)位置(”7”應(yīng)當(dāng)插入”8”之前),然后就可以忽略之后run1的元素(都比run0的最后一個(gè)元素大) 
          這樣需要排序的元素就僅剩下[5,7] [2,6],然后進(jìn)行mergeLow 
          完成之后的結(jié)果: 
          [1,2,5,6,7,8,10,12] [4,3,9,11,13,15,16,14] 
          => 入棧 (4.3.4) 
          當(dāng)前的棧區(qū)塊為[8] 
          退出當(dāng)前merge循環(huán)因?yàn)闂V械膮^(qū)塊僅為1 
          => 尋找連續(xù)的降序或升序序列 (4.3.2) 
          [1,2,5,6,7,8,10,12] [3,4] [9,11,13,15,16,14] 
          => 入棧 (4.3.4) 
          當(dāng)前的棧區(qū)塊大小為[8,2] 
          => 進(jìn)入merge循環(huán) (4.3.5) 
          do not merge因?yàn)閞unLen[0]>runLen[1] 
          => 尋找連續(xù)的降序或升序序列 (4.3.2) 
          [1,2,5,6,7,8,10,12] [3,4] [9,11,13,15,16] [14] 
          => 入棧 (4.3.4) 
          當(dāng)前的棧區(qū)塊為[8,2,5] 
          => 
          do not merege run1與run2因?yàn)椴粷M足runLen[0]<=runLen[1]+runLen[2] 
          merge run2與run3因?yàn)閞unLen[1]<=runLen[2] 
          1) gallopRight:發(fā)現(xiàn)run1和run2就已經(jīng)排好序 
          完成之后的結(jié)果: 
          [1,2,5,6,7,8,10,12] [3,4,9,11,13,15,16] [14] 
          => 入棧 (4.3.4) 
          當(dāng)前入棧的區(qū)塊大小為[8,7] 
          退出merge循環(huán)因?yàn)閞unLen[0]>runLen[1] 
          => 尋找連續(xù)的降序或升序序列 (4.3.2) 
          最后只剩下[14]這個(gè)元素:[1,2,5,6,7,8,10,12] [3,4,9,11,13,15,16] [14] 
          => 入棧 (4.3.4) 
          當(dāng)前入棧的區(qū)塊大小為[8,7,1] 
          => 進(jìn)入merge循環(huán) (4.3.5) 
          merge因?yàn)閞unLen[0]<=runLen[1]+runLen[2] 
          因?yàn)閞unLen[0]>runLen[2],所以將run1和run2先合并。(否則將run0和run1先合并) 
          1) gallopRight & 2) gallopLeft 
          這樣需要排序的元素剩下[13,15] [14],然后進(jìn)行mergeHigh 
          完成之后的結(jié)果: 
          [1,2,5,6,7,8,10,12] [3,4,9,11,13,14,15,16] 當(dāng)前入棧的區(qū)塊為[8,8] 
          => 
          繼續(xù)merge因?yàn)閞unLen[0]<=runLen[1] 
          1) gallopRight & 2) gallopLeft 
          需要排序的元素剩下[5,6,7,8,10,12] [3,4,9,11],然后進(jìn)行mergeHigh 
          完成之后的結(jié)果: 
          [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16] 當(dāng)前入棧的區(qū)塊大小為[16] 
          => 
          不需要final merge因?yàn)楫?dāng)前棧大小為1 
          => 
          結(jié)束

          6. 如何重現(xiàn)文章開始提到的Exception

          這一節(jié)將剝離復(fù)雜的業(yè)務(wù)邏輯,用一個(gè)最簡(jiǎn)單的例子(不修改TimSort.java內(nèi)置的各種參數(shù))重現(xiàn)文章開始提到的Exception。因?yàn)楸M管google出來的結(jié)果中非常多的人提到了這個(gè)Exception及解決方案,但并沒有人給出一個(gè)可以重現(xiàn)的例子和測(cè)試數(shù)據(jù)。另一方面,我也想從其他角度來加深對(duì)這個(gè)問題的理解。

          構(gòu)造測(cè)試數(shù)據(jù)的過程是個(gè)反人類的過程:( 大家不要學(xué)我。。

          以下是能重現(xiàn)這個(gè)問題的代碼:

          public class ReproJava7Exception { 
              public static void main(String[] args) { 
                  int[] sample = new int[] 
                        {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 
                          0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,-2,1,0,-2,0,0,0,0}; 
                  List<Integer> list = new ArrayList<Integer>(); 
                  for (int i : sample) 
                      list.add(i); 
                  // use the native TimSort in JDK 7 
                  Collections.sort(list, new Comparator<Integer>() { 
                      @Override 
                      public int compare(Integer o1, Integer o2) { 
                          // miss the o1 = o2 case on purpose 
                          return o1 > o2 ? 1 : -1; 
                      } 
                  }); 
              } 
          }

          7. Sample Code

          這篇文章的所有代碼可以到github:https://github.com/Huang-Wei/understanding-timsort-java7下載。

          8. References

          http://en.wikipedia.org/wiki/Timsort 
          http://www.geneffects.com/briarskin/theory/binary/index.html 
          http://docs.oracle.com/javase/6/docs/api/java/util/Comparator.html#compare%28T,%20T%29 
          http://www.oracle.com/technetwork/java/javase/compatibility-417013.html#source

          分類:01囈語(yǔ) | 標(biāo)簽: 、 |

          9條評(píng)論

          1. hute說道:

            太好了,找了一天.感謝.

          2. hute說道:

            重現(xiàn)bug的方法確實(shí)很逆天,作者花了很大力氣吧.

          3. superpippo說道:

            @hute 所以說這個(gè)過程是很反人類的。。。

          4. cjnetwork說道:

            你好,我測(cè)試了一下你提供的測(cè)試代碼,發(fā)現(xiàn)還是不能復(fù)現(xiàn)這個(gè)異常情況,能否幫忙一下呢。

            以下是我的jdk:
            java version “1.7.0-ea”
            Java(TM) SE Runtime Environment (build 1.7.0-ea-b45)
            Java HotSpot(TM) Client VM (build 14.0-b10, mixed mode, sharing)

          5. superpippo說道:

            @cjnetwork 我在jdk 1.7.0_17上用ReproJava7Exception.java能重現(xiàn)這個(gè)問題
            我猜測(cè)你不能重現(xiàn)有兩種可能:
            1) 你的這個(gè)jdk版本有點(diǎn)詭異(1.7.0-ea),嘗試升到最1.7的正式版本
            2) 確認(rèn)你在編譯時(shí)候使用的是JDK7,而不是JDK6 – 因?yàn)橛锌赡茉贓clipse或別的IDE中并沒有設(shè)置正確。你可以寫一段switch string的小例子看看有沒有編譯錯(cuò)誤

          6. ghsau說道:

            compare實(shí)現(xiàn)只需要這樣:
            return o1.getValue() – o2.getValue();
            不需要自己判斷.

          posted @ 2015-02-26 18:46 小馬歌 閱讀(292) | 評(píng)論 (0)編輯 收藏
           
               摘要: Python2.3中開始使用的timsort應(yīng)該說算是聲名在外了,不管是在穩(wěn)定性還是在速度上都十分的驚人。前一段剛剛看了《Python CookBook》中的一些章節(jié),對(duì)timsort產(chǎn)生了一些興趣。于是在網(wǎng)上看到了這邊文章,講的相當(dāng)清楚明了,于是產(chǎn)生了翻譯的念頭,也于是有了這篇文章。這應(yīng)該算是我翻譯的第一篇技術(shù)文章,真正做一次才明白能看懂和能翻譯出來還是有蠻大的差距的。翻譯質(zhì)量不可謂不差,諸位如...  閱讀全文
          posted @ 2015-02-26 18:30 小馬歌 閱讀(449) | 評(píng)論 (0)編輯 收藏
           
               摘要: 概要這個(gè)類在 Oracle 的官方文檔里是查不到的,但是確實(shí)在 OpenJDK 的源代碼里出現(xiàn)了,Arrays 中的 sort 函數(shù)用到了這個(gè)用于排序的類。它將歸并排序(merge sort) 與插入排序(insertion sort) 結(jié)合,并進(jìn)行了一些優(yōu)化。對(duì)于已經(jīng)部分排序的數(shù)組,時(shí)間復(fù)雜度遠(yuǎn)低于 O(n log(n)),最好可達(dá)&n...  閱讀全文
          posted @ 2015-02-26 15:59 小馬歌 閱讀(502) | 評(píng)論 (0)編輯 收藏
           
          from:http://www.infoq.com/cn/articles/netty-version-upgrade-history-thread-part

          1. 背景

          1.1. Netty 3.X系列版本現(xiàn)狀

          根據(jù)對(duì)Netty社區(qū)部分用戶的調(diào)查,結(jié)合Netty在其它開源項(xiàng)目中的使用情況,我們可以看出目前Netty商用的主流版本集中在3.X和4.X上,其中以Netty 3.X系列版本使用最為廣泛。

          Netty社區(qū)非?;钴S,3.X系列版本從2011年2月7日發(fā)布的netty-3.2.4 Final版本到2014年12月17日發(fā)布的netty-3.10.0 Final版本,版本跨度達(dá)3年多,期間共推出了61個(gè)Final版本。

          1.2. 升級(jí)還是堅(jiān)守老版本

          相比于其它開源項(xiàng)目,Netty用戶的版本升級(jí)之路更加艱辛,最根本的原因就是Netty 4對(duì)Netty 3沒有做到很好的前向兼容。

          由于版本不兼容,大多數(shù)老版本使用者的想法就是既然升級(jí)這么麻煩,我暫時(shí)又不需要使用到Netty 4的新特性,當(dāng)前版本還挺穩(wěn)定,就暫時(shí)先不升級(jí),以后看看再說。

          堅(jiān)守老版本還有很多其它的理由,例如考慮到線上系統(tǒng)的穩(wěn)定性、對(duì)新版本的熟悉程度等。無論如何升級(jí)Netty都是一件大事,特別是對(duì)Netty有直接強(qiáng)依賴的產(chǎn)品。

          從上面的分析可以看出,堅(jiān)守老版本似乎是個(gè)不錯(cuò)的選擇;但是,“理想是美好的,現(xiàn)實(shí)卻是殘酷的”,堅(jiān)守老版本并非總是那么容易,下面我們就看下被迫升級(jí)的案例。

          1.3. “被迫”升級(jí)到Netty 4.X

          除了為了使用新特性而主動(dòng)進(jìn)行的版本升級(jí),大多數(shù)升級(jí)都是“被迫的”。下面我們對(duì)這些升級(jí)原因進(jìn)行分析。

          1. 公司的開源軟件管理策略:對(duì)于那些大廠,不同部門和產(chǎn)品線依賴的開源軟件版本經(jīng)常不同,為了對(duì)開源依賴進(jìn)行統(tǒng)一管理,降低安全、維護(hù)和管理成本,往往會(huì)指定優(yōu)選的軟件版本。由于Netty 4.X 系列版本已經(jīng)非常成熟,因?yàn)椋芏喙径純?yōu)選Netty 4.X版本。
          2. 維護(hù)成本:無論是依賴Netty 3.X,還是Netty4.X,往往需要在原框架之上做定制。例如,客戶端的短連重連、心跳檢測(cè)、流控等。分別對(duì)Netty 4.X和3.X版本實(shí)現(xiàn)兩套定制框架,開發(fā)和維護(hù)成本都非常高。根據(jù)開源軟件的使用策略,當(dāng)存在版本沖突的時(shí)候,往往會(huì)選擇升級(jí)到更高的版本。對(duì)于Netty,依然遵循這個(gè)規(guī)則。
          3. 新特性:Netty 4.X相比于Netty 3.X,提供了很多新的特性,例如優(yōu)化的內(nèi)存管理池、對(duì)MQTT協(xié)議的支持等。如果用戶需要使用這些新特性,最簡(jiǎn)便的做法就是升級(jí)Netty到4.X系列版本。
          4. 更優(yōu)異的性能:Netty 4.X版本相比于3.X老版本,優(yōu)化了內(nèi)存池,減少了GC的頻率、降低了內(nèi)存消耗;通過優(yōu)化Rector線程池模型,用戶的開發(fā)更加簡(jiǎn)單,線程調(diào)度也更加高效。

          1.4. 升級(jí)不當(dāng)付出的代價(jià)

          表面上看,類庫(kù)包路徑的修改、API的重構(gòu)等似乎是升級(jí)的重頭戲,大家往往把注意力放到這些“明槍”上,但真正隱藏和致命的卻是“暗箭”。如果對(duì)Netty底層的事件調(diào)度機(jī)制和線程模型不熟悉,往往就會(huì)“中槍”。

          本文以幾個(gè)比較典型的真實(shí)案例為例,通過問題描述、問題定位和問題總結(jié),讓這些隱藏的“暗箭”不再傷人。

          由于Netty 4線程模型改變導(dǎo)致的升級(jí)事故還有很多,限于篇幅,本文不一一枚舉,這些問題萬變不離其宗,只要抓住線程模型這個(gè)關(guān)鍵點(diǎn),所謂的疑難雜癥都將迎刃而解。

          2. Netty升級(jí)之后遭遇內(nèi)存泄露

          2.1. 問題描述

          隨著JVM虛擬機(jī)和JIT即時(shí)編譯技術(shù)的發(fā)展,對(duì)象的分配和回收是個(gè)非常輕量級(jí)的工作。但是對(duì)于緩沖區(qū)Buffer,情況卻稍有不同,特別是對(duì)于堆外直接內(nèi)存的分配和回收,是一件耗時(shí)的操作。為了盡量重用緩沖區(qū),Netty4.X提供了基于內(nèi)存池的緩沖區(qū)重用機(jī)制。性能測(cè)試表明,采用內(nèi)存池的ByteBuf相比于朝生夕滅的ByteBuf,性能高23倍左右(性能數(shù)據(jù)與使用場(chǎng)景強(qiáng)相關(guān))。

          業(yè)務(wù)應(yīng)用的特點(diǎn)是高并發(fā)、短流程,大多數(shù)對(duì)象都是朝生夕滅的短生命周期對(duì)象。為了減少內(nèi)存的拷貝,用戶期望在序列化的時(shí)候直接將對(duì)象編碼到PooledByteBuf里,這樣就不需要為每個(gè)業(yè)務(wù)消息都重新申請(qǐng)和釋放內(nèi)存。

          業(yè)務(wù)的相關(guān)代碼示例如下:

          //在業(yè)務(wù)線程中初始化內(nèi)存池分配器,分配非堆內(nèi)存  ByteBufAllocator allocator = new PooledByteBufAllocator(true);  ByteBuf buffer = allocator.ioBuffer(1024); //構(gòu)造訂購(gòu)請(qǐng)求消息并賦值,業(yè)務(wù)邏輯省略 SubInfoReq infoReq = new SubInfoReq (); infoReq.setXXX(......); //將對(duì)象編碼到ByteBuf中 codec.encode(buffer, info); //調(diào)用ChannelHandlerContext進(jìn)行消息發(fā)送 ctx.writeAndFlush(buffer);

          業(yè)務(wù)代碼升級(jí)Netty版本并重構(gòu)之后,運(yùn)行一段時(shí)間,Java進(jìn)程就會(huì)宕機(jī),查看系統(tǒng)運(yùn)行日志發(fā)現(xiàn)系統(tǒng)發(fā)生了內(nèi)存泄露(示例堆棧):

          圖2-1 OOM內(nèi)存溢出堆棧

          對(duì)內(nèi)存進(jìn)行監(jiān)控(切換使用堆內(nèi)存池,方便對(duì)內(nèi)存進(jìn)行監(jiān)控),發(fā)現(xiàn)堆內(nèi)存一直飆升,如下所示(示例堆內(nèi)存監(jiān)控):

          圖2-2 堆內(nèi)存監(jiān)控

          2.2. 問題定位

          使用jmap -dump:format=b,file=netty.bin PID 將堆內(nèi)存dump出來,通過IBM的HeapAnalyzer工具進(jìn)行分析,發(fā)現(xiàn)ByteBuf發(fā)生了泄露。

          因?yàn)槭褂昧藘?nèi)存池,所以首先懷疑是不是申請(qǐng)的ByteBuf沒有被釋放導(dǎo)致?查看代碼,發(fā)現(xiàn)消息發(fā)送完成之后,Netty底層已經(jīng)調(diào)用ReferenceCountUtil.release(message)對(duì)內(nèi)存進(jìn)行了釋放。這是怎么回事呢?難道Netty 4.X的內(nèi)存池有Bug,調(diào)用release操作釋放內(nèi)存失敗?

          考慮到Netty 內(nèi)存池自身Bug的可能性不大,首先從業(yè)務(wù)的使用方式入手分析:

          1. 內(nèi)存的分配是在業(yè)務(wù)代碼中進(jìn)行,由于使用到了業(yè)務(wù)線程池做I/O操作和業(yè)務(wù)操作的隔離,實(shí)際上內(nèi)存是在業(yè)務(wù)線程中分配的;
          2. 內(nèi)存的釋放操作是在outbound中進(jìn)行,按照Netty 3的線程模型,downstream(對(duì)應(yīng)Netty 4的outbound,Netty 4取消了upstream和downstream)的handler也是由業(yè)務(wù)調(diào)用者線程執(zhí)行的,也就是說釋放跟分配在同一個(gè)業(yè)務(wù)線程中進(jìn)行。

          初次排查并沒有發(fā)現(xiàn)導(dǎo)致內(nèi)存泄露的根因,一籌莫展之際開始查看Netty的內(nèi)存池分配器PooledByteBufAllocator的Doc和源碼實(shí)現(xiàn),發(fā)現(xiàn)內(nèi)存池實(shí)際是基于線程上下文實(shí)現(xiàn)的,相關(guān)代碼如下:

          final ThreadLocal<PoolThreadCache> threadCache = new ThreadLocal<PoolThreadCache>() {         private final AtomicInteger index = new AtomicInteger();         @Override         protected PoolThreadCache initialValue() {             final int idx = index.getAndIncrement();             final PoolArena<byte[]> heapArena;             final PoolArena<ByteBuffer> directArena;             if (heapArenas != null) {                 heapArena = heapArenas[Math.abs(idx % heapArenas.length)];             } else {                 heapArena = null;             }             if (directArenas != null) {                 directArena = directArenas[Math.abs(idx % directArenas.length)];             } else {                 directArena = null;             }             return new PoolThreadCache(heapArena, directArena);         }

          也就是說內(nèi)存的申請(qǐng)和釋放必須在同一線程上下文中,不能跨線程??缇€程之后實(shí)際操作的就不是同一塊內(nèi)存區(qū)域,這會(huì)導(dǎo)致很多嚴(yán)重的問題,內(nèi)存泄露便是其中之一。內(nèi)存在A線程申請(qǐng),切換到B線程釋放,實(shí)際是無法正確回收的。

          通過對(duì)Netty內(nèi)存池的源碼分析,問題基本鎖定。保險(xiǎn)起見進(jìn)行簡(jiǎn)單驗(yàn)證,通過對(duì)單條業(yè)務(wù)消息進(jìn)行Debug,發(fā)現(xiàn)執(zhí)行釋放的果然不是業(yè)務(wù)線程,而是Netty的NioEventLoop線程:當(dāng)某個(gè)消息被完全發(fā)送成功之后,會(huì)通過ReferenceCountUtil.release(message)方法釋放已經(jīng)發(fā)送成功的ByteBuf。

          問題定位出來之后,繼續(xù)溯源,發(fā)現(xiàn)Netty 4修改了Netty 3的線程模型:在Netty 3的時(shí)候,upstream是在I/O線程里執(zhí)行的,而downstream是在業(yè)務(wù)線程里執(zhí)行。當(dāng)Netty從網(wǎng)絡(luò)讀取一個(gè)數(shù)據(jù)報(bào)投遞給業(yè)務(wù)handler的時(shí)候,handler是在I/O線程里執(zhí)行;而當(dāng)我們?cè)跇I(yè)務(wù)線程中調(diào)用write和writeAndFlush向網(wǎng)絡(luò)發(fā)送消息的時(shí)候,handler是在業(yè)務(wù)線程里執(zhí)行,直到最后一個(gè)Header handler將消息寫入到發(fā)送隊(duì)列中,業(yè)務(wù)線程才返回。

          Netty4修改了這一模型,在Netty 4里inbound(對(duì)應(yīng)Netty 3的upstream)和outbound(對(duì)應(yīng)Netty 3的downstream)都是在NioEventLoop(I/O線程)中執(zhí)行。當(dāng)我們?cè)跇I(yè)務(wù)線程里通過ChannelHandlerContext.write發(fā)送消息的時(shí)候,Netty 4在將消息發(fā)送事件調(diào)度到ChannelPipeline的時(shí)候,首先將待發(fā)送的消息封裝成一個(gè)Task,然后放到NioEventLoop的任務(wù)隊(duì)列中,由NioEventLoop線程異步執(zhí)行。后續(xù)所有handler的調(diào)度和執(zhí)行,包括消息的發(fā)送、I/O事件的通知,都由NioEventLoop線程負(fù)責(zé)處理。

          下面我們分別通過對(duì)比Netty 3和Netty 4的消息接收和發(fā)送流程,來理解兩個(gè)版本線程模型的差異:

          Netty 3的I/O事件處理流程:

          圖2-3 Netty 3 I/O事件處理線程模型

          Netty 4的I/O消息處理流程:

          圖2-4 Netty 4 I/O事件處理線程模型

          2.3. 問題總結(jié)

          Netty 4.X版本新增的內(nèi)存池確實(shí)非常高效,但是如果使用不當(dāng)則會(huì)導(dǎo)致各種嚴(yán)重的問題。諸如內(nèi)存泄露這類問題,功能測(cè)試并沒有異常,如果相關(guān)接口沒有進(jìn)行壓測(cè)或者穩(wěn)定性測(cè)試而直接上線,則會(huì)導(dǎo)致嚴(yán)重的線上問題。

          內(nèi)存池PooledByteBuf的使用建議:

          1. 申請(qǐng)之后一定要記得釋放,Netty自身Socket讀取和發(fā)送的ByteBuf系統(tǒng)會(huì)自動(dòng)釋放,用戶不需要做二次釋放;如果用戶使用Netty的內(nèi)存池在應(yīng)用中做ByteBuf的對(duì)象池使用,則需要自己主動(dòng)釋放;
          2. 避免錯(cuò)誤的釋放:跨線程釋放、重復(fù)釋放等都是非法操作,要避免。特別是跨線程申請(qǐng)和釋放,往往具有隱蔽性,問題定位難度較大;
          3. 防止隱式的申請(qǐng)和分配:之前曾經(jīng)發(fā)生過一個(gè)案例,為了解決內(nèi)存池跨線程申請(qǐng)和釋放問題,有用戶對(duì)內(nèi)存池做了二次包裝,以實(shí)現(xiàn)多線程操作時(shí),內(nèi)存始終由包裝的管理線程申請(qǐng)和釋放,這樣可以屏蔽用戶業(yè)務(wù)線程模型和訪問方式的差異。誰知運(yùn)行一段時(shí)間之后再次發(fā)生了內(nèi)存泄露,最后發(fā)現(xiàn)原來調(diào)用ByteBuf的write操作時(shí),如果內(nèi)存容量不足,會(huì)自動(dòng)進(jìn)行容量擴(kuò)展。擴(kuò)展操作由業(yè)務(wù)線程執(zhí)行,這就繞過了內(nèi)存池管理線程,發(fā)生了“引用逃逸”。該Bug只有在ByteBuf容量動(dòng)態(tài)擴(kuò)展的時(shí)候才發(fā)生,因此,上線很長(zhǎng)一段時(shí)間沒有發(fā)生,直到某一天......因此,大家在使用Netty 4.X的內(nèi)存池時(shí)要格外當(dāng)心,特別是做二次封裝時(shí),一定要對(duì)內(nèi)存池的實(shí)現(xiàn)細(xì)節(jié)有深刻的理解。

          3. Netty升級(jí)之后遭遇數(shù)據(jù)被篡改

          3.1. 問題描述

          某業(yè)務(wù)產(chǎn)品,Netty3.X升級(jí)到4.X之后,系統(tǒng)運(yùn)行過程中,偶現(xiàn)服務(wù)端發(fā)送給客戶端的應(yīng)答數(shù)據(jù)被莫名“篡改”。

          業(yè)務(wù)服務(wù)端的處理流程如下:

          1. 將解碼后的業(yè)務(wù)消息封裝成Task,投遞到后端的業(yè)務(wù)線程池中執(zhí)行;
          2. 業(yè)務(wù)線程處理業(yè)務(wù)邏輯,完成之后構(gòu)造應(yīng)答消息發(fā)送給客戶端;
          3. 業(yè)務(wù)應(yīng)答消息的編碼通過繼承Netty的CodeC框架實(shí)現(xiàn),即Encoder ChannelHandler;
          4. 調(diào)用Netty的消息發(fā)送接口之后,流程繼續(xù),根據(jù)業(yè)務(wù)場(chǎng)景,可能會(huì)繼續(xù)操作原發(fā)送的業(yè)務(wù)對(duì)象。

          業(yè)務(wù)相關(guān)代碼示例如下:

          //構(gòu)造訂購(gòu)應(yīng)答消息 SubInfoResp infoResp = new SubInfoResp(); //根據(jù)業(yè)務(wù)邏輯,對(duì)應(yīng)答消息賦值 infoResp.setResultCode(0); infoResp.setXXX(); 后續(xù)賦值操作省略...... //調(diào)用ChannelHandlerContext進(jìn)行消息發(fā)送 ctx.writeAndFlush(infoResp); //消息發(fā)送完成之后,后續(xù)根據(jù)業(yè)務(wù)流程進(jìn)行分支處理,修改infoResp對(duì)象 infoResp.setXXX(); 后續(xù)代碼省略......

          3.2. 問題定位

          首先對(duì)應(yīng)答消息被非法“篡改”的原因進(jìn)行分析,經(jīng)過定位發(fā)現(xiàn)當(dāng)發(fā)生問題時(shí),被“篡改”的內(nèi)容是調(diào)用writeAndFlush接口之后,由后續(xù)業(yè)務(wù)分支代碼修改應(yīng)答消息導(dǎo)致的。由于修改操作發(fā)生在writeAndFlush操作之后,按照Netty 3.X的線程模型不應(yīng)該出現(xiàn)該問題。

          在Netty3中,downstream是在業(yè)務(wù)線程里執(zhí)行的,也就是說對(duì)SubInfoResp的編碼操作是在業(yè)務(wù)線程中執(zhí)行的,當(dāng)編碼后的ByteBuf對(duì)象被投遞到消息發(fā)送隊(duì)列之后,業(yè)務(wù)線程才會(huì)返回并繼續(xù)執(zhí)行后續(xù)的業(yè)務(wù)邏輯,此時(shí)修改應(yīng)答消息是不會(huì)改變已完成編碼的ByteBuf對(duì)象的,所以肯定不會(huì)出現(xiàn)應(yīng)答消息被篡改的問題。

          初步分析應(yīng)該是由于線程模型發(fā)生變更導(dǎo)致的問題,隨后查驗(yàn)了Netty 4的線程模型,果然發(fā)生了變化:當(dāng)調(diào)用outbound向外發(fā)送消息的時(shí)候,Netty會(huì)將發(fā)送事件封裝成Task,投遞到NioEventLoop的任務(wù)隊(duì)列中異步執(zhí)行,相關(guān)代碼如下:

          @Override  public void invokeWrite(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) {         if (msg == null) {             throw new NullPointerException("msg");         }         validatePromise(ctx, promise, true);         if (executor.inEventLoop()) {             invokeWriteNow(ctx, msg, promise);         } else {             AbstractChannel channel = (AbstractChannel) ctx.channel();             int size = channel.estimatorHandle().size(msg);             if (size > 0) {                 ChannelOutboundBuffer buffer = channel.unsafe().outboundBuffer();                 // Check for null as it may be set to null if the channel is closed already                 if (buffer != null) {                     buffer.incrementPendingOutboundBytes(size);                 }             }             safeExecuteOutbound(WriteTask.newInstance(ctx, msg, size, promise), promise, msg);         }     }

          通過上述代碼可以看出,Netty首先對(duì)當(dāng)前的操作的線程進(jìn)行判斷,如果操作本身就是由NioEventLoop線程執(zhí)行,則調(diào)用寫操作;否則,執(zhí)行線程安全的寫操作,即將寫事件封裝成Task,放入到任務(wù)隊(duì)列中由Netty的I/O線程執(zhí)行,業(yè)務(wù)調(diào)用返回,流程繼續(xù)執(zhí)行。

          通過源碼分析,問題根源已經(jīng)很清楚:系統(tǒng)升級(jí)到Netty 4之后,線程模型發(fā)生變化,響應(yīng)消息的編碼由NioEventLoop線程異步執(zhí)行,業(yè)務(wù)線程返回。這時(shí)存在兩種可能:

          1. 如果編碼操作先于修改應(yīng)答消息的業(yè)務(wù)邏輯執(zhí)行,則運(yùn)行結(jié)果正確;
          2. 如果編碼操作在修改應(yīng)答消息的業(yè)務(wù)邏輯之后執(zhí)行,則運(yùn)行結(jié)果錯(cuò)誤。

          由于線程的執(zhí)行先后順序無法預(yù)測(cè),因此該問題隱藏的相當(dāng)深。如果對(duì)Netty 4和Netty3的線程模型不了解,就會(huì)掉入陷阱。

          Netty 3版本業(yè)務(wù)邏輯沒有問題,流程如下:

          圖3-1 升級(jí)之前的業(yè)務(wù)流程線程模型

          升級(jí)到Netty 4版本之后,業(yè)務(wù)流程由于Netty線程模型的變更而發(fā)生改變,導(dǎo)致業(yè)務(wù)邏輯發(fā)生問題:

          圖3-2 升級(jí)之后的業(yè)務(wù)處理流程發(fā)生改變

          3.3. 問題總結(jié)

          很多讀者在進(jìn)行Netty 版本升級(jí)的時(shí)候,只關(guān)注到了包路徑、類和API的變更,并沒有注意到隱藏在背后的“暗箭”- 線程模型變更。

          升級(jí)到Netty 4的用戶需要根據(jù)新的線程模型對(duì)已有的系統(tǒng)進(jìn)行評(píng)估,重點(diǎn)需要關(guān)注outbound的ChannelHandler,如果它的正確性依賴于Netty 3的線程模型,則很可能在新的線程模型中出問題,可能是功能問題或者其它問題。

          4. Netty升級(jí)之后性能嚴(yán)重下降

          4.1. 問題描述

          相信很多Netty用戶都看過如下相關(guān)報(bào)告:

          在Twitter,Netty 4 GC開銷降為五分之一:Netty 3使用Java對(duì)象表示I/O事件,這樣簡(jiǎn)單,但會(huì)產(chǎn)生大量的垃圾,尤其是在我們這樣的規(guī)模下。Netty 4在新版本中對(duì)此做出了更改,取代生存周期短的事件對(duì)象,而以定義在生存周期長(zhǎng)的通道對(duì)象上的方法處理I/O事件。它還有一個(gè)使用池的專用緩沖區(qū)分配器。

          每當(dāng)收到新信息或者用戶發(fā)送信息到遠(yuǎn)程端,Netty 3均會(huì)創(chuàng)建一個(gè)新的堆緩沖區(qū)。這意味著,對(duì)應(yīng)每一個(gè)新的緩沖區(qū),都會(huì)有一個(gè)‘new byte[capacity]’。這些緩沖區(qū)會(huì)導(dǎo)致GC壓力,并消耗內(nèi)存帶寬:為了安全起見,新的字節(jié)數(shù)組分配時(shí)會(huì)用零填充,這會(huì)消耗內(nèi)存帶寬。然而,用零填充的數(shù)組很可能會(huì)再次用實(shí)際的數(shù)據(jù)填充,這又會(huì)消耗同樣的內(nèi)存帶寬。如果Java虛擬機(jī)(JVM)提供了創(chuàng)建新字節(jié)數(shù)組而又無需用零填充的方式,那么我們本來就可以將內(nèi)存帶寬消耗減少50%,但是目前沒有那樣一種方式。

          在Netty 4中,代碼定義了粒度更細(xì)的API,用來處理不同的事件類型,而不是創(chuàng)建事件對(duì)象。它還實(shí)現(xiàn)了一個(gè)新緩沖池,那是一個(gè)純Java版本的 jemalloc (Facebook也在用)?,F(xiàn)在,Netty不會(huì)再因?yàn)橛昧闾畛渚彌_區(qū)而浪費(fèi)內(nèi)存帶寬了。

          我們比較了兩個(gè)分別建立在Netty 3和4基礎(chǔ)上echo協(xié)議服務(wù)器。(Echo非常簡(jiǎn)單,這樣,任何垃圾的產(chǎn)生都是Netty的原因,而不是協(xié)議的原因)。我使它們服務(wù)于相同的分布式echo協(xié)議客戶端,來自這些客戶端的16384個(gè)并發(fā)連接重復(fù)發(fā)送256字節(jié)的隨機(jī)負(fù)載,幾乎使千兆以太網(wǎng)飽和。

          根據(jù)測(cè)試結(jié)果,Netty 4:

          • GC中斷頻率是原來的1/5: 45.5 vs. 9.2次/分鐘
          • 垃圾生成速度是原來的1/5: 207.11 vs 41.81 MiB/秒

          正是看到了相關(guān)的Netty 4性能提升報(bào)告,很多用戶選擇了升級(jí)。事后一些用戶反饋Netty 4并沒有跟產(chǎn)品帶來預(yù)期的性能提升,有些甚至還發(fā)生了非常嚴(yán)重的性能下降,下面我們就以某業(yè)務(wù)產(chǎn)品的失敗升級(jí)經(jīng)歷為案例,詳細(xì)分析下導(dǎo)致性能下降的原因。

          4.2. 問題定位

          首先通過JMC等性能分析工具對(duì)性能熱點(diǎn)進(jìn)行分析,示例如下(信息安全等原因,只給出分析過程示例截圖):

          圖4-1 JMC性能監(jiān)控分析

          通過對(duì)熱點(diǎn)方法的分析,發(fā)現(xiàn)在消息發(fā)送過程中,有兩處熱點(diǎn):

          1. 消息發(fā)送性能統(tǒng)計(jì)相關(guān)Handler;
          2. 編碼Handler。

          對(duì)使用Netty 3版本的業(yè)務(wù)產(chǎn)品進(jìn)行性能對(duì)比測(cè)試,發(fā)現(xiàn)上述兩個(gè)Handler也是熱點(diǎn)方法。既然都是熱點(diǎn),為啥切換到Netty4之后性能下降這么厲害呢?

          通過方法的調(diào)用樹分析發(fā)現(xiàn)了兩個(gè)版本的差異:在Netty 3中,上述兩個(gè)熱點(diǎn)方法都是由業(yè)務(wù)線程負(fù)責(zé)執(zhí)行;而在Netty 4中,則是由NioEventLoop(I/O)線程執(zhí)行。對(duì)于某個(gè)鏈路,業(yè)務(wù)是擁有多個(gè)線程的線程池,而NioEventLoop只有一個(gè),所以執(zhí)行效率更低,返回給客戶端的應(yīng)答時(shí)延就大。時(shí)延增大之后,自然導(dǎo)致系統(tǒng)并發(fā)量降低,性能下降。

          找出問題根因之后,針對(duì)Netty 4的線程模型對(duì)業(yè)務(wù)進(jìn)行專項(xiàng)優(yōu)化,性能達(dá)到預(yù)期,遠(yuǎn)超過了Netty 3老版本的性能。

          Netty 3的業(yè)務(wù)線程調(diào)度模型圖如下所示:充分利用了業(yè)務(wù)多線程并行編碼和Handler處理的優(yōu)勢(shì),周期T內(nèi)可以處理N條業(yè)務(wù)消息。

          圖4-2 Netty 3業(yè)務(wù)調(diào)度性能模型

          切換到Netty 4之后,業(yè)務(wù)耗時(shí)Handler被I/O線程串行執(zhí)行,因此性能發(fā)生比較大的下降:

          圖4-3 Netty 4業(yè)務(wù)調(diào)度性能模型

          4.3. 問題總結(jié)

          該問題的根因還是由于Netty 4的線程模型變更引起,線程模型變更之后,不僅影響業(yè)務(wù)的功能,甚至對(duì)性能也會(huì)造成很大的影響。

          對(duì)Netty的升級(jí)需要從功能、兼容性和性能等多個(gè)角度進(jìn)行綜合考慮,切不可只盯著API變更這個(gè)芝麻,而丟掉了性能這個(gè)西瓜。API的變更會(huì)導(dǎo)致編譯錯(cuò)誤,但是性能下降卻隱藏于無形之中,稍不留意就會(huì)中招。

          對(duì)于講究快速交付、敏捷開發(fā)和灰度發(fā)布的互聯(lián)網(wǎng)應(yīng)用,升級(jí)的時(shí)候更應(yīng)該要當(dāng)心。

          5. Netty升級(jí)之后上下文丟失

          5.1. 問題描述

          為了提升業(yè)務(wù)的二次定制能力,降低對(duì)接口的侵入性,業(yè)務(wù)使用線程變量進(jìn)行消息上下文的傳遞。例如消息發(fā)送源地址信息、消息Id、會(huì)話Id等。

          業(yè)務(wù)同時(shí)使用到了一些第三方開源容器,也提供了線程級(jí)變量上下文的能力。業(yè)務(wù)通過容器上下文獲取第三方容器的系統(tǒng)變量信息。

          升級(jí)到Netty 4之后,業(yè)務(wù)繼承自Netty的ChannelHandler發(fā)生了空指針異常,無論是業(yè)務(wù)自定義的線程上下文、還是第三方容器的線程上下文,都獲取不到傳遞的變量值。

          5.2. 問題定位

          首先檢查代碼,看業(yè)務(wù)是否傳遞了相關(guān)變量,確認(rèn)業(yè)務(wù)傳遞之后懷疑跟Netty 版本升級(jí)相關(guān),調(diào)試發(fā)現(xiàn),業(yè)務(wù)ChannelHandler獲取的線程上下文對(duì)象和之前業(yè)務(wù)傳遞的上下文不是同一個(gè)。這就說明執(zhí)行ChannelHandler的線程跟處理業(yè)務(wù)的線程不是同一個(gè)線程!

          查看Netty 4線程模型的相關(guān)Doc發(fā)現(xiàn),Netty修改了outbound的線程模型,正好影響了業(yè)務(wù)消息發(fā)送時(shí)的線程上下文傳遞,最終導(dǎo)致線程變量丟失。

          5.3. 問題總結(jié)

          通常業(yè)務(wù)的線程模型有如下幾種:

          1. 業(yè)務(wù)自定義線程池/線程組處理業(yè)務(wù),例如使用JDK 1.5提供的ExecutorService;
          2. 使用J2EE Web容器自帶的線程模型,常見的如JBoss和Tomcat的HTTP接入線程等;
          3. 隱式的使用其它第三方框架的線程模型,例如使用NIO框架進(jìn)行協(xié)議處理,業(yè)務(wù)代碼隱式使用的就是NIO框架的線程模型,除非業(yè)務(wù)明確的實(shí)現(xiàn)自定義線程模型。

          在實(shí)踐中我們發(fā)現(xiàn)很多業(yè)務(wù)使用了第三方框架,但是只熟悉API和功能,對(duì)線程模型并不清楚。某個(gè)類庫(kù)由哪個(gè)線程調(diào)用,糊里糊涂。為了方便變量傳遞,又隨意的使用線程變量,實(shí)際對(duì)背后第三方類庫(kù)的線程模型產(chǎn)生了強(qiáng)依賴。當(dāng)容器或者第三方類庫(kù)升級(jí)之后,如果線程模型發(fā)生了變更,則原有功能就會(huì)發(fā)生問題。

          鑒于此,在實(shí)際工作中,盡量不要強(qiáng)依賴第三方類庫(kù)的線程模型,如果確實(shí)無法避免,則必須對(duì)它的線程模型有深入和清晰的了解。當(dāng)?shù)谌筋悗?kù)升級(jí)之后,需要檢查線程模型是否發(fā)生變更,如果發(fā)生變化,相關(guān)的代碼也需要考慮同步升級(jí)。

          6. Netty3.X VS Netty4.X 之線程模型

          通過對(duì)三個(gè)具有典型性的升級(jí)失敗案例進(jìn)行分析和總結(jié),我們發(fā)現(xiàn)有個(gè)共性:都是線程模型改變?nèi)堑牡?

          下面小節(jié)我們就詳細(xì)得對(duì)Netty3和Netty4版本的I/O線程模型進(jìn)行對(duì)比,以方便大家掌握兩者的差異,在升級(jí)和使用中盡量少踩雷。

          6.1 Netty 3.X 版本線程模型

          Netty 3.X的I/O操作線程模型比較復(fù)雜,它的處理模型包括兩部分:

          1. Inbound:主要包括鏈路建立事件、鏈路激活事件、讀事件、I/O異常事件、鏈路關(guān)閉事件等;
          2. Outbound:主要包括寫事件、連接事件、監(jiān)聽綁定事件、刷新事件等。

          我們首先分析下Inbound操作的線程模型:

          圖6-1 Netty 3 Inbound操作線程模型

          從上圖可以看出,Inbound操作的主要處理流程如下:

          1. I/O線程(Work線程)將消息從TCP緩沖區(qū)讀取到SocketChannel的接收緩沖區(qū)中;
          2. 由I/O線程負(fù)責(zé)生成相應(yīng)的事件,觸發(fā)事件向上執(zhí)行,調(diào)度到ChannelPipeline中;
          3. I/O線程調(diào)度執(zhí)行ChannelPipeline中Handler鏈的對(duì)應(yīng)方法,直到業(yè)務(wù)實(shí)現(xiàn)的Last Handler;
          4. Last Handler將消息封裝成Runnable,放入到業(yè)務(wù)線程池中執(zhí)行,I/O線程返回,繼續(xù)讀/寫等I/O操作;
          5. 業(yè)務(wù)線程池從任務(wù)隊(duì)列中彈出消息,并發(fā)執(zhí)行業(yè)務(wù)邏輯。

          通過對(duì)Netty 3的Inbound操作進(jìn)行分析我們可以看出,Inbound的Handler都是由Netty的I/O Work線程負(fù)責(zé)執(zhí)行。

          下面我們繼續(xù)分析Outbound操作的線程模型:

          圖6-2 Netty 3 Outbound操作線程模型

          從上圖可以看出,Outbound操作的主要處理流程如下:

          業(yè)務(wù)線程發(fā)起Channel Write操作,發(fā)送消息;

          1. Netty將寫操作封裝成寫事件,觸發(fā)事件向下傳播;
          2. 寫事件被調(diào)度到ChannelPipeline中,由業(yè)務(wù)線程按照Handler Chain串行調(diào)用支持Downstream事件的Channel Handler;
          3. 執(zhí)行到系統(tǒng)最后一個(gè)ChannelHandler,將編碼后的消息Push到發(fā)送隊(duì)列中,業(yè)務(wù)線程返回;
          4. Netty的I/O線程從發(fā)送消息隊(duì)列中取出消息,調(diào)用SocketChannel的write方法進(jìn)行消息發(fā)送。

          6.2 Netty 4.X 版本線程模型

          相比于Netty 3.X系列版本,Netty 4.X的I/O操作線程模型比較簡(jiǎn)答,它的原理圖如下所示:

          圖6-3 Netty 4 Inbound和Outbound操作線程模型

          從上圖可以看出,Outbound操作的主要處理流程如下:

          1. I/O線程N(yùn)ioEventLoop從SocketChannel中讀取數(shù)據(jù)報(bào),將ByteBuf投遞到ChannelPipeline,觸發(fā)ChannelRead事件;
          2. I/O線程N(yùn)ioEventLoop調(diào)用ChannelHandler鏈,直到將消息投遞到業(yè)務(wù)線程,然后I/O線程返回,繼續(xù)后續(xù)的讀寫操作;
          3. 業(yè)務(wù)線程調(diào)用ChannelHandlerContext.write(Object msg)方法進(jìn)行消息發(fā)送;
          4. 如果是由業(yè)務(wù)線程發(fā)起的寫操作,ChannelHandlerInvoker將發(fā)送消息封裝成Task,放入到I/O線程N(yùn)ioEventLoop的任務(wù)隊(duì)列中,由NioEventLoop在循環(huán)中統(tǒng)一調(diào)度和執(zhí)行。放入任務(wù)隊(duì)列之后,業(yè)務(wù)線程返回;
          5. I/O線程N(yùn)ioEventLoop調(diào)用ChannelHandler鏈,進(jìn)行消息發(fā)送,處理Outbound事件,直到將消息放入發(fā)送隊(duì)列,然后喚醒Selector,進(jìn)而執(zhí)行寫操作。

          通過流程分析,我們發(fā)現(xiàn)Netty 4修改了線程模型,無論是Inbound還是Outbound操作,統(tǒng)一由I/O線程N(yùn)ioEventLoop調(diào)度執(zhí)行。

          6.3. 線程模型對(duì)比

          在進(jìn)行新老版本線程模型PK之前,首先還是要熟悉下串行化設(shè)計(jì)的理念:

          我們知道當(dāng)系統(tǒng)在運(yùn)行過程中,如果頻繁的進(jìn)行線程上下文切換,會(huì)帶來額外的性能損耗。多線程并發(fā)執(zhí)行某個(gè)業(yè)務(wù)流程,業(yè)務(wù)開發(fā)者還需要時(shí)刻對(duì)線程安全保持警惕,哪些數(shù)據(jù)可能會(huì)被并發(fā)修改,如何保護(hù)?這不僅降低了開發(fā)效率,也會(huì)帶來額外的性能損耗。

          為了解決上述問題,Netty 4采用了串行化設(shè)計(jì)理念,從消息的讀取、編碼以及后續(xù)Handler的執(zhí)行,始終都由I/O線程N(yùn)ioEventLoop負(fù)責(zé),這就意外著整個(gè)流程不會(huì)進(jìn)行線程上下文的切換,數(shù)據(jù)也不會(huì)面臨被并發(fā)修改的風(fēng)險(xiǎn),對(duì)于用戶而言,甚至不需要了解Netty的線程細(xì)節(jié),這確實(shí)是個(gè)非常好的設(shè)計(jì)理念,它的工作原理圖如下:

          圖6-4 Netty 4的串行化設(shè)計(jì)理念

          一個(gè)NioEventLoop聚合了一個(gè)多路復(fù)用器Selector,因此可以處理成百上千的客戶端連接,Netty的處理策略是每當(dāng)有一個(gè)新的客戶端接入,則從NioEventLoop線程組中順序獲取一個(gè)可用的NioEventLoop,當(dāng)?shù)竭_(dá)數(shù)組上限之后,重新返回到0,通過這種方式,可以基本保證各個(gè)NioEventLoop的負(fù)載均衡。一個(gè)客戶端連接只注冊(cè)到一個(gè)NioEventLoop上,這樣就避免了多個(gè)I/O線程去并發(fā)操作它。

          Netty通過串行化設(shè)計(jì)理念降低了用戶的開發(fā)難度,提升了處理性能。利用線程組實(shí)現(xiàn)了多個(gè)串行化線程水平并行執(zhí)行,線程之間并沒有交集,這樣既可以充分利用多核提升并行處理能力,同時(shí)避免了線程上下文的切換和并發(fā)保護(hù)帶來的額外性能損耗。

          了解完了Netty 4的串行化設(shè)計(jì)理念之后,我們繼續(xù)看Netty 3線程模型存在的問題,總結(jié)起來,它的主要問題如下:

          1. Inbound和Outbound實(shí)質(zhì)都是I/O相關(guān)的操作,它們的線程模型竟然不統(tǒng)一,這給用戶帶來了更多的學(xué)習(xí)和使用成本;
          2. Outbound操作由業(yè)務(wù)線程執(zhí)行,通常業(yè)務(wù)會(huì)使用線程池并行處理業(yè)務(wù)消息,這就意味著在某一個(gè)時(shí)刻會(huì)有多個(gè)業(yè)務(wù)線程同時(shí)操作ChannelHandler,我們需要對(duì)ChannelHandler進(jìn)行并發(fā)保護(hù),通常需要加鎖。如果同步塊的范圍不當(dāng),可能會(huì)導(dǎo)致嚴(yán)重的性能瓶頸,這對(duì)開發(fā)者的技能要求非常高,降低了開發(fā)效率;
          3. Outbound操作過程中,例如消息編碼異常,會(huì)產(chǎn)生Exception,它會(huì)被轉(zhuǎn)換成Inbound的Exception并通知到ChannelPipeline,這就意味著業(yè)務(wù)線程發(fā)起了Inbound操作!它打破了Inbound操作由I/O線程操作的模型,如果開發(fā)者按照Inbound操作只會(huì)由一個(gè)I/O線程執(zhí)行的約束進(jìn)行設(shè)計(jì),則會(huì)發(fā)生線程并發(fā)訪問安全問題。由于該場(chǎng)景只在特定異常時(shí)發(fā)生,因此錯(cuò)誤非常隱蔽!一旦在生產(chǎn)環(huán)境中發(fā)生此類線程并發(fā)問題,定位難度和成本都非常大。

          講了這么多,似乎Netty 4 完勝 Netty 3的線程模型,其實(shí)并不盡然。在特定的場(chǎng)景下,Netty 3的性能可能更高,就如本文第4章節(jié)所講,如果編碼和其它Outbound操作非常耗時(shí),由多個(gè)業(yè)務(wù)線程并發(fā)執(zhí)行,性能肯定高于單個(gè)NioEventLoop線程。

          但是,這種性能優(yōu)勢(shì)不是不可逆轉(zhuǎn)的,如果我們修改業(yè)務(wù)代碼,將耗時(shí)的Handler操作前置,Outbound操作不做復(fù)雜業(yè)務(wù)邏輯處理,性能同樣不輸于Netty 3,但是考慮內(nèi)存池優(yōu)化、不會(huì)反復(fù)創(chuàng)建Event、不需要對(duì)Handler加鎖等Netty 4的優(yōu)化,整體性能Netty 4版本肯定會(huì)更高。

          總而言之,如果用戶真正熟悉并掌握了Netty 4的線程模型和功能類庫(kù),相信不僅僅開發(fā)會(huì)更加簡(jiǎn)單,性能也會(huì)更優(yōu)!

          6.4. 思考

          就Netty 而言,掌握線程模型的重要性不亞于熟悉它的API和功能。很多時(shí)候我遇到的功能、性能等問題,都是由于缺乏對(duì)它線程模型和原理的理解導(dǎo)致的,結(jié)果我們就以訛傳訛,認(rèn)為Netty 4版本不如3好用等。

          不能說所有開源軟件的版本升級(jí)一定都勝過老版本,就Netty而言,我認(rèn)為Netty 4版本相比于老的Netty 3,確實(shí)是歷史的一大進(jìn)步。

          7. 作者簡(jiǎn)介

          李林鋒,2007年畢業(yè)于東北大學(xué),2008年進(jìn)入華為公司從事高性能通信軟件的設(shè)計(jì)和開發(fā)工作,有7年NIO設(shè)計(jì)和開發(fā)經(jīng)驗(yàn),精通Netty、Mina等NIO框架和平臺(tái)中間件,現(xiàn)任華為軟件平臺(tái)架構(gòu)部架構(gòu)師,《Netty權(quán)威指南》作者。

          聯(lián)系方式:新浪微博 Nettying 微信:Nettying 微信公眾號(hào):Netty之家


          感謝郭蕾對(duì)本文的策劃和審校。

          給InfoQ中文站投稿或者參與內(nèi)容翻譯工作,請(qǐng)郵件至editors@cn.infoq.com。也歡迎大家通過新浪微博(@InfoQ)或者騰訊微博(@InfoQ)關(guān)注我們,并與我們的編輯和其他讀者朋友交流。

          posted @ 2015-02-10 12:03 小馬歌 閱讀(934) | 評(píng)論 (0)編輯 收藏
           

          這個(gè)帖子是關(guān)于JAVA中鮮為人知的特性的后續(xù)更新,如果想得到下次在線討論的更新,請(qǐng)通過郵件訂閱,并且不要忘了在評(píng)論區(qū)留下你的意見和建議。


              Java是一個(gè)安全的開發(fā)工具,它阻止開發(fā)人員犯很多低級(jí)的錯(cuò)誤,而大部份的錯(cuò)誤都是基于內(nèi)存管理方面的。如果你想搞破壞,可以使用Unsafe這個(gè)類。這個(gè)類是屬于sun.* API中的類,并且它不是J2SE中真正的一部份,因此你可能找不到任何的官方文檔,更可悲的是,它也沒有比較好的代碼文檔。


              實(shí)例化sun.misc.Unsafe

              如果你嘗試創(chuàng)建Unsafe類的實(shí)例,基于以下兩種原因是不被允許的。

              1)、Unsafe類的構(gòu)造函數(shù)是私有的;

              2)、雖然它有靜態(tài)的getUnsafe()方法,但是如果你嘗試調(diào)用Unsafe.getUnsafe(),會(huì)得到一個(gè)SecutiryException。這個(gè)類只有被JDK信任的類實(shí)例化。

              但是這總會(huì)是有變通的解決辦法的,一個(gè)簡(jiǎn)單的方式就是使用反射進(jìn)行實(shí)例化:

          1. Field f = Unsafe.class.getDeclaredField("theUnsafe"); //Internal reference  
          2. f.setAccessible(true);  
          3. Unsafe unsafe = (Unsafe) f.get(null);  

              注:IDE如Eclipse對(duì)會(huì)這樣的使用報(bào)錯(cuò),不過不用擔(dān)心,直接運(yùn)行代碼就行,可以正常運(yùn)行的。

              (譯者注:還有一種解決方案,就是將Eclipse中這種限制獲取由錯(cuò)誤,修改為警告,具體操作為將Windows->Preference...->Java->Compiler->Errors/Warnings中的"Deprecated and restricted API",級(jí)別由Error修改為Warning就可以了)

              現(xiàn)在進(jìn)入主題,使用這個(gè)對(duì)象我們可以做如下“有趣的”事情。


              使用sun.misc.Unsafe

              1)、突破限制創(chuàng)建實(shí)例

              通過allocateInstance()方法,你可以創(chuàng)建一個(gè)類的實(shí)例,但是卻不需要調(diào)用它的構(gòu)造函數(shù)、初使化代碼、各種JVM安全檢查以及其它的一些底層的東西。即使構(gòu)造函數(shù)是私有,我們也可以通過這個(gè)方法創(chuàng)建它的實(shí)例。

              (這個(gè)對(duì)單例模式情有獨(dú)鐘的程序員來說將會(huì)是一個(gè)噩夢(mèng),它們沒有辦法阻止這種方式調(diào)用大笑

              看下面一個(gè)實(shí)例(注:為了配合這個(gè)主題,譯者將原實(shí)例中的public構(gòu)造函數(shù)修改為了私有的):

           

          1. public class UnsafeDemo {  
          2.     public static void main(String[] args) throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException, InstantiationException {  
          3.         Field f = Unsafe.class.getDeclaredField("theUnsafe"); // Internal reference  
          4.         f.setAccessible(true);  
          5.         Unsafe unsafe = (Unsafe) f.get(null);  
          6.   
          7.         // This creates an instance of player class without any initialization  
          8.         Player p = (Player) unsafe.allocateInstance(Player.class);  
          9.         System.out.println(p.getAge()); // Print 0  
          10.   
          11.         p.setAge(45); // Let's now set age 45 to un-initialized object  
          12.         System.out.println(p.getAge()); // Print 45  
          13.     }  
          14. }  
          15.   
          16. class Player {  
          17.     private int age = 12;  
          18.   
          19.     private Player() {  
          20.         this.age = 50;  
          21.     }  
          22.   
          23.     public int getAge() {  
          24.         return this.age;  
          25.     }  
          26.   
          27.     public void setAge(int age) {  
          28.         this.age = age;  
          29.     }  
          30. }  


              2)、使用直接獲取內(nèi)存的方式實(shí)現(xiàn)淺克隆

              如何實(shí)現(xiàn)淺克???在clone(){...}方法中調(diào)用super.clone(),對(duì)嗎?這里存在的問題是首先你必須繼續(xù)Cloneable接口,并且在所有你需要做淺克隆的對(duì)象中實(shí)現(xiàn)clone()方法,對(duì)于一個(gè)懶懶的程序員來說,這個(gè)工作量太大了。

              我不推薦上面的做法而是直接使用Unsafe,我們可以僅使用幾行代碼就實(shí)現(xiàn)淺克隆,并且它可以像某些工具類一樣用于任意類的克隆。

              這個(gè)戲法就是把一個(gè)對(duì)象的字節(jié)碼拷貝到內(nèi)存的另外一個(gè)地方,然后再將這個(gè)對(duì)象轉(zhuǎn)換為被克隆的對(duì)象類型。

              

              3)、來自黑客的密碼安全

              這個(gè)好似很有趣吧?實(shí)事就是這樣的。開發(fā)人員創(chuàng)建密碼或者是保證密碼到字符串中,然后在應(yīng)用程序的代碼中使用這些密碼,使用過后,聰明的程序員會(huì)把字符串的引用設(shè)為NULL,因此它就不會(huì)被引用著并且很容易被垃圾收集器給回收掉。

              但是從你將引用設(shè)為NULL到被垃圾收集器收集的這個(gè)時(shí)間段之內(nèi)(原文:But from the time, you made the reference null to the time garbage collector kicks in),它是處于字符串池中的,并且在你系統(tǒng)中進(jìn)行一個(gè)復(fù)雜的攻擊(原文:And a sophisticated attack on your system),也是可以讀取到你的內(nèi)存區(qū)域并且獲得密碼,雖然機(jī)會(huì)很小,但是總是存在的。

              這就是為什么建議使用char[]數(shù)組存放密碼,當(dāng)使用完過后,你可以迭代處理當(dāng)前數(shù)組,修改/清空這些字符。

              另外一個(gè)方式就是使用魔術(shù)類Unsafe。你可以創(chuàng)建另外一個(gè)和當(dāng)前密碼字符串具有相同長(zhǎng)度的臨時(shí)字符串,將臨時(shí)密碼中的每個(gè)字符都設(shè)值為"?"或者"*"(任何字符都可以),當(dāng)你完成密碼的邏輯后,你只需要簡(jiǎn)單的將臨時(shí)密碼中的字節(jié)數(shù)組拷貝到原始的密碼串中,這就是使用臨時(shí)密碼覆蓋真實(shí)的密碼。

              示例代碼可能會(huì)是這樣:

          1. String password = new String("l00k@myHor$e");  
          2. String fake = new String(password.replaceAll(".", "?"));  
          3. System.out.println(password); // l00k@myHor$e  
          4. System.out.println(fake); // ????????????  
          5.    
          6. getUnsafe().copyMemory(fake, 0L, null, toAddress(password), sizeOf(password));  
          7.    
          8. System.out.println(password); // ????????????  
          9. System.out.println(fake); // ????????????  

              運(yùn)行時(shí)動(dòng)態(tài)創(chuàng)建類

              我們可以在運(yùn)行時(shí)運(yùn)態(tài)的創(chuàng)建類,例如通過編譯后的.class文件,操作方式就是將.class文件讀取到字節(jié)數(shù)據(jù)組中,并將其傳到defineClass方法中。

          1. //Sample code to craeet classes  
          2. byte[] classContents = getClassContent();  
          3. Class c = getUnsafe().defineClass(null, classContents, 0, classContents.length);  
          4. c.getMethod("a").invoke(c.newInstance(), null);  
          5.    
          6. //Method to read .class file  
          7. private static byte[] getClassContent() throws Exception {  
          8.     File f = new File("/home/mishadoff/tmp/A.class");  
          9.     FileInputStream input = new FileInputStream(f);  
          10.     byte[] content = new byte[(int)f.length()];  
          11.     input.read(content);  
          12.     input.close();  
          13.     return content;  
          14. }  
              

              4)、超大數(shù)組

              從所周知,常量Integer.MAX_VALUE是JAVA中數(shù)組長(zhǎng)度的最大值,如果你想創(chuàng)建一個(gè)非常大的數(shù)組(雖然在通常的應(yīng)用中不可能會(huì)用上),可以通過對(duì)內(nèi)存進(jìn)行直接分配實(shí)現(xiàn)。

              下面這個(gè)示例將會(huì)創(chuàng)建分配一段連續(xù)的內(nèi)存(數(shù)組),它的容易是允許最大容量的兩倍。

          1. class SuperArray {  
          2.     private final static int BYTE = 1;  
          3.     private long size;  
          4.     private long address;  
          5.        
          6.     public SuperArray(long size) {  
          7.         this.size = size;  
          8.         //得到分配內(nèi)存的起始地址  
          9.         address = getUnsafe().allocateMemory(size * BYTE);  
          10.     }  
          11.     public void set(long i, byte value) {  
          12.         getUnsafe().putByte(address + i * BYTE, value);  
          13.     }  
          14.     public int get(long idx) {  
          15.         return getUnsafe().getByte(address + idx * BYTE);  
          16.     }  
          17.     public long size() {  
          18.         return size;  
          19.     }  
          20. }  
              應(yīng)用示例

          1. long SUPER_SIZE = (long)Integer.MAX_VALUE * 2;  
          2. SuperArray array = new SuperArray(SUPER_SIZE);  
          3. System.out.println("Array size:" + array.size()); // 4294967294  
          4. for (int i = 0; i < 100; i++) {  
          5.     array.set((long)Integer.MAX_VALUE + i, (byte)3);  
          6.     sum += array.get((long)Integer.MAX_VALUE + i);  
          7. }  
          8. System.out.println("Sum of 100 elements:" + sum);  // 300  

              但請(qǐng)注意這可能會(huì)導(dǎo)致JVM掛掉。

              

              結(jié)束語(yǔ)

              sun.misc.Unsafe provides almost unlimited capabilities for exploring and modification of VM’s runtime data structures. Despite the fact that these capabilities are almost inapplicable in Java development itself, Unsafe is a great tool for anyone who want to study HotSpot VM without C++ code debugging or need to create ad hoc profiling instruments.

              sun.misc.Unsafe提供了可以隨意查看及修改JVM中運(yùn)行時(shí)的數(shù)據(jù)結(jié)構(gòu),盡管這些功能在JAVA開發(fā)本身是不適用的,Unsafe是一個(gè)用于研究學(xué)習(xí)HotSpot虛擬機(jī)非常棒的工具,因?yàn)樗恍枰{(diào)用C++代碼,或者需要?jiǎng)?chuàng)建即時(shí)分析的工具。


              參考

              http://mishadoff.github.io/blog/java-magic-part-4-sun-dot-misc-dot-unsafe/



          posted @ 2015-02-04 15:12 小馬歌 閱讀(283) | 評(píng)論 (0)編輯 收藏
           
          環(huán)境:
          os: MAC OS X 10.10.1 yosemite
          target build OPENJDK:jdk8
          Xcode:6.1
          LLVM Version:

          Apple LLVM version 6.0 (clang-600.0.54) (based on LLVM 3.5svn)

          Target: x86_64-apple-darwin14.0.0

          Thread model: posix

          步驟:
          1 國(guó)內(nèi)hg拉取代碼不靠譜,到這里打包下載,118M  
          http://download.java.net/openjdk/jdk8/

          安裝X11,系統(tǒng)默認(rèn)好像是沒有這個(gè)東西,需要下載安裝XQuartz,然后link下 sudo ln -s /usr/X11/include/X11 /usr/include/X11

          sudo ln -s /usr/bin/llvm-g++ /Applications/Xcode.app/Contents/Developer/usr/bin/llvm-g++  
          sudo ln -s /usr/bin/llvm-gcc /Applications/Xcode.app/Contents/Developer/usr/bin/llvm-gcc
          4 安裝
          Xcode的Command line tools、freetype
          ./configure --enable-debug --with-target-bits=64
          6 cd jdk8;
          unset JAVA_HOME
          unset CLASSPATH 
          make CC=clang COMPILER_WARNINGS_FATAL=false LFLAGS='-Xlinker -lstdc++' USE_CLANG=true LP64=1 LANG=C ALT_BOOTDIR=/System/Library/Java/JavaVirtualMachines/1.6.0.jdk/Contents/Home ARCH_DATA_MODEL=64 HOTSPOT_BUILD_JOBS=8 ALT_FREETYPE_HEADERS_PATH=/Users/mungo/Downloads/work/homebrew-master/Cellar/freetype/2.5.5/include ALT_FREETYPE_LIB_PATH=/Users/mungo/Downloads/work/homebrew-master/Cellar/freetype/2.5.5/lib
          7 驗(yàn)證結(jié)果:

          ./build/macosx-x86_64-normal-server-fastdebug/jdk/bin/java -version

          openjdk version "1.8.0-internal-fastdebug"

          OpenJDK Runtime Environment (build 1.8.0-internal-fastdebug-mungo_2015_01_29_16_11-b00)

          OpenJDK 64-Bit Server VM (build 25.0-b70-fastdebug, mixed mode)


          出現(xiàn)的問題:

          clang: error: unknown argument: '-fcheck-new' [-Wunused-command-line-argument-hard-error-in-future]  

                   于是直接打開YourOpenJDK/hotspot/make/bsd/makefiles/gcc.make,把這行(line 193)給注釋掉
          2 relocInfo.hpp錯(cuò)誤。 將接口的默認(rèn)值去掉,把默認(rèn)值放到的方法參數(shù)里即可。hotspot/src/share/vm/code/relocInfo.hpp 
          + inline friend relocInfo prefix_relocInfo(int datalen);

          +inline relocInfo prefix_relocInfo(int datalen = 0) {
             assert(relocInfo::fits_into_immediate(datalen), "datalen in limits");
             return relocInfo(relocInfo::data_prefix_tag, relocInfo::RAW_BITS, relocInfo::datalen_tag | datalen);
           }

          參考文章:
          http://hllvm.group.iteye.com/group/topic/39814
          http://yueyemaitian.iteye.com/blog/2038304

          另:openjdk 7 hotspot我可以編譯成功,但是最后打包的時(shí)候,出現(xiàn) 執(zhí)行cp 錯(cuò)誤.沒找到原因

          posted @ 2015-01-30 10:06 小馬歌 閱讀(635) | 評(píng)論 (0)編輯 收藏
           
               摘要: 這是我的WWDC2013系列筆記中的一篇,完整的筆記列表請(qǐng)參看這篇總覽。本文僅作為個(gè)人記錄使用,也歡迎在許可協(xié)議范圍內(nèi)轉(zhuǎn)載或使用,但是還煩請(qǐng)保留原文鏈接,謝謝您的理解合作。如果您覺得本站對(duì)您能有幫助,您可以使用RSS或郵件方式訂閱本站,這樣您將能在第一時(shí)間獲取本站信息。本文涉及到的WWDC2013 Session有Session 204 What's New with MultitaskingSe...  閱讀全文
          posted @ 2015-01-19 18:11 小馬歌 閱讀(211) | 評(píng)論 (0)編輯 收藏
           

          接近一周沒更新《Java線程》專欄了,主要是這周工作上比較忙,生活上也比較忙,呵呵,進(jìn)入正題,上一篇講述了并發(fā)包下的Lock,Lock可以更好的解決線程同步問題,使之更面向?qū)ο?,并且ReadWriteLock在處理同步時(shí)更強(qiáng)大,那么同樣,線程間僅僅互斥是不夠的,還需要通信,本篇的內(nèi)容是基于上篇之上,使用Lock如何處理線程通信。

                  那么引入本篇的主角,Condition,Condition 將 Object 監(jiān)視器方法(wait、notify 和 notifyAll)分解成截然不同的對(duì)象,以便通過將這些對(duì)象與任意 Lock 實(shí)現(xiàn)組合使用,為每個(gè)對(duì)象提供多個(gè)等待 set (wait-set)。其中,Lock 替代了 synchronized 方法和語(yǔ)句的使用,Condition 替代了 Object 監(jiān)視器方法的使用。下面將之前寫過的一個(gè)線程通信的例子替換成用Condition實(shí)現(xiàn)(Java線程(三)),代碼如下:

          1. public class ThreadTest2 {  
          2.     public static void main(String[] args) {  
          3.         final Business business = new Business();  
          4.         new Thread(new Runnable() {  
          5.             @Override  
          6.             public void run() {  
          7.                 threadExecute(business, "sub");  
          8.             }  
          9.         }).start();  
          10.         threadExecute(business, "main");  
          11.     }     
          12.     public static void threadExecute(Business business, String threadType) {  
          13.         for(int i = 0; i < 100; i++) {  
          14.             try {  
          15.                 if("main".equals(threadType)) {  
          16.                     business.main(i);  
          17.                 } else {  
          18.                     business.sub(i);  
          19.                 }  
          20.             } catch (InterruptedException e) {  
          21.                 e.printStackTrace();  
          22.             }  
          23.         }  
          24.     }  
          25. }  
          26. class Business {  
          27.     private boolean bool = true;  
          28.     private Lock lock = new ReentrantLock();  
          29.     private Condition condition = lock.newCondition();   
          30.     public /*synchronized*/ void main(int loop) throws InterruptedException {  
          31.         lock.lock();  
          32.         try {  
          33.             while(bool) {                 
          34.                 condition.await();//this.wait();  
          35.             }  
          36.             for(int i = 0; i < 100; i++) {  
          37.                 System.out.println("main thread seq of " + i + ", loop of " + loop);  
          38.             }  
          39.             bool = true;  
          40.             condition.signal();//this.notify();  
          41.         } finally {  
          42.             lock.unlock();  
          43.         }  
          44.     }     
          45.     public /*synchronized*/ void sub(int loop) throws InterruptedException {  
          46.         lock.lock();  
          47.         try {  
          48.             while(!bool) {  
          49.                 condition.await();//this.wait();  
          50.             }  
          51.             for(int i = 0; i < 10; i++) {  
          52.                 System.out.println("sub thread seq of " + i + ", loop of " + loop);  
          53.             }  
          54.             bool = false;  
          55.             condition.signal();//this.notify();  
          56.         } finally {  
          57.             lock.unlock();  
          58.         }  
          59.     }  
          60. }  
                  在Condition中,用await()替換wait(),用signal()替換notify(),用signalAll()替換notifyAll(),傳統(tǒng)線程的通信方式,Condition都可以實(shí)現(xiàn),這里注意,Condition是被綁定到Lock上的,要?jiǎng)?chuàng)建一個(gè)Lock的Condition必須用newCondition()方法。

                  這樣看來,Condition和傳統(tǒng)的線程通信沒什么區(qū)別,Condition的強(qiáng)大之處在于它可以為多個(gè)線程間建立不同的Condition,下面引入API中的一段代碼,加以說明。

          1. class BoundedBuffer {  
          2.    final Lock lock = new ReentrantLock();//鎖對(duì)象  
          3.    final Condition notFull  = lock.newCondition();//寫線程條件   
          4.    final Condition notEmpty = lock.newCondition();//讀線程條件   
          5.   
          6.    final Object[] items = new Object[100];//緩存隊(duì)列  
          7.    int putptr/*寫索引*/, takeptr/*讀索引*/, count/*隊(duì)列中存在的數(shù)據(jù)個(gè)數(shù)*/;  
          8.   
          9.    public void put(Object x) throws InterruptedException {  
          10.      lock.lock();  
          11.      try {  
          12.        while (count == items.length)//如果隊(duì)列滿了   
          13.          notFull.await();//阻塞寫線程  
          14.        items[putptr] = x;//賦值   
          15.        if (++putptr == items.length) putptr = 0;//如果寫索引寫到隊(duì)列的最后一個(gè)位置了,那么置為0  
          16.        ++count;//個(gè)數(shù)++  
          17.        notEmpty.signal();//喚醒讀線程  
          18.      } finally {  
          19.        lock.unlock();  
          20.      }  
          21.    }  
          22.   
          23.    public Object take() throws InterruptedException {  
          24.      lock.lock();  
          25.      try {  
          26.        while (count == 0)//如果隊(duì)列為空  
          27.          notEmpty.await();//阻塞讀線程  
          28.        Object x = items[takeptr];//取值   
          29.        if (++takeptr == items.length) takeptr = 0;//如果讀索引讀到隊(duì)列的最后一個(gè)位置了,那么置為0  
          30.        --count;//個(gè)數(shù)--  
          31.        notFull.signal();//喚醒寫線程  
          32.        return x;  
          33.      } finally {  
          34.        lock.unlock();  
          35.      }  
          36.    }   
          37.  }  
                  這是一個(gè)處于多線程工作環(huán)境下的緩存區(qū),緩存區(qū)提供了兩個(gè)方法,put和take,put是存數(shù)據(jù),take是取數(shù)據(jù),內(nèi)部有個(gè)緩存隊(duì)列,具體變量和方法說明見代碼,這個(gè)緩存區(qū)類實(shí)現(xiàn)的功能:有多個(gè)線程往里面存數(shù)據(jù)和從里面取數(shù)據(jù),其緩存隊(duì)列(先進(jìn)先出后進(jìn)后出)能緩存的最大數(shù)值是100,多個(gè)線程間是互斥的,當(dāng)緩存隊(duì)列中存儲(chǔ)的值達(dá)到100時(shí),將寫線程阻塞,并喚醒讀線程,當(dāng)緩存隊(duì)列中存儲(chǔ)的值為0時(shí),將讀線程阻塞,并喚醒寫線程,下面分析一下代碼的執(zhí)行過程:

                  1. 一個(gè)寫線程執(zhí)行,調(diào)用put方法;

                  2. 判斷count是否為100,顯然沒有100;

                  3. 繼續(xù)執(zhí)行,存入值;

                  4. 判斷當(dāng)前寫入的索引位置++后,是否和100相等,相等將寫入索引值變?yōu)?,并將count+1;

                  5. 僅喚醒讀線程阻塞隊(duì)列中的一個(gè);

                  6. 一個(gè)讀線程執(zhí)行,調(diào)用take方法;

                  7. ……

                  8. 僅喚醒寫線程阻塞隊(duì)列中的一個(gè)。

                  這就是多個(gè)Condition的強(qiáng)大之處,假設(shè)緩存隊(duì)列中已經(jīng)存滿,那么阻塞的肯定是寫線程,喚醒的肯定是讀線程,相反,阻塞的肯定是讀線程,喚醒的肯定是寫線程,那么假設(shè)只有一個(gè)Condition會(huì)有什么效果呢,緩存隊(duì)列中已經(jīng)存滿,這個(gè)Lock不知道喚醒的是讀線程還是寫線程了,如果喚醒的是讀線程,皆大歡喜,如果喚醒的是寫線程,那么線程剛被喚醒,又被阻塞了,這時(shí)又去喚醒,這樣就浪費(fèi)了很多時(shí)間。

                  本文來自:高爽|Coder,原文地址:http://blog.csdn.net/ghsau/article/details/7481142,轉(zhuǎn)載請(qǐng)注明。

            posted @ 2015-01-15 09:36 小馬歌 閱讀(223) | 評(píng)論 (0)編輯 收藏
             

            Gitlab是一個(gè)用Ruby on Rails開發(fā)的開源項(xiàng)目管理程序,可以通過WEB界面進(jìn)行訪問公開的或者私人項(xiàng)目。它和Github有類似的功能,能夠?yàn)g覽源代碼,管理缺陷和注釋。

            下面介紹如何在 Debian/Ubuntu 和 Centos 下搭建配置 GitLab。

            安裝依賴

            Debian/Ubuntu下:

            sudo apt-get install -y build-essential zlib1g-dev libyaml-dev libssl-dev libgdbm-dev libreadline-dev libncurses5-dev libffi-dev curl openssh-server redis-server checkinstall libxml2-dev libxslt-dev libcurl4-openssl-dev libicu-dev logrotate 

            安裝python(注意需要2.5以上版本):

            sudo apt-get install -y python python-docutils 

            安裝git(注意需要1.7.10以上版本):

            sudo apt-get install -y git-core 

            Centos下官方倉(cāng)庫(kù)的軟件比較老舊,推薦先添加epel源,然后再安裝依賴:

            sudo yum install git patch gcc-c++ readline-devel zlib-devel libffi-devel openssl-devel make autoconf automake libtool bison libxml2-devel libxslt-devel libyaml-devel git python python-docutils 

            安裝 Ruby 2.0

            需要安裝Ruby2.0,軟件倉(cāng)庫(kù)中的Ruby 1.8不支持:

            mkdir /tmp/ruby && cd /tmp/ruby curl --progress ftp://ftp.ruby-lang.org/pub/ruby/2.0/ruby-2.0.0-p353.tar.gz | tar xz cd ruby-2.0.0-p353 ./configure --disable-install-rdoc make sudo make install 

            安裝Bundler Gem:

            sudo gem install bundler --no-ri --no-rdoc 

            配置gitlab-shell

            創(chuàng)建git用戶:

            sudo adduser --system --create-home --comment 'GitLab' git   

            配置gitlab-shell

            su - git -c "git clone https://github.com/gitlabhq/gitlab-shell.git"   su - git -c "cd gitlab-shell && git checkout v1.3.0"   su - git -c "cp gitlab-shell/config.yml.example gitlab-shell/config.yml"   sed -i "s/localhost/gitlab.51yip.com/g" /home/git/gitlab-shell/config.yml   su - git -c "gitlab-shell/bin/install"   chmod 600 /home/git/.ssh/authorized_keys   chmod 700 /home/git/.ssh 

            數(shù)據(jù)庫(kù)

            GitLab支持 MySQL 和 PostgreSQL 數(shù)據(jù)庫(kù)。下面以 MySQL為例,介紹安裝方法:

            Debian/Ubuntu下使用如下命令安裝:

            sudo apt-get install -y mysql-server mysql-client libmysqlclient-dev 

            Centos下使用如下命令:

            sudo yum install mysql-server  sudo chkconfig mysqld on 

            配置MySQL:

            sudo echo "CREATE DATABASE IF NOT EXISTS gitlabhq_production DEFAULT CHARACTER SET 'utf8' COLLATE 'utf8_unicode_ci';" | mysql -u root  sudo echo "UPDATE mysql.user SET Password=PASSWORD('123456') WHERE User='root'; FLUSH PRIVILEGES;" | mysql -u root  

            注意,用你的密碼替換123456。

            安裝配置 gitlab

            su - git -c "git clone https://github.com/gitlabhq/gitlabhq.git gitlab"   su - git -c "cd gitlab;git checkout 5-1-stable"   su git -c "cp config/gitlab.yml.example config/gitlab.yml"   su git -c "mkdir /home/git/gitlab-satellites"   su git -c "mkdir public/uploads"   su git -c "mkdir -p tmp/sockets/"   su git -c "mkdir -p tmp/pids/"   sed -i "s/ host: localhost/ host: gitlab.segmentfault.com/g" config/gitlab.yml   sed -i "s/from: gitlab@localhost/from: gitlab@gitlab.segmentfault.com/g" config/gitlab.yml   su git -c "cp config/puma.rb.example config/puma.rb"   su git -c 'git config --global user.name "GitLab"'   su git -c 'git config --global user.email "gitlab@gitlab.segmentfault.com"' 

            注意將gitlab.segmentfault.com替換為你自己的內(nèi)容。

            配置數(shù)據(jù)庫(kù)連接:

            sudo su git -c "cp config/database.yml.mysql config/database.yml" sudo sed -i "s/secure password/mysql的root密碼/g" config/database.yml 

            安裝MySQL需要的Gems

            sudo -u git -H bundle install --deployment --without development test postgres aws 

            初始化:

            sudo -u git -H bundle exec rake gitlab:setup RAILS_ENV=production sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab sudo chmod +x /etc/init.d/gitlab sudo update-rc.d gitlab defaults 21 

            查看是否配置妥當(dāng):

            sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production 

            重啟GitLab:

            sudo service gitlab start 

            配置Nginx

            Debian/Ubuntu下:

            sudo apt-get install -y nginx 

            CentOS下:

            sudo yum install nginx 

            下載配置文件樣例:

            sudo cp lib/support/nginx/gitlab /etc/nginx/sites-available/gitlab sudo ln -s /etc/nginx/sites-available/gitlab /etc/nginx/sites-enabled/gitlab 

            修改 /etc/nginx/sites-available/gitlab,特別留意將 YOUR_SERVER_FQDN 改成自己的。

            重啟nginx:

            sudo service nginx restart 

            好了,你可以登錄GitLab了,默認(rèn)安裝后的用戶名:admin@local.host,密碼5iveL!fe。

            posted @ 2015-01-14 14:44 小馬歌 閱讀(669) | 評(píng)論 (0)編輯 收藏
            僅列出標(biāo)題
            共95頁(yè): First 上一頁(yè) 13 14 15 16 17 18 19 20 21 下一頁(yè) Last 
             
            主站蜘蛛池模板: 永靖县| 湘潭市| 都昌县| 星座| 邛崃市| 常州市| 湘乡市| 凤台县| 会泽县| 佛教| 文安县| 启东市| 鄂州市| 含山县| 莒南县| 宣威市| 甘孜县| 增城市| 广宁县| 南城县| 交城县| 和田县| 攀枝花市| 佛山市| 洛宁县| 泗水县| 射阳县| 新和县| 全州县| 嫩江县| 青铜峡市| 仁寿县| 中宁县| 太仓市| 郴州市| 哈密市| 石林| 富阳市| 新化县| 天台县| 平原县|