不輟集

JVM

JVM(Java Virtual Machine),即 Java 虛擬機,是操作系統上的一個程序,用於編譯、運行Java程序,使得 Java程序可以跨平台。關於 JVM 我們著重在內存區域、類的加載、對象的創建和內存管理四個部分。

內存區域分爲堆、方法區、程序計數器、虛擬機棧和本地方法棧。其中堆和方法區是線程共享的,程序計數器、虛擬機棧和本地方法棧則是線程私有的。堆是一大塊內存,幾乎所有的對象實例都在這裡分配;方法區在JVM 規範中是堆的一部分,不同的 JVM 可以有不同的實現,就 HotSpot VM 而言,在 JDK1.8 之前使用永久代實現方法區,在JDK1.8及之後使用直接內存上的元空間實現;程序計數器存放下一條指令的地址;虛擬機棧的每一個棧幀保存著方法的局部變量表、操作數棧、動態連接和方法返回地址;本地方法棧類似虛擬機棧,不過是調用 native 方法,在 HotSpot VM中虛擬機棧和本地方法棧合而爲一。

類的生命週期分爲加載、連接、初始化、使用和卸載四個過程,其中:

  1. 加載:將 .class 文件以二進制字節流方式讀入虛擬機,並在方法區給靜態變量分配空間,在堆中生成 Class 對象作爲訪問靜態變量的入口。
  2. 連接:分爲驗證、準備和解析三個階段,驗證階段驗證字節碼文件的合規性,準備階段將類變量賦予初始零值,解析階段將常量池中的符號引用轉爲直接引用。
  3. 初始化:執行 <clinit> 方法。

對象的創建過程依次是類加載檢查、分配內存、初始化零值、設置對象頭和執行<init> 方法。

  1. 類加載檢查:檢查類是否加載完畢。
  2. 分配內存:對象實例一般會分配在堆中,根據採用的垃圾回收器會選擇指針碰撞或空閒類表方式分配。JDK1.7之後啟用逃逸分析可以將未逃逸的對象分配到棧中。
  3. 初始化零值:給對象的成員變量設置初始的零值。
  4. 設置對象頭:對象頭包括所屬的類、對象哈希碼、GC分代年齡和鎖信息。
  5. 執行 <init> 方法。

Java 是自動內存管理的,內存的分配和回收由JVM進行控制。通常使用分代內存管理,分新生代、老年代和永久代(JDK1.8及之後無永久代),新對象優先在新生代 Eden 區分配,大對象直接分配到老年代,持續存活的對象也會被轉移到老年代。

內存回收涉及垃圾的判定、垃圾收集算法和垃圾收集器。

判定垃圾通常有引用計數法和可達性分析算法。

垃圾收集(GC)通常分部分收集(Partial GC)和整堆收集(Full GC),部分收集分新生代收集(Minor GC / Young GC)、老年代收集(Major GC / Old GC)和混合收集(Mixed GC)

垃圾收集算法通常有標記-清除算法、複製算法、標記-整理算法和分代收集算法。

垃圾收集器中 ParNew 最早採用並行收集,CMS 最早採用並發收集。JDK1.8 中默認使用 Parallel Scavenge(新生代) + Parallel Old(老年代) 收集器,JDK9之後默認使用 G1 收集器。

接續讀落

甲子話分類辭表(2020.12)

!!! 注意:本辭表已收錄在典合網中進行維護,請轉閱:https://dicthub.cn/dicts/kahtsi-ue !!!

序言(su⁶ ngiang⁵)

甲子鎮處在陸豐市,與惠來縣交界,語言文化上偏惠來(其實五百年前與惠來交界處同屬海豐縣)。甲子話是三甲地區(甲子、甲西、甲東三鎮)通行的語言,是甲子地方文化的重要載體之一。在學術上,甲子話被歸入粵東閩南語潮汕話片。

甲子話保留了好㩼中古乃至上古的漢語詞彙,比如:汝、諸母、新婦、箸、鼎、匏桸、雅、翹楚等等,還有極具地方特色的表達,比如:𨑨迌、走漆、理唔直、孤獨死相等等。然而無會寫甚至無會呾甲子話的人實在㩼,其中不少是受過義務教育其。 有鑑於此,本人草創此表,力求詞雅正且其音形義有所考據,權當拋磚引玉,歡迎大家儂做蜀討論改進。

