Java性能問題一直困擾着廣大程序員,因爲平臺複雜性,要定位問題,找出其根源確實很難。隨着10多年Java平臺的改進以及新出現的多核多處理器,Java軟件的性能和擴展性已經今非昔比了。現代JVM持續演進,內建了更爲成熟的優化技術、運行時技術和垃圾收集器。與此同時,底層的硬件平臺和操做系統也在演化。java
目錄:程序員
現代大規模關鍵性系統中的Java性能調優,是一項富有挑戰的任務。你須要關注各類問題,包括算法結構、內存分配模式以及磁盤和文件I/O的使用方式。性能調優最困難的一般是找到問題所在,即使是經驗豐富的人也會被他們的直覺所誤導。性能殺手老是隱藏在最意想不到的地方。面試
這一次,我將在本文中着重介紹Java性能優化的一系列舉措,但願可以對如今的你有所幫助。以爲有幫助的朋友也能夠轉發、收藏如下。算法
一、善於利用 Java 中的設計模式:享元模式、代理模式、裝飾器模式等。詳見個人上一篇文章設計模式之我說數據庫
文章連接:https://www.toutiao.com/i6540181471888409102/設計模式
二、Java 中的緩衝區:數組
(1)緩衝最經常使用的場景就是提升 IO 速度:好比 BufferedWriter 能夠用來裝飾 FileWriter ,爲 FileWriter 加上緩衝。 BufferedOutputStream 能夠用來裝飾 FileOutputStream 。使用這兩個裝飾器時候能夠指定緩衝區大小,默認的 size 爲 8K 。緩存
(2)JavaNIO 中的各類 Buffer 類族,有更增強大的緩衝區控制功能。安全
(3)除了性能上的優化,緩衝區也能夠做爲上層組件和下層組件的一種通訊工具,將上層組件好下層組件進行解耦。好比生產者消費者模式中的緩衝區。性能優化
二、緩存:
(1)好比 Hibernate 採用的兩級緩存:一級緩存和二級緩存。二級緩存指的是 sessionFactory層面上的緩存, Hibernate 採用的是 EHCache 。一級緩存指的是 session 層面上的緩存。
三、對象複用技術 -- 池的使用
(1)數據庫鏈接池:較常使用的數據庫鏈接池組件是 C3P0 和 Proxool 。其中 C3P0 是伴隨 Hibernate 一塊兒發佈的, Hibernate 默認採用的數據庫鏈接池。
(2)線程池:自定義線程池以及 jdk1.5 提供的線程池組件。
四、並行代替串行。
五、時間換空間:不引入中間變量實現兩個數字的交換。代價是增長 CPU 運算。
六、空間換時間:使用下標數組排序。
經常使用的程序設計優化技巧:
一、字符串優化處理
(1)String 類的特色:不變性、針對常量池的優化( String.intern() 方法的意義)
(2)subString 方法的內存泄漏 :
(3)字符串分割和查找不要使用 split 函數,效率低,而是使用 StringTokenizer 或者 indexOf結合 subString() 函數完成分割。
(4)用 charAt ()方法代替 startWith ()方法。
(5)對於靜態字符串或者變量字符串的鏈接操做, Java 在編譯的時候會進行完全的優化,將多個鏈接操做的字符串在編譯時合成一個單獨的字符串,而不是生成大量的 String 實例。只生成一個對象。
(6)在無需考慮線程安全狀況下儘可能使用 StringBuilder 。
(7)StringBuffer 和 StringBuilder 初始化的時候均可以設置一個初始值,默認是 16B 。若是字符串的長度大於 16B 的時候,則須要進行擴容。擴容策略是將原有的容量大小翻倍,以新的容量申請內存空間,創建 char 數組,而後將數組中的內容複製到這個新的數組中,使用 Arrays.copyOf() 函數。所以,若是能預先評估 StringBuilder 的大小,則能夠節省這些複製操做,從而提升系統的性能。
二、List 接口
( 1 ) ArrayList 和 Vector 的區別:它們幾乎使用了相同的算法,它們的惟一區別是對多線程的支持。 ArrayList 是不安全的,而 Vector 是線程安全的。
( 2 ) LinkedList 和 ArrayList 的區別:
|---1 、 linkedList 採用鏈表實現,適合於數據刪除和插入很是頻繁的狀況,不適合隨機訪問。
|---2 、 ArrayList 採用數組實現,適用於隨機查找和順序讀的狀況,不適合刪除和插 入數據很是頻繁的場景。
(3)基於數組的 List 都會有一個容量參數。當 ArrayList 所存儲的元素容量超過其已有大小的時候就會進行擴容,數組的擴容會致使整個數組進行一次內存複製。所以合理的數組大小會減少數組擴容的次數從而提升系統性能。
(4)遍歷列表的時候儘可能使用迭代器,速度塊。
二、Map 接口:
(1)HashMap 的實現原理:簡單的說, HashMap 就是將 key 作 hash 算法,而後將 hash 值映射到內存地址,直接取得 key 所對應的數據。在 HashMap 中,底層數據結構使用的是數組,所謂的內存地址指的是數組的下標索引。
(2)容量參數與擴容:默認狀況下, hashmap 的初始容量爲 16 ,負載因子爲 0.75 ,也就是說當 hashmap 的實際容量達到了初始容量 * 負載因子( hashmap 內部維護的一個 threshold 值)的時候, hashmap 就會進行擴容。在擴容時,會遍歷整個 hashmap ,所以應該設置合理的初始大小和負載因子,能夠減少 hashmap 擴容的次數。
(3)LinkedHashMap-- 有序的 HashMap : HashMap 的最大缺點是其無序性,被存入到 Hashmap 中的元素,在遍歷 HashMap 的時候,其輸出不必定按照輸入的順序,而是 HashMap 會根據 hash 算法設定一個查找高效的順序。若是但願保存輸入順序,則須要使用 LinkedHashMap 。LinkedHashmap 在內部又增長了一個鏈表,用於保存元素的順序。
(4)LinkedList 能夠提供兩種類型的順序:一個是元素插入時候的順序,一個是最近訪問的順序。注意: LinkedHashMap 在迭代過程當中,若是設置爲按照最後訪問時間進行排序,即:每當使用 get() 方法訪問某個元素時,該元素便會移動到鏈表的尾端。可是這個時候會出現異常,所以, LinkedHashMap 工做在這種模式的時候,不能在迭代器中使用 get() 操做。
(5)關於 ConcurrentModificationException :該異常通常會在集合迭代過程當中被修改時拋出。所以,不要在迭代器模式中修改集合的結構。這個特性適合於全部的集合類,包括 HashMap 、 Vector 、 ArrayList 等。
(6)TreeMap-- 若是要對元素進行排序,則使用 TreeMap 對 key 實現自定義排序,有兩種方式:在 TreeMap 的構造函數中注入一個 Comparator 或者使用一個實現了 Comparable 的 key 。
(7)若是須要將排序功能加入 HashMap ,最好是使用 Treemap 而不是在應用程序自定義排序。
(8)HashMap 基於 Hash 表實現, TreeMap 基於紅黑樹實現。
3 、 Map 和 Set 的關係:
( 1 )全部 Set 的實現都只是對應的 Map 的一種封裝,其內部維護一個 Map 對象。即: Set只是相應的 Map 的 Value 是一種特殊的表現形式的一種特例。
( 2 ) Set 主要有三種實現類: HashSet 、 LinkedHashSet 、 TreeSet 。其中 HashSet 是基於 Hash 的快速元素插入,元素之間無序。 LinkedHashSet 同時維護着元素插入順序,遍歷集合的時候,老是按照先進先出的順序排序。 TreeSet 是基於紅黑樹的實現,有着高效的基於元素 Key 的排序算法。
4 、優化集合訪問代碼:
( 1 )、分離循環中被重複調用的代碼:例如, for 循環中使用集合的 size() 函數,則不該該把這個函數的調用放到循環中,而是放到循環外邊、
( 2 )、省略相同的操做:
5 、 RandomAccess 接口:經過 RandomAccess 可知道 List 是否支持隨機快速訪問。同時,若是應用程序須要經過索引下標對 List 作隨機訪問,儘可能 buyaoshiyongLinkedList , ArrayList 或者 Vector 能夠。
6 、 JavaNIO 的特性:
1 、爲全部的原始類型提供 Buffer 支持。
2 、使用 Java.nio.charset.Charset 做爲字符編碼解碼解決方案。
3 、增長通道抽象代替原有的 IO 流抽象。
4 、支持鎖和內存映射文件的文件訪問接口。
5 、提供基於 Selector 的異步網絡 IO 。
7 、 Java 中 NIO 的使用。 Channel 是一個雙向通道,便可讀也可寫。應用程序不能直接操做 Channel ,必須藉助於 Buffer 。例如讀數據的時候,必須把數據從通道讀入到緩衝區,而後在緩衝區中進行讀取。以文件讀取爲例,首先經過文件輸入流得到文件通道,而後把文件通道的內容讀入到緩衝區中,而後就能夠對緩衝區操做。
8 、 Buffer 的基本原理:
1 、 Buffer 的建立: Buffer 的靜態 allocate(int size) 方法或者 Buffer.wrap(byte[]src) 。
2 、 Buffer 的工做原理:三個變量: position ,表明當前緩衝區的位置,寫緩衝區的時候,將從 position 的下一個位置寫數據。 Capacity ,表明緩衝區的總容量上限。 Limit ,緩衝區的實際上限,也就是說,讀數據的時候,數據便是從 position 到 limit 之間的數據
3 、 flip 操做: limit=position,position=0, 通常是在讀寫切換的時候使用。寫完數據以後,須要限定下有效數據範圍,才能讀數據;
4 、 clear 操做: position-0 , limit=capacity. 。爲從新寫入緩衝區作準備。
5 、 rewind 操做: position=0 ,爲讀取緩衝區中有效數據作準備,一半 limit 已經被合理設置。
9 、讀寫緩衝區:
1 、 public byte get() :順序讀取緩衝區的一個字節, position 會加一
2 、 public Buffer get(byte[]dst): 將緩衝區中的數據讀入到數組 dst 中,並適當的移動 position
3 、 public byte get(int index) :獲得第 index 個字節,但不移動 posoiion
4 、 public ByteBuffer put(byte b) :將字節 b 放入到緩衝區中,並移動 position
5 、 public ByteBuffer put(int index,byte b) :將字節 b 放到緩衝區的 index 位位置
6 、 pubglic final ByteBuffer(byte[]src) :將字節數組 src 放到緩衝區中。
10 、標誌緩衝區:相似於一個書籤的功能,在數據的處理過程當中,可隨時記錄當前位置。而後在任意時刻,回到這個位置。 Mark 用於記錄當前位置, reset 用於恢復到 mark 所在的位置、
11 、複製緩衝區:使用 Buffer 的 duplicate 方法能夠複製一個緩衝區,副本緩衝區和原緩衝區共享一份空間可是有有着獨立的 position 、 capacity 和 limit 值。
20 、緩衝區分片:緩衝區分片使用 slice 方法實現。它將在現有的緩衝區中,建立的子緩衝區。子緩衝區和父緩衝區共享數據。這個方法有助於將系統模塊化。緩衝區切片能夠將一個大緩衝區進行分割處理,獲得的子緩衝區都具備緩衝的緩衝區模型結構;所以。這個操做有助於系統的模塊化。
12 、只讀緩衝區:只讀緩衝區能夠保證核心數據的安全,若是不但願數據被隨意篡改,返回一個只讀緩衝區是頗有幫助的。
13 、文件映射到內存: NIO 提供了一種將文件映射到內存的方法進行 IO 操做,這種方法比基於流 IO 快不少。這個操做主要由 FileChanne.map() 操做。使用文件內存的方式,將文本經過 FileChannel 映射到內存中。而後從內存中讀取數據。同時,經過修改 Buffer, 將對內存中數據的修改寫到對應的硬盤文件中。
14 、處理結構化數據:散射和彙集。散射就是將數據讀入到一組 bytebuffer 中,而彙集正好相反。經過 ScatteringByteChannel 和 GatheringByteChannel 能夠簡化對結構數據的操做。
15 、直接內存訪問: DirectBuffer 直接分配在物理內存中,並不佔用對空間,所以也不受對空間限制。 DirectBuffer 的讀寫操做比普通 Buffer 塊,由於 DirectBuffer 直接操縱的就是內核緩衝區。
16 、引用類型:強、軟、若、虛四種引用類型。
WeakHashMap :是弱引用的一中典型應用,它使用弱引用做爲內部數據的存儲方案。能夠做爲簡單的緩存表解決方案。
若是在系統中,須要一張很大的 Map 表, Map 中的表項做爲緩存之用。這也意味着即便沒能從該 Map 中取得相應地數據,系統也能夠經過選項方案獲取這些數據,雖然這樣會消耗更多的時間,可是不影響系統的正常運行。這個時候,使用 WeakHashMap 是最合適的。由於 WeakHashMap 會在系統內存範圍內,保存全部表項,而一旦內存不夠,在 GC 時,沒有被引用的又會很快被清除掉,避免系統內存溢出。
17 、有助於改善系統性能的技巧:
1 、慎用異常: for 循環中使用 try-catch 會大大下降系統性能
2 、使用局部變量:局部變量的訪問速度遠遠高於類的靜態變量的訪問速度,由於類的 變量是存在在堆空間中的。
3 、位運算代替乘除法:右移表明除以2、左移表明乘以二。
4 、有的時候考慮是否能夠使用數組代替位運算。
5 、一維數組代替二維數組。
6 、提取表達式:儘量讓程序少作重複的計算,尤爲要關注在循環體的代碼,從循環提中提取重複的代碼能夠有效的提高系統性能。
一、併發程序設計模式:
( 1 )、 Future-Callable 模式: FutureTask 類實現了 Runnable 接口,能夠做爲單獨的線程運行,其 Run 方法中經過 Sync 內部類調用 Callable 接口,並維護 Callable 接口的返回值。當調用FutureTask.get() 的時候將返回 Callable 接口的返回對象。 Callable 接口是用戶自定義的實現,經過實現 Callable 接口的 call() 方法,指定 FutureTask 的實際工做內容和返回對象。 Future 取得的結果類型和 Callable 返回的類型必須一致,這是由定義 FutureTask 的時候指定泛型保證的。 Callable 要採用 ExecutorSevice 的 submit 方法提交,返回的 future 對象能夠取消任務。
( 2 )、 Master-Worker 格式:其核心思想是系統由兩類進程協做工做: Master 進程和 Worker 進程。 Master 進程負責接收和分配任務, Worker 負責處理子任務。當各個子任務處理完成後,將結果返回給 Master 進程。由 Master 進程進行概括會彙總,從而獲得系統的最終結果。
( 3 )、保護暫停模式:其核心思想是僅當服務進程準備好時,才提供服務。設想一種場景,服務器會在很短期內承受大量的客戶端請求,客戶端請求的數量可能超過服務器自己的即時處理能力。爲了避免丟棄任意一個請求,最好的方式就是將這個客戶端進行排列,由服務器逐個處理。
( 4 )、不變模式:爲了儘量的去除這些因爲線程安全而引起的同步操做,提升並行程序性能 ,能夠使用一種不可變的對象,依靠對象的不變性,能夠確保在沒有同步操做的多線程環境中依然保持內部狀態的一致性和正確性。
( 5 )、 Java 實現不變模式的條件:
1) 、去除 setter 方法以及全部修改自身屬性的方法。
2 )、將全部屬性設置爲私有,並用 final 標記,確保其不可修改。
3 )、確保沒有子類能夠重載修改它的行爲。
4 )、有一個能夠建立完整對象的構造函數。
Java 中,不變模式的使用有: java.lang.String 類。以及全部的元數據類包裝類。
(6)、生產者 - 消費者模式:生產者進程負責提交用戶請求,消費者進程負責具體處理生產者進程提交的任務。生產者和消費者之間經過共享內存緩衝區進行通訊。經過 Java 提供和餓 BlockingQueue 能夠實現生產者消費者模式。
二、JDK 多任務執行框架:
( 1 )、簡單線程池實現:線程池的基本功能就是進行線程的複用。當系統接受一個提交的任務,須要一個線程時,並不着急當即去建立進程,而是先去線程池查找是否有空餘的進程,如有則直接使用線程池中的線程工做。若是沒有,則再去建立新的進程。待任務完成後,不是簡單的銷燬進程,而是將線程放入線程池的空閒隊列,等待下次使用。使用線程池以後,線程的建立和關閉一般由線程池維護,線程一般不會由於會執行晚一次任務而被關閉,線程池中的線程會被多個任務重複使用。
( 2 )、 Executor 框架: Executor 框架提供了建立一個固定線程數量的線程池、返回一個只有一個線程的線程池、建立一個可根據實際狀況進行線程數量調整的線程池、可調度的單線程池以及可變線程數量的可調度的線程池。
( 3 )、自定義線程池 : 使用 ThreadPoolExecutor 接口: ThreadPoolExecutor 的構造函數參數以下:
corePoolSize :指的是保留的線程池大小
maximumPoolSize : 指的是線程池的最大大小
keepAliveTime :指的是空閒線程結束的超時時間
Unit : 是一個枚舉,表示 keepAliveTime 的單位
workQueue : 表示存聽任務的隊列。
ThreadFactory :建立線程的時候,使用到的線程工廠
handler : 當線程達到最大限制,而且工做隊列裏面也已近存放滿了任務的時候,決定如何處理提交到線程池的任務策略
上述的幾種線程池的內部實現均使用了 ThreadPoolExecutor 接口。咱們能夠自定義提交可是未被執行的任務隊列被執行的順序,常見的有直接提交的隊列、有界的任務隊列、無界的任務隊列、優先任務隊列,這樣能夠在系統繁忙的時候忽略任務的提交前後次序,老是讓優先級高的任務先執行。使用優先隊列時,必須讓 target 實現 Comparable 接口。
(4)、優化線程池大小: NThreads=Ncpi*Ucpu*(1+W/C) , Java 中使用: Runtime.getRuntime().availableProcesses() 獲取可用的 CPU 數量。
三、JDK 併發數據結構:
( 1 )併發 List : Vector 或者 CopyOnWriteArrayList 是兩個線程安全的 List 實現。
CopyOnWriteArrayList 很好的利用了對象的不變性,在沒有對對象進行寫操做以前,因爲對象未發生改變,所以不須要加鎖。而在試圖改變對象的時候,老是先得到對象的一個副本,而後對副本進行修改,最後將副本寫回。 CopyOnWriteArrayList 適合讀多寫少的高併發場合。而 Vector適合高併發寫的場合。
( 2 )併發 Set : synchronizedSet 適合高併發寫的情景、 CopyOnWriteSet 適合讀多寫少的高併發場合。
( 3 )併發 Map : ConcurrentHashMap 是專門爲線程併發而設計的 HashMap ,它的 get 操做是無鎖的,其 put 操做的鎖粒度小於 SynchronizedHashMap ,所以其總體性能優於 SynchronizedHashMap 。
( 4 )併發 Queue :在併發隊列上, JDK 提供了兩種實現,一個是以 ConcurrentLinkedQueue 爲表明的高性能隊列,一個是以 BlockingQueue 接口爲表明的阻塞隊列。若是須要一個可以在高併發時,仍能保持良好性能的隊列,能夠使用 ConcurrentLinkedQueue 對象。而 BlockingQueue的主要適用場景就是生產者消費者模式中的實現數據共享。 BlockingQueue 接口主要有兩種實現: ArrayBlockingQueue 是一種基於數組的阻塞隊列實現,也就是說其內部維護着一個定長數組,用於緩存隊列中的數據對象。 LinkedBlockingQueue 則使用一個鏈表構成的數據緩衝隊列。
4 、併發控制方法:
( 1 )、 Java 中的內存模型與 Volatile :在 Java 中,每個線程有一塊工做內存區,其中存放着被全部線程共享的主內存中的變量的值的拷貝。當線程執行時,它在本身的內存中操做變量。爲了存取一個共享的變量,一個線程一般要先獲取鎖定而且清除它的內存緩衝區,這保證該共享變量從全部線程的共享內存區正確地裝入到線程的工做內存區;當線程解鎖時保證該工做內存區中變量的值寫回到共享內存中。
( 2 )、 Volatile 關鍵字:聲明爲 Volatile 的變量能夠作如下保證:
1 )、其餘線程對變量的修改,能夠隨即反應在當前進程中。
2 )、確保當前線程對 Volatile 變量的修改,能隨即寫回到共享主內存中,並被其餘線程所見
3 )、使用 Volatile 聲明的變量,編譯器會保證其有序性。
4 )、 double 和 long 類型的非原子處理:若是一個 double 類型或者 long 類型的變量沒有被聲明爲 volatile 類型,則變量在進行 read 和 write 操做的時候,主內存會把它當成兩個 32 位的read 或者 write 操做。所以,在 32 爲操做系統中,必須對 double 或者 long 進行同步
緣由在於:使用 Volatile 標誌變量,將迫使全部線程均讀寫主內存中的對應變量,從而使得 Volatile 變量在多線程間可見。
(3)、同步關鍵字 -Synchronized ,其本質是一把鎖: Synchronized 關鍵字能夠做用在方法或者代碼塊中。看成用的是成員方法時,默認的鎖是該對象 this ,這個時候通常在共享資源上進行Synchronized 操做。該關鍵字通常和 wait ()和 notify ()方法一塊兒使用,調用這兩個方法的時候通常指的是資源自己。因爲全部的對象都能當成資源,所以這兩個方法是從 Object 繼承而來的,而不是 Thread 或者 Runnable 才具備的方法。
(4)、 ReentrantLock 鎖:比 Synchronized 的功能更強大,可中斷、可定時。全部使用內部鎖實現的功能,均可以使用重入鎖實現。重入鎖必須放入 finally 塊中進行釋放,而內部鎖能夠自動釋放。 重入鎖有着更強大的功能,好比提供了鎖等待時間 (boolean tryLock(long time.TimeUnit unit)) 、支持鎖中斷 (lockInterruptibly()) 和快速鎖輪詢 (boolean tryLock()) 以及一套 Condition 機制,這個機制相似於內部鎖的 wait() 和 notify() 方法。
(5)、 ReadWriteLock :讀寫分列鎖。若是 系統中讀操做次數遠遠大於寫操做,而讀寫鎖就能夠發揮巨大的做用。
(6)Condition 對象: await() 方法和 signal() 方法。 Condition 對象須要和重入鎖( ReentrantLock )配合工做以完成多線程協做的控制。
(7)Semaphore 信號量:信號量爲多線程寫做提供了更爲強大的控制方法。廣義上講,信號量是對鎖的擴展。不管是內部鎖( Synchronized )仍是重入鎖( ReentrantLock ),一次都只容許一個進程訪問一個資源。而信號量卻能夠指定多個線程同時訪問某一個資源。
(8)ThreadLocal 線程局部變量: ThreadLocal 是一種多線程間併發訪問變量的解決方案。與synchronized 等加鎖方式不一樣, ThreadLocal 徹底不提供鎖,而使用以空間換時間的手段,爲每一個線程提供變量的獨立副本,以保障線程安全,所以並非一種數據共享的解決方案。
五、同步工具類:
( 1 ) CountDownLatch (閉鎖):確保一個服務不會開始,直到它依賴的其餘服務都準備就緒。 CountDownLatch 做用猶如倒計時計數器,調用 CountDownLatch 對象的 countDown 方法就將計數器減 1 ,當計數到達 0 時,則全部等待者或單個等待者開始執行。好比有 10 個運動員的田徑比賽 , ,有兩個裁判 A 和 B , A 在起點吹哨起跑, B 在終點記錄記錄並公佈每一個運動員的成績。剛開始的時候,運動員們都趴在跑道上( A.await() )等到裁判吹哨。 A 吹哨耗費了 5 秒,此時調用 A.countDown() 方法將等待時間減爲 4 秒。當減爲 0 的時候,全部的運動員開始起跑。這個時候, B 裁判開始工做。啓動一個初始值爲 10 的定時器,每當有一個運動員跑到重點的時候,就將計數器減一,表明已經有一個運動員跑到終點。當計時器爲 0 的時候,表明全部的運動員都跑到了終點。此時能夠根據公佈成績了。
( 2 ) CylicBarrier (關卡):
1 )、相似於閉鎖,它們可以阻塞一組線程直到某些事件發生
2 )、與同步鎖的不一樣之處是一個能夠重用,一個不能夠重用
3 )、全部線程必須同時到達關卡點,才能繼續處理。
相似組團旅遊,導遊就是一個關卡。表示你們彼此等待,你們集合好後纔開始出發,分散活動後又在指定地點集合碰面,這就比如整個公司的人員利用週末時間集體郊遊同樣,先各自從家出發到公司集合後,再同時出發到公園遊玩,在指定地點集合後再同時開始就餐。
( 3 ) Exchanger :使用在兩個夥伴線程之間進行數據交換,這個交換對於兩個線程來講都是安全的。
講解 Exchanger 的比喻:比如兩個毒販要進行交易,一手交錢、一手交貨,無論誰先來到接頭地點後,就處於等待狀態了,當另一方也到達了接頭地點(所謂到達接頭地點,也就是到到達了準備接頭的狀態)時,二者的數據就當即交換了,而後就又能夠各忙各的了。
exchange 方法就至關於兩手高高舉着待交換物,等待人家前來交換,一旦人家到來(即人家也執行到 exchange 方法),則二者立馬完成數據的交換。
五、關於死鎖:
(1)死鎖的四個條件:
1) 、互斥條件:一個資源只能被一個線程使用:
2 )、請求與保持條件:一個線程因請求資源而阻塞時,對已得到則資源保持不放。
3 )、不剝奪條件:進程已經得到的資源,在未使用完以前,不能強行剝奪。
4 )、循環等待條件:若干個線程已經造成了一種頭尾相接的循環等待資源關係。
(2)常見的死鎖:靜態順序死鎖、動態順序死鎖、協做對象間的死鎖、線程飢餓死鎖。
(3)如何儘可能避免死鎖:
1 )、制定鎖的順序,來避免死鎖
2 )、嘗試使用定時鎖( lock.tryLock(timeout) )
3 )、在持有鎖的方法中進行其餘方法的調用,儘可能使用開放調用(當調用方法不須要持有鎖時,叫作開放調用)
4 )、減小鎖的持有時間、減少鎖代碼塊的粒度。
5 )、不要將功能互斥的 Task 放入到同一個 Executor 中執行。
6 、 代碼層面對鎖的優化機制:
1 、避免死鎖
2 、減小鎖持有時間,代碼塊級別的鎖,而不是方法級別的鎖
3 、減少鎖粒度, ConcurrentHashMap 分段加鎖
4 、讀寫鎖代替獨佔鎖
5 、鎖分離,例如 LinkedBlockingQueue 的尾插頭出的特色,用兩把鎖 (putLock takeLock) 分離兩種操做。
6 、重入鎖和內部鎖
重入鎖( ReentrantLock )和內部鎖( Synchronized ):全部使用內部鎖實現的功能,均可以使用重入鎖實現。重入鎖必須放入 finally 塊中進行釋放,而內部鎖能夠自動釋放。
重入鎖有着更強大的功能,好比提供了鎖等待時間 (boolean tryLock(long time.TimeUnit unit))、支持鎖中斷 (lockInterruptibly()) 和快速鎖輪詢 (boolean tryLock()) 以及一套 Condition 機制,這個機制相似於內部鎖的 wait() 和 notify() 方法。想要獲取多線程面試題的能夠加羣:650385180,面試題及答案在羣的共享區。
7 、鎖粗化:虛擬機在遇到一連串連續的對同一個鎖不斷進行請求和釋放從操做的時候,便會把全部的鎖操做整合成對鎖的一次請求,從而減小對鎖的請求同步次數。
7 、 Java 虛擬機層面對鎖的優化機制:
一、自旋鎖:因爲線程切換(線程的掛起和恢復)消耗的時間較大,則使線程在沒有得到鎖時,不被掛起,而轉而執行一個空循環。在若干空循環後,線程若是得到了鎖,而繼續執行,若線程依然不能得到鎖,而才被掛起。
二、鎖消除: JVM 經過對上下文的掃描,去除不可能存在共享資源競爭的鎖,這樣能夠節省毫無心義的請求鎖時間。好比單線程中或者非共享資源的常使用的 StringBuffer 和 Vector 。
三、鎖偏向:若某一個鎖被線程獲取後,便進入偏向模式,當線程再次請求這個鎖時,無需進行相關的同步操做,從而節省了操做時間。
8 、 Java 無鎖實現併發的機制:
( 1 )非阻塞的同步 / 無鎖: ThreadLocal ,讓每一個進程擁有各自獨立的變量副本,所以在並行計算時候,無須相互等待而形成阻塞。 CVS 算法的無鎖併發控制方法。
( 2 )原子操做: java.util.concurrent.atomic 包。
1 、 JVM 運行時數據區域。
( 1 )、程序計數器:每個 Java 線程都有一個程序計數器來用於保存程序執行到當前方法的哪個指令。此內存區域是惟一一個在 JVM Spec 中沒有規定任何 OutOfMemoryError 狀況的區域。
( 2 )、 Java 虛擬機棧:該塊內存描述的是 Java 方法調用的內存模型,每一個方法在被執行的時候,都會同時建立一個幀( Frame )用於存儲本地變量表、操做棧、動態連接、方法出入口等信息。
( 3 )、本地方法棧。本地方法調用的內存模型。
( 4 )、 Java 堆。 Java 中的對象以及類的靜態變量的存放地方。
( 5 )、方法區:方法區中存放了每一個 Class 的結構信息,包括常量池、字段描述、方法描述等等
( 6 )、運行時常量池: Class 文件中除了有類的版本、字段、方法、接口等描述等信息外,還有一項信息是常量表 (constant_pool table) ,用於存放編譯期已可知的常量,這部份內容將在類加載後進入方法區(永久代)存放。可是 Java 語言並不要求常量必定只有編譯期預置入 Class 的常量表的內容才能進入方法區常量池,運行期間也可將新內容放入常量池(最典型的 String.intern() 方法)。運行時常量池是方法區的一部分,天然受到方法區內存的限制,當常量池沒法在申請到內存時會拋出 OutOfMemoryError 異常。
( 7 )、本機直接內存( Direct Memory )
在 JDK1.4 中新加入了 NIO 類,引入一種基於渠道與緩衝區的 I/O 方式,它能夠經過本機 Native 函數庫直接分配本機內存,而後經過一個存儲在 Java 堆裏面的 DirectByteBuffer 對象做爲這塊內存的引用進行操做。這樣能在一些場景中顯著提升性能,由於避免了在 Java 對和本機堆中來回複製數據。
二、Java 類加載機制的特色:
(1)基於父類的委託機制:運行一個程序時,老是由 AppClass Loader (系統類加載器)開始加載指定的類,在加載類時,每一個類加載器會將加載任務上交給其父,若是其父找不到,再由本身去加載, Bootstrap Loader (啓動類加載器)是最頂級的類加載器了,其父加載器爲 null 。若是父類加載器找不到給定的類名,則交由子加載器去加載,若是最低一層的子加載器也沒法找到,則拋出異常。
(2)全盤負責機制:所謂全盤負責,就是當一個類加載器負責加載某個 Class 時,該 Class 鎖依賴的和引用的其餘 Class 也將由該類加載器負責載入,除非顯式使用另一個類加載器來載入。
(3)緩存機制:緩存機制將會保證全部加載過的 Class 對象都會被緩存,當程序中須要使用某個 Class 時,類加載器會先從緩衝區中搜尋該 Class ,只有當緩存區中不存在該 Class 對象時,系統纔會讀取該類對應的二進制數據,並將其轉化爲 Class 對象,存入緩存區中。這就是爲何修改了 Class 後,必須從新啓動 JVM ,程序所作的修改纔會生效的緣由。同時,往們比較 A.getClass() 與 B.getClass() 是否相等時,直接使用 == 比較,由於緩存機制保證類的字節碼在內存中只可能存在一份。
(4)類加載器的三種方法以及其區別:
1)、命令行啓動應用時候由 JVM 初始化加載
2)、經過 Class.forName() 方法動態加載
3)、經過 ClassLoader.loadClass() 方法動態加載 // 使用 Class.forName() 來加載類,默認會執行初始化塊 , // 使用 Class.forName() 來加載類,並指定 ClassLoader ,初始化時不執行靜態塊。
4)區別:使用 ClassLoader.loadClass() 來加載類,不會執行初始化塊,
3 、類的主動引用
什麼狀況下須要開始類加載過程的第一個階段,也即類的初始化階段。 Java 虛擬機規定了有且只有 5 種狀況下必須當即對類進行初始化:
(1)、遇到 new 、 getstatic 、 putstatic 或 invokestatic 這四條字節碼指令時,若是類沒有進行過初始化,則須要觸發其初始化。(並且初始化的時候按照先父後子的順序)。這四條指令最多見的 Java 代碼場景是:使用 new 關鍵字實例化對象的時候、讀取或設置一個類的靜態字段(被 final 修飾,已在編譯時期把結果放入常量池的靜態字段除外)、調用一個類的靜態方法的的時候。
(2)使用 java.lang.reflect 包的方法對類進行反射調用的時候,若是類沒有進行過初始化,則須要先對其進行初始化。
(3)當初始化一個類的時候,若是發現其父類尚未進行過初始化,則須要先觸發其父類的初始化。可是一個接口在初始化時,並不要求其父類接口所有都完成了初始化,只有在真正使用到父接口的時候(如引用接口中定義的常量)纔會被初始化。
(4)當虛擬機啓動時,用戶須要指定一個要執行的主類(包含 main ()方法的那個類),虛擬機會先初始化這個主類。
(5)當使用 jdk1.7 的動態語言支持時,若是一個 java.lang.invoke.MethodHandle 實例最後的解析結果 REF_getStatic 、 REF_putStatic 、 REF_invokeStatic 的方法句柄,而且這個方法句柄所對應的類沒有初始化過,則須要先觸發其初始化。
4 、類的被動引用
一、對於靜態字段,只有直接定義這個字段的類纔會被初始化,所以經過其子類來引用父類中定義的靜態字段,只會觸發父類的初始化而不會觸發子類的初始化。
二、經過數組定義來引用類,不會觸發類的初始化 SuperClass[]sca=new SuperClass[10].
三、常量在編譯階段會存入調用類的常量池中,本質上並無直接引用到定義常量的類,所以不會觸發常量的類的初始化。
5 、 Java 對象的建立過程以及如何保證對象建立的多線程的安全性:
虛擬機遇到一條 new 指令時,首先將去檢查這個指令的參數是否能在常量池中定位到一個類的符號引用,而且檢查這個符號引用表明的類是否已經被加載、解析和初始化過。若是沒有則進行類加載過程。
在類加載經過後,接下來虛擬機將爲新生對象分配內存。對象所需的內存的大小在類加載完成後即可徹底肯定。爲對象分配空間的任務等價於把一塊肯定大小的內存從 Java 堆中劃分出來。
保證多線程的安全性。 有兩種方案,一種是對分配內存的動做進行同步操做,實際上虛擬機採用 CAS 加上失敗重試的方式保證更新操做的原子性。另外一種是把內存分配的動做按照線程劃分在不一樣的空間中進行。即爲每一個線程在 Java 堆中預先分配一小塊內存,成爲本地線程分配緩衝( TLAB )。哪一個線程要分配內存,就在哪一個 TLAB 上分配,只有 TLAB 用完並分配新的 TLAB時,才須要分配新的 TLAB 。
六、 何時判斷一個對象能夠被回收?
用可達性分析算法。這個算法的基本思路就是經過一系列的成爲「 GC roots 」的對象做爲起始點,從這些節點開始向下搜索,若是一個對象到 GCroots 沒有任何引用鏈相連,則證實此對象是不可用的。可做爲 GCroots 的對象包括虛擬機棧中引用的對象、方法區中常量引用的對象、方法區中靜態屬性引用的對象或者本地方法棧中 JNI 引用的對象,這些對象的共同點都是生命週期與程序的生命週期同樣長,通常不會被 GC 。判斷一個對象死亡,至少經歷兩次標記過程:若是對象在進行可達性算法後,發現沒有與 GC Roots 相鏈接的引用鏈,那他將會被第一次標記,並在稍後執行其 finalize ()方法。執行是有機會,並不必定執行。稍後 GC 進行第二次標記,若是第一次標記的對象在 finalize ()方法中拯救本身,好比把本身賦值到某個引用上,則第二次標記時它將被移除出「即將回收」的集合,若是這個時候對象尚未逃脫,那基本上就會被 GC 了。
7 、 關於 finalize ()方法的做用的說明:
finalize ()方法的工做原理理論上是這樣的:一旦垃圾回收器準備好釋放佔用的存儲空間,將首先調用其 finalize ()方法,而且在下一次垃圾回收動做發生時,纔會真正回收對象佔用的內存,因此使用 finalize ()的目的就是在垃圾回收時刻作一些重要的清理工做。咱們知道,使用 GC 的惟一緣由就是回收程序再也不使用的內存,因此對於與垃圾回收有關的任何行爲來講,包括 finalize() 方法,它們也必須同內存及其回收有關。我的認爲 Java 對象的 finalize ()方法有兩個做用( 1 )回收經過建立對象方式之外的方式爲對象分配了存儲空間。好比,好比在 Java 代碼中採用了 JNI 操做,即在內存分配時,採用了相似 C 語言中的 malloc 函數來分配內存,並且沒有調用free 函數進行釋放。此時就須要在 finalize ()中用本地方法調用 free 函數以釋放內存。( 2 )對象終結條件的驗證,即用來斷定對象是否符合被回收條件。好比,若是要回收一個對象,對象被清理時應該處於某種狀態,好比說是一個打開的文件,在回收以前應該關閉這個文件。只要對象中存在沒有被適當清理的部分, finalize ()就能夠用來最終法相這種狀況。由於對象在被清理的時候確定處於生命週期的最後一個階段,若是此時還含有一些未釋放的資源,則有能力釋放這些資源。這個不是 C/C++ 裏面的析構函數,它運行代價高昂,不肯定性大,沒法保證各個對象的調用順序。須要關閉外部資源之類的事情,基本上它能作的使用 try-finally 能夠作的更好。
8 、 一個類被回收的條件。
(1)、該類全部的實例都已經爲 GC ,也就是說 JVM 中不存在該 Class 的任何實例。
(2)、加載該類的 ClassLoader 已經被 GC 。
(3)該類對應的 java.lang.Class 對象沒有在任何地方被引用,如不能在任何地方經過反射訪問類的方法。
九、 垃圾回收算法 :
(1)、標記 - 清除算法:標記階段根據根節點標記全部從根節點開始的可達對象。則未被標記的對象就是未被引用的垃圾對象,而後在清除階段,清楚全部未被標記的對象。其最大缺點是空間碎片。
(2)、複製算法:將原有的內存空間分爲兩塊,每次只使用其中一塊,在垃圾回收時,將正在使用的內存中的存活對象複製到未使用的內存塊中,以後清楚正在使用的內存快中的全部對象,而後交換兩個內存的角色。完成垃圾回收。這種算法比較適合新生代,由於在新生代,垃圾對象一般會多於存活對象,複製算法效果較好。 Java 的新生代串行 GC 中,就使用了複製算法的思想。新生代分爲 eden 空間、 from 空間和 to 空間三個部分。 From 和 to 空間能夠視爲用於複製的兩塊大小相同、地位相等、且能夠進行角色互換的空間塊。 From 和 to 空間也成爲 survivor 空間,即倖存者空間,用於存放未被回收的對象。
(3)、標記 - 壓縮算法:標記過程與標記清楚算法同樣,但後續不是直接對可回收對象進行清理,而是讓全部存活的對象向一段移動,而後直接清理掉端邊界之外的內存。適合老年代的回收。
(4)、分代收集算法。
10 、 垃圾回收器。
( 1 )、 Serial 收集器
單線程收集器,收集時會暫停全部工做線程(咱們將這件事情稱之爲 Stop The World ,下稱 STW ),使用複製收集算法,虛擬機運行在 Client 模式時的默認新生代收集器。
(2)、 ParNew 收集器就是 Serial 的多線程版本,除了使用多條收集線程外,其他行爲包括算法、 STW 、對象分配規則、回收策略等都與 Serial 收集器一摸同樣。對應的這種收集器是虛擬機運行在 Server 模式的默認新生代收集器,在單 CPU 的環境中, ParNew 收集器並不會比 Serial 收集器有更好的效果。
(3)Parallel Scavenge 收集器(下稱 PS 收集器)也是一個多線程收集器,也是使用複製算法,但它的對象分配規則與回收策略都與 ParNew 收集器有所不一樣,它是以吞吐量最大化(即 GC 時間佔總運行時間最小)爲目標的收集器實現,它容許較長時間的 STW 換取總吞吐量最大化。
(4)4.Serial Old 收集器 Serial Old 是單線程收集器,使用標記-整理算法,是老年代的收集器
(5) Parallel Old 收集器
老年代版本吞吐量優先收集器,使用多線程和標記-整理算法, JVM 1.6 提供,在此以前,新生代使用了 PS 收集器的話,老年代除 Serial Old 外別無選擇,由於 PS 沒法與 CMS 收集器配合工做。
(6)CMS ( Concurrent Mark Sweep )收集器
CMS 是一種以最短停頓時間爲目標的收集器,使用 CMS 並不能達到 GC 效率最高(整體 GC時間最小),但它能儘量下降 GC 時服務的停頓時間,這一點對於實時或者高交互性應用(譬如證券交易)來講相當重要。
( 7 )、 G1 收集器。
11 、內存分配與回收策略:
( 1 )、規則一:一般狀況下,對象在 eden 中分配。當 eden 沒法分配時,觸發一次 Minor GC 。
( 2 )、規則二:配置了 PretenureSizeThreshold 的狀況下,對象大於設置值將直接在老年代分配。
( 3 )、規則三:在 eden 通過 GC 後存活,而且 survivor 能容納的對象,將移動到 survivor 空間內,若是對象在 survivor 中繼續熬過若干次回收(默認爲 15 次)將會被移動到老年代中。回收次數由 MaxTenuringThreshold 設置。
( 4 )、規則四:若是在 survivor 空間中相同年齡全部對象大小的累計值大於 survivor 空間的一半,大於或等於該年齡的對象就能夠直接進入老年代,無需達到 MaxTenuringThreshold 中要求的年齡。
( 5 )、規則五:在 Minor GC 觸發時,會檢測以前每次晉升到老年代的平均大小是否大於老年代的剩餘空間,若是大於,改成直接進行一次 Full GC ,若是小於則查看 HandlePromotionFailure 設置看看是否容許擔保失敗,若是容許,那仍然進行 Minor GC ,若是不容許,則也要改成進行一次 Full GC 。
十一、 關於 Minor GC 與 Full GC
Java 堆,分配對象實例所在空間,是 GC 的主要對象。分爲新生代 (Young Generation/New)和老年代 (Tenured Generation/Old) 。新生代又劃分紅 Eden Space 、 From Survivor/Survivor 0 、
To Survivor/Survivor 1 。
新生代要如此劃分是由於新生代使用的 GC 算法是複製收集算法。新生代使用賦值收集算法,可是爲了內存利用率,只使用一個 Survivor 空間來做爲輪轉備份(之因此把該空間分爲 FromSpace 和 ToSpace 兩部分是爲了在 Minor GC 的時候把一些 age 大的對象重新生代空間中複製到老年代空間中)這種算法效率較高,而 GC 主要是發生在對象常常消亡的新生代,所以新生代適合使用這種複製收集算法。因爲有一個假設:在一次新生代的 GC(Minor GC) 後大部分的對象佔用的內存都會被回收,所以留存的放置 GC 後仍然活的對象的空間就比較小了。這個留存的空間就是 Survivor space : From Survivor 或 To Survivor 。這兩個 Survivor 空間是同樣大小的。例如,新生代大小是 10M(Xmn10M) ,那麼缺省狀況下 (-XX:SurvivorRatio=8) , Eden Space 是 8M , From 和 To 都是 1M 。
在 new 一個對象時,先在 Eden Space 上分配,若是 Eden Space 空間不夠就要作一次 Minor GC 。 Minor GC 後,要把 Eden 和 From 中仍然活着的對象們複製到 To 空間中去。若是 To 空間不能容納 Minor GC 後活着的某個對象,那麼該對象就被 promote 到老年代空間。從 Eden 空間被複制到 To 空間的對象就有了 age=1 。此 age=1 的對象若是在下一次的 Minor GC 後仍然存活,它還會被複制到另外一個 Survivor 空間 ( 若是認爲 From 和 To 是固定的,就是又從 To 回到了From 空間 ) ,而它的 age=2 。如此反覆,若是 age 大於某個閾值 (-XX:MaxTenuringThreshold=n),那個該對象就也能夠 promote 到老年代了。
若是 Survivor 空間中相同 age( 例如, age=5) 對象的總和大於等於 Survivor 空間的一半,那麼 age>=5 的對象在下一次 Minor GC 後就能夠直接 promote 到老年代,而不用等到 age 增加到閾值。
在作 Minor GC 時,只對新生代作回收,不會回收老年代。即便老年代的對象無人索引也將仍然存活,直到下一次 Full GC 。
在發生 Minor GC 以前,虛擬機會先檢查老年代最大可用的連續空間是否大於新生代全部對象總空間,若是這個條件成立,那麼 Minor GC 能夠確保是安全的。若是通過 Minor GC 後仍有大量對象存活的狀況,則須要老年代進行分配擔保,把 Survior 沒法容納的對象直接進入老年代。
13 、四種引用類型:
( 1 )、強引用:直接關聯,虛擬機永遠不會回收。
( 2 )、軟引用:描述一些還有用但並不是必須的對象,虛擬機會在拋出內存溢出異常以前會對 這些對象進行第二次回收。
( 3 )弱引用:虛擬機必定會回收的對象
( 4 )虛引用:爲一個對象設置虛引用關聯的惟一目的就是能在這個對象被收集器回收時收到一個系統通知。
14 、關於 Java 中生成對象的 4 種方式與區別:
( 1 )、使用 new 操做符,這是最廣泛的一種(會調用相應的構造函數):
如: String s=new String("abc");
( 2 )使用反射動態生成( 會調用相應的構造函數 ):
利用 Class , ClassLoader , Constructor 中的方法能夠動態的生成類實例
如: Object o=Class.forName("java.lang.String").newInstance();
Object o=String.class.getClassLoader.loadClass("java.lang.String").newInstance();
以上的方式須要目標類擁有公有無參構造函數
如下使用 Constructor 進行動態生成
class User{
public User(String user,Integer id){}
}
Constructor c=User.class.getConstructor(new Class[]{String.class,Integer.class});
User user=(User)c.newInstance(new Object[]{"zhang san",123});
( 3 )使用克隆生成對象( 不會調用構造函數 )
例如使用一個實現了 Cloneable 接口的對象,調用其 clone() 方法得到該對象的一份拷貝,使用 Java 序列化方式實現深拷貝。
( 4 )利用反序列化從流中生成對象( 不會調用構造函數 ):
利用 ObjectInptuStream 的 readObject() 方法生成對象
1 、關於 Java 序列化與反序列化:
(1)做用:
一、實現對象狀態的保存到本地,以便下一次啓動虛擬機的時候直接讀取保存的序列化字節生成對象,而不是初始化對象; 2 、實現對象的網絡傳輸( RMI 分佈對象); 3 、實現對象的深拷貝。
一:對象序列化能夠實現分佈式對象。主要應用例如: RMI 要利用對象序列化運行遠程主機上的服務,就像在本地機上運行對象時同樣。
二: java 對象序列化不只保留一個對象的數據,並且遞歸保存對象引用的每一個對象的數據。能夠將整個對象層次寫入字節流中,能夠保存在文件中或在網絡鏈接上傳遞。利用對象序列化能夠進行對象的 " 深複製 " ,即複製對象自己及引用的對象自己。序列化一個對象可能獲得整個對象序列。
(2)基本方式:
ObjectOutputStream 只能對 Serializable 接口的類的對象進行序列化。默認狀況下, ObjectOutputStream 按照默認方式序列化,這種序列化方式僅僅對對象的非 transient 的實例變量進行序列化,而不會序列化對象的 transient 的實例變量,也不會序列化靜態變量。
當 ObjectOutputStream 按照默認方式反序列化時,具備以下特色:
1 ) 若是在內存中對象所屬的類尚未被加載,那麼會先加載並初始化這個類。若是在 classpath 中不存在相應的類文件,那麼會拋出 ClassNotFoundException ;
2 ) 在反序列化時不會調用類的任何構造方法。
若是用戶但願控制類的序列化方式,能夠在可序列化類中提供如下形式的 writeObject() 和 readObject() 方法。
private void writeObject(java.io.ObjectOutputStream out) throws IOException
private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException;
當 ObjectOutputStream 對一個 Customer 對象進行序列化時,若是該對象具備 writeObject() 方法,那麼就會執行這一方法,不然就按默認方式序列化。在該對象的 writeObjectt() 方法中,能夠先調用 ObjectOutputStream 的 defaultWriteObject() 方法,使得對象輸出流先執行默認的序列化操做。同理可得出反序列化的狀況,不過此次是 defaultReadObject() 方法。
有些對象中包含一些敏感信息,這些信息不宜對外公開。若是按照默認方式對它們序列化,那麼它們的序列化數據在網絡上傳輸時,可能會被不法份子竊取。對於這類信息,能夠對它們進行加密後再序列化,在反序列化時則須要解密,再恢復爲原來的信息。
默認的序列化方式會序列化整個對象圖,這須要遞歸遍歷對象圖。若是對象圖很複雜,遞歸遍歷操做須要消耗不少的空間和時間,它的內部數據結構爲雙向列表。
在應用時,若是對某些成員變量都改成 transient 類型,將節省空間和時間,提升序列化的性能。
|-1 、實體對象實現 seriable 接口以及自定義 seriousid 。
|-2 、 ObjectOutputStream out= new ObjectOutputStream(baos);
out.writeObject(new PersonDemo("rollen", 20));
out.close();
|-3 、 ByteArrayInputStream bais=new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream input=new ObjectInputStream(bais);
Object obj =input.readObject();
input.close();
(3)Java 自定義序列化反序列化:複寫實現了 seriliable 的實體類的 readObject() 和 writeObject() 的方法的緣由:
有些對象中包含一些敏感信息,這些信息不宜對外公開。若是按照默認方式對它們序列化,那麼它們的序列化數據在網絡上傳輸時,可能會被不法份子竊取。對於這類信息,能夠對它們進行加密後再序列化,在反序列化時則須要解密,再恢復爲原來的信息。此時便不能使用默認的 readObject 和 writeObject() 方法。
private void writeObject(java.io.ObjectOutputStream out) throws IOException{
out.defaultWriteObject();
out.writeUTF(name);
}
private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException{
in.defaultReadObject();
name=in.readUTF();
}
通常狀況直接實現 Serializable 接口就能夠實現序列化的要求,可是有些狀況須要對序列化作一些特殊的要求。
(4)Transits 關鍵字的做用:屏蔽一些不想進行序列化的成員變量,解屏蔽的方法能夠用( 3)
(5)Externalize 的做用:
Externalizable 接口繼承自 Serializable 接口,若是一個類實現了 Externalizable 接口,那麼將徹底由這個類控制自身的序列化行爲。 Externalizable 接口聲明瞭兩個方法:
public void writeExternal(ObjectOutput out) throws IOException
public void readExternal(ObjectInput in) throws IOException , ClassNotFoundException
前者負責序列化操做,後者負責反序列化操做。
在對實現了 Externalizable 接口的類的對象進行反序列化時, 會先調用類的不帶參數的構造方法,這是有別於默認反序列方式的。若是把類的不帶參數的構造方法刪除 ,或者把該構造方法的訪問權限設置爲 private 、默認或 protected 級別,會拋出 java.io.InvalidException: no valid constructor 異常。
(6)與 Java 構造函數的關係:
實現了 Externalizable 接口的類的對象進行反序列化時,會先調用類的不帶參數的構造方法;而實現了 Serializable 接口的類的對象進行反序列化時,不會調用任何構造方法。僅僅是根據所保存的對象的狀態信息,在內存中從新構建對象!
(7)注意事項:
1) 、序列化運行時使用一個稱爲 serialVersionUID 的版本號與每一個可序列化類相關聯,該序列號在反序列化過程當中用於驗證序列化對象的發送者和接收者是否爲該對象加載了與序列化兼容的類。爲它賦予明確的值。顯式地定義 serialVersionUID 有兩種用途:
在某些場合,但願類的不一樣版本對序列化兼容,所以須要確保類的不一樣版本具備相同的 serialVersionUID ;
在某些場合,不但願類的不一樣版本對序列化兼容,所以須要確保類的不一樣版本具備不一樣的 serialVersionUID 。
2)、 java 有不少基礎類已經實現了 serializable 接口,好比 string,vector 等。可是好比 hashtable 就沒有實現 serializable 接口。
3)、並非全部的對象均可以被序列化。因爲安全方面的緣由一個對象擁有 private,public 等 field, 對於一個要傳輸的對象 , 好比寫到文件 , 或者進行 rmi 傳輸等等 , 在序列化進行傳輸的過程當中 ,這個對象的 private 等域是不受保護的;資源分配方面的緣由 , 好比 socket,thread 類 , 若是能夠序列化 , 進行傳輸或者保存 , 也沒法對他們進行從新的資源分配 , 並且 , 也是沒有必要這樣實現 .
4)、反序列化對象時,並不會調用該對象的任何構造方法,僅僅是根據所保存的對象的狀態信息,在內存中從新構建對象!
5)、當一個對象被序列化時,只保存對象的非靜態成員變量,不能保存任何的成員方法和靜態的成員變量
6)、若是一個對象的成員變量是一個對象,那麼這個對象的數據成員也會被保存!這是能用序列化解決深拷貝的重要緣由。
(8)序列化與單例模式的衝突解決辦法:
另外還有兩個自定義序列化方法 writeReplace 和 readResolve ,分別用來在序列化以前替換序列化對象 和 在反序列化以後的對返回對象的處理。通常能夠用來避免 singleTon 對象跨 jvm 序列化和反序列化時產生多個對象實例,事實上 singleTon 的對象一旦可序列化,它就不能保證 singleTon 了。 JVM 的 Enum 實現裏就是重寫了 readResolve 方法,由 JVM 保證 Enum 的值都是 singleTon 的,因此建議多使用 Enum 代替使用 writeReplace 和 readResolve 方法。
Java 代碼
private Object readResolve()
{
return INSTANCE;
}
private Object writeReplace(){
return INSTANCE;
}
注: writeReplace 調用在 writeObject 前 ;readResolve 調用在 readObject 以後。
(9)序列化解決深拷貝的代碼:
public Object deepClone() throws IOException, OptionalDataException,
ClassNotFoundException {
// 將對象寫到流裏
ByteArrayOutputStream bo = new ByteArrayOutputStream();
ObjectOutputStream oo = new ObjectOutputStream(bo);
oo.writeObject(this); // 從流裏讀出來
ByteArrayInputStream bi = new ByteArrayInputStream(bo.toByteArray());
ObjectInputStream oi = new ObjectInputStream(bi);
return (oi.readObject());
}
對象所屬的類要實現 Serializable 接口。同時將該方法寫入到對象所屬的類中。
深拷貝的時候,調用該方法便可。
二、JavaIO 中的裝飾模式:
Java 中使用的最普遍的裝飾器模式就是 JavaIO 類的設計。好比, OutPutStream 是輸出流的基類,其子類有 FileOutputStream 和 FilterOutputStream, 而 FilterOutputStream 的子類有 BufferedOutputStream 和 DataOutputStream 兩個子類。其中, FileOutputStream 爲系統的核心類,它實現了向文件寫數據的功能,使用 DataOutputStream 能夠在 FileOutputStream 的基礎上增長多種數據類型的寫操做支持( DataOutputStream 類中有 writeUTF 、 writeInt 等函數),而 BufferdOutputStream 裝飾器能夠對 FileOutputStream 增長緩衝功能,優化 I/O 性能。
三、JavaIO 流的使用場景:
(1)IO 流:用於處理設備上的數據,這裏的設備指的是:硬盤上的文件、內存、鍵盤輸入、屏幕顯示。
(2)字節流和字符流:字節流好理解,由於全部格式的文件都是以字節形式硬盤上存儲的,包括圖片、 MP3 、 avi 等,所以字節流能夠處理全部類型的數據。字符流讀取的時候讀到一個或多個字節時(中文對應的 字節數是兩個,在 UTF-8 碼錶中是三個字節)時,先去查指定的編碼表,將查到的字符返回。字符流之因此出現,就是由於有了文件編碼的不一樣,而有了對字符進行高效操做的字符流對象。所以,只要是處理純文本數據,就要優先考慮使用字符流,除此以外都使用字節流。
(3)流操做的基本規律:
1 )、明確數據源和數據匯,目的是明確使用輸入流仍是輸出流。
2 )、明確操做的數據是不是純文本數據。
3 )、是否須要進行字節流和字符流的轉換。
4 )、是否須要使用緩存。
(4)實例說明流操做的基本流程:把鍵盤上讀入的數據以指定的編碼存入到文件中。
1 )、明白數據源:鍵盤輸入, System.in ,可用 InputStream 和 Reader
2 )、發現 System.in 對應的流是字節讀入流,因此要將其進行轉換,將字節轉換爲字符。
3 )、因此要使用 InputStreamReader 轉換流
4 )、若是想提升效率,要加入緩存機制,那麼就要加入字符流的緩衝區。 BufferedReader,所以前四步構造出的輸入流爲:
BufferedReader bur = new BufferedReader(new InputStreamReader(System.in));
5 )、明白數據匯:既然是數據匯,則必定是輸出流,能夠用 OutputStream 或 Writer 。
6 )、往文件中存儲的都是文本文件,所以選用 Writer 。
7 )、由於要指定編碼表,因此使用 Writer 中的轉換流, OutputStreamWriter 。
注意:雖然最終是文件,可是不能夠選擇 FileWriter ,由於該對象是使用默認編碼表。
8 )是否要提升效率,選擇 BufferedWriter 。
9 )轉換輸出流須要接收一個字節輸出流進來,因此要是用 OutputStream 體系,而最終輸出到一個文件中。那麼就要使用 OutputStream 體系中能夠操做的文件的字符流對象, FileOutputStream 。
10 )、經過前面的分析,獲得的輸出流對象以下:
//String charSet = System.getProperty("file.encoding");
String charSet = "utf-8";
BufferedWriter bufw = new BufferedWriter(new OutputStreamWriter(new
FileOutputStream("a.txt"),charSet);
4 、能夠和流相關聯的集合對象 Properties 。
Map
|--HashTable
|--Properties
Properties :該集合不須要泛型,由於該集合中的鍵值都是 String 類型。
五、其餘流對象:
( 1 )打印流:
PrintStream :是一個字節打印流 System.out 對應的就是 PrintStream 。它的構造函數能夠接收三種數據類型的值:字符串路徑、 File 對象、 OutputStream (當爲 System.out 的時候即把輸入顯示到屏幕上)
PrintWriter :是一個字符打印流。構造函數能夠接收四種類型的值。字符串路徑、 File 對象(對於這兩中類型的數據,還能夠指定編碼表。也便是是字符集)、 OutPutSream 、 Writer (對於3、四類型的數據,能夠指定自動刷新,注意:當自動刷新的值爲 true 時,只有三個方法能夠用: printlf 、 printf 、 format )
(2)管道流: PipedOutputStream 和 PipedInputStream 。通常在多線程中通訊的時候用。
(3)RandomAccessFile :該對象不是流體系中的一員,可是該隊選中封裝了字節流,同時還封裝了一個緩衝區(字節數組),經過內部的指針來操做數組中的數據。該對象特色:只能操做文件和對文件讀寫均可以。多用於多線程下載。、
(4)合併流:能夠將多個讀取流合併成一個流。其實就是將每個讀取流對象存儲到一個集合中,最後一個流對象結尾做爲這個流的結尾。
(5)對象的序列化。 ObjectInputStream 和 ObjectInputStream 。
(6)操做基本數據類型的流對象: DataInputStream 和 DataOutputStream 。
(7)操縱內存數組的流對象,這些對象的數據源是內存,數據匯也是內存: ByteArrayInputStream 和 ByteArrayOutputStream , CharArrayReader 和 CharArrayWriter 。這些流並未調用系統資源,使用的是內存中的數組,因此在使用的時候不用 close 。
(8)編碼轉換:
在 IO 中涉及到編碼轉換的流是轉換流和打印流,可是打印流只有輸出。轉換流是能夠指定編碼表的,默認狀況下,都是本機默認的編碼表, GBK 。能夠經過: Syetem.getProperty( 「file.encoding」) 獲得。字符串到字節數組成爲編碼的過程,經過 getBytes(charset) 完成,從字節數組到字符串的過程是解碼的過程,經過 String 類的構造函數完成 String ( byte[],charset ) .
(9)編碼實例與解析:
(10)JavaNIO 的 Charset 類專門用來編碼和解碼。
想要了解更多分佈式知識點的,能夠關注我一下,我後續也會整理更多關於分佈式架構這一塊的知識點分享出來,另外順便給你們推薦一個交流學習羣:650385180,裏面會分享一些資深架構師錄製的視頻錄像:有Spring,MyBatis,Netty源碼分析,高併發、高性能、分佈式、微服務架構的原理,JVM性能優化這些成爲架構師必備的知識體系。還能領取免費的學習資源,目前受益良多,如下的課程體系圖也是在羣裏獲取。