《Java特種兵》學習筆記

fengye發表於2017-12-20

《Java特種兵》學習筆記

一、功底

1.1 編譯期優化

String a = "a" + "b" + 1;
String b = "ab1";
println(a == b);  // true 編譯期優化
複製程式碼
String a = "a";
final String c = "a"; 
//a並不是一個常量,而是區域性變數,位元組碼增強技術就可以修改a的實際賦值
String b = a + "b";	
String d = c + "b";	//final

//1.編譯器不會看方法內部做了什麼,2.遞迴深度不可預測
//3.返回的是對常量引用的拷貝,不是final的,可以修改
String e = getA() + "b"; 
String compare = "ab";
println(b == compare);	//fasle
println(d == compare);	//true
println(e == compare);	//fasle

private final static String getA() {
	return "a";
}
複製程式碼
//String b = a + "b" 的實際編譯效果
StringBuilder temp = new StringBuilder();
String b = temp.append(a).append("b");
複製程式碼

1.2 Intern

public native String intern();
與string pool關聯,加鎖尋找string字串,用equals方法判斷是否是目標字串
Jdk1.6: string pool在 Perm Gen中
Jdk1.7:string pool在堆中

public static void test3() {
    String a = "a";
    String b = a + "b";	//a是變數,new StringBuilder().append(a).append("b");
    String c = "ab";	//在string pool中新建ab字串
    String d = new String(b);//新物件
    println(b == c);	//F
    println(c == d);	//F
    println(c == d.intern());	//True:intern:尋找string pool中的ab,返回地址,沒有則建立後返回地址
    println(b.intern() == d.intern());	//True:intern:尋找string pool中的ab,返回地址,沒有則建立後返回地址
}
複製程式碼

老年代是否可以獨自清理?而新生代不清理?

1.3 StringBuilder

StringBuilder stringBuilder = new StringBuilder();
for (...) {
    //最壞的情況是它所佔用的記憶體空間接近Old區域1/3時發生擴容,導致OOM
    stringBuilder.append(string);	
}
複製程式碼

stringBuilder在append擴容的時候,取max(2倍,count+string.length),所以在小字串append大字串時,擴容的空間剛剛足夠,而大字串append小字串時,就會多出大量的記憶體空間

1.4 大量判定是|否操作

舉例:java位元組碼中的類修飾符,以下都是十六進位制,只取後四位顯示

public:0001
static:0100
final:1000
複製程式碼

對數字取&操作,不為0時就是true
若判斷 public static final, 先取或 public_static_final=public|static|final;判斷(value& public_static_final)== public_static_final

1.5 資料cache

型別 Cache範圍
Integer -128 ~127
Short -128 ~127
Long -128 ~127
Float double
Byte 256個值

二、計算機工作原理

2.1 棧

儲存區域性變數中的基本資料型別,新建物件時,儲存物件引用,而物件是在堆中建立
Jvm發出指令請求,OS完成具體計算,jvm自身無法做計算

2.2 Cache line

通常以連續64位位元組為單位進行cache的
如陣列獲取,當取二維陣列a[0][0]時,cache line操作通常會將一些臨近的陣列元素cache到CPU快取中,故而連續訪問a[0][0],a[0][1]…時,這些連續的數值只要cache一次

2.3 快取一致性協議

同一份資料cache在多個cpu中時,要求資料讀寫一致,多個CPU之間要遵循快取共享的一致性協議
CPU讀寫資料時都會廣播,其他CPU監聽,並儲存資料一致性

2.4 記憶體

所有程式中使用的地址都是虛擬地址(邏輯地址),在不同的程式中可以重複
實體地址:每個程式有一段記憶體區域,起始地址+邏輯地址=實體地址
OS預先給jvm分配-xms大小的記憶體空間,而不是立即分配一個-xmx大小的空間,許多空間是真正使用時才分配的(啟動java時,-xmx設定比實體記憶體大都可以)

2.5 磁碟

每秒讀取的次數IOPS越大越好
順序讀寫,減少定位延遲
Java中的日誌讀寫工具,會將日誌替換為buffer的append方式,將日誌寫入一個緩衝區,由專門的程式實現寫操作,或者只在緩衝區已滿的時候寫入磁碟,儘量一次寫入多條資料,順序IO,減少硬碟尋道定址

三、JVM

