JavaGuide–Java篇

本文避免重複造輪子,也是從JavaGuider中提取出來方便日後查閱的手冊

參考鏈接:

JavaGuider://javaguide.cn/java/basis/java-basic-questions-01/

JVM記憶體結構://blog.csdn.net/rongtaoup/article/details/89142396

csdn
csdn
csdn
csdn
csdn


🔥1.基礎概念與常識

1.1.Java語言特點

跨平台、面向對象(封裝繼承多態)、一次編寫到處運行、多執行緒可靠安全、編譯與解釋並存

1.2位元組碼與編譯和解釋並存

被編譯成.class後綴的文件就是位元組碼。而經過解釋器JIT第一次解釋後,後續熱點程式碼(頻繁使用的程式碼。消耗大部分系統資源的只有一小部分程式碼,這就是熱點程式碼)的位元組碼對應的機器碼就會被保存下來,然後每次程式碼執行的時候都會優化,這樣就會越來越快

1.3 字元常量和字元串常量區別

字元常量是單引號,佔一個位元組(但java中char是2個位元組),字元串常量是雙引號,占若干位元組

1.4 Java關鍵字
分類 關鍵字
訪問控制 private protected public
類,方法和變數修飾符 abstract class extends final implements interface native
new static strictfp synchronized transient volatile
程式控制 break continue return do while if else
for instanceof switch case default
錯誤處理 try catch throw throws finally
包相關 import package
基本類型 boolean byte char double float int long
short null true false
變數引用 super this void
保留字 goto const
1.5.泛型

Java泛型是偽泛型,即java運行期間所有類型擦除,數據類型被轉換成一個參數。比如:

  1. 泛型類:public Class Study{} 實例化方式 Study genericInteger = new Study(123456);

  2. 泛型介面:public interface Study{} 實例化方式 class StudyImpl implements Study{}

  3. 泛型方法:public static void printArray(){E[] inputArray}

項目哪裡用到了泛型?

  • 項目介面返回Result參數的時候
  • ExcelUtil生成excel的時候
  • 工具類Collections.sort這些地方

(註:個人覺得泛型適用性強,在和外部介面對接的時候,為防止外部參數類型經常變更,可以改成泛型。或者自己復用引用高的程式碼塊的時候,也可以把程式碼塊改成泛型。)

1.6. ==和equals(),HashCode()

==比較地址equals()比較對象,

HashCode是對堆上的對象產生獨特值(不唯一,因為因hash演算法也會產生不同對象相同hash值),如果重寫equals不重寫hashcode,那兩個相同對象也會有不同的hashcode

1.7 基本數據類型

這 8 種基本數據類型的默認值以及所佔空間的大小如下:

基本類型 位數 位元組 默認值
int 32 4 0
short 16 2 0
long 64 8 0L
byte 8 1 0
char 16 2 ‘u0000’
float 32 4 0f
double 64 8 0d
boolean 1 false

引用類型Byte,Short,IntegerLong創建了[-128,127]的快取,Character創建了[0,127]的快取

註:頻繁拆箱裝箱也非常影響系統性能

1.7 基本數據類型

​ 序列化:數據結構或對象轉成二進位位元組流

​ 反序列化:序列化生成的二進位轉成數據結構或對象

1.8 I/O操作

​ 分為InputStream/Reader:位元組輸入流與字元輸入流

​ OutputStream/Writer:位元組輸出流與字元輸出流

1.9 I/O操作

反射:程式在運行時分析類和執行類方法的能力。(比如@Value就能在運行時給某個對象賦值,相比正射的set,get方法更靈活)

1.10 Java值傳遞
1.11 靜態代理與動態代理

​ 靜態代理是由代理對象和目標對象實現一樣的介面

​ 動態代理是利用反射機制在運行時創建代理類。

​ 動態代理:JDK動態代理實現了介面的類或直接代理介面,而CGLIB可代理未實現任何介面的類。與基於Java位元組碼實現的Javassist

