紀念我曾經的 Java 知識

貓與麥子發表於2016-10-16

目前在搞 Node.js,曾經的 JAVA 知識忘了好多,為此整理了下,感嘆下工業語言還是有相當的優勢的。

目錄

  • 異常
  • 註解
  • 安全性
  • 類載入
  • 關鍵字
  • 初始化
  • 多執行緒
  • 執行緒池
  • 記憶體模型

Java所有的流類位於java.io包中,都分別繼承字以下四種抽象流型別。

Type 位元組流 字元流
輸入流 InputStream Reader
輸出流 OutputStream Writer

繼承自InputStream/OutputStream的流都是用於向程式中輸入/輸出資料,且資料的單位都是位元組(byte=8bit)。

繼承自Reader/Writer的流都是用於向程式中輸入/輸出資料,且資料的單位都是字元(2byte=16bit)。

異常

Java的異常(包括Exception和Error)分為:

可查的異常(checked exceptions)

除了RuntimeException及其子類以外,其他的Exception類及其子類都屬於可查異常。這種異常的特點是Java編譯器會檢查它,也就是說,當程式中可能出現這類異常,要麼用try-catch語句捕獲它,要麼用throws子句宣告丟擲它,否則編譯不會通過。

不可查的異常(unchecked exceptions)

包括執行時異常(RuntimeException與其子類)和錯誤(Error)。

執行時異常和非執行時異常:

RuntimeException

NullPointerException(空指標異常)、IndexOutOfBoundsException(下標越界異常)等這些異常是不檢查異常,程式中可以選擇捕獲處理,也可以不處理。這些異常一般是由程式邏輯錯誤引起的,程式應該從邏輯角度儘可能避免這類異常的發生。執行時異常的特點是Java編譯器不會檢查它,也就是說,當程式中可能出現這類異常,即使沒有用try-catch語句捕獲它,也沒有用throws子句宣告丟擲它,也會編譯通過。

RuntimeException以外的Exception

從程式語法角度講是必須進行處理的異常,如果不處理,程式就不能編譯通過。如IOException、SQLException等以及使用者自定義的Exception異常,一般情況下不自定義檢查異常。

註解

Java SE5內建了三種標準註解:

 @Override,表示當前的方法定義將覆蓋超類中的方法。

 @Deprecated,使用了註解為它的元素編譯器將發出警告,因為註解@Deprecated是不贊成使用的程式碼,被棄用的程式碼。

 @SuppressWarnings,關閉不當編譯器警告資訊。

Java還提供了4中註解,專門負責新註解的建立:

@Target:

表示該註解可以用於什麼地方,可能的ElementType引數有:
CONSTRUCTOR:構造器的宣告
FIELD:域宣告(包括enum例項)
LOCAL_VARIABLE:區域性變數宣告
METHOD:方法宣告
PACKAGE:包宣告
PARAMETER:引數宣告
TYPE:類、介面(包括註解型別)或enum宣告

@Retention

表示需要在什麼級別儲存該註解資訊。可選的RetentionPolicy引數包括:
SOURCE:註解將被編譯器丟棄
CLASS:註解在class檔案中可用,但會被VM丟棄
RUNTIME:VM將在執行期間保留註解,因此可以通過反射機制讀取註解的資訊

@Document

將註解包含在Javadoc中

@Inherited

允許子類繼承父類中的註解

Example

定義註解:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface UseCase {
    public String id();
    public String description() default "no description";
}

使用註解:

public class PasswordUtils {
     @UseCase(id = 47, description = "Passwords must contain at least one numeric")
     public boolean validatePassword(String password) {
         return (password.matches("\\w*\\d\\w*"));
     }

     @UseCase(id = 48)
     public String encryptPassword(String password) {
         return new StringBuilder(password).reverse().toString();
     }
 }

解析註解:

public static void main(String[] args) {
     List<Integer> useCases = new ArrayList<Integer>();
     Collections.addAll(useCases, 47, 48, 49, 50);
     trackUseCases(useCases, PasswordUtils.class);
 }

 public static void trackUseCases(List<Integer> useCases, Class<?> cl) {
     for (Method m : cl.getDeclaredMethods()) {
         UseCase uc = m.getAnnotation(UseCase.class);
         if (uc != null) {
             System.out.println("Found Use Case:" + uc.id() + " "
                         + uc.description());
             useCases.remove(new Integer(uc.id()));
         }
     }
     for (int i : useCases) {
         System.out.println("Warning: Missing use case-" + i);
     }
 }
 // Found Use Case:47 Passwords must contain at least one numeric
 // Found Use Case:48 no description
 // Warning: Missing use case-49
 // Warning: Missing use case-50