另附本表主要參考資料:

  1. 《潮汕方言詞考釋》(林倫倫)
  2. 《海豐話分類辭表》(羅志海、鍾顯坤)
  3. 《潮典》
  4. 《新潮汕字典》(張曉山)
  5. 《台灣閩南語常用詞辭典》
  6. 《小學堂閩語》

阿華
2020年10月成稿,12月修訂

接續讀落

Java 基礎

Java 自 95 年誕生至今,已有 25 年的歷史。它是一種半編譯的語言,先將代碼編譯成字節碼,然後在 JVM 中解釋執行。

  • 它是一種面向對象的語言,封裝、繼承和多態它都有,類可以多實現但不支持多繼承,而接口可以多繼承。
  • 它支持 8 種基本數據類型,同時還提供了其包裝類型,另外還提供了 BigDecimal 精確處理浮點數、提供了 BigInteger 處理大整數。
  • 它的方法只有值傳遞,傳遞對象時它是淺拷貝而非深拷貝。類的方法可以被子類重寫,同個類可以有多個同名的重載方法。
  • 它支持泛型,一種將類型參數化的技術。不過,也有人稱之爲「僞泛型」,因爲類型會在編譯時被擦除。
  • 它支持反射,一種在運行時操作任意對象的方法和屬性的技術,這在框架應用中很常見。
  • 它提供了豐富的集合類、迭代器及工具類。
  • 它支持多線程,一種在程序進程中同時執行多個任務的技術,同時還有豐富的鎖類型,所有對象的頭信息裏都有一個鎖標識。
  • 它支持異常處理,Exception 分編譯時異常和運行時異常,編譯時異常可以被編譯器檢查到,而運行時異常只能在程序運行時發生。
  • 它有豐富的 I/O API,派生自 4 個抽象類,InputStream、OutputStream、Reader、Writer,字符流的出現是爲了減少 JVM 進行字符編碼解碼的資源損耗和編解碼錯誤。
  • 另外,目前有兩大項目管理工具,Maven 和 Gradle。

接續讀落

Java 集合

Java 集合分 List、Set、Map 三大類,其中 List 和 Set 實現了 Collection 接口。List 的特點是數據有序、可重複;Set 的特點是數據無序、不可重複;Map 存儲鍵值映射,Key 不可重複,Value 可重複,且一個 Key 只能對應一個 Value。

List 有一實現 ArrayList,其底層實現是對象數組,默認容量是 10,但等到首次添加元素時才分配內存,每次遞增爲上次容量的 1.5 倍。在添加大量元素之前,建議調用 ensureCapacity 方法擴容,以減少遞增式再分配內存的次數。

Set 有一實現 HashSet,其底層實現是 HashMap ,其檢查重複的機制有賴於 hashCodeequals 方法。

Map 有一實現 HashMap ,JDK 1.8 之後其底層實現是:數組 + 鏈表 + 紅黑二叉樹。紅黑樹是爲了減少搜索時間,默認當鏈表長度大於 8 且當前數組長度大於等於 64 時,鏈表會轉爲紅黑樹。數組默認容量是 16,通過帶參構造方法傳入的容量值如非 2 的幂次會自動向上轉爲 2 的幂次,以便元素散列存儲(元素位置才可通過 hash & (length-1) 確定)。添加元素時,若元素數量大於數組長度的 75% 且該元素存在哈希衝突,則觸發擴容機制,數組容量翻倍。

總結下 HashMap 中解決哈希衝突的方式:

  1. 使用鏈表 — 拉鍊法;
  2. 使用紅黑二叉樹;
  3. 擴容底層數組;
  4. 強制數組容量爲 2 之幂次;
  5. 將元素的 hash 值的高位分散到低位等等。

ArrayListHashSetHashMap 都是線程不安全的。在多線程環境下應使用 J.U.C 包下的對應的並發類 CopyOnWriteArrayListConcurrentHashMapHashSetConcurrentHashMap 的 keySet 可得 Set 視圖)。CopyOnWriteArrayList 會在寫時加鎖並複製集合進行操作;ConcurrentHashMap 在 JDK 1.7 使用分段鎖,在 JDK 1.8 取消分段鎖採用 CAS(樂觀鎖) 和 synchronized(悲觀鎖) 只對鏈表或紅黑二叉樹的節點加鎖。

