Java性能問題一直困擾著廣大程序員桅锄,由于平臺復(fù)雜性,要定位問題样眠,找出其根源確實(shí)很難友瘤。隨著10多年Java平臺的改進(jìn)以及新出現(xiàn)的多核多處理器,Java軟件的性能和擴(kuò)展性已經(jīng)今非昔比了¢苁現(xiàn)代JVM持續(xù)演進(jìn)辫秧,內(nèi)建了更為成熟的優(yōu)化技術(shù)、運(yùn)行時技術(shù)和垃圾收集器被丧。與此同時盟戏,底層的硬件平臺和操作系統(tǒng)也在演化。
目錄:
一甥桂、Java性能優(yōu)化系列之一--設(shè)計優(yōu)化
二柿究、Java性能優(yōu)化系列之二--程序優(yōu)化
三、Java性能優(yōu)化系列之三--并發(fā)程序設(shè)計詳解
四黄选、Java性能優(yōu)化系列之四--Java內(nèi)存管理與垃圾回收機(jī)制詳解
五蝇摸、Java性能優(yōu)化系列之五--JavaIO
現(xiàn)代大規(guī)模關(guān)鍵性系統(tǒng)中的Java性能調(diào)優(yōu)婶肩,是一項(xiàng)富有挑戰(zhàn)的任務(wù)。你需要關(guān)注各種問題貌夕,包括算法結(jié)構(gòu)律歼、內(nèi)存分配模式以及磁盤和文件I/O的使用方式。性能調(diào)優(yōu)最困難的通常是找到問題所在啡专,即便是經(jīng)驗(yàn)豐富的人也會被他們的直覺所誤導(dǎo)险毁。性能殺手總是隱藏在最意想不到的地方。
這一次们童,我將在本文中著重介紹Java性能優(yōu)化的一系列舉措畔况,希望能夠?qū)ΜF(xiàn)在的你有所幫助。覺得有幫助的朋友也可以轉(zhuǎn)發(fā)病附、收藏以下问窃。
一、Java性能優(yōu)化系列之一--設(shè)計優(yōu)化
1完沪、善于利用 Java 中的設(shè)計模式:享元模式域庇、代理模式、裝飾器模式等覆积。詳見我的上一篇文章設(shè)計模式之我說
文章鏈接:https://www.toutiao.com/i6540181471888409102/
2听皿、Java 中的緩沖區(qū):
(1)緩沖最常用的場景就是提高 IO 速度:比如 BufferedWriter 可以用來裝飾 FileWriter ,為 FileWriter 加上緩沖宽档。 BufferedOutputStream 可以用來裝飾 FileOutputStream 尉姨。使用這兩個裝飾器時候可以指定緩沖區(qū)大小,默認(rèn)的 size 為 8K 吗冤。
(2)JavaNIO 中的各種 Buffer 類族又厉,有更加強(qiáng)大的緩沖區(qū)控制功能。
(3)除了性能上的優(yōu)化椎瘟,緩沖區(qū)也可以作為上層組件和下層組件的一種通信工具覆致,將上層組件好下層組件進(jìn)行解耦。比如生產(chǎn)者消費(fèi)者模式中的緩沖區(qū)肺蔚。
2煌妈、緩存:
(1)比如 Hibernate 采用的兩級緩存:一級緩存和二級緩存。二級緩存指的是 sessionFactory層面上的緩存宣羊, Hibernate 采用的是 EHCache 璧诵。一級緩存指的是 session 層面上的緩存。
3仇冯、對象復(fù)用技術(shù) -- 池的使用
(1)數(shù)據(jù)庫連接池:較常使用的數(shù)據(jù)庫連接池組件是 C3P0 和 Proxool 之宿。其中 C3P0 是伴隨 Hibernate 一起發(fā)布的, Hibernate 默認(rèn)采用的數(shù)據(jù)庫連接池苛坚。
(2)線程池:自定義線程池以及 jdk1.5 提供的線程池組件澈缺。
4坪创、并行代替串行。
5姐赡、時間換空間:不引入中間變量實(shí)現(xiàn)兩個數(shù)字的交換莱预。代價是增加 CPU 運(yùn)算。
6项滑、空間換時間:使用下標(biāo)數(shù)組排序依沮。
二、Java性能優(yōu)化系列之二--程序優(yōu)化
常用的程序設(shè)計優(yōu)化技巧:
1枪狂、字符串優(yōu)化處理
(1)String 類的特點(diǎn):不變性危喉、針對常量池的優(yōu)化( String.intern() 方法的意義)
(2)subString 方法的內(nèi)存泄漏 :
(3)字符串分割和查找不要使用 split 函數(shù),效率低州疾,而是使用 StringTokenizer 或者 indexOf結(jié)合 subString() 函數(shù)完成分割辜限。
(4)用 charAt ()方法代替 startWith ()方法。
(5)對于靜態(tài)字符串或者變量字符串的連接操作严蓖, Java 在編譯的時候會進(jìn)行徹底的優(yōu)化薄嫡,將多個連接操作的字符串在編譯時合成一個單獨(dú)的字符串,而不是生成大量的 String 實(shí)例颗胡。只生成一個對象毫深。
(6)在無需考慮線程安全情況下盡量使用 StringBuilder 。
(7)StringBuffer 和 StringBuilder 初始化的時候都可以設(shè)置一個初始值毒姨,默認(rèn)是 16B 哑蔫。如果字符串的長度大于 16B 的時候,則需要進(jìn)行擴(kuò)容弧呐。擴(kuò)容策略是將原有的容量大小翻倍闸迷,以新的容量申請內(nèi)存空間,建立 char 數(shù)組俘枫,然后將數(shù)組中的內(nèi)容復(fù)制到這個新的數(shù)組中腥沽,使用 Arrays.copyOf() 函數(shù)。因此崩哩,如果能預(yù)先評估 StringBuilder 的大小,則可以節(jié)省這些復(fù)制操作言沐,從而提高系統(tǒng)的性能邓嘹。
2、List 接口
( 1 ) ArrayList 和 Vector 的區(qū)別:它們幾乎使用了相同的算法险胰,它們的唯一區(qū)別是對多線程的支持汹押。 ArrayList 是不安全的,而 Vector 是線程安全的起便。
( 2 ) LinkedList 和 ArrayList 的區(qū)別:
|---1 棚贾、 linkedList 采用鏈表實(shí)現(xiàn)窖维,適合于數(shù)據(jù)刪除和插入非常頻繁的情況,不適合隨機(jī)訪問妙痹。
|---2 铸史、 ArrayList 采用數(shù)組實(shí)現(xiàn),適用于隨機(jī)查找和順序讀的情況怯伊,不適合刪除和插 入數(shù)據(jù)非常頻繁的場景琳轿。
(3)基于數(shù)組的 List 都會有一個容量參數(shù)。當(dāng) ArrayList 所存儲的元素容量超過其已有大小的時候就會進(jìn)行擴(kuò)容耿芹,數(shù)組的擴(kuò)容會導(dǎo)致整個數(shù)組進(jìn)行一次內(nèi)存復(fù)制崭篡。因此合理的數(shù)組大小會減小數(shù)組擴(kuò)容的次數(shù)從而提高系統(tǒng)性能。
(4)遍歷列表的時候盡量使用迭代器吧秕,速度塊琉闪。
2、Map 接口:
(1)HashMap 的實(shí)現(xiàn)原理:簡單的說砸彬, HashMap 就是將 key 做 hash 算法颠毙,然后將 hash 值映射到內(nèi)存地址,直接取得 key 所對應(yīng)的數(shù)據(jù)拿霉。在 HashMap 中吟秩,底層數(shù)據(jù)結(jié)構(gòu)使用的是數(shù)組,所謂的內(nèi)存地址指的是數(shù)組的下標(biāo)索引绽淘。
(2)容量參數(shù)與擴(kuò)容:默認(rèn)情況下涵防, hashmap 的初始容量為 16 ,負(fù)載因子為 0.75 沪铭,也就是說當(dāng) hashmap 的實(shí)際容量達(dá)到了初始容量 * 負(fù)載因子( hashmap 內(nèi)部維護(hù)的一個 threshold 值)的時候壮池, hashmap 就會進(jìn)行擴(kuò)容。在擴(kuò)容時杀怠,會遍歷整個 hashmap 椰憋,因此應(yīng)該設(shè)置合理的初始大小和負(fù)載因子,可以減小 hashmap 擴(kuò)容的次數(shù)赔退。
(3)LinkedHashMap-- 有序的 HashMap : HashMap 的最大缺點(diǎn)是其無序性橙依,被存入到 Hashmap 中的元素,在遍歷 HashMap 的時候硕旗,其輸出不一定按照輸入的順序窗骑,而是 HashMap 會根據(jù) hash 算法設(shè)定一個查找高效的順序。如果希望保存輸入順序漆枚,則需要使用 LinkedHashMap 创译。LinkedHashmap 在內(nèi)部又增加了一個鏈表,用于保存元素的順序墙基。
(4)LinkedList 可以提供兩種類型的順序:一個是元素插入時候的順序软族,一個是最近訪問的順序刷喜。注意: LinkedHashMap 在迭代過程中,如果設(shè)置為按照最后訪問時間進(jìn)行排序立砸,即:每當(dāng)使用 get() 方法訪問某個元素時掖疮,該元素便會移動到鏈表的尾端。但是這個時候會出現(xiàn)異常仰禽,因此氮墨, LinkedHashMap 工作在這種模式的時候,不能在迭代器中使用 get() 操作吐葵。
(5)關(guān)于 ConcurrentModificationException :該異常一般會在集合迭代過程中被修改時拋出规揪。因此,不要在迭代器模式中修改集合的結(jié)構(gòu)温峭。這個特性適合于所有的集合類凤藏,包括 HashMap 、 Vector 揖庄、 ArrayList 等蹄梢。
(6)TreeMap-- 如果要對元素進(jìn)行排序疙筹,則使用 TreeMap 對 key 實(shí)現(xiàn)自定義排序禁炒,有兩種方式:在 TreeMap 的構(gòu)造函數(shù)中注入一個 Comparator 或者使用一個實(shí)現(xiàn)了 Comparable 的 key 幕袱。
(7)如果需要將排序功能加入 HashMap 们豌,最好是使用 Treemap 而不是在應(yīng)用程序自定義排序障癌。
(8)HashMap 基于 Hash 表實(shí)現(xiàn)混弥, TreeMap 基于紅黑樹實(shí)現(xiàn)蝗拿。
3 、 Map 和 Set 的關(guān)系:
( 1 )所有 Set 的實(shí)現(xiàn)都只是對應(yīng)的 Map 的一種封裝劳秋,其內(nèi)部維護(hù)一個 Map 對象嗽冒。即: Set只是相應(yīng)的 Map 的 Value 是一種特殊的表現(xiàn)形式的一種特例添坊。
( 2 ) Set 主要有三種實(shí)現(xiàn)類: HashSet 、 LinkedHashSet 阳准、 TreeSet 野蝇。其中 HashSet 是基于 Hash 的快速元素插入,元素之間無序七冲。 LinkedHashSet 同時維護(hù)著元素插入順序,遍歷集合的時候掘鄙,總是按照先進(jìn)先出的順序排序操漠。 TreeSet 是基于紅黑樹的實(shí)現(xiàn)撞秋,有著高效的基于元素 Key 的排序算法吻贿。
4 、優(yōu)化集合訪問代碼:
( 1 )帐要、分離循環(huán)中被重復(fù)調(diào)用的代碼:例如, for 循環(huán)中使用集合的 size() 函數(shù)冒冬,則不應(yīng)該把這個函數(shù)的調(diào)用放到循環(huán)中,而是放到循環(huán)外邊横侦、
( 2 )、省略相同的操作:
5 榨馁、 RandomAccess 接口:通過 RandomAccess 可知道 List 是否支持隨機(jī)快速訪問。同時珍剑,如果應(yīng)用程序需要通過索引下標(biāo)對 List 做隨機(jī)訪問,盡量 buyaoshiyongLinkedList 别凤, ArrayList 或者 Vector 可以。
6 由缆、 JavaNIO 的特性:
1 、為所有的原始類型提供 Buffer 支持舔箭。
2 、使用 Java.nio.charset.Charset 作為字符編碼解碼解決方案镜会。
3 、增加通道抽象代替原有的 IO 流抽象匾旭。
4 、支持鎖和內(nèi)存映射文件的文件訪問接口飒泻。
5 、提供基于 Selector 的異步網(wǎng)絡(luò) IO 史辙。
7 惭载、 Java 中 NIO 的使用见妒。 Channel 是一個雙向通道,即可讀也可寫。應(yīng)用程序不能直接操作 Channel 卵酪,必須借助于 Buffer 。例如讀數(shù)據(jù)的時候塑煎,必須把數(shù)據(jù)從通道讀入到緩沖區(qū),然后在緩沖區(qū)中進(jìn)行讀取冷尉。以文件讀取為例,首先通過文件輸入流獲得文件通道,然后把文件通道的內(nèi)容讀入到緩沖區(qū)中捌浩,然后就可以對緩沖區(qū)操作进统。
8 、 Buffer 的基本原理:
1 掉分、 Buffer 的創(chuàng)建: Buffer 的靜態(tài) allocate(int size) 方法或者 Buffer.wrap(byte[]src) 。
2 、 Buffer 的工作原理:三個變量: position ,代表當(dāng)前緩沖區(qū)的位置耘拇,寫緩沖區(qū)的時候,將從 position 的下一個位置寫數(shù)據(jù)。 Capacity 仑最,代表緩沖區(qū)的總?cè)萘可舷蕖?Limit ,緩沖區(qū)的實(shí)際上限预皇,也就是說,讀數(shù)據(jù)的時候诱桂,數(shù)據(jù)即是從 position 到 limit 之間的數(shù)據(jù)
3 、 flip 操作: limit=position,position=0, 一般是在讀寫切換的時候使用肝劲。寫完數(shù)據(jù)之后,需要限定下有效數(shù)據(jù)范圍,才能讀數(shù)據(jù)鹿榜;
4 、 clear 操作: position-0 , limit=capacity. 冈绊。為重新寫入緩沖區(qū)做準(zhǔn)備。
5 、 rewind 操作: position=0 鹃骂,為讀取緩沖區(qū)中有效數(shù)據(jù)做準(zhǔn)備,一半 limit 已經(jīng)被合理設(shè)置蒿叠。
9 、讀寫緩沖區(qū):
1 施绎、 public byte get() :順序讀取緩沖區(qū)的一個字節(jié), position 會加一
2 、 public Buffer get(byte[]dst): 將緩沖區(qū)中的數(shù)據(jù)讀入到數(shù)組 dst 中遇八,并適當(dāng)?shù)囊苿?position
3 蔑歌、 public byte get(int index) :得到第 index 個字節(jié)园匹,但不移動 posoiion
4 、 public ByteBuffer put(byte b) :將字節(jié) b 放入到緩沖區(qū)中供汛,并移動 position
5 宿稀、 public ByteBuffer put(int index,byte b) :將字節(jié) b 放到緩沖區(qū)的 index 位位置
6 矮烹、 pubglic final ByteBuffer(byte[]src) :將字節(jié)數(shù)組 src 放到緩沖區(qū)中卤唉。
10 、標(biāo)志緩沖區(qū):類似于一個書簽的功能,在數(shù)據(jù)的處理過程中悦析,可隨時記錄當(dāng)前位置挡鞍。然后在任意時刻道媚,回到這個位置。 Mark 用于記錄當(dāng)前位置, reset 用于恢復(fù)到 mark 所在的位置、
11 翘魄、復(fù)制緩沖區(qū):使用 Buffer 的 duplicate 方法可以復(fù)制一個緩沖區(qū)伪节,副本緩沖區(qū)和原緩沖區(qū)共享一份空間但是有有著獨(dú)立的 position 纱兑、 capacity 和 limit 值捡多。
20 倒信、緩沖區(qū)分片:緩沖區(qū)分片使用 slice 方法實(shí)現(xiàn)榜掌。它將在現(xiàn)有的緩沖區(qū)中,創(chuàng)建的子緩沖區(qū)乘综。子緩沖區(qū)和父緩沖區(qū)共享數(shù)據(jù)憎账。這個方法有助于將系統(tǒng)模塊化。緩沖區(qū)切片可以將一個大緩沖區(qū)進(jìn)行分割處理卡辰,得到的子緩沖區(qū)都具有緩沖的緩沖區(qū)模型結(jié)構(gòu);因此看政。這個操作有助于系統(tǒng)的模塊化朴恳。
12 抄罕、只讀緩沖區(qū):只讀緩沖區(qū)可以保證核心數(shù)據(jù)的安全允蚣,如果不希望數(shù)據(jù)被隨意篡改,返回一個只讀緩沖區(qū)是很有幫助的呆贿。
13 嚷兔、文件映射到內(nèi)存: NIO 提供了一種將文件映射到內(nèi)存的方法進(jìn)行 IO 操作,這種方法比基于流 IO 快很多做入。這個操作主要由 FileChanne.map() 操作冒晰。使用文件內(nèi)存的方式,將文本通過 FileChannel 映射到內(nèi)存中竟块。然后從內(nèi)存中讀取數(shù)據(jù)壶运。同時,通過修改 Buffer, 將對內(nèi)存中數(shù)據(jù)的修改寫到對應(yīng)的硬盤文件中浪秘。
14 蒋情、處理結(jié)構(gòu)化數(shù)據(jù):散射和聚集埠况。散射就是將數(shù)據(jù)讀入到一組 bytebuffer 中,而聚集正好相反棵癣。通過 ScatteringByteChannel 和 GatheringByteChannel 可以簡化對結(jié)構(gòu)數(shù)據(jù)的操作辕翰。
15 、直接內(nèi)存訪問: DirectBuffer 直接分配在物理內(nèi)存中狈谊,并不占用對空間喜命,因此也不受對空間限制。 DirectBuffer 的讀寫操作比普通 Buffer 塊河劝,因?yàn)?DirectBuffer 直接操縱的就是內(nèi)核緩沖區(qū)壁榕。
16 、引用類型:強(qiáng)赎瞎、軟护桦、若、虛四種引用類型煎娇。
WeakHashMap :是弱引用的一中典型應(yīng)用二庵,它使用弱引用作為內(nèi)部數(shù)據(jù)的存儲方案』呵海可以作為簡單的緩存表解決方案催享。
如果在系統(tǒng)中,需要一張很大的 Map 表哟绊, Map 中的表項(xiàng)作為緩存之用因妙。這也意味著即使沒能從該 Map 中取得相應(yīng)地數(shù)據(jù),系統(tǒng)也可以通過選項(xiàng)方案獲取這些數(shù)據(jù)票髓,雖然這樣會消耗更多的時間攀涵,但是不影響系統(tǒng)的正常運(yùn)行。這個時候洽沟,使用 WeakHashMap 是最合適的以故。因?yàn)?WeakHashMap 會在系統(tǒng)內(nèi)存范圍內(nèi),保存所有表項(xiàng)裆操,而一旦內(nèi)存不夠怒详,在 GC 時,沒有被引用的又會很快被清除掉踪区,避免系統(tǒng)內(nèi)存溢出昆烁。
17 、有助于改善系統(tǒng)性能的技巧:
1 缎岗、慎用異常: for 循環(huán)中使用 try-catch 會大大降低系統(tǒng)性能
2 静尼、使用局部變量:局部變量的訪問速度遠(yuǎn)遠(yuǎn)高于類的靜態(tài)變量的訪問速度,因?yàn)轭惖?變量是存在在堆空間中的。
3 鼠渺、位運(yùn)算代替乘除法:右移代表除以二蜗元、左移代表乘以二。
4 系冗、有的時候考慮是否可以使用數(shù)組代替位運(yùn)算奕扣。
5 、一維數(shù)組代替二維數(shù)組掌敬。
6 惯豆、提取表達(dá)式:盡可能讓程序少做重復(fù)的計算,尤其要關(guān)注在循環(huán)體的代碼奔害,從循環(huán)提中提取重復(fù)的代碼可以有效的提升系統(tǒng)性能楷兽。
三、Java性能優(yōu)化系列之三--并發(fā)程序設(shè)計詳解
1华临、并發(fā)程序設(shè)計模式:
( 1 )芯杀、 Future-Callable 模式: FutureTask 類實(shí)現(xiàn)了 Runnable 接口,可以作為單獨(dú)的線程運(yùn)行雅潭,其 Run 方法中通過 Sync 內(nèi)部類調(diào)用 Callable 接口揭厚,并維護(hù) Callable 接口的返回值。當(dāng)調(diào)用FutureTask.get() 的時候?qū)⒎祷?Callable 接口的返回對象扶供。 Callable 接口是用戶自定義的實(shí)現(xiàn)筛圆,通過實(shí)現(xiàn) Callable 接口的 call() 方法,指定 FutureTask 的實(shí)際工作內(nèi)容和返回對象椿浓。 Future 取得的結(jié)果類型和 Callable 返回的類型必須一致太援,這是由定義 FutureTask 的時候指定泛型保證的。 Callable 要采用 ExecutorSevice 的 submit 方法提交扳碍,返回的 future 對象可以取消任務(wù)提岔。
( 2 )、 Master-Worker 格式:其核心思想是系統(tǒng)由兩類進(jìn)程協(xié)作工作: Master 進(jìn)程和 Worker 進(jìn)程笋敞。 Master 進(jìn)程負(fù)責(zé)接收和分配任務(wù)碱蒙, Worker 負(fù)責(zé)處理子任務(wù)。當(dāng)各個子任務(wù)處理完成后液样,將結(jié)果返回給 Master 進(jìn)程振亮。由 Master 進(jìn)程進(jìn)行歸納會匯總巧还,從而得到系統(tǒng)的最終結(jié)果鞭莽。
( 3 )、保護(hù)暫停模式:其核心思想是僅當(dāng)服務(wù)進(jìn)程準(zhǔn)備好時麸祷,才提供服務(wù)澎怒。設(shè)想一種場景,服務(wù)器會在很短時間內(nèi)承受大量的客戶端請求,客戶端請求的數(shù)量可能超過服務(wù)器本身的即時處理能力喷面。為了不丟棄任意一個請求星瘾,最好的方式就是將這個客戶端進(jìn)行排列,由服務(wù)器逐個處理惧辈。
( 4 )琳状、不變模式:為了盡可能的去除這些由于線程安全而引發(fā)的同步操作,提高并行程序性能 盒齿,可以使用一種不可變的對象念逞,依靠對象的不變性,可以確保在沒有同步操作的多線程環(huán)境中依然保持內(nèi)部狀態(tài)的一致性和正確性边翁。
( 5 )翎承、 Java 實(shí)現(xiàn)不變模式的條件:
1) 、去除 setter 方法以及所有修改自身屬性的方法符匾。
2 )叨咖、將所有屬性設(shè)置為私有,并用 final 標(biāo)記啊胶,確保其不可修改甸各。
3 )、確保沒有子類可以重載修改它的行為焰坪。
4 )、有一個可以創(chuàng)建完整對象的構(gòu)造函數(shù)琳彩。
Java 中誊酌,不變模式的使用有: java.lang.String 類。以及所有的元數(shù)據(jù)類包裝類露乏。
(6)碧浊、生產(chǎn)者 - 消費(fèi)者模式:生產(chǎn)者進(jìn)程負(fù)責(zé)提交用戶請求,消費(fèi)者進(jìn)程負(fù)責(zé)具體處理生產(chǎn)者進(jìn)程提交的任務(wù)瘟仿。生產(chǎn)者和消費(fèi)者之間通過共享內(nèi)存緩沖區(qū)進(jìn)行通信箱锐。通過 Java 提供和餓 BlockingQueue 可以實(shí)現(xiàn)生產(chǎn)者消費(fèi)者模式。
2劳较、JDK 多任務(wù)執(zhí)行框架:
( 1 )驹止、簡單線程池實(shí)現(xiàn):線程池的基本功能就是進(jìn)行線程的復(fù)用。當(dāng)系統(tǒng)接受一個提交的任務(wù)观蜗,需要一個線程時臊恋,并不著急立即去創(chuàng)建進(jìn)程,而是先去線程池查找是否有空余的進(jìn)程墓捻,若有則直接使用線程池中的線程工作抖仅。如果沒有,則再去創(chuàng)建新的進(jìn)程撤卢。待任務(wù)完成后,不是簡單的銷毀進(jìn)程,而是將線程放入線程池的空閑隊列,等待下次使用悔雹。使用線程池之后腌零,線程的創(chuàng)建和關(guān)閉通常由線程池維護(hù)闲询,線程通常不會因?yàn)闀?zhí)行晚一次任務(wù)而被關(guān)閉记舆,線程池中的線程會被多個任務(wù)重復(fù)使用。
( 2 )痰滋、 Executor 框架: Executor 框架提供了創(chuàng)建一個固定線程數(shù)量的線程池严望、返回一個只有一個線程的線程池、創(chuàng)建一個可根據(jù)實(shí)際情況進(jìn)行線程數(shù)量調(diào)整的線程池、可調(diào)度的單線程池以及可變線程數(shù)量的可調(diào)度的線程池。
( 3 )休吠、自定義線程池 : 使用 ThreadPoolExecutor 接口: ThreadPoolExecutor 的構(gòu)造函數(shù)參數(shù)如下:
corePoolSize :指的是保留的線程池大小
maximumPoolSize : 指的是線程池的最大大小
keepAliveTime :指的是空閑線程結(jié)束的超時時間
Unit : 是一個枚舉,表示 keepAliveTime 的單位
workQueue : 表示存放任務(wù)的隊列蔚携。
ThreadFactory :創(chuàng)建線程的時候希太,使用到的線程工廠
handler : 當(dāng)線程達(dá)到最大限制,并且工作隊列里面也已近存放滿了任務(wù)的時候酝蜒,決定如何處理提交到線程池的任務(wù)策略
上述的幾種線程池的內(nèi)部實(shí)現(xiàn)均使用了 ThreadPoolExecutor 接口誊辉。我們可以自定義提交但是未被執(zhí)行的任務(wù)隊列被執(zhí)行的順序,常見的有直接提交的隊列亡脑、有界的任務(wù)隊列堕澄、無界的任務(wù)隊列邀跃、優(yōu)先任務(wù)隊列,這樣可以在系統(tǒng)繁忙的時候忽略任務(wù)的提交先后次序蛙紫,總是讓優(yōu)先級高的任務(wù)先執(zhí)行拍屑。使用優(yōu)先隊列時,必須讓 target 實(shí)現(xiàn) Comparable 接口坑傅。
(4)僵驰、優(yōu)化線程池大小: NThreads=Ncpi*Ucpu*(1+W/C) 唁毒, Java 中使用: Runtime.getRuntime().availableProcesses() 獲取可用的 CPU 數(shù)量蒜茴。
3、JDK 并發(fā)數(shù)據(jù)結(jié)構(gòu):
( 1 )并發(fā) List : Vector 或者 CopyOnWriteArrayList 是兩個線程安全的 List 實(shí)現(xiàn)浆西。
CopyOnWriteArrayList 很好的利用了對象的不變性粉私,在沒有對對象進(jìn)行寫操作之前,由于對象未發(fā)生改變近零,因此不需要加鎖诺核。而在試圖改變對象的時候,總是先獲得對象的一個副本秒赤,然后對副本進(jìn)行修改猪瞬,最后將副本寫回。 CopyOnWriteArrayList 適合讀多寫少的高并發(fā)場合入篮。而 Vector適合高并發(fā)寫的場合陈瘦。
( 2 )并發(fā) Set : synchronizedSet 適合高并發(fā)寫的情景、 CopyOnWriteSet 適合讀多寫少的高并發(fā)場合潮售。
( 3 )并發(fā) Map : ConcurrentHashMap 是專門為線程并發(fā)而設(shè)計的 HashMap 痊项,它的 get 操作是無鎖的,其 put 操作的鎖粒度小于 SynchronizedHashMap 酥诽,因此其整體性能優(yōu)于 SynchronizedHashMap 鞍泉。
( 4 )并發(fā) Queue :在并發(fā)隊列上, JDK 提供了兩種實(shí)現(xiàn)肮帐,一個是以 ConcurrentLinkedQueue 為代表的高性能隊列咖驮,一個是以 BlockingQueue 接口為代表的阻塞隊列。如果需要一個能夠在高并發(fā)時训枢,仍能保持良好性能的隊列托修,可以使用 ConcurrentLinkedQueue 對象。而 BlockingQueue的主要適用場景就是生產(chǎn)者消費(fèi)者模式中的實(shí)現(xiàn)數(shù)據(jù)共享恒界。 BlockingQueue 接口主要有兩種實(shí)現(xiàn): ArrayBlockingQueue 是一種基于數(shù)組的阻塞隊列實(shí)現(xiàn)睦刃,也就是說其內(nèi)部維護(hù)著一個定長數(shù)組,用于緩存隊列中的數(shù)據(jù)對象十酣。 LinkedBlockingQueue 則使用一個鏈表構(gòu)成的數(shù)據(jù)緩沖隊列涩拙。
4 际长、并發(fā)控制方法:
( 1 )、 Java 中的內(nèi)存模型與 Volatile :在 Java 中兴泥,每一個線程有一塊工作內(nèi)存區(qū)工育,其中存放著被所有線程共享的主內(nèi)存中的變量的值的拷貝。當(dāng)線程執(zhí)行時郁轻,它在自己的內(nèi)存中操作變量翅娶。為了存取一個共享的變量文留,一個線程通常要先獲取鎖定并且清除它的內(nèi)存緩沖區(qū)好唯,這保證該共享變量從所有線程的共享內(nèi)存區(qū)正確地裝入到線程的工作內(nèi)存區(qū);當(dāng)線程解鎖時保證該工作內(nèi)存區(qū)中變量的值寫回到共享內(nèi)存中燥翅。
( 2 )骑篙、 Volatile 關(guān)鍵字:聲明為 Volatile 的變量可以做以下保證:
1 )、其他線程對變量的修改森书,可以隨即反應(yīng)在當(dāng)前進(jìn)程中靶端。
2 )、確保當(dāng)前線程對 Volatile 變量的修改凛膏,能隨即寫回到共享主內(nèi)存中杨名,并被其他線程所見
3 )、使用 Volatile 聲明的變量猖毫,編譯器會保證其有序性台谍。
4 )、 double 和 long 類型的非原子處理:如果一個 double 類型或者 long 類型的變量沒有被聲明為 volatile 類型吁断,則變量在進(jìn)行 read 和 write 操作的時候趁蕊,主內(nèi)存會把它當(dāng)成兩個 32 位的read 或者 write 操作。因此仔役,在 32 為操作系統(tǒng)中掷伙,必須對 double 或者 long 進(jìn)行同步
原因在于:使用 Volatile 標(biāo)志變量,將迫使所有線程均讀寫主內(nèi)存中的對應(yīng)變量又兵,從而使得 Volatile 變量在多線程間可見任柜。
(3)、同步關(guān)鍵字 -Synchronized 沛厨,其本質(zhì)是一把鎖: Synchronized 關(guān)鍵字可以作用在方法或者代碼塊中宙地。當(dāng)作用的是成員方法時,默認(rèn)的鎖是該對象 this 俄烁,這個時候一般在共享資源上進(jìn)行Synchronized 操作绸栅。該關(guān)鍵字一般和 wait ()和 notify ()方法一起使用,調(diào)用這兩個方法的時候一般指的是資源本身页屠。由于所有的對象都能當(dāng)成資源粹胯,因此這兩個方法是從 Object 繼承而來的蓖柔,而不是 Thread 或者 Runnable 才具有的方法卦碾。
(4)祷愉、 ReentrantLock 鎖:比 Synchronized 的功能更強(qiáng)大恬砂,可中斷谈火、可定時印屁。所有使用內(nèi)部鎖實(shí)現(xiàn)的功能阿弃,都可以使用重入鎖實(shí)現(xiàn)支救。重入鎖必須放入 finally 塊中進(jìn)行釋放循诉,而內(nèi)部鎖可以自動釋放臭增。 重入鎖有著更強(qiáng)大的功能懂酱,比如提供了鎖等待時間 (boolean tryLock(long time.TimeUnit unit)) 、支持鎖中斷 (lockInterruptibly()) 和快速鎖輪詢 (boolean tryLock()) 以及一套 Condition 機(jī)制誊抛,這個機(jī)制類似于內(nèi)部鎖的 wait() 和 notify() 方法列牺。
(5)、 ReadWriteLock :讀寫分列鎖拗窃。如果 系統(tǒng)中讀操作次數(shù)遠(yuǎn)遠(yuǎn)大于寫操作瞎领,而讀寫鎖就可以發(fā)揮巨大的作用。
(6)Condition 對象: await() 方法和 signal() 方法随夸。 Condition 對象需要和重入鎖( ReentrantLock )配合工作以完成多線程協(xié)作的控制九默。
(7)Semaphore 信號量:信號量為多線程寫作提供了更為強(qiáng)大的控制方法。廣義上講宾毒,信號量是對鎖的擴(kuò)展驼修。無論是內(nèi)部鎖( Synchronized )還是重入鎖( ReentrantLock ),一次都只允許一個進(jìn)程訪問一個資源伍俘。而信號量卻可以指定多個線程同時訪問某一個資源邪锌。
(8)ThreadLocal 線程局部變量: ThreadLocal 是一種多線程間并發(fā)訪問變量的解決方案。與synchronized 等加鎖方式不同癌瘾, ThreadLocal 完全不提供鎖觅丰,而使用以空間換時間的手段,為每個線程提供變量的獨(dú)立副本妨退,以保障線程安全妇萄,因此并不是一種數(shù)據(jù)共享的解決方案。
5咬荷、同步工具類:
( 1 ) CountDownLatch (閉鎖):確保一個服務(wù)不會開始冠句,直到它依賴的其他服務(wù)都準(zhǔn)備就緒。 CountDownLatch 作用猶如倒計時計數(shù)器幸乒,調(diào)用 CountDownLatch 對象的 countDown 方法就將計數(shù)器減 1 懦底,當(dāng)計數(shù)到達(dá) 0 時,則所有等待者或單個等待者開始執(zhí)行罕扎。比如有 10 個運(yùn)動員的田徑比賽 , 聚唐,有兩個裁判 A 和 B 丐重, A 在起點(diǎn)吹哨起跑, B 在終點(diǎn)記錄記錄并公布每個運(yùn)動員的成績杆查。剛開始的時候扮惦,運(yùn)動員們都趴在跑道上( A.await() )等到裁判吹哨。 A 吹哨耗費(fèi)了 5 秒亲桦,此時調(diào)用 A.countDown() 方法將等待時間減為 4 秒崖蜜。當(dāng)減為 0 的時候,所有的運(yùn)動員開始起跑客峭。這個時候豫领, B 裁判開始工作。啟動一個初始值為 10 的定時器桃笙,每當(dāng)有一個運(yùn)動員跑到重點(diǎn)的時候氏堤,就將計數(shù)器減一沙绝,代表已經(jīng)有一個運(yùn)動員跑到終點(diǎn)搏明。當(dāng)計時器為 0 的時候,代表所有的運(yùn)動員都跑到了終點(diǎn)闪檬。此時可以根據(jù)公布成績了星著。
( 2 ) CylicBarrier (關(guān)卡):
1 )、類似于閉鎖粗悯,它們能夠阻塞一組線程直到某些事件發(fā)生
2 )虚循、與同步鎖的不同之處是一個可以重用,一個不可以重用
3 )样傍、所有線程必須同時到達(dá)關(guān)卡點(diǎn)横缔,才能繼續(xù)處理。
類似組團(tuán)旅游衫哥,導(dǎo)游就是一個關(guān)卡茎刚。表示大家彼此等待,大家集合好后才開始出發(fā)撤逢,分散活動后又在指定地點(diǎn)集合碰面膛锭,這就好比整個公司的人員利用周末時間集體郊游一樣,先各自從家出發(fā)到公司集合后蚊荣,再同時出發(fā)到公園游玩初狰,在指定地點(diǎn)集合后再同時開始就餐。
( 3 ) Exchanger :使用在兩個伙伴線程之間進(jìn)行數(shù)據(jù)交換互例,這個交換對于兩個線程來說都是安全的奢入。
講解 Exchanger 的比喻:好比兩個毒販要進(jìn)行交易,一手交錢媳叨、一手交貨腥光,不管誰先來到接頭地點(diǎn)后丁存,就處于等待狀態(tài)了,當(dāng)另外一方也到達(dá)了接頭地點(diǎn)(所謂到達(dá)接頭地點(diǎn)柴我,也就是到到達(dá)了準(zhǔn)備接頭的狀態(tài))時解寝,兩者的數(shù)據(jù)就立即交換了,然后就又可以各忙各的了艘儒。
exchange 方法就相當(dāng)于兩手高高舉著待交換物聋伦,等待人家前來交換,一旦人家到來(即人家也執(zhí)行到 exchange 方法)界睁,則兩者立馬完成數(shù)據(jù)的交換觉增。
5、關(guān)于死鎖:
(1)死鎖的四個條件:
1) 翻斟、互斥條件:一個資源只能被一個線程使用:
2 )逾礁、請求與保持條件:一個線程因請求資源而阻塞時,對已獲得則資源保持不放访惜。
3 )嘹履、不剝奪條件:進(jìn)程已經(jīng)獲得的資源,在未使用完之前债热,不能強(qiáng)行剝奪砾嫉。
4 )、循環(huán)等待條件:若干個線程已經(jīng)形成了一種頭尾相接的循環(huán)等待資源關(guān)系窒篱。
(2)常見的死鎖:靜態(tài)順序死鎖焕刮、動態(tài)順序死鎖、協(xié)作對象間的死鎖墙杯、線程饑餓死鎖配并。
(3)如何盡量避免死鎖:
1 )、制定鎖的順序高镐,來避免死鎖
2 )溉旋、嘗試使用定時鎖( lock.tryLock(timeout) )
3 )、在持有鎖的方法中進(jìn)行其他方法的調(diào)用避消,盡量使用開放調(diào)用(當(dāng)調(diào)用方法不需要持有鎖時低滩,叫做開放調(diào)用)
4 )、減少鎖的持有時間岩喷、減小鎖代碼塊的粒度恕沫。
5 )、不要將功能互斥的 Task 放入到同一個 Executor 中執(zhí)行纱意。
6 婶溯、 代碼層面對鎖的優(yōu)化機(jī)制:
1 、避免死鎖
2 、減少鎖持有時間迄委,代碼塊級別的鎖褐筛,而不是方法級別的鎖
3 、減小鎖粒度叙身, ConcurrentHashMap 分段加鎖
4 渔扎、讀寫鎖代替獨(dú)占鎖
5 、鎖分離信轿,例如 LinkedBlockingQueue 的尾插頭出的特點(diǎn)晃痴,用兩把鎖 (putLock takeLock) 分離兩種操作。
6 财忽、重入鎖和內(nèi)部鎖
重入鎖( ReentrantLock )和內(nèi)部鎖( Synchronized ):所有使用內(nèi)部鎖實(shí)現(xiàn)的功能倘核,都可以使用重入鎖實(shí)現(xiàn)。重入鎖必須放入 finally 塊中進(jìn)行釋放即彪,而內(nèi)部鎖可以自動釋放紧唱。
重入鎖有著更強(qiáng)大的功能,比如提供了鎖等待時間 (boolean tryLock(long time.TimeUnit unit))隶校、支持鎖中斷 (lockInterruptibly()) 和快速鎖輪詢 (boolean tryLock()) 以及一套 Condition 機(jī)制漏益,這個機(jī)制類似于內(nèi)部鎖的 wait() 和 notify() 方法。想要獲取多線程面試題的可以加群:650385180惠况,面試題及答案在群的共享區(qū)遭庶。
7 、鎖粗化:虛擬機(jī)在遇到一連串連續(xù)的對同一個鎖不斷進(jìn)行請求和釋放從操作的時候稠屠,便會把所有的鎖操作整合成對鎖的一次請求,從而減少對鎖的請求同步次數(shù)翎苫。
7 权埠、 Java 虛擬機(jī)層面對鎖的優(yōu)化機(jī)制:
1、自旋鎖:由于線程切換(線程的掛起和恢復(fù))消耗的時間較大煎谍,則使線程在沒有獲得鎖時攘蔽,不被掛起,而轉(zhuǎn)而執(zhí)行一個空循環(huán)呐粘。在若干空循環(huán)后满俗,線程如果獲得了鎖,而繼續(xù)執(zhí)行作岖,若線程依然不能獲得鎖唆垃,而才被掛起。
2痘儡、鎖消除: JVM 通過對上下文的掃描辕万,去除不可能存在共享資源競爭的鎖,這樣可以節(jié)省毫無意義的請求鎖時間。比如單線程中或者非共享資源的常使用的 StringBuffer 和 Vector 渐尿。
3醉途、鎖偏向:若某一個鎖被線程獲取后,便進(jìn)入偏向模式砖茸,當(dāng)線程再次請求這個鎖時隘擎,無需進(jìn)行相關(guān)的同步操作,從而節(jié)省了操作時間凉夯。
8 嵌屎、 Java 無鎖實(shí)現(xiàn)并發(fā)的機(jī)制:
( 1 )非阻塞的同步 / 無鎖: ThreadLocal ,讓每個進(jìn)程擁有各自獨(dú)立的變量副本恍涂,因此在并行計算時候宝惰,無須相互等待而造成阻塞。 CVS 算法的無鎖并發(fā)控制方法再沧。
( 2 )原子操作: java.util.concurrent.atomic 包尼夺。
四、Java性能優(yōu)化系列之四--Java內(nèi)存管理與垃圾回收機(jī)制詳解
1 炒瘸、 JVM 運(yùn)行時數(shù)據(jù)區(qū)域淤堵。
( 1 )、程序計數(shù)器:每一個 Java 線程都有一個程序計數(shù)器來用于保存程序執(zhí)行到當(dāng)前方法的哪一個指令顷扩。此內(nèi)存區(qū)域是唯一一個在 JVM Spec 中沒有規(guī)定任何 OutOfMemoryError 情況的區(qū)域拐邪。
( 2 )、 Java 虛擬機(jī)棧:該塊內(nèi)存描述的是 Java 方法調(diào)用的內(nèi)存模型隘截,每個方法在被執(zhí)行的時候扎阶,都會同時創(chuàng)建一個幀( Frame )用于存儲本地變量表、操作棧婶芭、動態(tài)鏈接东臀、方法出入口等信息。
( 3 )犀农、本地方法棧惰赋。本地方法調(diào)用的內(nèi)存模型。
( 4 )呵哨、 Java 堆赁濒。 Java 中的對象以及類的靜態(tài)變量的存放地方。
( 5 )孟害、方法區(qū):方法區(qū)中存放了每個 Class 的結(jié)構(gòu)信息拒炎,包括常量池、字段描述纹坐、方法描述等等
( 6 )枝冀、運(yùn)行時常量池: Class 文件中除了有類的版本舞丛、字段、方法果漾、接口等描述等信息外球切,還有一項(xiàng)信息是常量表 (constant_pool table) ,用于存放編譯期已可知的常量绒障,這部分內(nèi)容將在類加載后進(jìn)入方法區(qū)(永久代)存放吨凑。但是 Java 語言并不要求常量一定只有編譯期預(yù)置入 Class 的常量表的內(nèi)容才能進(jìn)入方法區(qū)常量池,運(yùn)行期間也可將新內(nèi)容放入常量池(最典型的 String.intern() 方法)户辱。運(yùn)行時常量池是方法區(qū)的一部分鸵钝,自然受到方法區(qū)內(nèi)存的限制,當(dāng)常量池?zé)o法在申請到內(nèi)存時會拋出 OutOfMemoryError 異常庐镐。
( 7 )恩商、本機(jī)直接內(nèi)存( Direct Memory )
在 JDK1.4 中新加入了 NIO 類,引入一種基于渠道與緩沖區(qū)的 I/O 方式必逆,它可以通過本機(jī) Native 函數(shù)庫直接分配本機(jī)內(nèi)存怠堪,然后通過一個存儲在 Java 堆里面的 DirectByteBuffer 對象作為這塊內(nèi)存的引用進(jìn)行操作。這樣能在一些場景中顯著提高性能名眉,因?yàn)楸苊饬嗽?Java 對和本機(jī)堆中來回復(fù)制數(shù)據(jù)粟矿。
2、Java 類加載機(jī)制的特點(diǎn):
(1)基于父類的委托機(jī)制:運(yùn)行一個程序時损拢,總是由 AppClass Loader (系統(tǒng)類加載器)開始加載指定的類陌粹,在加載類時,每個類加載器會將加載任務(wù)上交給其父福压,如果其父找不到掏秩,再由自己去加載, Bootstrap Loader (啟動類加載器)是最頂級的類加載器了隧膏,其父加載器為 null 哗讥。如果父類加載器找不到給定的類名,則交由子加載器去加載胞枕,如果最低一層的子加載器也無法找到,則拋出異常魏宽。
(2)全盤負(fù)責(zé)機(jī)制:所謂全盤負(fù)責(zé)腐泻,就是當(dāng)一個類加載器負(fù)責(zé)加載某個 Class 時,該 Class 鎖依賴的和引用的其他 Class 也將由該類加載器負(fù)責(zé)載入队询,除非顯式使用另外一個類加載器來載入派桩。
(3)緩存機(jī)制:緩存機(jī)制將會保證所有加載過的 Class 對象都會被緩存,當(dāng)程序中需要使用某個 Class 時蚌斩,類加載器會先從緩沖區(qū)中搜尋該 Class 铆惑,只有當(dāng)緩存區(qū)中不存在該 Class 對象時,系統(tǒng)才會讀取該類對應(yīng)的二進(jìn)制數(shù)據(jù),并將其轉(zhuǎn)化為 Class 對象员魏,存入緩存區(qū)中丑蛤。這就是為什么修改了 Class 后,必須重新啟動 JVM 撕阎,程序所做的修改才會生效的原因受裹。同時,往們比較 A.getClass() 與 B.getClass() 是否相等時虏束,直接使用 == 比較棉饶,因?yàn)榫彺鏅C(jī)制保證類的字節(jié)碼在內(nèi)存中只可能存在一份。
(4)類加載器的三種方法以及其區(qū)別:
1)镇匀、命令行啟動應(yīng)用時候由 JVM 初始化加載
2)照藻、通過 Class.forName() 方法動態(tài)加載
3)、通過 ClassLoader.loadClass() 方法動態(tài)加載 // 使用 Class.forName() 來加載類汗侵,默認(rèn)會執(zhí)行初始化塊 幸缕, // 使用 Class.forName() 來加載類,并指定 ClassLoader 晃择,初始化時不執(zhí)行靜態(tài)塊冀值。
4)區(qū)別:使用 ClassLoader.loadClass() 來加載類,不會執(zhí)行初始化塊宫屠,
3 列疗、類的主動引用
什么情況下需要開始類加載過程的第一個階段,也即類的初始化階段浪蹂。 Java 虛擬機(jī)規(guī)定了有且只有 5 種情況下必須立即對類進(jìn)行初始化:
(1)抵栈、遇到 new 、 getstatic 坤次、 putstatic 或 invokestatic 這四條字節(jié)碼指令時古劲,如果類沒有進(jìn)行過初始化,則需要觸發(fā)其初始化缰猴。(而且初始化的時候按照先父后子的順序)产艾。這四條指令最常見的 Java 代碼場景是:使用 new 關(guān)鍵字實(shí)例化對象的時候、讀取或設(shè)置一個類的靜態(tài)字段(被 final 修飾滑绒,已在編譯時期把結(jié)果放入常量池的靜態(tài)字段除外)闷堡、調(diào)用一個類的靜態(tài)方法的的時候。
(2)使用 java.lang.reflect 包的方法對類進(jìn)行反射調(diào)用的時候疑故,如果類沒有進(jìn)行過初始化杠览,則需要先對其進(jìn)行初始化。
(3)當(dāng)初始化一個類的時候纵势,如果發(fā)現(xiàn)其父類還沒有進(jìn)行過初始化踱阿,則需要先觸發(fā)其父類的初始化管钳。但是一個接口在初始化時,并不要求其父類接口全部都完成了初始化软舌,只有在真正使用到父接口的時候(如引用接口中定義的常量)才會被初始化才漆。
(4)當(dāng)虛擬機(jī)啟動時,用戶需要指定一個要執(zhí)行的主類(包含 main ()方法的那個類)葫隙,虛擬機(jī)會先初始化這個主類栽烂。
(5)當(dāng)使用 jdk1.7 的動態(tài)語言支持時,如果一個 java.lang.invoke.MethodHandle 實(shí)例最后的解析結(jié)果 REF_getStatic 恋脚、 REF_putStatic 腺办、 REF_invokeStatic 的方法句柄,并且這個方法句柄所對應(yīng)的類沒有初始化過糟描,則需要先觸發(fā)其初始化怀喉。
4 、類的被動引用
1船响、對于靜態(tài)字段躬拢,只有直接定義這個字段的類才會被初始化,因此通過其子類來引用父類中定義的靜態(tài)字段见间,只會觸發(fā)父類的初始化而不會觸發(fā)子類的初始化聊闯。
2、通過數(shù)組定義來引用類米诉,不會觸發(fā)類的初始化 SuperClass[]sca=new SuperClass[10].
3菱蔬、常量在編譯階段會存入調(diào)用類的常量池中,本質(zhì)上并沒有直接引用到定義常量的類史侣,因此不會觸發(fā)常量的類的初始化拴泌。
5 、 Java 對象的創(chuàng)建過程以及如何保證對象創(chuàng)建的多線程的安全性:
虛擬機(jī)遇到一條 new 指令時惊橱,首先將去檢查這個指令的參數(shù)是否能在常量池中定位到一個類的符號引用蚪腐,并且檢查這個符號引用代表的類是否已經(jīng)被加載、解析和初始化過税朴。如果沒有則進(jìn)行類加載過程回季。
在類加載通過后,接下來虛擬機(jī)將為新生對象分配內(nèi)存正林。對象所需的內(nèi)存的大小在類加載完成后便可完全確定茧跋。為對象分配空間的任務(wù)等價于把一塊確定大小的內(nèi)存從 Java 堆中劃分出來。
保證多線程的安全性卓囚。 有兩種方案,一種是對分配內(nèi)存的動作進(jìn)行同步操作诅病,實(shí)際上虛擬機(jī)采用 CAS 加上失敗重試的方式保證更新操作的原子性哪亿。另一種是把內(nèi)存分配的動作按照線程劃分在不同的空間中進(jìn)行粥烁。即為每個線程在 Java 堆中預(yù)先分配一小塊內(nèi)存,成為本地線程分配緩沖( TLAB )蝇棉。哪個線程要分配內(nèi)存讨阻,就在哪個 TLAB 上分配,只有 TLAB 用完并分配新的 TLAB時篡殷,才需要分配新的 TLAB 钝吮。
6、 什么時候判斷一個對象可以被回收板辽?
用可達(dá)性分析算法奇瘦。這個算法的基本思路就是通過一系列的成為“ GC roots ”的對象作為起始點(diǎn)涂臣,從這些節(jié)點(diǎn)開始向下搜索演痒,如果一個對象到 GCroots 沒有任何引用鏈相連,則證明此對象是不可用的胜嗓∫毓颍可作為 GCroots 的對象包括虛擬機(jī)棧中引用的對象次坡、方法區(qū)中常量引用的對象、方法區(qū)中靜態(tài)屬性引用的對象或者本地方法棧中 JNI 引用的對象画畅,這些對象的共同點(diǎn)都是生命周期與程序的生命周期一樣長砸琅,一般不會被 GC 。判斷一個對象死亡轴踱,至少經(jīng)歷兩次標(biāo)記過程:如果對象在進(jìn)行可達(dá)性算法后症脂,發(fā)現(xiàn)沒有與 GC Roots 相連接的引用鏈,那他將會被第一次標(biāo)記寇僧,并在稍后執(zhí)行其 finalize ()方法摊腋。執(zhí)行是有機(jī)會,并不一定執(zhí)行嘁傀。稍后 GC 進(jìn)行第二次標(biāo)記兴蒸,如果第一次標(biāo)記的對象在 finalize ()方法中拯救自己,比如把自己賦值到某個引用上细办,則第二次標(biāo)記時它將被移除出“即將回收”的集合橙凳,如果這個時候?qū)ο筮€沒有逃脫,那基本上就會被 GC 了笑撞。
7 岛啸、 關(guān)于 finalize ()方法的作用的說明:
finalize ()方法的工作原理理論上是這樣的:一旦垃圾回收器準(zhǔn)備好釋放占用的存儲空間,將首先調(diào)用其 finalize ()方法茴肥,并且在下一次垃圾回收動作發(fā)生時坚踩,才會真正回收對象占用的內(nèi)存,所以使用 finalize ()的目的就是在垃圾回收時刻做一些重要的清理工作瓤狐。我們知道瞬铸,使用 GC 的唯一原因就是回收程序不再使用的內(nèi)存批幌,所以對于與垃圾回收有關(guān)的任何行為來說,包括 finalize() 方法嗓节,它們也必須同內(nèi)存及其回收有關(guān)荧缘。個人認(rèn)為 Java 對象的 finalize ()方法有兩個作用( 1 )回收通過創(chuàng)建對象方式以外的方式為對象分配了存儲空間。比如拦宣,比如在 Java 代碼中采用了 JNI 操作截粗,即在內(nèi)存分配時,采用了類似 C 語言中的 malloc 函數(shù)來分配內(nèi)存鸵隧,而且沒有調(diào)用free 函數(shù)進(jìn)行釋放绸罗。此時就需要在 finalize ()中用本地方法調(diào)用 free 函數(shù)以釋放內(nèi)存。( 2 )對象終結(jié)條件的驗(yàn)證掰派,即用來判定對象是否符合被回收條件从诲。比如,如果要回收一個對象靡羡,對象被清理時應(yīng)該處于某種狀態(tài)系洛,比如說是一個打開的文件,在回收之前應(yīng)該關(guān)閉這個文件略步。只要對象中存在沒有被適當(dāng)清理的部分描扯, finalize ()就可以用來最終法相這種情況。因?yàn)閷ο笤诒磺謇淼臅r候肯定處于生命周期的最后一個階段趟薄,如果此時還含有一些未釋放的資源绽诚,則有能力釋放這些資源。這個不是 C/C++ 里面的析構(gòu)函數(shù)杭煎,它運(yùn)行代價高昂恩够,不確定性大,無法保證各個對象的調(diào)用順序羡铲。需要關(guān)閉外部資源之類的事情蜂桶,基本上它能做的使用 try-finally 可以做的更好。
8 也切、 一個類被回收的條件扑媚。
(1)、該類所有的實(shí)例都已經(jīng)為 GC 雷恃,也就是說 JVM 中不存在該 Class 的任何實(shí)例疆股。
(2)、加載該類的 ClassLoader 已經(jīng)被 GC 倒槐。
(3)該類對應(yīng)的 java.lang.Class 對象沒有在任何地方被引用旬痹,如不能在任何地方通過反射訪問類的方法。
9、 垃圾回收算法 :
(1)唱凯、標(biāo)記 - 清除算法:標(biāo)記階段根據(jù)根節(jié)點(diǎn)標(biāo)記所有從根節(jié)點(diǎn)開始的可達(dá)對象羡忘。則未被標(biāo)記的對象就是未被引用的垃圾對象,然后在清除階段磕昼,清楚所有未被標(biāo)記的對象。其最大缺點(diǎn)是空間碎片节猿。
(2)票从、復(fù)制算法:將原有的內(nèi)存空間分為兩塊,每次只使用其中一塊滨嘱,在垃圾回收時峰鄙,將正在使用的內(nèi)存中的存活對象復(fù)制到未使用的內(nèi)存塊中,之后清楚正在使用的內(nèi)存快中的所有對象太雨,然后交換兩個內(nèi)存的角色吟榴。完成垃圾回收。這種算法比較適合新生代囊扳,因?yàn)樵谛律苑鴮ο笸ǔ嘤诖婊顚ο螅瑥?fù)制算法效果較好锥咸。 Java 的新生代串行 GC 中狭瞎,就使用了復(fù)制算法的思想。新生代分為 eden 空間搏予、 from 空間和 to 空間三個部分熊锭。 From 和 to 空間可以視為用于復(fù)制的兩塊大小相同、地位相等雪侥、且可以進(jìn)行角色互換的空間塊碗殷。 From 和 to 空間也成為 survivor 空間,即幸存者空間速缨,用于存放未被回收的對象锌妻。
(3)、標(biāo)記 - 壓縮算法:標(biāo)記過程與標(biāo)記清楚算法一樣鸟廓,但后續(xù)不是直接對可回收對象進(jìn)行清理从祝,而是讓所有存活的對象向一段移動,然后直接清理掉端邊界以外的內(nèi)存引谜。適合老年代的回收牍陌。
(4)、分代收集算法员咽。
10 毒涧、 垃圾回收器。
( 1 )贝室、 Serial 收集器
單線程收集器契讲,收集時會暫停所有工作線程(我們將這件事情稱之為 Stop The World 仿吞,下稱 STW ),使用復(fù)制收集算法捡偏,虛擬機(jī)運(yùn)行在 Client 模式時的默認(rèn)新生代收集器唤冈。
(2)、 ParNew 收集器就是 Serial 的多線程版本银伟,除了使用多條收集線程外你虹,其余行為包括算法、 STW 彤避、對象分配規(guī)則傅物、回收策略等都與 Serial 收集器一摸一樣。對應(yīng)的這種收集器是虛擬機(jī)運(yùn)行在 Server 模式的默認(rèn)新生代收集器琉预,在單 CPU 的環(huán)境中董饰, ParNew 收集器并不會比 Serial 收集器有更好的效果。
(3)Parallel Scavenge 收集器(下稱 PS 收集器)也是一個多線程收集器圆米,也是使用復(fù)制算法卒暂,但它的對象分配規(guī)則與回收策略都與 ParNew 收集器有所不同,它是以吞吐量最大化(即 GC 時間占總運(yùn)行時間最姓ジ馈)為目標(biāo)的收集器實(shí)現(xiàn)介却,它允許較長時間的 STW 換取總吞吐量最大化。
(4)4.Serial Old 收集器 Serial Old 是單線程收集器块茁,使用標(biāo)記-整理算法齿坷,是老年代的收集器
(5) Parallel Old 收集器
老年代版本吞吐量優(yōu)先收集器,使用多線程和標(biāo)記-整理算法数焊, JVM 1.6 提供永淌,在此之前,新生代使用了 PS 收集器的話佩耳,老年代除 Serial Old 外別無選擇遂蛀,因?yàn)?PS 無法與 CMS 收集器配合工作。
(6)CMS ( Concurrent Mark Sweep )收集器
CMS 是一種以最短停頓時間為目標(biāo)的收集器干厚,使用 CMS 并不能達(dá)到 GC 效率最高(總體 GC時間最欣畹巍),但它能盡可能降低 GC 時服務(wù)的停頓時間蛮瞄,這一點(diǎn)對于實(shí)時或者高交互性應(yīng)用(譬如證券交易)來說至關(guān)重要所坯。
( 7 )、 G1 收集器挂捅。
11 芹助、內(nèi)存分配與回收策略:
( 1 )、規(guī)則一:通常情況下,對象在 eden 中分配状土。當(dāng) eden 無法分配時无蜂,觸發(fā)一次 Minor GC 。
( 2 )蒙谓、規(guī)則二:配置了 PretenureSizeThreshold 的情況下斥季,對象大于設(shè)置值將直接在老年代分配。
( 3 )彼乌、規(guī)則三:在 eden 經(jīng)過 GC 后存活泻肯,并且 survivor 能容納的對象,將移動到 survivor 空間內(nèi)慰照,如果對象在 survivor 中繼續(xù)熬過若干次回收(默認(rèn)為 15 次)將會被移動到老年代中×鹦啵回收次數(shù)由 MaxTenuringThreshold 設(shè)置毒租。
( 4 )、規(guī)則四:如果在 survivor 空間中相同年齡所有對象大小的累計值大于 survivor 空間的一半箱叁,大于或等于該年齡的對象就可以直接進(jìn)入老年代墅垮,無需達(dá)到 MaxTenuringThreshold 中要求的年齡。
( 5 )耕漱、規(guī)則五:在 Minor GC 觸發(fā)時算色,會檢測之前每次晉升到老年代的平均大小是否大于老年代的剩余空間,如果大于螟够,改為直接進(jìn)行一次 Full GC 灾梦,如果小于則查看 HandlePromotionFailure 設(shè)置看看是否允許擔(dān)保失敗,如果允許妓笙,那仍然進(jìn)行 Minor GC 若河,如果不允許,則也要改為進(jìn)行一次 Full GC 寞宫。
11萧福、 關(guān)于 Minor GC 與 Full GC
Java 堆,分配對象實(shí)例所在空間辈赋,是 GC 的主要對象鲫忍。分為新生代 (Young Generation/New)和老年代 (Tenured Generation/Old) 。新生代又劃分成 Eden Space 钥屈、 From Survivor/Survivor 0 悟民、
To Survivor/Survivor 1 。
新生代要如此劃分是因?yàn)樾律褂玫?GC 算法是復(fù)制收集算法焕蹄。新生代使用賦值收集算法逾雄,但是為了內(nèi)存利用率,只使用一個 Survivor 空間來作為輪轉(zhuǎn)備份(之所以把該空間分為 FromSpace 和 ToSpace 兩部分是為了在 Minor GC 的時候把一些 age 大的對象從新生代空間中復(fù)制到老年代空間中)這種算法效率較高,而 GC 主要是發(fā)生在對象經(jīng)常消亡的新生代鸦泳,因此新生代適合使用這種復(fù)制收集算法银锻。由于有一個假設(shè):在一次新生代的 GC(Minor GC) 后大部分的對象占用的內(nèi)存都會被回收,因此留存的放置 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 中仍然活著的對象們復(fù)制到 To 空間中去涉瘾。如果 To 空間不能容納 Minor GC 后活著的某個對象知态,那么該對象就被 promote 到老年代空間。從 Eden 空間被復(fù)制到 To 空間的對象就有了 age=1 立叛。此 age=1 的對象如果在下一次的 Minor GC 后仍然存活负敏,它還會被復(fù)制到另一個 Survivor 空間 ( 如果認(rèn)為 From 和 To 是固定的,就是又從 To 回到了From 空間 ) 秘蛇,而它的 age=2 其做。如此反復(fù),如果 age 大于某個閾值 (-XX:MaxTenuringThreshold=n)彤叉,那個該對象就也可以 promote 到老年代了庶柿。
如果 Survivor 空間中相同 age( 例如, age=5) 對象的總和大于等于 Survivor 空間的一半秽浇,那么 age>=5 的對象在下一次 Minor GC 后就可以直接 promote 到老年代浮庐,而不用等到 age 增長到閾值。
在做 Minor GC 時柬焕,只對新生代做回收审残,不會回收老年代。即使老年代的對象無人索引也將仍然存活斑举,直到下一次 Full GC 搅轿。
在發(fā)生 Minor GC 之前,虛擬機(jī)會先檢查老年代最大可用的連續(xù)空間是否大于新生代所有對象總空間富玷,如果這個條件成立璧坟,那么 Minor GC 可以確保是安全的既穆。如果經(jīng)過 Minor GC 后仍有大量對象存活的情況,則需要老年代進(jìn)行分配擔(dān)保雀鹃,把 Survior 無法容納的對象直接進(jìn)入老年代幻工。
13 、四種引用類型:
( 1 )黎茎、強(qiáng)引用:直接關(guān)聯(lián)囊颅,虛擬機(jī)永遠(yuǎn)不會回收。
( 2 )傅瞻、軟引用:描述一些還有用但并非必須的對象踢代,虛擬機(jī)會在拋出內(nèi)存溢出異常之前會對 這些對象進(jìn)行第二次回收。
( 3 )弱引用:虛擬機(jī)一定會回收的對象
( 4 )虛引用:為一個對象設(shè)置虛引用關(guān)聯(lián)的唯一目的就是能在這個對象被收集器回收時收到一個系統(tǒng)通知嗅骄。
14 胳挎、關(guān)于 Java 中生成對象的 4 種方式與區(qū)別:
( 1 )、使用 new 操作符溺森,這是最普遍的一種(會調(diào)用相應(yīng)的構(gòu)造函數(shù)):
如: String s=new String("abc");
( 2 )使用反射動態(tài)生成( 會調(diào)用相應(yīng)的構(gòu)造函數(shù) ):
利用 Class 串远, ClassLoader , Constructor 中的方法可以動態(tài)的生成類實(shí)例
如: Object o=Class.forName("java.lang.String").newInstance();
Object o=String.class.getClassLoader.loadClass("java.lang.String").newInstance();
以上的方式需要目標(biāo)類擁有公有無參構(gòu)造函數(shù)
以下使用 Constructor 進(jìn)行動態(tài)生成
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 )使用克隆生成對象( 不會調(diào)用構(gòu)造函數(shù) )
例如使用一個實(shí)現(xiàn)了 Cloneable 接口的對象儿惫,調(diào)用其 clone() 方法獲得該對象的一份拷貝,使用 Java 序列化方式實(shí)現(xiàn)深拷貝伸但。
( 4 )利用反序列化從流中生成對象( 不會調(diào)用構(gòu)造函數(shù) ):
利用 ObjectInptuStream 的 readObject() 方法生成對象
五肾请、Java性能優(yōu)化系列之五--JavaIO
1 、關(guān)于 Java 序列化與反序列化:
(1)作用:
1更胖、實(shí)現(xiàn)對象狀態(tài)的保存到本地铛铁,以便下一次啟動虛擬機(jī)的時候直接讀取保存的序列化字節(jié)生成對象,而不是初始化對象却妨; 2 饵逐、實(shí)現(xiàn)對象的網(wǎng)絡(luò)傳輸( RMI 分布對象); 3 彪标、實(shí)現(xiàn)對象的深拷貝倍权。
一:對象序列化可以實(shí)現(xiàn)分布式對象。主要應(yīng)用例如: RMI 要利用對象序列化運(yùn)行遠(yuǎn)程主機(jī)上的服務(wù)捞烟,就像在本地機(jī)上運(yùn)行對象時一樣薄声。
二: java 對象序列化不僅保留一個對象的數(shù)據(jù),而且遞歸保存對象引用的每個對象的數(shù)據(jù)题画∧妫可以將整個對象層次寫入字節(jié)流中,可以保存在文件中或在網(wǎng)絡(luò)連接上傳遞苍息。利用對象序列化可以進(jìn)行對象的 " 深復(fù)制 " 缩幸,即復(fù)制對象本身及引用的對象本身壹置。序列化一個對象可能得到整個對象序列。
(2)基本方式:
ObjectOutputStream 只能對 Serializable 接口的類的對象進(jìn)行序列化表谊。默認(rèn)情況下钞护, ObjectOutputStream 按照默認(rèn)方式序列化,這種序列化方式僅僅對對象的非 transient 的實(shí)例變量進(jìn)行序列化铃肯,而不會序列化對象的 transient 的實(shí)例變量患亿,也不會序列化靜態(tài)變量。
當(dāng) ObjectOutputStream 按照默認(rèn)方式反序列化時押逼,具有如下特點(diǎn):
1 ) 如果在內(nèi)存中對象所屬的類還沒有被加載步藕,那么會先加載并初始化這個類。如果在 classpath 中不存在相應(yīng)的類文件挑格,那么會拋出 ClassNotFoundException 咙冗;
2 ) 在反序列化時不會調(diào)用類的任何構(gòu)造方法。
如果用戶希望控制類的序列化方式漂彤,可以在可序列化類中提供以下形式的 writeObject() 和 readObject() 方法雾消。
private void writeObject(java.io.ObjectOutputStream out) throws IOException
private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException;
當(dāng) ObjectOutputStream 對一個 Customer 對象進(jìn)行序列化時,如果該對象具有 writeObject() 方法挫望,那么就會執(zhí)行這一方法立润,否則就按默認(rèn)方式序列化。在該對象的 writeObjectt() 方法中媳板,可以先調(diào)用 ObjectOutputStream 的 defaultWriteObject() 方法桑腮,使得對象輸出流先執(zhí)行默認(rèn)的序列化操作。同理可得出反序列化的情況蛉幸,不過這次是 defaultReadObject() 方法破讨。
有些對象中包含一些敏感信息,這些信息不宜對外公開奕纫。如果按照默認(rèn)方式對它們序列化提陶,那么它們的序列化數(shù)據(jù)在網(wǎng)絡(luò)上傳輸時,可能會被不法份子竊取匹层。對于這類信息隙笆,可以對它們進(jìn)行加密后再序列化,在反序列化時則需要解密又固,再恢復(fù)為原來的信息仲器。
默認(rèn)的序列化方式會序列化整個對象圖,這需要遞歸遍歷對象圖仰冠。如果對象圖很復(fù)雜乏冀,遞歸遍歷操作需要消耗很多的空間和時間,它的內(nèi)部數(shù)據(jù)結(jié)構(gòu)為雙向列表洋只。
在應(yīng)用時辆沦,如果對某些成員變量都改為 transient 類型昼捍,將節(jié)省空間和時間,提高序列化的性能肢扯。
|-1 妒茬、實(shí)體對象實(shí)現(xiàn) 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 自定義序列化反序列化:復(fù)寫實(shí)現(xiàn)了 seriliable 的實(shí)體類的 readObject() 和 writeObject() 的方法的原因:
有些對象中包含一些敏感信息,這些信息不宜對外公開铭腕。如果按照默認(rèn)方式對它們序列化银择,那么它們的序列化數(shù)據(jù)在網(wǎng)絡(luò)上傳輸時,可能會被不法份子竊取累舷。對于這類信息浩考,可以對它們進(jìn)行加密后再序列化,在反序列化時則需要解密被盈,再恢復(fù)為原來的信息析孽。此時便不能使用默認(rèn)的 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();
}
一般情況直接實(shí)現(xiàn) Serializable 接口就可以實(shí)現(xiàn)序列化的要求只怎,但是有些情況需要對序列化做一些特殊的要求袜瞬。
(4)Transits 關(guān)鍵字的作用:屏蔽一些不想進(jìn)行序列化的成員變量,解屏蔽的方法可以用( 3)
(5)Externalize 的作用:
Externalizable 接口繼承自 Serializable 接口身堡,如果一個類實(shí)現(xiàn)了 Externalizable 接口吞滞,那么將完全由這個類控制自身的序列化行為。 Externalizable 接口聲明了兩個方法:
public void writeExternal(ObjectOutput out) throws IOException
public void readExternal(ObjectInput in) throws IOException , ClassNotFoundException
前者負(fù)責(zé)序列化操作盾沫,后者負(fù)責(zé)反序列化操作。
在對實(shí)現(xiàn)了 Externalizable 接口的類的對象進(jìn)行反序列化時殿漠, 會先調(diào)用類的不帶參數(shù)的構(gòu)造方法赴精,這是有別于默認(rèn)反序列方式的。如果把類的不帶參數(shù)的構(gòu)造方法刪除 绞幌,或者把該構(gòu)造方法的訪問權(quán)限設(shè)置為 private 蕾哟、默認(rèn)或 protected 級別,會拋出 java.io.InvalidException: no valid constructor 異常莲蜘。
(6)與 Java 構(gòu)造函數(shù)的關(guān)系:
實(shí)現(xiàn)了 Externalizable 接口的類的對象進(jìn)行反序列化時谭确,會先調(diào)用類的不帶參數(shù)的構(gòu)造方法;而實(shí)現(xiàn)了 Serializable 接口的類的對象進(jìn)行反序列化時票渠,不會調(diào)用任何構(gòu)造方法逐哈。僅僅是根據(jù)所保存的對象的狀態(tài)信息,在內(nèi)存中重新構(gòu)建對象问顷!
(7)注意事項(xiàng):
1) 昂秃、序列化運(yùn)行時使用一個稱為 serialVersionUID 的版本號與每個可序列化類相關(guān)聯(lián)禀梳,該序列號在反序列化過程中用于驗(yàn)證序列化對象的發(fā)送者和接收者是否為該對象加載了與序列化兼容的類。為它賦予明確的值肠骆。顯式地定義 serialVersionUID 有兩種用途:
在某些場合算途,希望類的不同版本對序列化兼容,因此需要確保類的不同版本具有相同的 serialVersionUID 蚀腿;
在某些場合嘴瓤,不希望類的不同版本對序列化兼容,因此需要確保類的不同版本具有不同的 serialVersionUID 莉钙。
2)廓脆、 java 有很多基礎(chǔ)類已經(jīng)實(shí)現(xiàn)了 serializable 接口,比如 string,vector 等胆胰。但是比如 hashtable 就沒有實(shí)現(xiàn) serializable 接口疾呻。
3)、并不是所有的對象都可以被序列化塌鸯。由于安全方面的原因一個對象擁有 private,public 等 field, 對于一個要傳輸?shù)膶ο?, 比如寫到文件 , 或者進(jìn)行 rmi 傳輸?shù)鹊?, 在序列化進(jìn)行傳輸?shù)倪^程中 ,這個對象的 private 等域是不受保護(hù)的兜蠕;資源分配方面的原因 , 比如 socket,thread 類 , 如果可以序列化 , 進(jìn)行傳輸或者保存 , 也無法對他們進(jìn)行重新的資源分配 , 而且 , 也是沒有必要這樣實(shí)現(xiàn) .
4)、反序列化對象時厚柳,并不會調(diào)用該對象的任何構(gòu)造方法氧枣,僅僅是根據(jù)所保存的對象的狀態(tài)信息,在內(nèi)存中重新構(gòu)建對象别垮!
5)便监、當(dāng)一個對象被序列化時,只保存對象的非靜態(tài)成員變量碳想,不能保存任何的成員方法和靜態(tài)的成員變量
6)烧董、如果一個對象的成員變量是一個對象,那么這個對象的數(shù)據(jù)成員也會被保存胧奔!這是能用序列化解決深拷貝的重要原因逊移。
(8)序列化與單例模式的沖突解決辦法:
另外還有兩個自定義序列化方法 writeReplace 和 readResolve ,分別用來在序列化之前替換序列化對象 和 在反序列化之后的對返回對象的處理龙填。一般可以用來避免 singleTon 對象跨 jvm 序列化和反序列化時產(chǎn)生多個對象實(shí)例胳泉,事實(shí)上 singleTon 的對象一旦可序列化,它就不能保證 singleTon 了岩遗。 JVM 的 Enum 實(shí)現(xiàn)里就是重寫了 readResolve 方法扇商,由 JVM 保證 Enum 的值都是 singleTon 的,所以建議多使用 Enum 代替使用 writeReplace 和 readResolve 方法宿礁。
Java 代碼
private Object readResolve()
{
return INSTANCE;
}
private Object writeReplace(){
return INSTANCE;
}
注: writeReplace 調(diào)用在 writeObject 前 ;readResolve 調(diào)用在 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());
}
對象所屬的類要實(shí)現(xiàn) Serializable 接口。同時將該方法寫入到對象所屬的類中梆靖。
深拷貝的時候红且,調(diào)用該方法即可坝茎。
2、JavaIO 中的裝飾模式:
Java 中使用的最廣泛的裝飾器模式就是 JavaIO 類的設(shè)計暇番。比如嗤放, OutPutStream 是輸出流的基類,其子類有 FileOutputStream 和 FilterOutputStream, 而 FilterOutputStream 的子類有 BufferedOutputStream 和 DataOutputStream 兩個子類壁酬。其中次酌, FileOutputStream 為系統(tǒng)的核心類,它實(shí)現(xiàn)了向文件寫數(shù)據(jù)的功能舆乔,使用 DataOutputStream 可以在 FileOutputStream 的基礎(chǔ)上增加多種數(shù)據(jù)類型的寫操作支持( DataOutputStream 類中有 writeUTF 岳服、 writeInt 等函數(shù)),而 BufferdOutputStream 裝飾器可以對 FileOutputStream 增加緩沖功能希俩,優(yōu)化 I/O 性能吊宋。
3、JavaIO 流的使用場景:
(1)IO 流:用于處理設(shè)備上的數(shù)據(jù)颜武,這里的設(shè)備指的是:硬盤上的文件璃搜、內(nèi)存、鍵盤輸入鳞上、屏幕顯示这吻。
(2)字節(jié)流和字符流:字節(jié)流好理解,因?yàn)樗懈袷降奈募际且宰止?jié)形式硬盤上存儲的篙议,包括圖片唾糯、 MP3 、 avi 等鬼贱,因此字節(jié)流可以處理所有類型的數(shù)據(jù)移怯。字符流讀取的時候讀到一個或多個字節(jié)時(中文對應(yīng)的 字節(jié)數(shù)是兩個,在 UTF-8 碼表中是三個字節(jié))時这难,先去查指定的編碼表芋酌,將查到的字符返回。字符流之所以出現(xiàn)雁佳,就是因?yàn)橛辛宋募幋a的不同,而有了對字符進(jìn)行高效操作的字符流對象同云。因此糖权,只要是處理純文本數(shù)據(jù),就要優(yōu)先考慮使用字符流炸站,除此之外都使用字節(jié)流星澳。
(3)流操作的基本規(guī)律:
1 )、明確數(shù)據(jù)源和數(shù)據(jù)匯旱易,目的是明確使用輸入流還是輸出流禁偎。
2 )腿堤、明確操作的數(shù)據(jù)是否是純文本數(shù)據(jù)。
3 )如暖、是否需要進(jìn)行字節(jié)流和字符流的轉(zhuǎn)換笆檀。
4 )、是否需要使用緩存盒至。
(4)實(shí)例說明流操作的基本流程:把鍵盤上讀入的數(shù)據(jù)以指定的編碼存入到文件中酗洒。
1 )、明白數(shù)據(jù)源:鍵盤輸入枷遂, System.in 樱衷,可用 InputStream 和 Reader
2 )、發(fā)現(xiàn) System.in 對應(yīng)的流是字節(jié)讀入流酒唉,所以要將其進(jìn)行轉(zhuǎn)換矩桂,將字節(jié)轉(zhuǎn)換為字符。
3 )痪伦、所以要使用 InputStreamReader 轉(zhuǎn)換流
4 )侄榴、如果想提高效率,要加入緩存機(jī)制流妻,那么就要加入字符流的緩沖區(qū)牲蜀。 BufferedReader,因此前四步構(gòu)造出的輸入流為:
BufferedReader bur = new BufferedReader(new InputStreamReader(System.in));
5 )绅这、明白數(shù)據(jù)匯:既然是數(shù)據(jù)匯涣达,則一定是輸出流,可以用 OutputStream 或 Writer 证薇。
6 )度苔、往文件中存儲的都是文本文件,因此選用 Writer 浑度。
7 )寇窑、因?yàn)橐付ň幋a表,所以使用 Writer 中的轉(zhuǎn)換流箩张, OutputStreamWriter 甩骏。
注意:雖然最終是文件,但是不可以選擇 FileWriter 先慷,因?yàn)樵搶ο笫鞘褂媚J(rèn)編碼表饮笛。
8 )是否要提高效率,選擇 BufferedWriter 论熙。
9 )轉(zhuǎn)換輸出流需要接收一個字節(jié)輸出流進(jìn)來福青,所以要是用 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 酣衷、可以和流相關(guān)聯(lián)的集合對象 Properties 。
Map
|--HashTable
|--Properties
Properties :該集合不需要泛型踩验,因?yàn)樵摷现械逆I值都是 String 類型鸥诽。
5、其他流對象:
( 1 )打印流:
PrintStream :是一個字節(jié)打印流 System.out 對應(yīng)的就是 PrintStream 箕憾。它的構(gòu)造函數(shù)可以接收三種數(shù)據(jù)類型的值:字符串路徑牡借、 File 對象、 OutputStream (當(dāng)為 System.out 的時候即把輸入顯示到屏幕上)
PrintWriter :是一個字符打印流袭异。構(gòu)造函數(shù)可以接收四種類型的值钠龙。字符串路徑、 File 對象(對于這兩中類型的數(shù)據(jù)御铃,還可以指定編碼表碴里。也即是是字符集)、 OutPutSream 上真、 Writer (對于三咬腋、四類型的數(shù)據(jù),可以指定自動刷新睡互,注意:當(dāng)自動刷新的值為 true 時根竿,只有三個方法可以用: printlf 、 printf 就珠、 format )
(2)管道流: PipedOutputStream 和 PipedInputStream 寇壳。一般在多線程中通信的時候用。
(3)RandomAccessFile :該對象不是流體系中的一員妻怎,但是該隊選中封裝了字節(jié)流壳炎,同時還封裝了一個緩沖區(qū)(字節(jié)數(shù)組),通過內(nèi)部的指針來操作數(shù)組中的數(shù)據(jù)逼侦。該對象特點(diǎn):只能操作文件和對文件讀寫都可以匿辩。多用于多線程下載。榛丢、
(4)合并流:可以將多個讀取流合并成一個流铲球。其實(shí)就是將每一個讀取流對象存儲到一個集合中,最后一個流對象結(jié)尾作為這個流的結(jié)尾涕滋。
(5)對象的序列化。 ObjectInputStream 和 ObjectInputStream 挠阁。
(6)操作基本數(shù)據(jù)類型的流對象: DataInputStream 和 DataOutputStream 宾肺。
(7)操縱內(nèi)存數(shù)組的流對象溯饵,這些對象的數(shù)據(jù)源是內(nèi)存,數(shù)據(jù)匯也是內(nèi)存: ByteArrayInputStream 和 ByteArrayOutputStream 锨用, CharArrayReader 和 CharArrayWriter 丰刊。這些流并未調(diào)用系統(tǒng)資源,使用的是內(nèi)存中的數(shù)組增拥,所以在使用的時候不用 close 啄巧。
(8)編碼轉(zhuǎn)換:
在 IO 中涉及到編碼轉(zhuǎn)換的流是轉(zhuǎn)換流和打印流,但是打印流只有輸出掌栅。轉(zhuǎn)換流是可以指定編碼表的秩仆,默認(rèn)情況下,都是本機(jī)默認(rèn)的編碼表猾封, GBK 澄耍。可以通過: Syetem.getProperty( “file.encoding”) 得到晌缘。字符串到字節(jié)數(shù)組成為編碼的過程齐莲,通過 getBytes(charset) 完成,從字節(jié)數(shù)組到字符串的過程是解碼的過程磷箕,通過 String 類的構(gòu)造函數(shù)完成 String ( byte[],charset ) .
(9)編碼實(shí)例與解析:
(10)JavaNIO 的 Charset 類專門用來編碼和解碼选酗。
想要了解更多分布式知識點(diǎn)的,可以關(guān)注我一下岳枷,我后續(xù)也會整理更多關(guān)于分布式架構(gòu)這一塊的知識點(diǎn)分享出來芒填,另外順便給大家推薦一個交流學(xué)習(xí)群:650385180,里面會分享一些資深架構(gòu)師錄制的視頻錄像:有Spring嫩舟,MyBatis氢烘,Netty源碼分析,高并發(fā)家厌、高性能播玖、分布式、微服務(wù)架構(gòu)的原理饭于,JVM性能優(yōu)化這些成為架構(gòu)師必備的知識體系蜀踏。還能領(lǐng)取免費(fèi)的學(xué)習(xí)資源,目前受益良多掰吕,以下的課程體系圖也是在群里獲取果覆。