安全性

  1. 嚴格遵循物件導向的規範。這樣封裝了資料細節,只提供介面給使用者。增加了資料級的安全性。
  2. 無指標運算。java中的操作,除了基本型別都是引用的操作。引用是不能進行增減運算,不能被直接賦予記憶體地址的,從而增加了記憶體級的安全性。
  3. 陣列邊界檢查。這樣就不會出現C/C++中的快取溢位等安全漏洞。
  4. 強制型別轉換。非同型別的物件之間不能進行轉換,否則會丟擲ClassCastException
  5. 語言對執行緒安全的支援。java從語言級支援執行緒。從而從語法和語言本身做了很多對執行緒的控制和支援。
  6. 垃圾回收。
  7. Exception。

類載入

原理

ClassLoader使用的是雙親委託模型來搜尋類的,每個ClassLoader例項都有一個父類載入器的引用(不是繼承的關係,是一個包含的關係),虛擬機器內建的類載入器(Bootstrap ClassLoader)本身沒有父類載入器,但可以用作其它ClassLoader例項的的父類載入器。

當一個ClassLoader例項需要載入某個類時,它會試圖親自搜尋某個類之前,先把這個任務委託給它的父類載入器,這個過程是由上至下依次檢查的,首先由最頂層的類載入器Bootstrap ClassLoader試圖載入,如果沒載入到,則把任務轉交給Extension ClassLoader試圖載入,如果也沒載入到,則轉交給App ClassLoader 進行載入,如果它也沒有載入得到的話,則返回給委託的發起者,由它到指定的檔案系統或網路等URL中載入該類。

如果它們都沒有載入到這個類時,則丟擲ClassNotFoundException異常。否則將這個找到的類生成一個類的定義,並將它載入到記憶體當中,最後返回這個類在記憶體中的Class例項物件。

JVM在判定兩個class是否相同時,不僅要判斷兩個類名是否相同,而且要判斷是否由同一個類載入器例項載入的。只有兩者同時滿足的情況下,JVM才認為這兩個class是相同的。

載入器

BootStrap ClassLoader

啟動類載入器,是Java類載入層次中最頂層的類載入器,負責載入JDK中的核心類庫,如:rt.jar、resources.jar、charsets.jar等。

   URL[] urls = sun.misc.Launcher.getBootstrapClassPath().getURLs();
   for (int i = 0; i < urls.length; i++) {
       System.out.println(urls[i].toExternalForm());  
   }
   // 也可以通過sun.boot.class.path獲取
   System.out.println(System.getProperty("sun.boot.class.path"))

Extension ClassLoader

擴充套件類載入器,負責載入Java的擴充套件類庫,預設載入JAVA_HOME/jre/lib/ext/目下的所有jar。

App ClassLoader

系統類載入器,負責載入應用程式classpath目錄下的所有jar和class檔案

注意:

除了Java預設提供的三個ClassLoader之外,使用者還可以根據需要定義自已的ClassLoader,而這些自定義的ClassLoader都必須繼承自java.lang.ClassLoader類,也包括Java提供的另外二個ClassLoader(Extension ClassLoader和App ClassLoader)在內。Bootstrap ClassLoader不繼承自ClassLoader,因為它不是一個普通的Java類,底層由C++編寫,已嵌入到了JVM核心當中,當JVM啟動後,Bootstrap ClassLoader也隨著啟動,負責載入完核心類庫後,並構造Extension ClassLoader和App ClassLoader類載入器。

關鍵字

strictfp(strict float point)

strictfp 關鍵字可應用於類、介面或方法。使用strictfp關鍵字宣告一個方法時,該方法中所有的float和double表示式都嚴格遵守FP-strict的限制,符合IEEE-754規範。當對一個類或介面使用strictfp關鍵字時,該類中的所有程式碼,包括巢狀型別中的初始設定值和程式碼,都將嚴格地進行計算。嚴格約束意味著所有表示式的結果都必須是 IEEE 754演算法對運算元預期的結果,以單精度和雙精度格式表示。

如果你想讓你的浮點運算更加精確,而且不會因為不同的硬體平臺所執行的結果不一致的話,可以用關鍵字strictfp。

transiant

變數修飾符,如果用transient宣告一個例項變數,當物件儲存時,它的值不需要維持。

Volatile

作為指令關鍵字,確保本條指令不會因編譯器的優化而省略,修飾變數,保證變數每次都是從記憶體中重新讀取。