​ 動態代理更靈活,不需要實現介面就可以代理實現類。靜態代理是在編譯時將介面、實現類、代理類生成.class文件

​ 動態代理底層:反射

​ Proxy:生產代理實例

​ InvocationHandler:處理代理實例並返回結果

1.12 IO模型

同步阻塞IO:BIO

同步非阻塞IO:NIO

非同步IO:AIO

🔥2.容器

2.1.Java集合

Java集合:

1. Collection介面:list,queue,set
2. Map介面

2.2.==與 equals 的區別

對於基本類型來說,== 比較的是值是否相等;

對於引用類型來說,== 比較的是兩個引用是否指向同一個對象地址(兩者在記憶體中存放的地址(堆記憶體地址)是否指向同一個地方);

對於引用類型(包括包裝類型)來說,equals 如果沒有被重寫,對比它們的地址是否相等;如果 equals()方法被重寫(例如 String),則比較的是字元串值是否相等。

2.3 HashMap底層實現(JDK1.8前後)

JDK1.8 之前 HashMap 底層是 數組和鏈表 結合在一起使用也就是 鏈表散列

JDK1.8之後HashMap 通過 key 的 hashCode 經過擾動函數處理過後得到 hash 值,然後通過 (n – 1) & hash 判斷當前元素存放的位置(這裡的 n 指的是數組的長度),如果當前位置存在元素的話,就判斷該元素與要存入的元素的 hash 值以及 key 是否相同,如果相同的話,直接覆蓋,不相同就通過拉鏈法解決衝突。

(put邏輯:如果定位到的數組位置沒有元素 就直接插入。

如果定位到的數組位置有元素就和要插入的 key 比較,如果 key 相同就直接覆蓋,如果 key 不相同,就判斷 p 是否是一個樹節點,如果是就調用e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value)將元素添加進入。如果不是就遍歷鏈表插入(插入的是鏈表尾部)。)

相比於之前的版本, JDK1.8 之後在解決哈希衝突時有了較大的變化,當鏈表長度大於閾值(默認為 8)(將鏈錶轉換成紅黑樹前會判斷,如果當前數組的長度小於 64,那麼會選擇先進行數組擴容,而不是轉換為紅黑樹)時,將鏈錶轉化為紅黑樹,以減少搜索時間。

2.4 ConcurrentHashmap底層原理

JDK1.8之後ConcurrentHashMap 取消了 Segment 分段鎖,採用 CAS 和 synchronized 來保證並發安全。數據結構跟 HashMap1.8 的結構類似,數組+鏈表/紅黑二叉樹。Java 8 在鏈表長度超過一定閾值(8)時將鏈表(定址時間複雜度為 O(N))轉換為紅黑樹(定址時間複雜度為 O(log(N)))

synchronized 只鎖定當前鏈表或紅黑二叉樹的首節點,這樣只要 hash 不衝突,就不會產生並發,效率又提升 N 倍

2.6 Synchronized的升降級

對象分為對象頭(MarkWord)、實例變數、填充位元組。

鎖的4中狀態:無鎖狀態、偏向鎖狀態(弄人開啟)、輕量級鎖狀態、重量級鎖狀態(級別從低到高)

Java8 中的 ConcurrentHashMap 使用的 Synchronized 鎖加 CAS 的機制。結構也由 Java7 中的 Segment 數組 + HashEntry 數組 + 鏈表 進化成了 Node 數組 + 鏈表 / 紅黑樹,Node 是類似於一個 HashEntry 的結構。它的衝突再達到一定大小時會轉化成紅黑樹,在衝突小於一定數量時又退回鏈表。

🔥3.並發編程

3.1.進程與執行緒

進程:啟動一個程式(啟動main函數就是啟動了一個jvm進程),而main函數的的執行緒就是這個進程中的一部分