JVM記憶體管理模型
Old區域更大一些
靜態資料存放於方法區
String物件的intern方法將string物件拷貝到常量池

3.1 類位元組碼

類常量池
方法:編譯時會自動生成構造方法位元組碼
略過先

3.2 Class位元組碼載入器

  1. 繼承關係
    ClassLoader.loadClass(“類名”)時,會先從當前ClassLoader查詢該類是否已經載入,然後逐步往父類查詢類,最後由父類往子類載入類,最後ClassNotFoundException
    父類先載入類 BootStrapClassLoader -> ExtClassLoader -> AppClassLoader -> 自定義ClassLoader
  2. BootStrapClassLoader
    載入java自帶的核心類,如java.Lang.*( Object, Class, Number, Thread, System, Throwable…),由jvm核心實現,不能被替換掉
  3. ExtClassLoader
    載入 jre/lib/ext目錄下的jar包
    • 擴充套件
      jre
      C:\Program Files\Java\jre : 使用者執行java程式的jre環境,為jvm用
      Jdk
      另外安裝的jdk則是開發程式時需要用到的:
      jdk\lib: 包括java開發環境的jar包
      jdk\jre: 開發環境下執行的是 jdk 下的 jre
      jdk\jre\lib: 開發環境中,執行時需要的jar包,如匯入的外部jar包
  4. AppClassLoader
    載入classPath下面的內容
  5. 自定義ClassLoader
    載入class,jar檔案,甚至其他檔案,載入位置可本地,也可遠端
    自定義的ClassLoader可以識別到parentClassLoader載入的類,而其他的ClassLoader載入的類需要重新拼接出classpath作為引數動態編譯
    若未指定parentClassLoader,則parentClassLoader預設為呼叫者類對應的ClassLoader;初始化時可以設定parentClassLoader為null
  6. 啟動載入
    啟動時,只載入jvm核心庫(如BootStrapClassLoader)和main方法相關類

3.3 class載入過程

所有類在使用前都必須被載入和初始化,初始化過程由<clinit>方法確保執行緒安全,若多個執行緒同時嘗試獲取該類,則必須等到static塊執行完成

  1. 讀取檔案(ClassNotFoundException)
    載入.class檔案到方法區內部(包含所有的class和static變數,都是程式中唯一的元素) 先從父classloader載入,找不到就子載入器載入,最後丟擲異常
    (BootStrapClassLoader -> ExtClassLoader -> AppClassLoader ->自定義ClassLoader->classnotfoundexception)
  2. 連結(NoClassDefFoundError)
    解析校驗位元組碼,不符合規範就丟擲NoClassDefFoundError
    為class物件分配記憶體
  3. 初始化
    呼叫class物件構造方法,靜態變數,static塊賦值
    初始化順序
    static塊 -> 程式碼塊 -> 構造方法
class Parent {
    public Parent() {
	System.out.println("parent constructor init....");	//4
    }
    static {
	System.out.println("parent static block init....");	//1
    }
    {
	System.out.println("parent normal block call....");	//3
    }
}

class Child extends Parent {
    static {
	System.out.println("child static block call....");	//2
    }
    {
	System.out.println("child block call....");		//5
    }
    public Child() {
	System.out.println("child constructor call....");	//6
    }
}
public class LoadObjectStepDemo {
    public static void main(String[] args) {
	new Child();
    }
}
複製程式碼
parent static block init....
child static block call....
parent normal block call....
parent constructor init....
child block call....
child constructor call....
複製程式碼

錯誤初始化例項ExceptionInInitializerError

class B {
    // 載入類時先呼叫static塊
    private final static B instance = new B();
    public static B getInstance() {
	return instance;
    }
    public B() {
	instance.test(); // new B()引用instance例項,但又發現這個類未載入完成,instance為NULL
    }
    public void test() {
	System.out.println("test");
    }
}
複製程式碼