接續讀落

Java 多線程

首先區分進程和線程。進程是程序運行的基本單位,也是系統分配資源的最小單位;而線程是 CPU 調度的最小單位,一個進程可以有多個線程,而各個線程擁有獨立的程序計數器、虛擬機棧和本地方法棧,但共享同一個堆和方法區。爲著充分利用系統資源,減少 CPU 空等,多線程技術應運而生。系統使用時間片輪轉法分配 CPU 資源到各個線程,如果線程在分配的時間片內未能處理完任務,則會導致上下文切換。

Java 中線程類爲 Thread ,其 start 方法會使線程進入 Runnable 狀態;其 sleep 方法會阻塞線程而不釋放鎖,跟 Object.wait 有別;其 interrupt 方法並不停止線程,而是設置一個標誌位通知線程應當關閉,線程可以根據該標誌位決定是否要停止運行。

多個線程可以通過線程池進行統一管理,好處是還可以提前 Ready 好一些線程避免等待線程創建的時間損耗;還可以使用舊的線程,減少線程的頻繁創建和銷毀的資源損耗。線程池推薦直接使用 ThreadPoolExecutor 的構造方法去創建,以便設置合適的構造參數。調用 ThreadPoolExecutor.execute 可以將一個 Runnable 任務放入線程池處理; 調用ThreadPoolExecutor.submit 可以將一個 Callable 任務放入線程池處理,返回一個 Future 代表處理結果,後續通過調用 Future.get 獲取處理結果,其過程是阻塞的。

Java 1.5 開始提供AbstractQueuedSynchronizer,用於創建多線程訪問共享資源的同步器,其內部使用一個名爲 CLH 的 FIFO 的雙向隊列進行資源分配。其常見實現類有:ReentrantLockReadWriteLockCountDownLatch 。其中 ReadWriteLock 性能優於 ReentrantLock ,因爲兩個讀操作在 ReadWriteLock 中不互斥而在 ReentrantLock 中互斥。

Java 1.8 開始提供了 CompletableFuture ,其實現了 Future 接口,並提供了基於回調的函數式異步編程方式和對CompletableFuture 的組合,使用者可以不關心底層的線程池,大大簡便了異步編程。

Java 還提供了 ThreadLocal 類可以使得各個線程擁有變量的副本而不會相互影響,底層是使用 Thread 類的 ThreadLocalMap,一個類似 HashMap 的結構,其 Key 爲 ThreadLocal 對象的弱引用。

多線程因會有多個線程操作共享的資源而引發了線程安全的擔憂。線程安全要求原子性、可見性和有序性。Java 提供了多種方式可以保證線程安全:

  1. synchronized 同步鎖,這是一個重量級鎖。可以使用當前類的 class 對象,當前類的對象或者任意對象對方法和代碼塊進行加鎖,方法結束或代碼塊結束則鎖自動釋放。當使用非靜態同步方法時,會使用當前對象爲鎖,因此多個非靜態同步方法共享同一鎖。Java 1.6 後引入偏向鎖輕量級鎖的概念,使得 synchronized 不那麼「重」了。
  2. volatile 關鍵字。使用該關鍵字修飾的變量在編譯時不會有寄存器緩存而是直接使用主存、不會進行代碼重排序優化,保證了可見性和有序性;在解釋執行時使用 CPU 內存屏障技術防止指令重排序。
  3. CAS(Compare and Swap) 技術,相比於 synchronized,其假定操作是不會產生衝突的,將舊的預期值和內存中的值進行比較,若相同則更新內存中的值,否則自旋。因此被稱之爲「樂觀鎖」,而 synchronized 被稱之爲「悲觀鎖」。Java 中的 Atomic 類是典型的 CAS 實現。

多線程操作共享資源還會出現死鎖。死鎖產生的條件是:互斥、請求/等待、不可剝奪和循環等待。解決死鎖問題的關鍵是破壞死鎖產生的條件。

接續讀落