final

  1. 修飾基礎資料成員(as const)
  2. 修飾類或物件的引用
  3. 修飾方法的final(cannot overwrite)
  4. 修飾類或者引數

初始化

父靜態->子靜態
父變數->父初始化區->父構造
子變數->子初始化區->子構造

多執行緒

java多執行緒實現方式主要有三種:繼承Thread類、實現Runnable介面、使用ExecutorService、Callable、Future實現有返回結果的多執行緒。其中前兩種方式執行緒執行完後都沒有返回值,只有最後一種是帶返回值的。

執行緒池

concurrent下的執行緒池:

名稱 功能
ExecutorService 真正的執行緒池介面
ScheduledExecutorService 能和Timer/TimerTask類似,解決那些需要任務重複執行的問題
ThreadPoolExecutor ExecutorService的預設實現
ScheduledThreadPoolExecutor 繼承ThreadPoolExecutor的ScheduledExecutorService介面實現,週期性任務排程的類實現

Executors

newSingleThreadExecutor

建立一個單執行緒的執行緒池。這個執行緒池只有一個執行緒在工作,也就是相當於單執行緒序列執行所有任務。如果這個唯一的執行緒因為異常結束,那麼會有一個新的執行緒來替代它。此執行緒池保證所有任務的執行順序按照任務的提交順序執行。

newFixedThreadPool

建立固定大小的執行緒池。每次提交一個任務就建立一個執行緒,直到執行緒達到執行緒池的最大大小。執行緒池的大小一旦達到最大值就會保持不變,如果某個執行緒因為執行異常而結束,那麼執行緒池會補充一個新執行緒。

newCachedThreadPool

建立一個可快取的執行緒池。如果執行緒池的大小超過了處理任務所需要的執行緒,那麼就會回收部分空閒(60秒不執行任務)的執行緒,當任務數增加時,此執行緒池又可以智慧的新增新執行緒來處理任務。此執行緒池不會對執行緒池大小做限制,執行緒池大小完全依賴於作業系統(或者說JVM)能夠建立的最大執行緒大小。

newScheduledThreadPool

建立一個大小無限的執行緒池。此執行緒池支援定時以及週期性執行任務的需求。

記憶體模型

Java記憶體模型規定,對於多個執行緒共享的變數,儲存在主記憶體當中,每個執行緒都有自己獨立的工作記憶體,執行緒只能訪問自己的工作記憶體,不可以訪問其它執行緒的 工作記憶體。工作記憶體中儲存了主記憶體共享變數的副本,執行緒要操作這些共享變數,只能通過操作工作記憶體中的副本來實現,操作完畢之後再同步回到主記憶體當中。

如何保證多個執行緒操作主記憶體的資料完整性是一個難題,Java記憶體模型也規定了工作記憶體與主記憶體之間互動的協議,首先是定義了8種原子操作:

  • lock:將主記憶體中的變數鎖定,為一個執行緒所獨佔
  • unclock:將lock加的鎖定解除,此時其它的執行緒可以有機會訪問此變數
  • read:將主記憶體中的變數值讀到工作記憶體當中
  • load:將read讀取的值儲存到工作記憶體中的變數副本中。
  • use:將值傳遞給執行緒的程式碼執行引擎
  • assign:將執行引擎處理返回的值重新賦值給變數副本
  • store:將變數副本的值儲存到主記憶體中。
  • write:將store儲存的值寫入到主記憶體的共享變數當中。

記憶體組成

堆(Heap)

執行時資料區域,所有類例項和陣列的記憶體均從此處分配。Java虛擬機器啟動時建立。物件的堆記憶體由稱為垃圾回收器 的自動記憶體管理系統回收。

  • News Generation(Young Generation即圖中的Eden + From Space + To Space)
    • Eden 存放新生的物件
    • Survivor Space 兩個 存放每次垃圾回收後存活的物件
  • Old Generation(Tenured Generation 即圖中的Old Space) 主要存放應用程式中生命週期長的存活物件

非堆記憶體

JVM具有一個由所有執行緒共享的方法區。方法區屬於非堆記憶體。它儲存每個類結構,如執行時常數池、欄位和方法資料,以及方法和構造方法的程式碼。它是在Java虛擬機器啟動時建立的。除了方法區外,Java虛擬機器實現可能需要用於內部處理或優化的記憶體,這種記憶體也是非堆記憶體。例如,JIT編譯器需要記憶體來儲存從Java虛擬機器程式碼轉換而來的本機程式碼,從而獲得高效能。

  • Permanent Generation  (圖中的Permanent Space)存放JVM自己的反射物件,比如類物件和方法物件
  • native heap

相關文章