3.4 class其他知識點

  1. 容器跨應用訪問
    在web容器中使用了不同的ClassLoader來載入不同的delopy(不同應用),但可以跨classLoader互相訪問資訊
  2. ClassLoader一個類只載入一個
    同一個ClassLoader一個類只會載入一個,同一個類可能會被不同ClassLoader載入,在單例模式時應該考慮這個問題
  3. Full GC釋放class
    Jvm做fullGC時,只有當相應的ClassLoader下所有的Class都沒有例項引用時,可以釋放ClassLoader及其下所有class
  4. ClassLoader載入
    ClassLoader本身就是class,在沒有ClassLoader時,由jvm核心載入
  5. Class載入與父類
    先載入父類,先初始化父類static方法
  6. JIT執行時優化
    逐步優化,會將優化後的程式碼存放在codeCache中
    -XX:ReservedCodeCacheSize : 修改codeCache大小,64bit server java7預設48M
    -XX:+UseCodeCacheFlushing: 清理codeCache
    -XX:CICompilerCount : 最大並行編譯數,越大提高編譯速度
  7. 同名類載入衝突
    同名類出現在不同jar包中,可以使用instance.getClass().getResource("").getPath();獲得class來源jar包
  8. 根引用
    GC時的根引用是本地變數引用,運算元棧引用,PC暫存器,本地方法棧引用,靜態引用等
    即程式執行時棧中的引用+靜態引用列表
  9. 引起Full GC
    1. Old區域滿 || 小於平均晉升空間大小
    2. Perm區域滿:class string
    3. System.gc()
    4. Dump記憶體

3.5 回收演算法

見jvm筆記

3.6 常用GC引數

  1. 跟 Java 堆大小相關的 JVM 記憶體引數

    引數 含義
    -Xms 設定 Java 堆的初始化大小
    -Xmx 設定最大的 Java 堆大小
    -Xss 設定Java執行緒堆疊大小
    -Xmn 設定新生代空間大小
  2. 關於列印垃圾收集器詳情的 JVM 引數

    引數 含義
    -verbose:gc 記錄 GC 執行以及執行時間,一般用來檢視 GC 是否是應用的瓶頸
    -XX:+PrintGCDetails 記錄 GC 執行時的詳細資料資訊,包括新生成物件的佔用記憶體大小以及耗費時間等
    -XX:-PrintGCTimeStamps 列印垃圾收集的時間戳
  3. 設定 Java 垃圾收集器行為的 JVM 引數

    引數 含義
    -XX:+UseParallelGC 使用並行垃圾收集
    -XX:-UseConcMarkSweepGC 使用併發標誌掃描收集
    -XX:-UseSerialGC 使用序列垃圾收集
  4. JVM除錯引數,用於遠端除錯

-Xdebug -Xnoagent -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=8000
複製程式碼
  1. 關於類路徑方面的 JVM 引數
    -Xbootclasspath用來指定需要載入,但不想通過校驗的類路徑。JVM 會對所有的類在載入前進行校驗併為每個類通過一個int數值來應用。這個是保證 JVM 穩定的必要過程,但比較耗時,如果希望跳過這個過程,就把類通過這個引數來指定。
  2. 用於修改 Perm Gen 大小的 JVM 引數
    下面的這三個引數主要用來解決 JVM 錯誤: java.lang.OutOfMemoryError:Perm Gen Space.
    -XX:PermSize and -XX:MaxPermSize
    -XX:NewRatio=2  Ratio of new/old generation sizes.
    -XX:MaxPermSize=64m     Size of the Permanent Generation.
    複製程式碼
  3. 用來跟蹤類載入和解除安裝的資訊
    -XX:+TraceClassLoading-XX:+TraceClassUnloading 用來列印類被載入和解除安裝的過程資訊,這個用來診斷應用的記憶體洩漏問題非常有用。
  4. JVM switches related to logging
    -XX:+PrintCompilation: prints out the name of each Java method Hotspot decides to JIT compile.
  5. 用於除錯目的的 JVM 開關引數
    引數 含義
    -XX:HeapDumpPath=./java_pid.hprof Path to directory or file name for heap dump.
    -XX:-PrintConcurrentLocks Print java.util.concurrent locks in Ctrl-Break thread dump.
    -XX:-PrintCommandLineFlags Print flags that appeared on the command line.

3.7 Java物件記憶體結構

Java物件將以8位元組對齊在記憶體中,不足則補齊
靜態引用所佔的空間通常不計算到物件空間本身的空間上,它的引用在方法區

物件記憶體結構

//32bit
class A{
   byte b1;
}
複製程式碼

8位元組頭部+1位元組b1
要對齊,故16位元組

  1. 繼承關係的物件屬性排布
    在內部結構中,父類的屬性依然要被分配到相應的子類物件中,這樣才能在程式中通過父類訪問它的屬性 父類的屬性不能和子類混用,它們必須單獨排布在一個地方
