qileilove

          blog已經(jīng)轉(zhuǎn)移至github,大家請(qǐng)?jiān)L問 http://qaseven.github.io/

          Java中線程安全問題個(gè)人理解

           線程安全問題是一個(gè)比較高深的問題,是很多程序員比較難掌握的一個(gè)技術(shù)難點(diǎn),如果一個(gè)程序員對(duì)線程掌握的很好的話,那么這個(gè)程序員的內(nèi)功修煉的是相當(dāng)?shù)暮谩?/p>

            在這里我主要說一下我對(duì)java中如何保證線程安全的一些個(gè)人見解,希望對(duì)各位有所幫助,那里有不對(duì)的地方敬請(qǐng)給位不吝賜教。

            線程安全問題主要出現(xiàn)在訪問臨界資源的時(shí)候,就是訪問同一個(gè)對(duì)象的時(shí)候,可能會(huì)出現(xiàn)無法挽回的損失,特別是在關(guān)于資金安全方面的時(shí)候,當(dāng)然還有數(shù)據(jù)庫事務(wù)方面的問題。他們很類似,都是要保證數(shù)據(jù)的原子性。

            那么在java中如何保證線程安全呢?

            對(duì)與共同使用的對(duì)象進(jìn)行加鎖,意思是我使用的時(shí)候,那么你就必須等待,等我用完之后你再用,反之依然。就像上廁所,你去的時(shí)候我是不能去的。

            如何加鎖呢?下面寫三個(gè)加鎖的方式

            首先看一下實(shí)例代碼

          1. public class TraditionalSynchornizedTest {  
          2.  /** 
          3.   * @param args 
          4.   */ 
          5.  public static void main(String[] args) {  
          6.   new TraditonalSynchornizedTest().sartThread();  
          7.  }  
          8.  public void sartThread(){  
          9.   final Outerput outerput = new Outerput();  
          10.   new Thread(new Runnable(){  
          11.    @Override 
          12.    public void run() {  
          13.     while(true){  
          14.      try {  
          15.       Thread.sleep(5);  
          16.      } catch (InterruptedException e) {  
          17.       e.printStackTrace();  
          18.      }  
          19.      outerput.print("zhangsanfeng");  
          20.     }  
          21.    }  
          22.      
          23.   }).start();  
          24.   new Thread(new Runnable(){  
          25.    @Override 
          26.    public void run() {  
          27.     while(true){  
          28.      try {  
          29.       Thread.sleep(5);  
          30.      } catch (InterruptedException e) {  
          31.       e.printStackTrace();  
          32.      }  
          33.      outerput.print("luxiaofeng");  
          34.     }  
          35.    }  
          36.      
          37.   }).start();  
          38.  }  
          39.  public class Outerput{  
          40.   public void print(String name){  
          41.    for(int i = 0;i < name.length(); i++){  
          42.     System.out.print(name.charAt(i));  
          43.    }  
          44.    System.out.println();  
          45.   }  
          46.  }  
          47. }

            以上代碼沒有對(duì)共同持有的對(duì)象outerput加鎖,所以會(huì)出現(xiàn)線程安全問題

          1、對(duì)代碼塊加鎖

            對(duì)共同持有的對(duì)象加鎖可以把內(nèi)部類寫成這樣的

          1. public class Outerput{  
          2.   public void print(String name){  
          3.    synchronized (this) {  
          4.     for(int i = 0;i < name.length(); i++){  
          5.      System.out.print(name.charAt(i));  
          6.     }  
          7.     System.out.println();  
          8.    }  
          9.   }  
          10.  }

            2、對(duì)非靜態(tài)方法加鎖,加鎖的對(duì)象是this

          1. public class Outerput{  
          2.   public synchronized void print(String name){  
          3.    for(int i = 0;i < name.length(); i++){  
          4.     System.out.print(name.charAt(i));  
          5.    }  
          6.    System.out.println();  
          7.   }  
          8.  }

            3、對(duì)靜態(tài)方法加鎖的對(duì)象到底是誰?

          1. public static synchronized  void print2(String name){  
          2.    for(int i = 0;i < name.length(); i++){  
          3.     System.out.print(name.charAt(i));  
          4.    }  
          5.    System.out.println();  
          6.   }

            其實(shí)加鎖的對(duì)象是字節(jié)碼對(duì)象,Outerput.class

            如果和非靜態(tài)方法同時(shí)持有同一個(gè)對(duì)象時(shí),可以持有同一個(gè)字節(jié)碼對(duì)象。



          posted @ 2012-02-23 17:09 順其自然EVO 閱讀(155) | 評(píng)論 (0)編輯 收藏

          利用Java進(jìn)行MySql數(shù)據(jù)庫的導(dǎo)入和導(dǎo)出

          利用Java進(jìn)行MySql數(shù)據(jù)庫的導(dǎo)入和導(dǎo)出

           利用Java來進(jìn)行Mysql數(shù)據(jù)庫的導(dǎo)入和導(dǎo)出的總體思想是通過Java來調(diào)用命令窗口執(zhí)行相應(yīng)的命令。

            MySql導(dǎo)出數(shù)據(jù)庫的命令如下:

          mysqldump -uusername -ppassword -hhost -Pport exportDatabaseName > exportPath

            利用Java調(diào)用命令窗口執(zhí)行命令來進(jìn)行MySql導(dǎo)入數(shù)據(jù)庫一般分三步走:

            第一步:登錄Mysql數(shù)據(jù)庫,在登錄數(shù)據(jù)庫的時(shí)候也可以指定登錄到哪個(gè)數(shù)據(jù)庫,如果指定了則可以跳過第二步;

            第二步:切換數(shù)據(jù)庫到需要導(dǎo)入的目標(biāo)數(shù)據(jù)庫

            第三步:利用命令開始導(dǎo)入

            在進(jìn)行導(dǎo)出的時(shí)候,需要注意命令語句的運(yùn)行環(huán)境,如果已經(jīng)將mysql安裝路徑下的bin加入到系統(tǒng)的path變量中,那么在導(dǎo)出的時(shí)候可以直接使用命令語句,否則,就需要在執(zhí)行命令語句的時(shí)候加上命令所在位置的路徑,即mysql安裝路徑想的bin下的mysqldump命令。

            基本代碼如下:

          1. import java.io.IOException;  
          2. import java.io.InputStream;  
          3. import java.io.OutputStream;  
          4. import java.io.OutputStreamWriter;  
          5. import java.util.Properties;  
          6. /** 
          7.  * 在進(jìn)行導(dǎo)出的時(shí)候,需要注意命令語句的運(yùn)行環(huán)境,如果已經(jīng)將mysql安裝路徑下的bin加入到 
          8.  * 系統(tǒng)的path變量中,那么在導(dǎo)出的時(shí)候可以直接使用命令語句,否則,就需要在執(zhí)行命令語句的 
          9.  * 時(shí)候加上命令所在位置的路徑,即mysql安裝路徑想的bin下的mysqldump命令 
          10.  * @author andy 
          11.  * 
          12.  */ 
          13. public class MySqlImportAndExport {  
          14.     public static void main(String args[]) throws IOException {  
          15.         InputStream is = MySqlImportAndExport.class.getClassLoader().getResourceAsStream("jdbc.properties");  
          16.         Properties properties = new Properties();  
          17.         properties.load(is);  
          18. //      MySqlImportAndExport.export(properties);//這里簡(jiǎn)單點(diǎn)異常我就直接往上拋 
          19.         MySqlImportAndExport.importSql(properties);  
          20.     }  
          21.       
          22.     /** 
          23.      * 根據(jù)屬性文件的配置導(dǎo)出指定位置的指定數(shù)據(jù)庫到指定位置 
          24.      * @param properties 
          25.      * @throws IOException 
          26.      */ 
          27.     public static void export(Properties properties) throws IOException {  
          28.         Runtime runtime = Runtime.getRuntime();  
          29.         String command = getExportCommand(properties);  
          30.         runtime.exec(command);//這里簡(jiǎn)單一點(diǎn)異常我就直接往上拋 
          31.     }  
          32.       
          33.     /** 
          34.      * 根據(jù)屬性文件的配置把指定位置的指定文件內(nèi)容導(dǎo)入到指定的數(shù)據(jù)庫中 
          35.      * 在命令窗口進(jìn)行mysql的數(shù)據(jù)庫導(dǎo)入一般分三步走: 
          36.      * 第一步是登到到mysql; mysql -uusername -ppassword -hhost -Pport -DdatabaseName;如果在登錄的時(shí)候指定了數(shù)據(jù)庫名則會(huì) 
          37.      * 直接轉(zhuǎn)向該數(shù)據(jù)庫,這樣就可以跳過第二步,直接第三步;  
          38.      * 第二步是切換到導(dǎo)入的目標(biāo)數(shù)據(jù)庫;use importDatabaseName; 
          39.      * 第三步是開始從目標(biāo)文件導(dǎo)入數(shù)據(jù)到目標(biāo)數(shù)據(jù)庫;source importPath; 
          40.      * @param properties 
          41.      * @throws IOException  
          42.      */ 
          43.     public static void importSql(Properties properties) throws IOException {  
          44.         Runtime runtime = Runtime.getRuntime();  
          45.         //因?yàn)樵诿畲翱谶M(jìn)行mysql數(shù)據(jù)庫的導(dǎo)入一般分三步走,所以所執(zhí)行的命令將以字符串?dāng)?shù)組的形式出現(xiàn) 
          46.         String cmdarray[] = getImportCommand(properties);//根據(jù)屬性文件的配置獲取數(shù)據(jù)庫導(dǎo)入所需的命令,組成一個(gè)數(shù)組 
          47.         //runtime.exec(cmdarray);//這里也是簡(jiǎn)單的直接拋出異常 
          48.         Process process = runtime.exec(cmdarray[0]);  
          49.         //執(zhí)行了第一條命令以后已經(jīng)登錄到mysql了,所以之后就是利用mysql的命令窗口 
          50.         //進(jìn)程執(zhí)行后面的代碼 
          51.         OutputStream os = process.getOutputStream();  
          52.         OutputStreamWriter writer = new OutputStreamWriter(os);  
          53.         //命令1和命令2要放在一起執(zhí)行 
          54.         writer.write(cmdarray[1] + "\r\n" + cmdarray[2]);  
          55.         writer.flush();  
          56.         writer.close();  
          57.         os.close();  
          58.     }  
          59.       
          60.     /** 
          61.      * 利用屬性文件提供的配置來拼裝命令語句 
          62.      * 在拼裝命令語句的時(shí)候有一點(diǎn)是需要注意的:一般我們?cè)诿畲翱谥苯邮褂妹顏?/span> 
          63.      * 進(jìn)行導(dǎo)出的時(shí)候可以簡(jiǎn)單使用“>”來表示導(dǎo)出到什么地方,即mysqldump -uusername -ppassword databaseName > exportPath, 
          64.      * 但在Java中這樣寫是不行的,它需要你用-r明確的指出導(dǎo)出到什么地方,如: 
          65.      * mysqldump -uusername -ppassword databaseName -r exportPath。 
          66.      * @param properties 
          67.      * @return 
          68.      */ 
          69.     private static String getExportCommand(Properties properties) {  
          70.         StringBuffer command = new StringBuffer();  
          71.         String username = properties.getProperty("jdbc.username");//用戶名 
          72.         String password = properties.getProperty("jdbc.password");//用戶密碼 
          73.         String exportDatabaseName = properties.getProperty("jdbc.exportDatabaseName");//需要導(dǎo)出的數(shù)據(jù)庫名 
          74.         String host = properties.getProperty("jdbc.host");//從哪個(gè)主機(jī)導(dǎo)出數(shù)據(jù)庫,如果沒有指定這個(gè)值,則默認(rèn)取localhost 
          75.         String port = properties.getProperty("jdbc.port");//使用的端口號(hào) 
          76.         String exportPath = properties.getProperty("jdbc.exportPath");//導(dǎo)出路徑 
          77.           
          78.         //注意哪些地方要空格,哪些不要空格 
          79.         command.append("mysqldump -u").append(username).append(" -p").append(password)//密碼是用的小p,而端口是用的大P。 
          80.         .append(" -h").append(host).append(" -P").append(port).append(" ").append(exportDatabaseName).append(" -r ").append(exportPath);  
          81.         return command.toString();  
          82.     }  
          83.       
          84.     /** 
          85.      * 根據(jù)屬性文件的配置,分三步走獲取從目標(biāo)文件導(dǎo)入數(shù)據(jù)到目標(biāo)數(shù)據(jù)庫所需的命令 
          86.      * 如果在登錄的時(shí)候指定了數(shù)據(jù)庫名則會(huì) 
          87.      * 直接轉(zhuǎn)向該數(shù)據(jù)庫,這樣就可以跳過第二步,直接第三步;  
          88.      * @param properties 
          89.      * @return 
          90.      */ 
          91.     private static String[] getImportCommand(Properties properties) {  
          92.         String username = properties.getProperty("jdbc.username");//用戶名 
          93.         String password = properties.getProperty("jdbc.password");//密碼 
          94.         String host = properties.getProperty("jdbc.host");//導(dǎo)入的目標(biāo)數(shù)據(jù)庫所在的主機(jī) 
          95.         String port = properties.getProperty("jdbc.port");//使用的端口號(hào) 
          96.         String importDatabaseName = properties.getProperty("jdbc.importDatabaseName");//導(dǎo)入的目標(biāo)數(shù)據(jù)庫的名稱 
          97.         String importPath = properties.getProperty("jdbc.importPath");//導(dǎo)入的目標(biāo)文件所在的位置 
          98.         //第一步,獲取登錄命令語句 
          99.         String loginCommand = new StringBuffer().append("mysql -u").append(username).append(" -p").append(password).append(" -h").append(host)  
          100.         .append(" -P").append(port).toString();  
          101.         //第二步,獲取切換數(shù)據(jù)庫到目標(biāo)數(shù)據(jù)庫的命令語句 
          102.         String switchCommand = new StringBuffer("use ").append(importDatabaseName).toString();  
          103.         //第三步,獲取導(dǎo)入的命令語句 
          104.         String importCommand = new StringBuffer("source ").append(importPath).toString();  
          105.         //需要返回的命令語句數(shù)組 
          106.         String[] commands = new String[] {loginCommand, switchCommand, importCommand};  
          107.         return commands;  
          108.     }  
          109.       
          110. }

            上述使用的jdbc.properties文件

          1. jdbc.username=root  
          2. jdbc.password=password 
          3. jdbc.host=localhost  
          4. jdbc.port=3306  
          5. jdbc.exportDatabaseName=dbName  
          6. jdbc.exportPath=d\:\\dbName.sql  
          7. jdbc.importDatabaseName=test  
          8. jdbc.importPath=d\:\\dbName.sql

          posted @ 2012-02-22 14:58 順其自然EVO 閱讀(283) | 評(píng)論 (0)編輯 收藏

          Java自定義范型的應(yīng)用技巧

           我們?cè)?a target="_self" style="word-break: break-all; color: #202859; text-decoration: none; line-height: normal !important; ">JAVA中處處都用到了范型,JAVA中的范型是從C++模板繼承來的,不過JAVA的范型的功能遠(yuǎn)遠(yuǎn)沒有C++那么強(qiáng)大。

            我們知道在C++中模板可以很方便的代替任意類型的數(shù)據(jù)如下;

          template<class T>
          void show(T x)
          {
          cout<<x<<endl ;
          }

            上面的T可以代表任意類型的數(shù)據(jù),這樣不是大大減少了函數(shù)的重載次數(shù),提高了效率呢。java是從C++過來的,理解了C++,jav也不在話下。

            在java中自定義范型也可以用在方法上如下:

            1、//這樣聲明的范型可以代替任意類型數(shù)據(jù)我們市場(chǎng)用到的鍵值對(duì)Map.Entry<K,V>不就是給予范型的嗎

            KV都可以代替任意類型的值,但是在java中范型的實(shí)際類型必須是引用類型

          <K,V> void get(K k,V v)
          {

          }

            2、Java中的范型不能像C++那么靈活

          <T>  T  add(T a,T b)
          {
             //return  a+b   ;//很多人以為java也想C++一樣可以這樣 ,但是不可以 。     
          return  null;
          }

            這個(gè)返回的null也是有類型限制的,比如上面的ab分別是Integer和String那么就會(huì)取他們共同的基類Object做為返回值類型,其他的同理

            3、實(shí)現(xiàn)任意類型的數(shù)組的成員值的交換,注意在自定義范型中范型的實(shí)際類型只能是引用數(shù)據(jù)類型不能是基本數(shù)據(jù)類型

          public  static <T> void  swap(T[]a,int x,int y)
          {
            T  tem  =a[x]  ;
            a[x]=a[y]  ;
            a[y]=tem ;
           
          }

            上面這個(gè)方法如果我  swap(new Integer[]{1,2,3,4,5},1,2);       //這樣就會(huì)自動(dòng)交換下標(biāo)12的值

            但是這樣調(diào)用就錯(cuò)了   swao(new int[]{1,2,3,5,6},2,3) ;  //所以說Java的范型的實(shí)際類型 只能是引用數(shù)據(jù)類型

            4、

            <T  extends  String>     表示類型只能是String或者String的派生類
            <T super  String >   表示范型類型只能是String或者String的父類

            用法同上

            5、

            下面這個(gè)函數(shù)利用范型來實(shí)現(xiàn)類型自動(dòng)轉(zhuǎn)換的功能

          public static  <T> T autoConvert(Object obj)  //因?yàn)榉祷刂凳?T標(biāo)識(shí)任意類型 所喲可以 將返回結(jié)果賦值給任意類型對(duì)象
           {
            return (T)obj;
           } 

          Object  obj=="";

          String str=autoConvert(obj);

            可以完成自動(dòng)轉(zhuǎn)換,因?yàn)榉缎蚑代表任意類型,因此他可以賦值給String類型的對(duì)象

          6、將任意類型的對(duì)象填充到任意類型的數(shù)組中,與是fillArray(newInteger[]{2,3,4},"ddd");這樣調(diào)用是正確的,這樣做忽略類型限制

          public  static <T> void  fillArray(T[] a,T b)  //將任意一個(gè)對(duì)象填充到任意類型的數(shù)組
           {
            for(int i =0;i<a.length;i++)
            {
             a[i] =b ;
            }
           }

            7、以自定義范型的形式顯示一個(gè)集合的數(shù)據(jù),下面一個(gè)是利用自定義范型一個(gè)是利用通配符來實(shí)現(xiàn),但是不同的是利用通配符操作的集合不能向集合中插入元素

            但是自定義范型卻可以。原因是通配符代表的集合我們不知道集合內(nèi)部具體元素是什么類型所以不能對(duì)集合進(jìn)行add操作。

          public static  <T> void showCollection(Collection<T> col,T  obj)  //利用范型來輸出任意類型集合
           {  
            col.add(obj) ;
            for(T a:col)
            {
             System.out.println(a);
            }
           }

          public static void showCollection(Collection<?> col)  //利用范型來輸出任意類型集合 
          {  
            for(Object obj:col)
            {
             System.out.println(obj);
            }
          }

            8、如果一個(gè)類中多個(gè)方法都需要范型那么就是用類級(jí)別的范型。例如

          class  A<E>  
          {  
               public void  add(E obj){}
               public  E  get(){}  
               private E data;  
          }

            這樣聲明范型和在函數(shù)前面聲明其實(shí)是一樣的只不過是在類的級(jí)別上作用于整個(gè)類而已

            9、要注意范型只是給編譯器看的。

            也就是說Vector<Integer>Vector<String>他們用到的都是同一份字節(jié)碼,字節(jié)碼只有class文件加載到內(nèi)存中的時(shí)候才有

            所以在一個(gè)類中下面2個(gè)方法不能同時(shí)存在

          void show(Vector<Integer>) {}
          void show(Vector<String>){}

            這兩個(gè)方法都不是重載因?yàn)榫幾g后要去掉類型信息。

          posted @ 2012-02-21 16:58 順其自然EVO 閱讀(224) | 評(píng)論 (0)編輯 收藏

          Java類加載器以及類加載器的委托模型

           我們知道,我們?cè)?a target="_self" style="word-break: break-all; color: #202859; text-decoration: none; line-height: normal !important; ">Java中用到的所有的類都是通過類加載器ClassLoader加載到JVM中的,我們還知道類加載器也對(duì)應(yīng)著一個(gè)類,既然這樣那么我們會(huì)想那么ClassLoader類是由誰加載的呢?

            其實(shí)在Java中有許許多多的類加載器,我們甚至可以寫自己的類加載器。

            其中主要三個(gè)類加載器(他們是樹形關(guān)系)是:

            BootStrap:在java虛擬機(jī)啟動(dòng)的時(shí)候會(huì)利用這個(gè)類加載器來加載 JDK安裝目錄下的 /JRE/LIB/rt.jar 也就是系統(tǒng)默認(rèn)導(dǎo)入的一些類例如System類,這個(gè)類加載器不是類 。只是作為一個(gè)java中類的起源工具。

            ExpClassLoader:這個(gè)類加載器加載JDK安裝目錄下的/JRE/LIB/ext 目錄中的類 我們只要把我們的類打包成JAR包放在這里即可。

            AppClassLoader:我們?cè)趈ava程序中classpath對(duì)應(yīng)的類都有這個(gè)AppClassLoader導(dǎo)入進(jìn)來。

            看下面一段代碼:

          1. package me.test;  
          2. /** 
          3. *   BootStrap   
          4. *   加載  JRE/lib/rt.jar 包中的類   包括我們常用到的類 
          5. *   
          6. *   ExtClassLoader 
          7. *   專門家在 JDK/JRE/libEXT/*.jar  中的類  只要把我們的類放在這里 就會(huì)被 這個(gè)加載器加載  
          8. * 
          9. *   AppClassLoader   
          10. *   加載ClassPath指定的所有jar和目錄 
          11. * 
          12. * **/ 
          13. public class Test1  
          14. {    
          15. public static void main(String []args)  
          16. {  
          17.      
          18.   System.out.println(Test1.class.getClassLoader().getClass().getName() );    //獲取主類的類加載器 
          19.   System.out.println(System.class.getClassLoader()); 
          20. //BootStrap    獲取System類的類加載器  因?yàn)榧虞d器是 BootStrap所以返回null  以為內(nèi)他不是一個(gè)類 
          21.   ClassLoader l=Test1.class.getClassLoader() ;  //獲取Test1的類加載器 
          22.   while(l!=null)   //循環(huán)出 ClassLoader樹 
          23.   {  
          24.    System.out.println(l.getClass().getName());    
          25.    l=l.getParent();  
          26.   }  
          27.    
          28.   System.out.println(l);  
          29.    
          30. }  
          31. }

            ClassLoader的委托模型

            比如說我們?cè)诩虞d一個(gè)類的時(shí)候AppClassLoader他先讓BootStrap來加載類,如果BootStrap已經(jīng)加載了,那么就返回。如果找不到這個(gè)類那么BootStrap就傳遞給ExtClassLoader 來查找,和BootStrap一樣。如果找到就加載,如果找不到就繼續(xù)傳遞給AppClassLoader 來加載。如果AppClassLoader還找不到的話,那么AppClassLoader就會(huì)跑出ClassNotFoundException 異常。

            我們?yōu)槭裁床焕肁ppClassLoader下級(jí)的加載器呢?因?yàn)锳ppClassLoader下級(jí)可能有多個(gè)類加載器多個(gè)類加載器相互獨(dú)立,如果加載類那么就會(huì)導(dǎo)致內(nèi)存中出現(xiàn)多份字節(jié)碼,造成不必要的的內(nèi)存浪費(fèi)。這就是類加載器的委托模型。

          posted @ 2012-02-21 16:57 順其自然EVO 閱讀(179) | 評(píng)論 (0)編輯 收藏

          Java堆內(nèi)存的10個(gè)要點(diǎn)

            當(dāng)我開始學(xué)習(xí)Java編程時(shí),我不知道什么是堆內(nèi)存或堆空間,我甚至不知道當(dāng)對(duì)象創(chuàng)建時(shí),它們被放在了哪里。當(dāng)我開始正式寫一些程序后,我會(huì)經(jīng)常遇到j(luò)ava.lang.outOfMemoryError的報(bào)錯(cuò),之后我才開始關(guān)注什么是堆內(nèi)存或者說堆空間(heap space)。對(duì)大多數(shù)程序員都經(jīng)歷過這樣的過程,因?yàn)?a target="_self" style="word-break: break-all; color: #202859; text-decoration: none; line-height: normal !important; ">學(xué)習(xí)一種語言是非常容易來的,但是學(xué)習(xí)基礎(chǔ)是非常難的,因?yàn)闆]有什么特定的流程讓你學(xué)習(xí)編程的每個(gè)基礎(chǔ),使你發(fā)覺編程的秘訣。

            對(duì)于程序員來說,知道堆空間,設(shè)置堆空間,處理堆空間的outOfMemoryError錯(cuò)誤,分析heap dump是非常重要的。這個(gè)關(guān)于Java堆的教程是給我剛開始學(xué)編程的兄弟看的。如果你知道這個(gè)基礎(chǔ)知識(shí)或者知道底層發(fā)生了什么,當(dāng)然可能幫助不是那么大。除非你知道了對(duì)象被創(chuàng)建在堆中,否則你不會(huì)意識(shí)到OutOfMemoryError是發(fā)生在堆空間中的。我盡可能的將我所知道的所有關(guān)于堆的知識(shí)都寫下來了,也希望你們能夠盡可能多的貢獻(xiàn)和分享你的知識(shí),以便可以讓其他人也受益。

            Java中的堆空間是什么?

            當(dāng)Java程序開始運(yùn)行時(shí),JVM會(huì)從操作系統(tǒng)獲取一些內(nèi)存。JVM使用這些內(nèi)存,這些內(nèi)存的一部分就是堆內(nèi)存。堆內(nèi)存通常在存儲(chǔ)地址的底層,向上排列。當(dāng)一個(gè)對(duì)象通過new關(guān)鍵字或通過其他方式創(chuàng)建后,對(duì)象從堆中獲得內(nèi)存。當(dāng)對(duì)象不再使用了,被當(dāng)做垃圾回收掉后,這些內(nèi)存又重新回到堆內(nèi)存中。要學(xué)習(xí)垃圾回收,請(qǐng)閱讀”Java中垃圾回收的工作原理”。

            如何增加Java堆空間

            在大多數(shù)32位機(jī)、Sun的JVM上,Java的堆空間默認(rèn)的大小為128MB,但也有例外,例如在32未Solaris操作系統(tǒng)(SPARC平臺(tái)版本)上,默認(rèn)的最大堆空間和起始堆空間大小為 -Xms=3670K 和 -Xmx=64M。對(duì)于64位操作系統(tǒng),一般堆空間大小增加約30%。但你使用Java 1.5的throughput垃圾回收器,默認(rèn)最大的堆大小為物理內(nèi)存的四分之一,而起始堆大小為物理內(nèi)存的十六分之一。要想知道默認(rèn)的堆大小的方法,可以用默認(rèn)的設(shè)置參數(shù)打開一個(gè)程序,使用JConsole(JDK 1.5之后都支持)來查看,在VM Summary頁面可以看到最大的堆大小。

            用這種方法你可以根據(jù)你的程序的需要來改變堆內(nèi)存大小,我強(qiáng)烈建議采用這種方法而不是默認(rèn)值。如果你的程序很大,有很多對(duì)象需要被創(chuàng)建的話,你可以用-Xms and -Xmx這兩個(gè)參數(shù)來改變堆內(nèi)存的大小。Xms表示起始的堆內(nèi)存大小,Xmx表示最大的堆內(nèi)存的大小。另外有一個(gè)參數(shù) -Xmn,它表示new generation(后面會(huì)提到)的大小。有一件事你需要注意,你不能任意改變堆內(nèi)存的大小,你只能在啟動(dòng)JVM時(shí)設(shè)定它。

            堆和垃圾回收

            我們知道對(duì)象創(chuàng)建在堆內(nèi)存中,垃圾回收這樣一個(gè)進(jìn)程,它將已死對(duì)象清除出堆空間,并將這些內(nèi)存再還給堆。為了給垃圾回收器使用,堆主要分成三個(gè)區(qū)域,分別叫作New Generation,Old Generation或叫Tenured Generation,以及Perm space。New Generation是用來存放新建的對(duì)象的空間,在對(duì)象新建的時(shí)候被使用。如果長(zhǎng)時(shí)間還使用的話,它們會(huì)被垃圾回收器移動(dòng)到Old Generation(或叫Tenured Generation)。Perm space是JVM存放Meta數(shù)據(jù)的地方,例如類,方法,字符串池和類級(jí)別的詳細(xì)信息。你可以查看“Java中垃圾回收的工作原理”來獲得更多關(guān)于堆和垃圾回收的信息。

            Java堆中的OutOfMemoryError錯(cuò)誤

            當(dāng)JVM啟動(dòng)時(shí),使用了-Xms 參數(shù)設(shè)置的對(duì)內(nèi)存。當(dāng)程序繼續(xù)進(jìn)行,創(chuàng)建更多對(duì)象,JVM開始擴(kuò)大堆內(nèi)存以容納更多對(duì)象。JVM也會(huì)使用垃圾回收器來回收內(nèi)存。當(dāng)快達(dá)到-Xmx設(shè)置的最大堆內(nèi)存時(shí),如果沒有更多的內(nèi)存可被分配給新對(duì)象的話,JVM就會(huì)拋出java.lang.outofmemoryerror,你的程序就會(huì)當(dāng)?shù)?。在拋?OutOfMemoryError之前,JVM會(huì)嘗試著用垃圾回收器來釋放足夠的空間,但是發(fā)現(xiàn)仍舊沒有足夠的空間時(shí),就會(huì)拋出這個(gè)錯(cuò)誤。為了解決這個(gè)問題,你需要清楚你的程序?qū)ο蟮男畔?,例如,你?chuàng)建了哪些對(duì)象,哪些對(duì)象占用了多少空間等等。你可以使用profiler或者堆分析器來處理 OutOfMemoryError錯(cuò)誤。”java.lang.OutOfMemoryError: Java heap space”表示堆沒有足夠的空間了,不能繼續(xù)擴(kuò)大了。”java.lang.OutOfMemoryError: PermGen space”表示permanent generation已經(jīng)裝滿了,你的程序不能再裝在類或者再分配一個(gè)字符串了。

            Java Heap dump

            Heap dump是在某一時(shí)間對(duì)Java堆內(nèi)存的快照。它對(duì)于分析堆內(nèi)存或處理內(nèi)存泄露和Java.lang.outofmemoryerror錯(cuò)誤是非常有用的。在JDK中有一些工具可以幫你獲取heap dump,也有一些堆分析工具來幫你分析heap dump。你可以用“jmap”來獲取heap dump,它幫你創(chuàng)建heap dump文件,然后,你可以用“jhat”(堆分析工具)來分析這些heap dump。

            Java堆內(nèi)存(heap memory)的十個(gè)要點(diǎn):

            1、Java堆內(nèi)存是操作系統(tǒng)分配給JVM的內(nèi)存的一部分。

            2、當(dāng)我們創(chuàng)建對(duì)象時(shí),它們存儲(chǔ)在Java堆內(nèi)存中。

            3、為了便于垃圾回收,Java堆空間分成三個(gè)區(qū)域,分別叫作New Generation, Old Generation或叫作Tenured Generation,還有Perm Space。

            4、你可以通過用JVM的命令行選項(xiàng) -Xms, -Xmx, -Xmn來調(diào)整Java堆空間的大小。不要忘了在大小后面加上”M”或者”G”來表示單位。舉個(gè)例子,你可以用 -Xmx256m來設(shè)置堆內(nèi)存最大的大小為256MB。

            5、你可以用JConsole或者 Runtime.maxMemory(), Runtime.totalMemory(), Runtime.freeMemory()來查看Java中堆內(nèi)存的大小。

            6、你可以使用命令“jmap”來獲得heap dump,用“jhat”來分析heap dump。

            7、Java堆空間不同于??臻g,棧空間是用來儲(chǔ)存調(diào)用棧和局部變量的。

            8、Java垃圾回收器是用來將死掉的對(duì)象(不再使用的對(duì)象)所占用的內(nèi)存回收回來,再釋放到Java堆空間中。

            9、當(dāng)你遇到j(luò)ava.lang.outOfMemoryError時(shí),不要緊張,有時(shí)候僅僅增加堆空間就可以了,但如果經(jīng)常出現(xiàn)的話,就要看看Java程序中是不是存在內(nèi)存泄露了。

            10、請(qǐng)使用Profiler和Heap dump分析工具來查看Java堆空間,可以查看給每個(gè)對(duì)象分配了多少內(nèi)存。

          posted @ 2012-02-20 15:16 順其自然EVO 閱讀(161) | 評(píng)論 (0)編輯 收藏

          編寫高質(zhì)量代碼:改善Java程序的151個(gè)建議(1)

           第1章 Java開發(fā)中通用的方法和準(zhǔn)則

            Thereasonablemanadaptshimselftotheworld;theunreasonableonepersistsintryingtoadapttheworldtohimself.

            明白事理的人使自己適應(yīng)世界;不明事理的人想讓世界適應(yīng)自己。

            —蕭伯納

            Java的世界豐富又多彩,但同時(shí)也布滿了荊棘陷阱,大家一不小心就可能跌入黑暗深淵,只有在了解了其通行規(guī)則后才能使自己在技術(shù)的海洋里遨游飛翔,恣意馳騁。

            “千里之行始于足下”,本章主要講述與Java語言基礎(chǔ)有關(guān)的問題及建議的解決方案,例如常量和變量的注意事項(xiàng)、如何更安全地序列化、斷言到底該如何使用等。

            建議1:不要在常量和變量中出現(xiàn)易混淆的字母

            包名全小寫,類名首字母全大寫,常量全部大寫并用下劃線分隔,變量采用駝峰命名法(CamelCase)命名等,這些都是最基本的Java編碼規(guī)范,是每個(gè)Javaer都應(yīng)熟知的規(guī)則,但是在變量的聲明中要注意不要引入容易混淆的字母。嘗試閱讀如下代碼,思考一下打印出的i等于多少:

          1. public class Client {  
          2.      public static void main(String[] args) {  
          3.            long i = 1l;  
          4.            System.out.println("i的兩倍是:" + (i+i));  
          5.      }  
          6. }

            肯定有人會(huì)說:這么簡(jiǎn)單的例子還能出錯(cuò)?運(yùn)行結(jié)果肯定是22!實(shí)踐是檢驗(yàn)真理的唯一標(biāo)準(zhǔn),將其拷貝到Eclipse中,然后Run一下看看,或許你會(huì)很奇怪,結(jié)果是2,而不是22,難道是Eclipse的顯示有問題,少了個(gè)“2”?

            因?yàn)橘x給變量i的數(shù)字就是“1”,只是后面加了長(zhǎng)整型變量的標(biāo)示字母“l”而已。別說是我挖坑讓你跳,如果有類似程序出現(xiàn)在項(xiàng)目中,當(dāng)你試圖通過閱讀代碼來理解作者的思想時(shí),此情此景就有可能會(huì)出現(xiàn)。所以,為了讓您的程序更容易理解,字母“l”(還包括大寫字母“O”)盡量不要和數(shù)字混用,以免使閱讀者的理解與程序意圖產(chǎn)生偏差。如果字母和數(shù)字必須混合使用,字母“l”務(wù)必大寫,字母“O”則增加注釋。

            注意 字母“l”作為長(zhǎng)整型標(biāo)志時(shí)務(wù)必大寫。

            建議2:莫讓常量蛻變成變量

            常量蛻變成變量?你胡扯吧,加了final和static的常量怎么可能會(huì)變呢?不可能二次賦值的呀。真的不可能嗎?看我們神奇的魔術(shù),代碼如下:

          1. public class Client {     
          2.      public static void main(String[] args) {  
          3.            System.out.println("常量會(huì)變哦:" + Const.RAND_CONST);  
          4.      }  
          5. }  
          6. /*接口常量*/  
          7. interface Const{  
          8.     //這還是常量嗎?  
          9.     public static final int RAND_CONST = new Random().nextInt();  
          10. }

            RAND_CONST是常量嗎?它的值會(huì)變嗎?絕對(duì)會(huì)變!這種常量的定義方式是極不可取的,常量就是常量,在編譯期就必須確定其值,不應(yīng)該在運(yùn)行期更改,否則程序的可讀性會(huì)非常差,甚至連作者自己都不能確定在運(yùn)行期發(fā)生了何種神奇的事情。

            甭想著使用常量會(huì)變的這個(gè)功能來實(shí)現(xiàn)序列號(hào)算法、隨機(jī)種子生成,除非這真的是項(xiàng)目中的唯一方案,否則就放棄吧,常量還是當(dāng)常量使用。

            注意:務(wù)必讓常量的值在運(yùn)行期保持不變。

          建議3:三元操作符的類型務(wù)必一致

            三元操作符是if-else的簡(jiǎn)化寫法,在項(xiàng)目中使用它的地方很多,也非常好用,但是好用又簡(jiǎn)單的東西并不表示就可以隨便用,我們來看看下面這段代碼:

          1. public class Client {  
          2.      public static void main(String[] args) {  
          3.            int i = 80;  
          4.            String s = String.valueOf(i<100?90:100);  
          5.            String s1 = String.valueOf(i<100?90:100.0);  
          6.            System.out.println("兩者是否相等:"+s.equals(s1));  
          7.      }  
          8. }

            分析一下這段程序:i是80,那它當(dāng)然小于100,兩者的返回值肯定都是90,再轉(zhuǎn)成String類型,其值也絕對(duì)相等,毋庸置疑的。恩,分析得有點(diǎn)道理,但是變量s中三元操作符的第二個(gè)操作數(shù)是100,而s1的第二個(gè)操作數(shù)是100.0,難道沒有影響嗎?不可能有影響吧,三元操作符的條件都為真了,只返回第一個(gè)值嘛,與第二個(gè)值有一毛錢的關(guān)系嗎?貌似有道理。

            果真如此嗎?我們通過結(jié)果來驗(yàn)證一下,運(yùn)行結(jié)果是:“兩者是否相等:false”,什么?不相等,Why?

            問題就出在了100和100.0這兩個(gè)數(shù)字上,在變量s中,三元操作符中的第一個(gè)操作數(shù)(90)和第二個(gè)操作數(shù)(100)都是int類型,類型相同,返回的結(jié)果也就是int類型的90,而變量s1的情況就有點(diǎn)不同了,第一個(gè)操作數(shù)是90(int類型),第二個(gè)操作數(shù)卻是100.0,而這是個(gè)浮點(diǎn)數(shù),也就是說兩個(gè)操作數(shù)的類型不一致,可三元操作符必須要返回一個(gè)數(shù)據(jù),而且類型要確定,不可能條件為真時(shí)返回int類型,條件為假時(shí)返回float類型,編譯器是不允許如此的,所以它就會(huì)進(jìn)行類型轉(zhuǎn)換了,int型轉(zhuǎn)換為浮點(diǎn)數(shù)90.0,也就是說三元操作符的返回值是浮點(diǎn)數(shù)90.0,那這當(dāng)然與整型的90不相等了。這里可能有讀者疑惑了:為什么是整型轉(zhuǎn)為浮點(diǎn),而不是浮點(diǎn)轉(zhuǎn)為整型呢?這就涉及三元操作符類型的轉(zhuǎn)換規(guī)則:

            若兩個(gè)操作數(shù)不可轉(zhuǎn)換,則不做轉(zhuǎn)換,返回值為Object類型。

            若兩個(gè)操作數(shù)是明確類型的表達(dá)式(比如變量),則按照正常的二進(jìn)制數(shù)字來轉(zhuǎn)換,int類型轉(zhuǎn)換為long類型,long類型轉(zhuǎn)換為float類型等。

            若兩個(gè)操作數(shù)中有一個(gè)是數(shù)字S,另外一個(gè)是表達(dá)式,且其類型標(biāo)示為T,那么,若數(shù)字S在T的范圍內(nèi),則轉(zhuǎn)換為T類型;若S超出了T類型的范圍,則T轉(zhuǎn)換為S類型(可以參考“建議22”,會(huì)對(duì)該問題進(jìn)行展開描述)。

            若兩個(gè)操作數(shù)都是直接量數(shù)字(Literal),則返回值類型為范圍較大者。

            知道是什么原因了,相應(yīng)的解決辦法也就有了:保證三元操作符中的兩個(gè)操作數(shù)類型一致,即可減少可能錯(cuò)誤的發(fā)生。

            建議4:避免帶有變長(zhǎng)參數(shù)的方法重載

            在項(xiàng)目和系統(tǒng)的開發(fā)中,為了提高方法的靈活度和可復(fù)用性,我們經(jīng)常要傳遞不確定數(shù)量的參數(shù)到方法中,在Java 5之前常用的設(shè)計(jì)技巧就是把形參定義成Collection類型或其子類類型,或者是數(shù)組類型,這種方法的缺點(diǎn)就是需要對(duì)空參數(shù)進(jìn)行判斷和篩選,比如實(shí)參為null值和長(zhǎng)度為0的Collection或數(shù)組。而 Java 5引入變長(zhǎng)參數(shù)(varags)就是為了更好地提高方法的復(fù)用性,讓方法的調(diào)用者可以“隨心所欲”地傳遞實(shí)參數(shù)量,當(dāng)然變長(zhǎng)參數(shù)也是要遵循一定規(guī)則的,比如變長(zhǎng)參數(shù)必須是方法中的最后一個(gè)參數(shù);一個(gè)方法不能定義多個(gè)變長(zhǎng)參數(shù)等,這些基本規(guī)則需要牢記,但是即使記住了這些規(guī)則,仍然有可能出現(xiàn)錯(cuò)誤,我們來看如下代碼:

          1. public class Client {     
          2.      //簡(jiǎn)單折扣計(jì)算  
          3.      public void calPrice(int price,int discount){  
          4.            float knockdownPrice =price * discount / 100.0F;  
          5.            System.out.println("簡(jiǎn)單折扣后的價(jià)格是:"+formateCurrency(knockdownPrice));  
          6.      }    
          7.      //復(fù)雜多折扣計(jì)算  
          8.      public void calPrice(int price,int... discounts){  
          9.            float knockdownPrice = price;  
          10.            for(int discount:discounts){  
          11.                    knockdownPriceknockdownPrice = knockdownPrice * discount / 100;  
          12.         }  
          13.         System.out.println("復(fù)雜折扣后的價(jià)格是:" +formateCurrency(knockdownPrice));  
          14.      }  
          15.      //格式化成本的貨幣形式  
          16.      private String formateCurrency(float price){  
          17.             return NumberFormat.getCurrencyInstance().format(price/100);  
          18.      }  
          19.       
          20.      public static void main(String[] args) {  
          21.            Client client = new Client();  
          22.            //499元的貨物,打75折  
          23.            client.calPrice(49900, 75);  
          24.     }  
          25. }

            這是一個(gè)計(jì)算商品價(jià)格折扣的模擬類,帶有兩個(gè)參數(shù)的calPrice方法(該方法的業(yè)務(wù)邏輯是:提供商品的原價(jià)和折扣率,即可獲得商品的折扣價(jià))是一個(gè)簡(jiǎn)單的折扣計(jì)算方法,該方法在實(shí)際項(xiàng)目中經(jīng)常會(huì)用到,這是單一的打折方法。而帶有變長(zhǎng)參數(shù)的calPrice方法則是較復(fù)雜的折扣計(jì)算方式,多種折扣的疊加運(yùn)算(模擬類是一種比較簡(jiǎn)單的實(shí)現(xiàn))在實(shí)際生活中也是經(jīng)常見到的,比如在大甩賣期間對(duì)VIP會(huì)員再度進(jìn)行打折;或者當(dāng)天是你的生日,再給你打個(gè)9折,也就是俗話說的“折上折”。

            業(yè)務(wù)邏輯清楚了,我們來仔細(xì)看看這兩個(gè)方法,它們是重載嗎?當(dāng)然是了,重載的定義是“方法名相同,參數(shù)類型或數(shù)量不同”,很明顯這兩個(gè)方法是重載。但是再仔細(xì)瞧瞧,這個(gè)重載有點(diǎn)特殊:calPrice(int price,int... discounts)的參數(shù)范疇覆蓋了calPrice(int price,int discount)的參數(shù)范疇。那問題就出來了:對(duì)于calPrice(49900,75)這樣的計(jì)算,到底該調(diào)用哪個(gè)方法來處理呢?

            我們知道Java編譯器是很聰明的,它在編譯時(shí)會(huì)根據(jù)方法簽名(Method Signature)來確定調(diào)用哪個(gè)方法,比如calPrice(499900,75,95)這個(gè)調(diào)用,很明顯75和95會(huì)被轉(zhuǎn)成一個(gè)包含兩個(gè)元素的數(shù)組,并傳遞到calPrice(int price,in.. discounts)中,因?yàn)橹挥羞@一個(gè)方法簽名符合該實(shí)參類型,這很容易理解。但是我們現(xiàn)在面對(duì)的是calPrice(49900,75)調(diào)用,這個(gè)“75”既可以被編譯成int類型的“75”,也可以被編譯成int數(shù)組“{75}”,即只包含一個(gè)元素的數(shù)組。那到底該調(diào)用哪一個(gè)方法呢?我們先運(yùn)行一下看看結(jié)果,運(yùn)行結(jié)果是:

            簡(jiǎn)單折扣后的價(jià)格是:¥374.25。

            看來是調(diào)用了第一個(gè)方法,為什么會(huì)調(diào)用第一個(gè)方法,而不是第二個(gè)變長(zhǎng)參數(shù)方法呢?因?yàn)镴ava在編譯時(shí),首先會(huì)根據(jù)實(shí)參的數(shù)量和類型(這里是2個(gè)實(shí)參,都為int類型,注意沒有轉(zhuǎn)成int數(shù)組)來進(jìn)行處理,也就是查找到calPrice(int price,int discount)方法,而且確認(rèn)它是否符合方法簽名條件?,F(xiàn)在的問題是編譯器為什么會(huì)首先根據(jù)2個(gè)int類型的實(shí)參而不是1個(gè)int類型、1個(gè)int數(shù)組類型的實(shí)參來查找方法呢?這是個(gè)好問題,也非常好回答:因?yàn)閕nt是一個(gè)原生數(shù)據(jù)類型,而數(shù)組本身是一個(gè)對(duì)象,編譯器想要“偷懶”,于是它會(huì)從最簡(jiǎn)單的開始“猜想”,只要符合編譯條件的即可通過,于是就出現(xiàn)了此問題。

            問題是闡述清楚了,為了讓我們的程序能被“人類”看懂,還是慎重考慮變長(zhǎng)參數(shù)的方法重載吧,否則讓人傷腦筋不說,說不定哪天就陷入這類小陷阱里了。

            建議5:別讓null值和空值威脅到變長(zhǎng)方法

            上一建議講解了變長(zhǎng)參數(shù)的重載問題,本建議還會(huì)繼續(xù)討論變長(zhǎng)參數(shù)的重載問題。上一建議的例子是變長(zhǎng)參數(shù)的范圍覆蓋了非變長(zhǎng)參數(shù)的范圍,這次我們從兩個(gè)都是變長(zhǎng)參數(shù)的方法說起,代碼如下:

          1. public class Client {  
          2.      public void methodA(String str,Integer... is){       
          3.      }  
          4.       
          5.      public void methodA(String str,String... strs){          
          6.      }  
          7.       
          8.      public static void main(String[] args) {  
          9.            Client client = new Client();  
          10.            client.methodA("China", 0);  
          11.            client.methodA("China", "People");  
          12.            client.methodA("China");  
          13.            client.methodA("China",null);  
          14.      }  
          15. }

            兩個(gè)methodA都進(jìn)行了重載,現(xiàn)在的問題是:上面的代碼編譯通不過,問題出在什么地方?看似很簡(jiǎn)單哦。

            有兩處編譯通不過:client.methodA("China")和client.methodA("China",null),估計(jì)你已經(jīng)猜到了,兩處的提示是相同的:方法模糊不清,編譯器不知道調(diào)用哪一個(gè)方法,但這兩處代碼反映的代碼味道可是不同的。

            對(duì)于methodA("China")方法,根據(jù)實(shí)參“China”(String類型),兩個(gè)方法都符合形參格式,編譯器不知道該調(diào)用哪個(gè)方法,于是報(bào)錯(cuò)。我們來思考這個(gè)問題:Client類是一個(gè)復(fù)雜的商業(yè)邏輯,提供了兩個(gè)重載方法,從其他模塊調(diào)用(系統(tǒng)內(nèi)本地調(diào)用或系統(tǒng)外遠(yuǎn)程調(diào)用)時(shí),調(diào)用者根據(jù)變長(zhǎng)參數(shù)的規(guī)范調(diào)用, 傳入變長(zhǎng)參數(shù)的實(shí)參數(shù)量可以是N個(gè)(N>=0),那當(dāng)然可以寫成client.methodA("china")方法啊!完全符合規(guī)范,但是這卻讓編譯器和調(diào)用者都很郁悶,程序符合規(guī)則卻不能運(yùn)行,如此問題,誰之責(zé)任呢?是Client類的設(shè)計(jì)者,他違反了KISS原則(Keep It Simple, Stupid,即懶人原則),按照此規(guī)則設(shè)計(jì)的方法應(yīng)該很容易調(diào)用,可是現(xiàn)在在遵循規(guī)范的情況下,程序竟然出錯(cuò)了,這對(duì)設(shè)計(jì)者和開發(fā)者而言都是應(yīng)該嚴(yán)禁出現(xiàn)的。

            對(duì)于client.methodA("china",null)方法,直接量null是沒有類型的,雖然兩個(gè)methodA方法都符合調(diào)用請(qǐng)求,但不知道調(diào)用哪一個(gè),于是報(bào)錯(cuò)了。我們來體會(huì)一下它的壞味道:除了不符合上面的懶人原則外,這里還有一個(gè)非常不好的編碼習(xí)慣,即調(diào)用者隱藏了實(shí)參類型,這是非常危險(xiǎn)的,不僅僅調(diào)用者需要“猜測(cè)”該調(diào)用哪個(gè)方法,而且被調(diào)用者也可能產(chǎn)生內(nèi)部邏輯混亂的情況。對(duì)于本例來說應(yīng)該做如下修改:

          1. public static void main(String[] args) {  
          2.      Client client = new Client();  
          3.      String[] strs = null;  
          4.      client.methodA("China",strs);  
          5. }

            也就是說讓編譯器知道這個(gè)null值是String類型的,編譯即可順利通過,也就減少了錯(cuò)誤的發(fā)生。

          posted @ 2012-02-17 16:46 順其自然EVO 閱讀(393) | 評(píng)論 (0)編輯 收藏

          Java Socket重要參數(shù)講解

          Java Socket的api可能很多人會(huì)用,但是Java Socket的參數(shù)可能很多人都不知道用來干嘛的,甚至都不知道有這些參數(shù)。

            backlog

            用于ServerSocket,配置ServerSocket的最大客戶端等待隊(duì)列。等待隊(duì)列的意思,先看下面代碼

          1. public class Main {  
          2.     public static void main(String[] args) throws Exception {  
          3.         int port = 8999;  
          4.         int backlog = 2;  
          5.         ServerSocket serverSocket = new ServerSocket(port, backlog);  
          6.         Socket clientSock = serverSocket.accept();  
          7.         System.out.println("revcive from " + clientSock.getPort());  
          8.         while (true) {  
          9.             byte buf[] = new byte[1024];  
          10.             int len = clientSock.getInputStream().read(buf);  
          11.             System.out.println(new String(buf, 0, len));  
          12.         }  
          13.     }  
          14. }

            這段測(cè)試代碼在第一次處理一個(gè)客戶端時(shí),就不會(huì)處理第二個(gè)客戶端,所以除了第一個(gè)客戶端,其他客戶端就是等待隊(duì)列了。所以這個(gè)服務(wù)器最多可以同時(shí)連接3個(gè)客戶端,其中2個(gè)等待隊(duì)列。大家可以telnet localhost 8999測(cè)試下。

            這個(gè)參數(shù)設(shè)置為-1表示無限制,默認(rèn)是50個(gè)最大等待隊(duì)列,如果設(shè)置無限制,那么你要小心了,如果你服務(wù)器無法處理那么多連接,那么當(dāng)很多客戶端連到你的服務(wù)器時(shí),每一個(gè)TCP連接都會(huì)占用服務(wù)器的內(nèi)存,最后會(huì)讓服務(wù)器崩潰的。

            另外,就算你設(shè)置了backlog為10,如果你的代碼中是一直Socket clientSock = serverSocket.accept(),假設(shè)我們的機(jī)器最多可以同時(shí)處理100個(gè)請(qǐng)求,總共有100個(gè)線程在運(yùn)行,然后你把在100個(gè)線程的線程池處理clientSock,不能處理的clientSock就排隊(duì),最后clientSock越來越多,也意味著TCP連接越來越多,也意味著我們的服務(wù)器的內(nèi)存使用越來越高(客戶端連接進(jìn)程,肯定會(huì)發(fā)送數(shù)據(jù)過來,數(shù)據(jù)會(huì)保存到服務(wù)器端的TCP接收緩存區(qū)),最后服務(wù)器就宕機(jī)了。所以如果你不能處理那么多請(qǐng)求,請(qǐng)不要循環(huán)無限制地調(diào)用serverSocket.accept(),否則backlog也無法生效。如果真的請(qǐng)求過多,只會(huì)讓你的服務(wù)器宕機(jī)(相信很多人都是這么寫,要注意點(diǎn))

            TcpNoDelay

            禁用納格算法,將數(shù)據(jù)立即發(fā)送出去。納格算法是以減少封包傳送量來增進(jìn)TCP/IP網(wǎng)絡(luò)的效能,當(dāng)我們調(diào)用下面代碼,如:

          1. Socket socket = new Socket();    
          2. socket.connect(new InetSocketAddress(host, 8000));    
          3. InputStream in = socket.getInputStream();    
          4. OutputStream out = socket.getOutputStream();    
          5. String head = "hello ";    
          6. String body = "world\r\n";    
          7. out.write(head.getBytes());    
          8. out.write(body.getBytes());

            我們發(fā)送了hello,當(dāng)hello沒有收到ack確認(rèn)(TCP是可靠連接,發(fā)送的每一個(gè)數(shù)據(jù)都要收到對(duì)方的一個(gè)ack確認(rèn),否則就要重發(fā))的時(shí)候,根據(jù)納格算法,world不會(huì)立馬發(fā)送,會(huì)等待,要么等到ack確認(rèn)(最多等100ms對(duì)方會(huì)發(fā)過來的),要么等到TCP緩沖區(qū)內(nèi)容>=MSS,很明顯這里沒有機(jī)會(huì),我們寫了world后再也沒有寫數(shù)據(jù)了,所以只能等到hello的ack我們才會(huì)發(fā)送world,除非我們禁用納格算法,數(shù)據(jù)就會(huì)立即發(fā)送了。

            SoLinger

            當(dāng)我們調(diào)用socket.close()返回時(shí),socket已經(jīng)write的數(shù)據(jù)未必已經(jīng)發(fā)送到對(duì)方了,例如

          1. Socket socket = new Socket();    
          2. socket.connect(new InetSocketAddress(host, 8000));    
          3. InputStream in = socket.getInputStream();    
          4. OutputStream out = socket.getOutputStream();    
          5. String head = "hello ";    
          6. String body = "world\r\n";    
          7. out.write(head.getBytes());    
          8. out.write(body.getBytes());   
          9. socket.close();



           這里調(diào)用了socket.close()返回時(shí),hello和world未必已經(jīng)成功發(fā)送到對(duì)方了,如果我們?cè)O(shè)置了linger而不小于0,如:

          1. bool on = true;  
          2. int linger = 100;  
          3. ....  
          4. socket.setSoLinger(boolean on, int linger)  
          5. ......  
          6. socket.close();

            那么close會(huì)等到發(fā)送的數(shù)據(jù)已經(jīng)確認(rèn)了才返回。但是如果對(duì)方宕機(jī),超時(shí),那么會(huì)根據(jù)linger設(shè)定的時(shí)間返回。

            UrgentData和OOBInline

            TCP的緊急指針,一般都不建議使用,而且不同的TCP/IP實(shí)現(xiàn),也不同,一般說如果你有緊急數(shù)據(jù)寧愿再建立一個(gè)新的TCP/IP連接發(fā)送數(shù)據(jù),讓對(duì)方緊急處理。

            所以這兩個(gè)參數(shù),你們可以忽略吧,想知道更多的,自己查下資料。

            SoTimeout

            設(shè)置socket調(diào)用InputStream讀數(shù)據(jù)的超時(shí)時(shí)間,以毫秒為單位,如果超過這個(gè)時(shí)候,會(huì)拋出java.net.SocketTimeoutException。

            KeepAlive

            keepalive不是說TCP的常連接,當(dāng)我們作為服務(wù)端,一個(gè)客戶端連接上來,如果設(shè)置了keeplive為true,當(dāng)對(duì)方?jīng)]有發(fā)送任何數(shù)據(jù)過來,超過一個(gè)時(shí)間(看系統(tǒng)內(nèi)核參數(shù)配置),那么我們這邊會(huì)發(fā)送一個(gè)ack探測(cè)包發(fā)到對(duì)方,探測(cè)雙方的TCP/IP連接是否有效(對(duì)方可能斷點(diǎn),斷網(wǎng)),在Linux好像這個(gè)時(shí)間是75秒。如果不設(shè)置,那么客戶端宕機(jī)時(shí),服務(wù)器永遠(yuǎn)也不知道客戶端宕機(jī)了,仍然保存這個(gè)失效的連接。

            SendBufferSize和ReceiveBufferSize

            TCP發(fā)送緩存區(qū)和接收緩存區(qū),默認(rèn)是8192,一般情況下足夠了,而且就算你增加了發(fā)送緩存區(qū),對(duì)方?jīng)]有增加它對(duì)應(yīng)的接收緩沖,那么在TCP三握手時(shí),最后確定的最大發(fā)送窗口還是雙方最小的那個(gè)緩沖區(qū),就算你無視,發(fā)了更多的數(shù)據(jù),那么多出來的數(shù)據(jù)也會(huì)被丟棄。除非雙方都協(xié)商好。

            以上的參數(shù)都是比較重要的Java Socket參數(shù)了,其他就不另外說明了。

          posted @ 2012-02-16 11:39 順其自然EVO 閱讀(203) | 評(píng)論 (0)編輯 收藏

          Java反射機(jī)制剖析:簡(jiǎn)單談?wù)剟?dòng)態(tài)代理

               摘要: 通過《Java反射機(jī)制剖析:定義和API》和《Java反射機(jī)制剖析:功能以及舉例》的學(xué)習(xí),已經(jīng)對(duì)反射有了一定的了解,這一篇通過動(dòng)態(tài)代理的例子來進(jìn)一步學(xué)習(xí)反射機(jī)制?! ?、代理模式  代理模式就是為其他對(duì)象提供一種代理來控制對(duì)這個(gè)對(duì)象的訪問。其實(shí)代理模式是在訪問的對(duì)象時(shí)引入一定程度的間接性,這種間接性可以附加多種用途?! ∷奶卣魇谴眍惻c委托類有同樣的接口,代理類主要負(fù)責(zé)為委托類預(yù)處理消息、過濾消...  閱讀全文

          posted @ 2012-02-13 13:35 順其自然EVO 閱讀(182) | 評(píng)論 (0)編輯 收藏

          Java反射機(jī)制剖析:定義和API

            1、什么是Java反射機(jī)制

            Java的反射機(jī)制是在程序運(yùn)行時(shí),能夠完全知道任何一個(gè)類,及其它的屬性和方法,并且能夠任意調(diào)用一個(gè)對(duì)象的屬性和方法。這種運(yùn)行時(shí)的動(dòng)態(tài)獲取就是Java的反射機(jī)制。其實(shí)這也是Java是動(dòng)態(tài)語言的一個(gè)象征。

            用一句話來概括反射就是加載一個(gè)運(yùn)行時(shí)才知道的類以及它的完整內(nèi)部結(jié)構(gòu)。

            2、為什么要有Java反射機(jī)制

            我們?yōu)槭裁匆肑ava的反射機(jī)制呢?

            我認(rèn)為有兩種:

            第一種:反射的目的就是為了擴(kuò)展未知的應(yīng)用。比如你寫了一個(gè)程序,這個(gè)程序定義了一些接口,只要實(shí)現(xiàn)了這些接口的dll都可以作為插件來插入到這個(gè)程序中。那么怎么實(shí)現(xiàn)呢?就可以通過反射來實(shí)現(xiàn)。就是把dll加載進(jìn)內(nèi)存,然后通過反射的方式來調(diào)用dll中的方法。

            第二種:在編碼階段不知道那個(gè)類名,要在運(yùn)行期從配置文件讀取類名, 這時(shí)候就沒有辦法硬編碼new ClassName(),而必須用到反射才能創(chuàng)建這個(gè)對(duì)象。

            一個(gè)生活中??吹降睦佑兄诶斫馕覀?yōu)槭裁匆肑ava的反射機(jī)制:你進(jìn)了一家飯店,你不知道他們都有那些菜,要多少錢。那么你第一件事情是干啥“服務(wù)員拿個(gè)菜單過來”,然后指著菜單說“我要這個(gè),我要那個(gè)”。

            3、一起來看ReflectionAPI

            在生活中,我們使用一個(gè)未知的東西的時(shí)候總會(huì)用幫助來解決我們的使用問題,電視機(jī)有幫助,電腦有幫助,幾乎所有的事物都攜帶著它的一本幫助,Java的反射機(jī)制也不例外。

            在JDK中有Reflection API的幫助,它主要說明了什么是Java反射機(jī)制,這種反射機(jī)制提供了什么樣的屬性和方法,進(jìn)一步我們能夠知道能夠通過它完成什么樣的工作。

            下面咱就一起看看這部分的API。這些接口和類都位于lang包里。

            如圖:

            接口:

            類:


            簡(jiǎn)單介紹一些類和接口的用法:

            1)Member成員是一種接口,反映有關(guān)單個(gè)成員(字段或方法)或構(gòu)造方法的標(biāo)識(shí)信息

            2)InvocationHandler是代理實(shí)例的調(diào)用處理程序?qū)崿F(xiàn)的接口(這個(gè)接口的具體用法等到j(luò)ava反射機(jī)制剖析4著重介紹)

            3)Method提供一個(gè)類的方法的信息以及訪問類的方法的接口。

            示例:

          1. import java.lang.reflect.Method;  
          2.  
          3. public class TestMethod {  
          4.  
          5.     /**  
          6.      * @param args  
          7.      * @throws Exception   
          8.      */ 
          9.     public static void main(String[] args) throws Exception {  
          10.         // TODO Auto-generated method stub  
          11.         Class classType=Class.forName(args[0]);  
          12.         Method methods[]=classType.getDeclaredMethods();  
          13.         for(int i=0;i<methods.length;i++){  
          14.             System.out.println(methods[i].toString());  
          15.         }  
          16.     }  
          17.  
          18. }

            4)Filed提供一個(gè)類的域的信息以及訪問類的域的接口。

            示例:

          1. import java.lang.reflect.Field;  
          2.  
          3.  
          4. public class TestField {  
          5.  
          6.     /**  
          7.      * @param args  
          8.      * @throws Exception   
          9.      */ 
          10.     public static void main(String[] args) throws Exception {  
          11.         // TODO Auto-generated method stub  
          12.         Class classType=Class.forName(args[0]);  
          13.         Field[] fields = classType.getFields();  
          14.         for(int i=0;i<fields.length;i++){  
          15.         System.out.println(fields[i].toString());  
          16.         }  
          17.     }  
          18.  
          19. }

            5)Array類提供了動(dòng)態(tài)創(chuàng)建和訪問Java數(shù)組的方法。

            示例:

          1. import java.lang.reflect.Array;  
          2.  
          3.  
          4. public class TestArray {  
          5.  
          6.     public TestArray(){  
          7.           
          8.     }  
          9.     /**  
          10.      * @param args  
          11.      * @throws Exception   
          12.      */ 
          13.     public static void main(String[] args) throws Exception {  
          14.           
          15.         Class<?> classType = Class.forName("java.lang.String");  
          16.           
          17.         Object array = Array.newInstance(classType, 10);  
          18.           
          19.         Array.set(array, 5"hello");  
          20.           
          21.         String s = (String)Array.get(array, 5);  
          22.         System.out.println(s);  
          23.           
          24.     }  
          25.  
          26. }

            6)Proxy提供動(dòng)態(tài)地生成代理類和類實(shí)例的靜態(tài)方法(這個(gè)方法在java放射機(jī)制剖析4著重介紹)。

            其余的類和接口的使用方法詳見API


          posted @ 2012-02-09 12:00 順其自然EVO 閱讀(174) | 評(píng)論 (0)編輯 收藏

          測(cè)試網(wǎng)站

          http://www.jointest.org/forum.php?mod=viewthread&tid=9&extra=page%3D1

          posted @ 2012-02-09 09:36 順其自然EVO 閱讀(143) | 評(píng)論 (0)編輯 收藏

          僅列出標(biāo)題
          共394頁: First 上一頁 341 342 343 344 345 346 347 348 349 下一頁 Last 
          <2025年7月>
          293012345
          6789101112
          13141516171819
          20212223242526
          272829303112
          3456789

          導(dǎo)航

          統(tǒng)計(jì)

          常用鏈接

          留言簿(55)

          隨筆分類

          隨筆檔案

          文章分類

          文章檔案

          搜索

          最新評(píng)論

          閱讀排行榜

          評(píng)論排行榜

          主站蜘蛛池模板: 黄冈市| 乌兰浩特市| 武城县| 宽甸| 乌审旗| 樟树市| 嘉黎县| 丹阳市| 巩留县| 石台县| 水富县| 蓝田县| 普洱| 蒙自县| 大城县| 博野县| 和林格尔县| 蒙阴县| 西乌珠穆沁旗| 安化县| 嫩江县| 额尔古纳市| 丽水市| 且末县| 江川县| 青州市| 巩留县| 龙南县| 拉萨市| 分宜县| 乌什县| 枣阳市| 克什克腾旗| 绵阳市| 扎鲁特旗| 西华县| 积石山| 曲周县| 沂水县| 红安县| 怀化市|