2012年9月5日

          Exception VS Control Flow

          每當提到Exeption就會有人跳出來說“Exception not use for flow control”,那到底是什么意思呢?什么情況下Exception就算控制流程了,什么時候就該拋出Exception了呢?

          首先什么是Exception?

          Definition: 

          An exception is an event, which occurs during the execution of a program, that disrupts the normal flow of the program's instructions.


          再看什么是“流程”?如果流程是指程序的每一步執行,那異常就是控制流程的,它就是用來區分程序的正常流程和非正常流程的,從上面異常的定義就可以看出。因此為了明確我們應該說”不要用異常控制程序的正常流程“。如何定義正常流程和非正常流程很難,這是一個主觀的決定,沒有一個統一的標準,只能根據實際情況。網上找個例子:
          bool isDouble(string someString) {
              
          try {
                  
          double d = Convert.ParseInt32(someString);
              } 
          catch(FormatException e) {
                  
          return false;
              }
              
          return true;
          }
          這個程序其實不是想convert數字,而是想知道一個字符串是否包含一個數字,通過判斷是不是有異常的方式來決定返回true還是false,這是個Smell,這種應該算”異常控制了正常流程“。我們可以通過正則表達式或其他方式來判斷。

          另外Clean Code上一個例子:
              try {  
                  MealExpenses expenses 
          = expenseReportDAO.getMeals(employee.getID());  
                  m_total 
          += expenses.getTotal();  
              } 
          catch(MealExpensesNotFound e) {  
                  m_total 
          += getMealPerDiem();  
              } 
          MealExpensesNotFound異常影響了正常的計算m_total的業務邏輯。對于這種情況可以通過一下方式改進
              public class PerDiemMealExpenses implements MealExpenses {  
                  
          public int getTotal() {  
                      
          // return the per diem default  
                  }  
              } 

          以上兩個例子是比較明顯的異常控制正常流程,Smell很明顯,不會有很大爭議,但是實際情況中可能有很多例子沒有這么明顯,因為都是主觀判定的。比如一下代碼,算不算異常控制正常流程?

          public int doSomething()
          {
              doA();
              
          try {
                  doB();
              } 
          catch (MyException e) {
                  
          return ERROR;
              }
              doC();
              
          return SUCCESS;
          }

          看到這樣一段程序,如果沒有上下文,我們無法判斷。但是如果doSomething是想讓我們回答yes or no,success or error,我們不應該通過有無異常來判斷yes or no,success or error,應該有個單獨的方法來判斷,這個方法就只做這一件事情。如果doSometing是執行一個操作,那么在這個過程中我們假定是不會出現問題的,否則拋出異常是比較合理的。






          posted @ 2012-10-30 17:03 *** 閱讀(246) | 評論 (0)編輯 收藏

          ClassLoader 加載機制

          1. Java Class Loading Mechanism

          首先當編譯一個Java文件時,編譯器就會在生成的字節碼中內置一個public,static,final的class字段,該字段屬于java.lang.Class類型,該class字段使用點來訪問,所以可以有:
          java.lang.Class clazz = MyClass.class

          當class被JVM加載,就不再加載相同的class。class在JVM中通過(ClassLoader,Package,ClassName)來唯一決定。ClassLoader指定了一個class的scope,這意味著如果兩個相同的包下面的class被不同的ClassLoader加載,它們是不一樣的,并且不是type-compatible的。

          JVM中所有的ClassLoader(bootstrap ClassLoader除外)都是直接或間接繼承于java.lang.ClassLoader抽象類,并且人為邏輯上指定了parent-child關系,實現上child不一定繼承于parent,我們也可以通過繼承它來實現自己的ClassLoader。

          JVM ClassLoder架構,從上到下依次為parent-child關系:
          • Bootstrap ClassLoader - 啟動類加載器,主要負責加載核心Java類如java.lang.Object和其他運行時所需class,位于JRE/lib目錄下或-Xbootclasspath指定的目錄。我們不知道過多的關于Bootstrap ClassLoader的細節,因為它是一個native的實現,不是Java實現,所以不同JVMs的Bootstrap ClassLoader的行為也不盡相同。調用java.lang.String.getClassLoder() 返回null。
          • sun.misc.ExtClassLoader - 擴展類加載器,負責加載JRE/lib/ext目錄及-Djava.ext.dirs指定目錄。
          • sun.misc.AppClassLoader - 應用類加載器,負責加載java.class.path目錄
          • 另外,還有一些其他的ClassLoader如:java.net.URLClassLoader,java.security.SecureClassLoader,java.rmi.server.RMIClassLoader,sun.applet.AppletClassLoader
          • 用戶還可以自己繼承java.lang.ClassLoader來實現自己的ClassLoader,用來動態加載class文件。
          ClassLoader特性
          • 每個ClassLoader維護一份自己的命名空間,同一個ClassLoader命名空間不能加載兩個同名的類。
          • 為實現Java安全沙箱模型,默認采用parent-child加載鏈結構,除Bootstrap ClassLoader沒有parent外,每個ClassLoader都有一個邏輯上的parent,就是加載這個ClassLoader的ClassLoader,因為ClassLoader本身也是一個類,直接或間接的繼承java.lang.ClassLoader抽象類。
          java.lang.Thread中包含一個public的方法public ClassLoader getContextClassLoader(),它返回某一線程相關的ClassLoader,該ClassLoader是線程的創建者提供的用來加載線程中運行的classes和資源的。如果沒有顯式的設置其ClassLoader,默認是parent線程的Context ClassLoader。Java默認的線程上下文加載器是AppClassLoader。

          ClassLoader工作原理:

          了解ClassLoader工作原理,先來看一個ClassLoader類簡化版的loadClass()方法源碼
           1 protected Class<?> loadClass(String name, boolean resolve)
           2         throws ClassNotFoundException
           3     {
           4         synchronized (getClassLoadingLock(name)) {
           5             // First, check if the class has already been loaded
           6             Class c = findLoadedClass(name);
           7             if (c == null) {
           8                 long t0 = System.nanoTime();
           9                 try {
          10                     if (parent != null) {
          11                         c = parent.loadClass(name, false);
          12                     } else {
          13                         c = findBootstrapClassOrNull(name);
          14                     }
          15                 } catch (ClassNotFoundException e) {
          16                     // ClassNotFoundException thrown if class not found
          17                     // from the non-null parent class loader
          18                 }
          19 
          20                 if (c == null) {
          21                     // If still not found, then invoke findClass in order
          22                     // to find the class.
          24                     c = findClass(name);
          25                 }
          26             }
          27             if (resolve) {
          28                 resolveClass(c);
          29             }
          30             return c;
          31         }
          32     }

          首先查看該class是否已被加載,如果已被加載則直接返回,否則調用parent的loadClass來加載,如果parent是null代表是Bootstrap ClassLoader,則有Bootstrap ClassLoader來加載,如果都未加載成功,最后由該ClassLoader自己加載。這種parent-child委派模型,保證了惡意的替換Java核心類不會發生,因為如果定義了一個惡意java.lang.String,它首先會被JVM的Bootstrap ClassLoader加載自己JRE/lib下的,而不會加載惡意的。另外,Java允許同一package下的類可以訪問受保護成員的訪問權限,如定義一個java.lang.Bad,但是因為java.lang.String由Bootstrap ClassLoader加載而java.lang.Bad由AppClassLoader加載,不是同一ClassLoader加載,仍不能訪問。

          2. Hotswap - 熱部署

          即不重啟JVM,直接替換class。因為ClassLoader特性,同一個ClassLoader命名空間不能加載兩個同名的類,所以在不重啟JVM的情況下,只能通過新的ClassLoader來重新load新的class。

           1  public static void main(String[] args) throws InterruptedException, MalformedURLException {
           2         IExample oldExample = new Example();
           3         oldExample.plus();
           4         System.out.println(oldExample.getCount());
           5 
           6         Hotswap hotswap = new Hotswap();
           7         while (true) {
           8             IExample newExample = hotswap.swap(oldExample);
           9             String message = newExample.message();
          10             int count = newExample.plus();
          11             System.out.println(message.concat(" : " + count));
          12             oldExample = newExample;
          13             Thread.sleep(5000);
          14         }
          15     }
          16 
          利用hotswap替換就的Example,每5秒鐘輪詢一次,swap方法實現如下:
           1  private IExample swap(IExample old) {
           2         try {
           3             String sourceFile = srcPath().concat("Example.java");
           4             if (isChanged(sourceFile)) {
           5                 comiple(sourceFile, classPath());
           6                 MyClassLoader classLoader = new MyClassLoader(new URL[]{new URL("file:"+classPath())});
           7                 Class<?> clazz = classLoader.loadClass("Example");
           8                 System.out.println(IExample.class.getClassLoader());
           9                 IExample exampleInstance = ((IExample) clazz.newInstance()).copy(old);
          10                 System.out.println(exampleInstance.getClass().getClassLoader());
          11                 return exampleInstance;
          12             }
          13         } catch ...
          24         return old;
          25     }
          這里必須將exampleInstance轉型為IExample接口而不是Exmaple,否則會拋出ClassCastExecption,這是因為swap方法所在類Hotswap是有AppClassLoader加載的,而且加載Hotswap的同時會加載該類引用的Exmaple的symbol link,而Example是MyClassLoader加載的,不同的ClassLoader加載的類之間直接用會拋出ClassCastException, 在本例中ClassLoader實現如下:
           1 public class MyClassLoader extends URLClassLoader {
           2 
           3     public MyClassLoader(URL[] urls) {
           4         super(urls);
           5     }
           6 
           7     @Override
           8     public Class<?> loadClass(String name) throws ClassNotFoundException {
           9         if ("Example".equals(name)) {
          10             return findClass(name);
          11         }
          12         return super.loadClass(name);
          13     }
          14 }
          而對IExample我們還是調用super的loadClass方法,該方法實現仍是JVM的parent-child委派方式,因此最終由AppClassLoader加載,加載Hotswap時加載的symbol link也是由AppClassLoader加載的,因此能夠成功。

          此外再熱部署時,被替換的類的所有引用及狀態都要遷移到新的類上,本例中只是很簡單的調用copy函數遷移了count的狀態。

          Tomcat的jsp熱部署機制就是基于ClassLoader實現的,對于其類的熱部署機制是通過修改內存中的class字節碼實現的。

          Resource:
          Reloading Java Classes 101: Objects, Classes and ClassLoaders
          Internals of Java Class Loading

          posted @ 2012-09-08 17:58 *** 閱讀(635) | 評論 (0)編輯 收藏

          Java Runtime exec問題

          1. java.lang.IllegalThreadStateException: process hasn't exited

          1 public static void main(String[] args) {
          2         try {
          3             Process process = Runtime.getRuntime().exec("javac");
          4             System.out.println(process.exitValue());
          5         } catch (IOException e) {
          6             e.printStackTrace();
          7         }
          8     }

          exec方法創建了一個native的進程,并返回該process的對象,如果進程還沒有返回,調用exitValue方法就會出現此異常,因為該方法沒有阻塞,其實現如下:
          1 public synchronized int exitValue() {
          2         if (!hasExited) {
          3             throw new IllegalThreadStateException("process hasn't exited");
          4         }
          5         return exitcode;
          6     }

          2. waitFor方法

           1 public static void main(String[] args) {
           2         try {
           3             Process process = Runtime.getRuntime().exec("javac");
           4             int result = process.waitFor();
           5             System.out.println(result);
           6         } catch (IOException e) {
           7             e.printStackTrace();
           8         } catch (InterruptedException e) {
           9             e.printStackTrace();
          10         }
          11     }

          waitFor方法會一直阻塞直到native進程完成,并返回native進程的執行結果。如果native進程無法執行完成,waitFor方法將一直阻塞下去,其實現如下:
          1 public synchronized int waitFor() throws InterruptedException {
          2         while (!hasExited) {
          3             wait();
          4         }
          5         return exitcode;
          6     }

          該程序在jdk1.7 windows下測試工作正常,返回2; 但是jdk1.4 windows下測試出現hang。JDK documention的解釋是
          The methods that create processes may not work well for special processes on certain native platforms,
          such as 
          native windowing processes, daemon processes, Win16/DOS processes on Microsoft Windows,or shell scripts.
          The created subprocess does not have its own terminal or console. All its standard io (i.e. stdin, stdout, stderr)
          operations will be redirected to the parent process through three streams (getOutputStream(), getInputStream(),
          getErrorStream()). The parent process uses these streams to feed input to and get output from the subprocess. Because some 
          native platforms only provide limited buffer size for standard input and output streams,
          failure to promptly write the input stream or read the output stream of the subprocess may cause
          the subprocess to block, and even deadlock.

          所以,出現hang時,及時的flush標準輸入輸出或者錯誤流能夠消除hang,如上面的javac,我們知道redirect到stderr中,所以解決hang后的代碼
           1 public static void main(String[] args) {
           2         try {
           3             Process process = Runtime.getRuntime().exec("echo 'abc'>b.txt");
           4             BufferedReader reader = new BufferedReader(new InputStreamReader(process.getErrorStream()));
           5             String line;
           6             while((line=reader.readLine())!=null){
           7                 System.out.println(line);
           8             }
           9             int result = process.waitFor();
          10             System.out.println(result);
          11         } catch (IOException e) {
          12             e.printStackTrace();
          13         } catch (InterruptedException e) {
          14             e.printStackTrace();
          15         }
          16     }


          3. exec() is not a command line 并不是所有的command line命令都可以用exec

           1  public static void main(String[] args) {
           2         try {
           3             Process process = Runtime.getRuntime().exec("echo 'abc'>a.txt");
           4             BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
           5             String line;
           6             while((line=reader.readLine())!=null){
           7                 System.out.println(line);
           8             }
           9             int result = process.waitFor();
          10             System.out.println(result);
          11         } catch (IOException e) {
          12             e.printStackTrace();
          13         } catch (InterruptedException e) {
          14             e.printStackTrace();
          15         }
          16     }
          結果為:
          1 'abc'>a.txt
          2 0
          并沒有將創建a.txt,而從命令行執行"echo 'abc'>a.txt"卻正確創建了a.txt

          posted @ 2012-09-05 22:43 *** 閱讀(5084) | 評論 (0)編輯 收藏

          <2012年9月>
          2627282930311
          2345678
          9101112131415
          16171819202122
          23242526272829
          30123456

          導航

          統計

          常用鏈接

          留言簿

          隨筆檔案

          搜索

          最新評論

          閱讀排行榜

          評論排行榜

          主站蜘蛛池模板: 修水县| 阳谷县| 托里县| 漳州市| 项城市| 大宁县| 丽江市| 阿拉善右旗| 周至县| 盘山县| 台州市| 华宁县| 永昌县| 清原| 桑日县| 赣榆县| 方山县| 大田县| 克东县| 固安县| 清水县| 黑水县| 文成县| 中阳县| 苍梧县| 上犹县| 龙胜| 巧家县| 泰顺县| 甘孜县| 宝山区| 德保县| 洪洞县| 英山县| 嘉黎县| 黄浦区| 克什克腾旗| 湄潭县| 马公市| 杭州市| 来安县|