class A{byte b;}
class B extends A{byte b;}
class C extends B{byte b;}
複製程式碼

物件結構圖

  1. 陣列佔用空間例項(32bit)
int size = 100 * 1024 * 1024;
//1
int[] values = new int[size];
for (int i = 0; i < size; i++) {
    values[i] = i;
}
//2
Integer[] valueIntegers = new Integer[size];
for (int i = 0; i < size; i++) {
    valueIntegers[i] = i;  // 自動裝箱了 new Integer(i)
}
複製程式碼
  • 對1:
    int[]陣列佔用空間
    8位元組頭部+4位元組描述陣列長度+4X100x1024X1024=400MB(int 值4位元組)+padding4位元組 ≈ 400MB
  • 對2:
    每個Integer物件佔8位元組頭部+4位元組int值,又需要對齊,故16位元組
    例項物件一共佔用空間16 X 100x1024X1024=1600MB
    Integer[] 陣列佔用空間
    8位元組頭部+4位元組描述陣列長度+引用空間4X100x1024X1024=400MB(每個引用4位元組)+padding4位元組 ≈ 400MB
    總空間1600+400=2000MB
  1. Int[2][100] PK int[100][2]
    維度 Int[2][100] int[100][2]
    第一維陣列 物件頭部 8 8
    第一維 陣列長度描述符 4 4
    第一維 引用寬度 2X4=8 100X4=400
    第一維 Padding 4 4
    第一維 合計 24 416
    第二維 物件頭部 8 8
    第二維 陣列長度描述符 4 4
    第二維 引用寬度 100X4=400 2X4=8
    第二維 Padding 4 4
    第二維 合計 416 24
    總計 24+2X416=856 416+100X24=2816

3.8 常見OOM

  1. java.lang.OutOfMemoryError: Java heap space
public static void main(String[] args) {
	List<String> list = new ArrayList<String>();
	while (true) {
	    list.add("記憶體溢位了");
	}
}
複製程式碼
  • 解決方法
    1. 對可能存活較久的大物件:object = null
    2. 程式碼提速:程式碼執行速度提升,縮短物件生命週期
    3. 修改堆大小
  1. java.lang.OutOfMemoryError: PermGen space
    1. jdk1.6 PermGen空間
    int i = 0;
    while (true) {
        ("在JDK 1.6下執行,在JDK 1.7中執行的結果將完全不同 "
        	+ "string常量在jdk1.7以上就不再存放在PermGen中" + i++).intern();
    }
    複製程式碼
    1. 動態載入class,如使用位元組碼增強技術,CGlib一直建立載入class
      若需要動態載入類,動態編譯java程式碼,最好是有單獨的classLoader,當class被替換時,原來的class可以被當做垃圾釋放掉
      釋放class的條件是classLoader下的class都沒有活著的物件
  2. DirectBuffer OOM
    java.lang.OutOfMemoryError: Direct buffer memory
    DirectBuffer區域不是java的heap,而是C heap的一部分,通常FULL GC時回收
    // -XX:MaxDirectMemorySize=26m
    public static void main(String[] args) {
    	ByteBuffer.allocateDirect(27 * 1024 * 1024);
    }
    複製程式碼
  3. StackOverflowError
    public void testStackOver() {
    	testStackOver();
    }
    複製程式碼
    注意遞迴層數,將遞迴呼叫次數作為引數,到達一定次數後結束遞迴
    子類和父類相互間呼叫方法

四、Java通訊

4.1 字元編碼轉換

若字元編碼和解碼的方式不一致,很可能損壞源字元,造成無法正確讀取
比如變長的UTF-8編碼可以由3個位元組組成一個漢字,而GBK由2個位元組組成漢字,GBK按2個單位 長度讀取時,不在其編碼範圍內的則用?或其他字元替代,這就修改了原來的字串了

4.2 流繼承巢狀

  1. 繼承
    有部分流的具體實現中會繼承FilterInputStream, FilterInputStream提供InputStream的預設實現.則流只覆寫特定的方法即可
  2. 巢狀
    可以對同一個stream使用多個stream實現類來巢狀,注意當其中一個實現stream close時,底層基礎stream關閉了,而其他stream沒有呼叫其close方法,可能會出錯
    比如多個BufferedInputStream巢狀一個流,關閉其中一個BufferedInputStream後,底層input關閉,但剩餘的其他BufferedInputStream沒有執行close方法,其buffer的資料不會重新整理到磁碟上,造成資料問題