​ 多個執行緒共享記憶體中的堆與方法區,但是各個執行緒又有自己的虛擬機棧、本地方法棧、程式計數器。

3.2.上下文切換

​ 執行緒會有主動阻塞(Sleep,wait)讓出CPU、時間片用完、IO阻塞等執行緒切換情況,這時需要保留現場方便後續CPU調用,同時載入下一次執行緒用CPU的上下文,這就叫上下文切換。

3.3 Sleep與wait區別

​ Sleep沒有釋放鎖,會自動喚醒,而wait釋放了鎖,必須要notify/notifyall喚醒

3.4 start與run

​ start啟動執行緒做了執行緒準備工作後調用run方法並讓執行緒進入就緒狀態,CPU分配了時間片後就可以開始執行,而run方法並不會在某個執行緒中執行,不屬於多執行緒。

3.5 Synchronized關鍵字

​ 掛起或喚醒一個執行緒,都需要OS操作用戶態到核心態的轉換,這個過程非常耗時。

Synchronized作用域

  1. 修飾實例方法:作用於對象實例
  2. 修飾靜態方法,作用於類.class
  3. 修飾程式碼塊,作用域對象的鎖(this,OBject)或類的鎖(類.calss)
3.6 其他參數

​ 以下內容見《JUC並發編程

​ volatile(非執行緒安全)

​ ThreadLocal(執行緒本地變數)

​ 執行緒池(三大方法、七大參數,四種拒絕策略)

3.7 AQS

​ 簡介:是juc下具體類

​ 核心原理:多執行緒情況下,空閑的共享資源加鎖,非空閑的共享資源使用CLH隊列鎖(內部是虛擬的雙向隊列,FIFO)進行執行緒隊列等待與喚醒的鎖分配策略

​ 定義資源的共享方式:

​ 1. Exclusive(獨佔):Reentranlock,synchronized

​ 2. Share(共享):Countdownlatch,semaphore,cyclicBarrier,readwriteLock

3.8 補充

this逃逸:構造函數返回之前其他執行緒就持有該對象引用,其他執行緒調用尚未構造完全的對象方法引發錯誤

🔥4.JVM

4.1 JVM記憶體區域詳解
4.1.0 JVM記憶體區域
1. PC程式計數器:執行緒中的訊號指示器,用於用來讀取下一條指令功能。、
2. Java虛擬機棧:包含一個個棧幀(局部變數表等等,局部變數表又包含8個基本數據類型(),對象應用)
3. 本地方法棧:虛擬機棧為java服務,本地方法棧為虛擬機用到的Native服務(也有本地方法的局部變數表等待)
4. 堆:存放分配幾乎所有對象實例與數組記憶體、**字元串常量池、靜態變數(jdk1.8之後)**
 	1. 字元串常量池:存放"asd" + "abc"(**常量摺疊**)
5. 方法區:存儲已被虛擬機載入的類資訊(類如何放入方法區,reference如何指向就是這裡)、常量。**方法區和永久代的關係很像 Java 中介面和類的關係**
 	1. 運行時常量池:類的版本、欄位、方法、介面等描述資訊外,還有常量池表(類的相關資訊)
 	2. **jdk1.8之後是把永久代實現方法區方式換成了元空間實現方式**
6. 直接記憶體(堆外記憶體):NIO
4.1.1對象的創建

​ Java對象創建分如下5步:

  1. 類載入檢查:遇到new 指令時,進行類的檢查與載入。
  2. 分配記憶體:在堆中分配記憶體,有指針碰撞與空閑列表2種分配方式。Java堆是否規整由垃圾回收演算法決定。

​ 保證記憶體分配的執行緒安全:

CAS+失敗重試: CAS 是樂觀鎖的一種實現方式。所謂樂觀鎖就是,每次不加鎖而是假設沒有衝突而去完成某項操作,如果因為衝突失敗就重試,直到成功為止。虛擬機採用 CAS 配上失敗重試的方式保證更新操作的原子性。