4.3 I/O與記憶體

  • 檔案讀入與讀出  
    檔案讀入與讀出

 不關閉流會有什麼問題?記憶體溢位吧

  • 阻塞與非阻塞 同步與非同步
    同步和非同步關注的是訊息通訊機制,重點在於被呼叫的結果
    阻塞和非阻塞關注的是程式在等待呼叫結果(訊息,返回值)時的狀態,重點在於程式自身
    1. 同步
      是在發出一個呼叫時,在沒有得到結果之前,該呼叫就不返回。但是一旦呼叫返回,就得到返回值了, 由呼叫者主動等待這個呼叫的結果
    2. 非同步
      呼叫在發出之後,這個呼叫就直接返回了,所以沒有返回結果。在呼叫發出後,被呼叫者通過狀態、通知來通知呼叫者,或通過回撥函式處理這個呼叫。
    3. 阻塞
      阻塞呼叫是指呼叫結果返回之前,當前執行緒會被掛起。呼叫執行緒只有在得到結果之後才會返回。
    4. 非阻塞
      非阻塞呼叫指在不能立刻得到結果之前,該呼叫不會阻塞當前執行緒,當前執行緒繼續執行

五、資料庫

5.1 資料庫基本原理

資料庫基本結構層次

  1. 資料塊/頁
    塊,資料儲存單位:4KB,8KB,16KB, 塊也是虛擬出來的,為資料庫規範,可由資料庫識別
    每個塊可儲存多條資料,塊都有識別符號(不唯一,只有和更高層次的識別符號組合才唯一)
    塊內部每條資料通常都有自己的行號
    資料效能瓶頸在於磁碟IOPS上,讀取1KB和1MB的連續資料,耗時相差無幾
    慢原因:每個磁碟IO請求都是序列化,每個IO請求需要磁軌定址,故而慢
    優化:可以將多個塊的資料連續放在一起組成一組資料
    當連續塊中有部分資料常用,部分不常用時,不常用的資料會被寫回磁碟,則程式再次載入這些塊時,只是載入未載入的部分,這就造成不連續載入,成為隨機讀
  • 資料字典
    通常以表的方式儲存,記錄資料庫資訊,包括每個塊在檔案系統中的偏移量
    資料量少,更新少,通常載入到記憶體中提供訪問
    資料庫通常會以LRU的演算法來管理塊,讓某些訪問多的塊留在記憶體,或讓訪問少的塊從記憶體釋放掉
  1. 分組extends
    將多個塊組成一個分組,理論上是連續儲存的
  2. segments
    多個extends組成一個segments,一個表物件可以劃分到多個segments,以實現分割槽表獨立管理
  3. 修改表
    新增一個表格欄位
    某些資料庫會事先在寫入每個塊時預留少部分空間,以便新增欄位時使用
    當預留空間的欄位不夠用時,會將一行資料的某個部分寫入到另一個塊中,一般不在連續塊上則需要多次IO讀取資料了
    某些開源資料庫是沒有資料字典概念,大部分描述資訊存放在以表為單位的檔案頭部中,當表結構修改時,通常會建立一個新表,並將原表中的資料全部拷貝到新表中,為保持資料一致性,拷貝時通常會鎖表
    對於大資料儲存,通常採用分庫分表策略
  4. 刪除表
    偽刪除:刪除某些資料庫中的表,為了防止誤刪除,會在預設的處理刪除動作時,只是修改一個名字並將修改後的表名與原來的表名做一個簡單對映,同時也改變其後設資料的狀態
  5. SQL執行
    解析獲得關聯結構資訊,執行計劃等
    • 軟解析
      快取SQL解析過程,同樣SQL,只有引數不同,可以直接拿解析好的結果直接執行,提高效率
    • 硬解析
      拼接處引數,而非預編譯,SQL不復用,低效
    • SQL儲存空間
      SQL儲存空間有限,使用類LRU演算法管理記憶體區域
    • 執行計劃
      單表是否走索引,多表時是jion還是union,排序方法選擇,分鐘策略確定等
    select * from table small,table big where small.id =big.id and small.type=’1’;
    複製程式碼
    small.id =big.id:用小表做驅動表,更快
    對於過濾性好的條件,可以檢視執行計劃,讓該條件先執行(Oracle和mysql執行順序不一致)
  6. 加鎖
    讀寫表時,通常會在資料字典上加鎖,保證表結構不會被修改,一般類似於讀寫鎖,不過寫時卻也可以讀,可能會造成讀髒資料的問題,解決方法是增加時間戳或版本號做標識
    對於某些資料庫,鎖住的行可能並不僅僅是最終where條件篩選出的行,可能是where條件中有索引欄位篩選出的所有行
    update table setwhere a=xx and b=xxxx
    複製程式碼
    若a有索引,而b沒有,則可能會鎖住所有a條件篩選出的資料,鎖的範圍更大 有些資料庫為了加快加鎖的速度,會以”塊”為單位加鎖,塊頭部有內部的行資訊,每一行都有1bit來標識是否已經加鎖
    對應大量的資料修改,可能因鎖而導致序列速度問題,可以利用cas機制代替鎖
  7. 提交
    Commit操作時,會將日誌寫入到磁碟
    資料庫會在記憶體分配一個日誌緩衝區,隔一定時間/日誌達到一定容量就存檔
    日誌的存在可以為資料庫提供回滾功能
    另外,也有可能是資料庫記錄每一個塊的相應版本到某個空間,只要空間夠用,就不會將相應版本的資料清空,回滾時直接查詢對應版本號/時間點的資料

5.2 索引原理

索引方法:B+樹,也有hash
索引是與資料分開儲存的
索引的目的是快速定位資料,索引通過某種識別符號與原表關聯起來(也叫回表),識別符號可以是主鍵,或者建立索引的欄位值,也可以是資料物理位置(oracle用rowid做識別符號:表空間編號+檔案編號+物件編號+塊號+塊內行號)
除非查詢的資訊全部在索引上,否則索引至少會走兩次操作(索引+回表),所以小規模資料索引表現不好
索引更新時,索引先刪除再插入,其中刪除是偽刪除,索引空間不會變小
重新編譯索引才會真正刪除索引

  • 索引管理
    樹狀管理索引,資料量越大,層數越多,索引間有序,底層所有的葉子塊通過雙向連結串列互相關聯

    三層索引管理
    SQL走索引時,會從樹根開始查詢,索引塊通常也cache在快取中(若索引塊資料量過大,只快取高層索引塊),可能就不需要I/O,另外,由於索引有序,可以通過二分法快速查詢,若查完索引後仍需要查詢非索引欄位,此時回表,否則直接走索引內部資料
    SQL某些統計操作可以通過索引完成(比如count),只統計葉子塊,就可以得到全表資訊
    Min,max操作,有where條件,則正常遍歷索引,沒有條件,直接查第一個和最後一個塊
    like字首索引可以通過範圍查詢,而字尾索引就可能是全表掃描

  • 欄位有索引並不一定要走索引
    in檢索時,通常解析為OR方式完成,檢索在多個離散塊中進行,每個條件都需要單獨查詢,in條件多就走全表掃描(將in改為exists?)
    再如,某些狀態欄位作為條件,可能也不會走索引

  • 點陣圖索引bitMap
    但欄位只有幾種值時,bitmap可以實現高效的統計.
    點陣圖索引結構類似於B+樹,儲存時以欄位值型別分開,在每種值的空間中單獨儲存每個資料行是否有這個值,1有,0沒有
    如欄位型別為A,索引中大致可以儲存為101010100,表示第1 3 5 7行有A值
    缺點:鎖粒度太大
    若修改狀態1為2,則會鎖住所有值為1和2的行,commit或rollback才會釋放

5.3 資料庫主從原理

主庫更新資料,再同步到從庫

  1. 同步方式:邏輯模式
    主庫執行SQL,到從庫上執行相同SQL
    主庫可以優化SQL,如生成基於主鍵更新的SQL,從庫執行會簡單一些
    從庫基本只有一條執行緒寫,其餘讀,壓力小,更高效
    缺點:SQL較多,從庫寫速度更不上主庫,導致不一致
  2. 物理模式
    基於修改的資料塊做複製
    提取比從庫版本號更高的資料塊複製到從庫,不用SQL解析,鎖,排程等問題,更加高效

5.4 執行計劃

小表做驅動表,大表走索引

  • Oracle
  1. explain plan for sql,select * from table(DBMS_XPLAN.DISPLAY)
  2. SET AUTOTRACE ON EXPLAIN , SET AUTOTRACE ON, SET AUTOTRACE TRACEONLY STAT
    eg:
    explain plan for select * from tableAA a where a.tt=:vv1; :開頭代表佔位符
    INDEX UNIQUE SACN:走唯一索引
    INDEX RANCE SCAN:普通索引是走範圍索引的,因為可以重複
    如果Oracle執行計劃不對或不理想,可以通過Hint方式告訴Oracle應該如何走索引
  • Mysql
    explan<sql>

  • 執行計劃
    在JOIN表之前,需要被JOIN的表能先過濾掉大部分資料
    小表驅動大表,巢狀迴圈時,有序會快一些
    多個結果集JOIN,可以使用Hash Join的方式,當表太大,Hash Join key太多,記憶體會放不下,用普通巢狀方式做

  • 函式轉換
    函式轉換通常不屬於執行計劃的範疇
    select fun1(a),fun(b)… from table
    執行路徑是看不到函式的處理

5.5 模型結構優化

表示樹狀結構,如省,市,區縣,鎮,鄉,村,門牌,多級結構,可以使用常用的遞迴方式,只保留父級id 也可以按序保留下所有的父級id,避免遞迴,索引也方便

六、原始碼基礎

6.1 原始碼呼叫路徑

Thread.currentThread().getStackTrace();
複製程式碼
new Exception().printStackTrace();
複製程式碼

6.2 反射

Boolean型別的屬性注入

Boolean型別屬性名稱還是不要加is字首

class Node {
    private boolean good;
    public boolean isGood() {
	return good;
    }
    public void setGood(boolean good) {
	this.good = good;
    }
}
複製程式碼

當使用PropertyDescriptor去獲取屬性讀寫方法時,boolean型別預設都會加上is字首
若屬性名為isGood,則預設呼叫isIsGood方法,這時類中isGood方法就不管用了

反射與範型擦除

List<Integer> list = new ArrayList<Integer>();
Method method = ArrayList.class.getDeclaredMethod("add",Object.class);
method.invoke(list, 7);
method.invoke(list, "dfsd");
method.invoke(list, new A("aa"));
System.out.println(list);
複製程式碼
  1. 泛型實參只會在類、欄位及方法引數內儲存其簽名(即可以獲得實際的引數型別),無法通過反射動態獲取泛型例項的具體實參,比如入參為範型,方法內部就會擦除為object。
  2. 需要獲取泛型實參的情況下,方法有三: ①通過傳遞實參型別
    ②明確定義泛型實參型別,通過反射獲取簽名
    ③通過匿名類捕獲相關的泛型實參

AOP

面向切面,動態代理和位元組碼增強技術

  1. 如果目標物件實現了介面,預設情況下會採用JDK的動態代理實現AOP
  2. 如果目標物件實現了介面,可以強制使用CGLIB實現AOP
  3. 如果目標物件沒有實現了介面,必須採用CGLIB庫,spring會自動在JDK動態代理和CGLIB之間轉換
  • 動態代理
    動態代理在生成動態位元組碼時,並不是通過實現類建立子類的方式,而是通過類所擁有的介面列表來完成,即構造出的類和實現類是沒有關係的
    在構造動態代理時,類必須基於介面實現動態代理,在最終獲得動態代理的的例項引用時,也只能用介面來獲取
    本質上還是將實現類作為引用傳入到Handler中,還是會呼叫實現類的方法,只是包裝了一層
    實現介面的方法,方法內再通過引用呼叫實現類的方法
    如果在內部方法再呼叫內部方法,那麼在第二層內部方法中是無法完成AOP切入的,那已經是直接呼叫了
  • 位元組碼增強
    如果在內部方法再呼叫內部方法,可以一直完成AOP切入,這是因為位元組碼增強是構建子類或者直接修改類的位元組碼,若是子類,由於子類覆寫父類方法,就一直呼叫被修改的子類方法,可以一直AOP;若是直接修改位元組碼,那就是方法修改了

Annotation

註解根本上還是依靠反射實現的

七、JDBC

7.1 JDBC註冊

驅動只需載入一次,不需要反覆載入,也不需要自己new,註冊的driver以列表的形式儲存,驅動載入也與classloader有關,所有也可以在不同的classloader載入同一驅動的不同版本
當程式中存在多個驅動時,DriverManager通過遍歷的方式查詢其classloader中所有的driver,與jdbcurl匹配,匹配上就返回

相關文章