TLAB: 為每一個執行緒預先在 Eden 區分配一塊兒記憶體,JVM 在給執行緒中的對象分配記憶體時,首先在 TLAB 分配,當對象大於 TLAB 中的剩餘記憶體或 TLAB 的記憶體已用盡時,再採用上述的 CAS 進行記憶體分配

  1. 初始化零值:給每個new 對象有初始化值
  2. 設置對象頭:markword
  3. 執行init方法:按照程式碼進行init,比如定義數組大小。上述可以說只是定義分配了記憶體位置,這裡根據init定義具體大小。
new ArrayList<>(3);
4.1.2 對象訪問
  1. 句柄訪問:Java棧的本地變數表訪問堆中的句柄池中的實例對象指針(類似於作業系統中的虛擬地址表),然後訪問堆或者方法區中的實例對象。缺點就是多訪問了一次指針定位實例數據的時間。
  2. 直接指針:Java棧的本地變數表存的就是直接訪問堆中的實例對象的地址。訪問方法區就是堆中存了實例對象地址。缺點就是後續垃圾回收移動對象地址的時候,需要改變棧中的本地變數表。
// 上面說了new ArrayList<3>,這裡說的2種方法就是一整條語句了。
List<Integer> list = new ArrayList<>(3);
4.2 JVM垃圾回收詳解
4.2.1 Jvm垃圾回收相關問題

​ 註:JVM垃圾回收在於執行緒共享區域:即堆、方法區(已被虛擬機載入的靜態變數、常量等程式碼與運行時常量池)

下面介紹JVM垃圾回收具體流程:

4.2.2 對象是否死亡

​ 1.如何判斷對象是否死亡:引用計數器與可達性演算法

​ 2.指向對象的引用:強引用,軟引用,弱引用,幻想引用(虛引用)

目前尚有一個疑問?

​ 目前垃圾回收器普遍都是分代回收演算法,但是又說JDK1.8是默認採用Parallel scavenge+Serial Old收集器,但這2個收集器並不是GC演算法,是否衝突?

​ 答:相當於所有垃圾回收器都默認使用了分代回收演算法

4.2.3. 垃圾回收(演算法與工具)

垃圾回收演算法:

1. Mark-sweep(標記-清除 MS演算法):標記全部堆中可回收資源進行回收
2. Copying(複製 CP演算法):堆記憶體分成2半,一半用完了複製並整理整齊到另一半上,然後把這一半清空。
3. Mark-Compact(標記-整理 MC演算法):MS與CP結合,將全部堆內容標記,然後整理整齊清空。
4. Generation Collection(分代收集 GC演算法):根據對象存活周期將記憶體分為新生代與老生代進行收集。垃圾收集器:

1. Serial Old收集器:MC演算法
2. ParNew收集器:Serial的多執行緒版本,MC演算法
3. Parallel Scavenge:Cp演算法
4. Parallel Old:MC演算法
5. CMS:MS演算法
6. G1:MS演算法
4.3 類文件結構、類載入過程(略)
4.4 JVM性能調優(待續)

​ 能力有限,暫時用不到這些,後面用到了進一步了解。

​ 學習鏈接://javaguide.cn/java/jvm/jvm-parameters-intro/#_3-2-gc記錄

4.5 JVM排查命令

​ 參考命令://javaguide.cn/java/jvm/jdk-monitoring-and-troubleshooting-tools/#jps-查看所有-java-進程

註:Jdk1.8默認採用Parallel Scavenge(新生代) + Serial Old(老年代)

🔥5.新特性

5.1 Java8新特性實戰、《Java8指南》、JDK9~15、小技巧(略)

​ 權當手冊查了。

書山有路勤為徑,學海無涯苦作舟。程式設計師不僅要懂程式碼,更要懂生活,關注我,一起進步。

Tags: