1.barrier和latch區別,barrier更多的是等待其餘線程(await),latch是在等待一個事件php
2.volatile適用於可見性,可是不要求一致性的地方,適用於新值不依賴於舊值html
3.讀寫鎖,其實用的仍是一把鎖,fairSync和unfairSync,讀鎖是共享鎖,寫鎖是互斥鎖前端
4.threadLocal,見jdk的實現,每一個threadLocal對應的thread有不一樣的threadLocalMap,利用threadLocal作key,所以不一樣的線程可以拿到不一樣的值(Thread.threadLocals),web應用中threadLocal用完以後要把值清除,由於web中通常都是用線程池處理用戶請求,若是不及時清除,可能會有髒數據java
5.sleep和yield的區別,sleep使得當前線程進入睡眠狀態,釋放鎖,其餘線程不管優先級能夠得到鎖,yield是讓同優先級的線程得到鎖python
6.避免aba問題,通常採用版本戳mysql
7.配置線程池時CPU密集型任務能夠少配置線程數,大概和機器的cpu核數至關,可使得每一個線程都在執行任務IO密集型時,大部分線程都阻塞,故須要多配置線程數,2*cpu核數linux
有界隊列和無界隊列的配置需區分業務場景,通常狀況下配置有界隊列,在一些可能會有爆發性增加的狀況下使用無界隊列。android
8.CopyOnWriteArrayList這個容器適用於多讀少寫…讀寫並非在同一個對象上。在寫時會大面積複製數組,因此寫的性能差,在寫完成後將讀的引用改成執行寫的對象ios
9.主要利用方法去,類加載過程裝載(把字節碼裝載到內存,jvm最終生成class對象存在堆上)、鏈接(驗證:驗證裝載的字節碼是否符合規範、類型是否符合要求等。解析:給靜態變量分配內存。準備:把常量池中的符號引用替換爲常量引用)、初始化(給靜態變量賦值)c++
10。使用B(B+)樹是由於高度低,查詢快O(h) = logd(N), d爲內部節點出度,b+數比b樹好的緣由是b+的非葉子節點只保存鍵值 不保存具體的data,因此可以使得出度更大(d更大),
dmax = floor(pagesize / (keySize + pointSize + dataSize))
11.mysql的explain,包含type,table,possible_keys,keys,keylength(最左前綴索引的時候可能有用),rows(檢索的行)extra(其餘信息using where, file sort等)
12,mysql最左前綴索引,只能使用一個範圍查詢的索引,索引嚴格意義上是順序敏感的 可是查詢優化器會作一些事情,使用函數或者表達式不能使用索引,若是組合索引缺乏部分值(好比三個組合,缺乏中間那個) 會explain 的type爲range 過濾
13.volatile禁止指令衝排序。volatile的讀不比普通變量慢,可是寫會慢一些,由於要插入許多屏障 保障處理器不會亂序執行
14.事務隔離級別
串行化(SERIALIZABLE):全部事務都一個接一個地串行執行,這樣能夠避免幻讀(phantom reads)。對於基於鎖來實現併發控制的數據庫來講,串行化要求在執行範圍查詢(如選取年齡在10到30之間的用戶)的時候,須要獲取範圍鎖(range lock)。若是不是基於鎖實現併發控制的數據庫,則檢查到有違反串行操做的事務時,須要滾回該事務。
可重複讀(REPEATABLE READ):全部被Select獲取的數據都不能被修改,這樣就能夠避免一個事務先後讀取數據不一致的狀況。可是卻沒有辦法控制幻讀,由於這個時候其餘事務不能更改所選的數據,可是能夠增長數據,由於前一個事務沒有範圍鎖。
讀已提交(READ COMMITED):被讀取的數據能夠被其餘事務修改。這樣就可能致使不可重複讀。也就是說,事務的讀取數據的時候獲取讀鎖,可是讀完以後當即釋放(不須要等到事務結束),而寫鎖則是事務提交以後才釋放。釋放讀鎖以後,就可能被其餘事物修改數據。該等級也是SQL Server默認的隔離等級。
讀未提交(READ UNCOMMITED):這是最低的隔離等級,容許其餘事務看到沒有提交的數據。這種等級會致使髒讀(Dirty Read)。
15. get/set注入,接口注入、構造器注入
16.spring重要的類 BeanFactory, ApplicationContext, BeanFactory提供了管理bean,加載配置文件,維護bean之間的關係等。ApplcationText除了以上功能還有國際化支持、事件傳遞等功能
17.tcp/ip
- TCP協議和UDP協議的區別是什麼
- TCP協議是有鏈接的,有鏈接的意思是開始傳輸實際數據以前TCP的客戶端和服務器端必須經過三次握手創建鏈接,會話結束以後也要結束鏈接。而UDP是無鏈接的
- TCP協議保證數據按序發送,按序到達,提供超時重傳來保證可靠性,可是UDP不保證按序到達,甚至不保證到達,只是努力交付,即使是按序發送的序列,也不保證按序送到。
- TCP協議所需資源多,TCP首部需20個字節(不算可選項),UDP首部字段只需8個字節。
- TCP有流量控制和擁塞控制,UDP沒有,網絡擁堵不會影響發送端的發送速率
- TCP是一對一的鏈接,而UDP則能夠支持一對一,多對多,一對多的通訊。
- TCP面向的是字節流的服務,UDP面向的是報文的服務。
- TCP介紹和UDP介紹
- 請詳細介紹一下TCP協議創建鏈接和終止鏈接的過程?
- 助於理解的一段話
- 兩幅圖(來源):
- 創建鏈接:三次握手
- 關閉鏈接:四次揮手
- 三次握手創建鏈接時,發送方再次發送確認的必要性?
- 主要是爲了防止已失效的鏈接請求報文段忽然又傳到了B,於是產生錯誤。假定出現一種異常狀況,即A發出的第一個鏈接請求報文段並無丟失,而是在某些網絡結點長時間滯留了,一直延遲到鏈接釋放之後的某個時間纔到達B,原本這是一個早已失效的報文段。但B收到此失效的鏈接請求報文段後,就誤認爲是A又發出一次新的鏈接請求,因而就向A發出確認報文段,贊成創建鏈接。假定不採用三次握手,那麼只要B發出確認,新的鏈接就創建了,這樣一直等待A發來數據,B的許多資源就這樣白白浪費了。
- 四次揮手釋放鏈接時,等待2MSL的意義?
- 第一,爲了保證A發送的最有一個ACK報文段可以到達B。這個ACK報文段有可能丟失,於是使處在LAST-ACK狀態的B收不到對已發送的FIN和ACK報文段的確認。B會超時重傳這個FIN和ACK報文段,而A就能在2MSL時間內收到這個重傳的ACK+FIN報文段。接着A重傳一次確認。
- 第二,就是防止上面提到的已失效的鏈接請求報文段出如今本鏈接中,A在發送完最有一個ACK報文段後,再通過2MSL,就可使本鏈接持續的時間內所產生的全部報文段都從網絡中消失。
- 常見的應用中有哪些是應用TCP協議的,哪些又是應用UDP協議的,爲何它們被如此設計?
- 如下應用通常或必須用udp實現?
- 多播的信息必定要用udp實現,由於tcp只支持一對一通訊。
- 若是一個應用場景中大可能是簡短的信息,適合用udp實現,由於udp是基於報文段的,它直接對上層應用的數據封裝成報文段,而後丟在網絡中,若是信息量太大,會在鏈路層中被分片,影響傳輸效率。
- 若是一個應用場景重性能甚於重完整性和安全性,那麼適合於udp,好比多媒體應用,缺一兩幀不影響用戶體驗,可是須要流媒體到達的速度快,所以比較適合用udp
- 若是要求快速響應,那麼udp聽起來比較合適
- 若是又要利用udp的快速響應優勢,又想可靠傳輸,那麼只能考上層應用本身制定規則了。
- 常見的使用udp的例子:ICQ,QQ的聊天模塊。
- 以qq爲例的一個說明(轉載自知乎)
登錄採用TCP協議和HTTP協議,你和好友之間發送消息,主要採用UDP協議,內網傳文件採用了P2P技術。總來的說:
1.登錄過程,客戶端client 採用TCP協議向服務器server發送信息,HTTP協議下載信息。登錄以後,會有一個TCP鏈接來保持在線狀態。
2.和好友發消息,客戶端client採用UDP協議,可是須要經過服務器轉發。騰訊爲了確保傳輸消息的可靠,採用上層協議來保證可靠傳輸。若是消息發送失敗,客戶端會提示消息發送失敗,並可從新發送。
3.若是是在內網裏面的兩個客戶端傳文件,QQ採用的是P2P技術,不須要服務器中轉。
18. shell進程間共享信息,利用命名管道、全局變量(export)、寫文件
19. redis高可用,sentinel、zookeeper、keepalived
1,keepalived:經過keepalived的虛擬IP,提供主從的統一訪問,在主出現問題時,經過keepalived運行腳本將從提高爲主,待主恢復後先同步後自動變爲主,該方案的好處是主從切換後,應用程序不須要知道(由於訪問的虛擬IP不變),壞處是引入keepalived增長部署複雜性;
2,zookeeper:經過zookeeper來監控主從實例,維護最新有效的IP,應用經過zookeeper取得IP,對Redis進行訪問;
3,sentinel:經過Sentinel監控主從實例,自動進行故障恢復,該方案有個缺陷:由於主從實例地址(IP&PORT)是不一樣的,當故障發生進行主從切換後,應用程序沒法知道新地址,故在Jedis2.2.2中新增了對Sentinel的支持,應用經過redis.clients.jedis.JedisSentinelPool.getResource()取得的Jedis實例會及時更新到新的主實例地址。
筆者所在的公司先使用了方案1一段時間後,發現keepalived在有些狀況下會致使數據丟失,keepalived經過shell腳本進行主從切換,配置複雜,並且keepalived成爲新的單點,後來選用了方案3,使用Redis官方解決方案;(方案2須要編寫大量的監控代碼,沒有方案3簡便,網上有人使用方案2讀者可自行查看)
20.MySQL默認操做模式就是autocommit自動提交模式。這就表示除非顯式地開始一個事務,不然每一個查詢都被當作一個單獨的事務自動執行。咱們能夠經過設置autocommit的值改變是不是自動提交autocommit模式。
21.jdbc優化。
使用鏈接池,合理設置min值和max值。
p 儘可能使用批量處理接口:
p 批量寫入(addBatch,executeBatch)
p 批量查詢 (inlist,fetchsize)。
p 減小事務數,合併沒必要要的事務。(autocommit=false)
p 選擇Preparedstatement, Preparedstatementcache。
p 存儲過程和匿名塊能夠減小網絡傳輸,但會給數據庫帶來複
雜度。
p 其它層面的sql調優,如儘可能不要使用select *等。
算法相關:
我們試着一步一步解決這個問題(注意闡述中數列有序無序的區別):
- 直接窮舉,從數組中任意選取兩個數,斷定它們的和是否爲輸入的那個數字。此舉複雜度爲O(N^2)。很顯然,咱們要尋找效率更高的解法。
- 題目至關於,對每一個a[i],查找sum-a[i]是否也在原始序列中,每一次要查找的時間都要花費爲O(N),這樣下來,最終找到兩個數仍是須要O(N^2)的複雜度。那如何提升查找判斷的速度呢?答案是二分查找,能夠將O(N)的查找時間提升到O(logN),這樣對於N個a[i],都要花logN的時間去查找相對應的sum-a[i]是否在原始序列中,總的時間複雜度已降爲O(N*logN),且空間複雜度爲O(1)。(若是有序,直接二分O(N*logN),若是無序,先排序後二分,複雜度一樣爲O(N*logN+N*logN)=O(N*logN),空間總爲O(1))。
- 有沒有更好的辦法呢?我們能夠依據上述思路2的思想,a[i]在序列中,若是a[i]+a[k]=sum的話,那麼sum-a[i](a[k])也必然在序列中,舉個例子,以下:
原始序列:一、 二、 四、 七、十一、15 用輸入數字15減一下各個數,獲得對應的序列爲:
對應序列:1四、1三、十一、八、四、 0
第一個數組以一指針i 從數組最左端開始向右掃描,第二個數組以一指針j 從數組最右端開始向左掃描,若是下面出現了和上面同樣的數,即a[*i]=a[*j],就找出這倆個數來了。如上,i,j最終在第一個,和第二個序列中找到了相同的數4和11,,因此符合條件的兩個數,即爲4+11=15。怎麼樣,兩端同時查找,時間複雜度瞬間縮短到了O(N),但卻同時須要O(N)的空間存儲第二個數組(@飛羽:要達到O(N)的複雜度,第一個數組以一指針i 從數組最左端開始向右掃描,第二個數組以一指針j 從數組最右端開始向左掃描,首先初始i指向元素1,j指向元素0,誰指的元素小,誰先移動,因爲1(i)>0(j),因此i不動,j向左移動。而後j移動到元素4發現大於元素1,故而中止移動j,開始移動i,直到i指向4,這時,i指向的元素與j指向的元素相等,故而判斷4是知足條件的第一個數;而後同時移動i,j再進行判斷,直到它們到達邊界)。
- 固然,你還能夠構造hash表,正如編程之美上的所述,給定一個數字,根據hash映射查找另外一個數字是否也在數組中,只需用O(1)的時間,這樣的話,整體的算法通上述思路3 同樣,也能降到O(N),但有個缺陷,就是構造hash額外增長了O(N)的空間,此點同上述思路 3。不過,空間換時間,仍不失爲在時間要求較嚴格的狀況下的一種好辦法。
- 若是數組是無序的,先排序(n*logn),而後用兩個指針i,j,各自指向數組的首尾兩端,令i=0,j=n-1,而後i++,j--,逐次判斷a[i]+a[j]?=sum,若是某一刻a[i]+a[j]>sum,則要想辦法讓sum的值減少,因此此刻i不動,j--,若是某一刻a[i]+a[j]<sum,則要想辦法讓sum的值增大,因此此刻i++,j不動。因此,數組無序的時候,時間複雜度最終爲O(n*logn+n)=O(n*logn),若原數組是有序的,則不須要事先的排序,直接O(n)搞定,且空間複雜度仍是O(1),此思路是相對於上述全部思路的一種改進。(若是有序,直接兩個指針兩端掃描,時間O(N),若是無序,先排序後兩端掃描,時間O(N*logN+N)=O(N*logN),空間始終都爲O(1))。(與上述思路2相比,排序後的時間開銷由以前的二分的n*logn降到了掃描的O(N))。
21.rpc和rmi
遠程對象方法調用並非新概念,遠程過程調用 (RPC) 已經使用不少年了。遠程過程調用被設計爲在應用程序間通訊的平臺中立的方式,它不理會操做系統之間以及語言之間的差別。即 RPC 支持多種語言,而 RMI 只支持 Java 寫的應用程序。 [1]
另外 RMI 調用遠程對象方法,容許方法返回 Java 對象以及基本數據類型。而 RPC 不支持對象的概念,傳送到 RPC 服務的消息由外部數據表示 (External Data Representation, XDR) 語言表示,這種語言抽象了字節序類和數據類型結構之間的差別。只有由 XDR 定義的數據類型才能被傳遞, RPC 不容許傳遞對象。能夠說 RMI 是面向對象方式的 Java RPC 。
RMI (Remote Method Invocation)
RMI 採用stubs 和 skeletons 來進行遠程對象(remote object)的通信。stub 充當遠程對象的客戶端代理,有着和遠程對象相同的遠程接口,遠程對象的調用實際是經過調用該對象的客戶端代理對象stub來完成的,經過該機制RMI就比如它是本地工做,採用tcp/ip協議,客戶端直接調用服務端上的一些方法。優勢是強類型,編譯期可檢查錯誤,缺點是隻能基於JAVA語言,客戶機與服務器緊耦合。
RPC(Remote Procedure Call Protocol)
RPC使用C/S方式,採用http協議,發送請求到服務器,等待服務器返回結果。這個請求包括一個參數集和一個文本集,一般造成「classname.methodname」形式。優勢是跨語言跨平臺,C端、S端有更大的獨立性,缺點是不支持對象,沒法在編譯器檢查錯誤,只能在運行期檢查。
22. java和c通信
字節序問題:這個是通信的大問題。。前面幾篇文章也轉載了查閱到的一些資料。總的來講C通常使用的是小尾存儲數據,而java使用大尾存儲,所謂大 尾存儲就是數據高字節在前,低字節在後存儲。而網絡中的數據則都是大尾存儲。另字符串在傳輸過程當中不會發生變化,而int,long等數值類型的數據會經 過根據大小尾進行存儲傳輸。因此當java與c進行通訊的時候,java一段數據基本不用進行大小尾轉化,而c收到數據後要進行NToH轉化,發送數據的 時候也要進行HToN數據轉化。再加上字符串,打成包傳輸便可。
23.innodb 多版本一致性讀
MVCC多版本一致性讀
在innodb中,建立一個新事務的時候,innodb會將當前系統中的活躍事務列表(trx_sys->trx_list)建立一個副本(read view),副本中保存的是系統當前不該該被本事務看到的其餘事務id列表。當用戶在這個事務中要讀取該行記錄的時候,innodb會將該行當前的版本號與該read view進行比較。
具體的算法以下:
1. 設該行的當前事務id爲trx_id_0,read view中最先的事務id爲trx_id_1, 最遲的事務id爲trx_id_2。
2. 若是trx_id_0< trx_id_1的話,那麼代表該行記錄所在的事務已經在本次新事務建立以前就提交了,因此該行記錄的當前值是可見的。跳到步驟6.
3. 若是trx_id_0>trx_id_2的話,那麼代表該行記錄所在的事務在本次新事務建立以後纔開啓,因此該行記錄的當前值不可見.跳到步驟5。
4. 若是trx_id_1<=trx_id_0<=trx_id_2, 那麼代表該行記錄所在事務在本次新事務建立的時候處於活動狀態,從trx_id_1到trx_id_2進行遍歷,若是trx_id_0等於他們之中的某個事務id的話,那麼不可見。跳到步驟5.
5. 從該行記錄的DB_ROLL_PTR指針所指向的回滾段中取出最新的undo-log的版本號,將它賦值該trx_id_0,而後跳到步驟2.
6. 將該可見行的值返回。
24.IOC機制模擬:讀配置文件、利用反射實例化
spring中的BeanHelper作這個事情,反射建立實例,而後賦值給對應的引用
25. aop實現方案,aspectj和spring aop ,spring aop運用了代理,對目標對象動態生成代理對象,包含目標對象全部方法,可是會有回調。
26.二叉樹最近公共父節點
狀況一:root未知,可是每一個節點都有parent指針
此時能夠分別從兩個節點開始,沿着parent指針走向根節點,獲得兩個鏈表,而後求兩個鏈表的第一個公共節點,這個方法很簡單,不須要詳細解釋的。
兩個鏈表相交問題
狀況二:節點只有左、右指針,沒有parent指針,root已知
思路:有兩種狀況,一是要找的這兩個節點(a, b),在要遍歷的節點(root)的兩側,那麼這個節點就是這兩個節點的最近公共父節點;
二是兩個節點在同一側,則 root->left 或者 root->right 爲 NULL,另外一邊返回a或者b。那麼另外一邊返回的就是他們的最小公共父節點。
遞歸有兩個出口,一是沒有找到a或者b,則返回NULL;二是隻要碰到a或者b,就馬上返回。
27.java集合類
集合類
- Set
- HashSet
- LinkedHashSet
- TreeSet
-
優勢:
經過一個HashMap來實現數據的保存,內部實現紅黑樹數據結構,使全部元素按升序保存。
提供高效的get和contains方法,保存操做的效率爲log(n)
缺點:
默認大小爲16, 若是超過則須要從新申請內存空間,大小爲原來的兩倍,並把原來的數據內容複製到
新的內存空間中(來自HashMap)。
線程不安全(需經過Collections.synchronizedList方法設置)
加入的元素升級排序而改變
注:treeset對元素有要求,必須實現Comparable接口或是Comparator 接口)
注:一般缺省的load factor 0.75較好地實現了時間和空間的均衡。增大load factor能夠節省空間但相應的查找時間將增大,這會影響像get和put這樣的操做。
- CopyOnWriteArraySet
- 優勢:
針對於對Set操做的狀況有不少變化時使用,優其是在高併發的狀況不想使用同步控制鎖時
缺點:
消耗比較大的資料,每次做更新操做時,都會從新Copy一塊內存後,再作合併操做。
- List
- ArrayList
-
優勢:
使用數組,提供快速的get,add和iterate方法,佔用比較小的內存空間
缺點:
線程不安全(需經過Collections.synchronizedList方法設置)
insert和remove操做,很是慢(須要移動數組元素來實現)
當size超過期,須要新建一個較大的數據(默認大小是10,增量是 (size * 3)/2 + 1,
且把原來的數據都複製到新的上面)
- LinkedList
-
優勢:
使用鏈表結構,提供快速的add, insert, remove方法,佔用比較小的內存空間
缺點:
線程不安全(需經過Collections.synchronizedList方法設置)
get操做,很是慢(須要從head一級級遍歷查找)
- Vector
- 優勢:
線程安全。
缺點:
相對於ArrayList效率要低。擁有ArrayList的缺點。
- CopyOnWriteArrayList
- 優勢:
針對於對List操做的狀況有不少變化時使用,優其是在高併發的狀況不想使用同步控制鎖時
缺點:
消耗比較大的資料,每次做更新操做時,都會從新Copy一塊內存後,再作合併操做。
- TreeList(apache commons-collections)提供
-
優勢:
基於二叉數 提供比較快速的get, add,insert,iterate,remove方法。其中get,add和iterate方法比ArrayList稍慢一點。
缺點:
相對於ArrayList和LinkedList佔比較多的內存空間
線程不安全(需經過Collections.synchronizedList方法設置)
- Map
總結
若是涉及到堆棧,隊列等操做,應該考慮用List,對於須要快速插入,刪除元素,應該使用LinkedList,若是須要快速隨機訪問元素,應該使用ArrayList。
若是程序在單線程環境中,或者訪問僅僅在一個線程中進行,考慮非同步的類,其效率較高,若是多個線程可能同時操做一個類,應該使用同步的類。
要特別注意對哈希表的操做,做爲key的對象要正確複寫equals和hashCode方法。
儘可能返回接口而非實際的類型,如返回List而非ArrayList,這樣若是之後須要將ArrayList換成LinkedList時,客戶端代碼不用改變。這就是針對抽象編程。
TreeMap:內部實現是紅黑樹,有序的map
28。jvm內存模型:jvm堆、方法區、虛擬機棧、程序計數器、本地方法棧
線程隔離數據區
程序計數器(Program Counter Register):
一小塊內存空間,單前線程所執行的字節碼行號指示器。字節碼解釋器工做時,經過改變這個計數器的值來選取下一條須要執行的字節碼指令,分支、循環、跳轉、異常處理、線程恢復等基礎功能都須要依賴這個計數器來完成。
JVM虛擬機棧(Java Virtual Machine Stacks):
Java方法執行內存模型,用於存儲局部變量,操做數棧,動態連接,方法出口等信息。是線程私有的。
本地方法棧(Native Method Stacks):
爲JVM用到的Native方法服務,Sun HotSpot 虛擬機把本地方法棧和JVM虛擬機棧合二爲一。是線程私有的。
線程共享的數據區
方法區(Method Area):
用於存儲JVM加載的類信息、常量、靜態變量、即便編譯器編譯後的代碼等數據。
運行時常量池(Runtime Constant Pool):
是方法區的一部分,用於存放編譯器生成的各類字面量和符號引用,這部份內容將在類加載後存放到方法取得運行時常量池中。具有動態性,用的比較多的就是String類的intern()方法。
JVM堆( Java Virtual Machine Heap):
存放全部對象實例的地方。
新生代,由Eden Space 和大小相同的兩塊Survivor組成
舊生待,存放通過屢次垃圾回收仍然存活的對象
爲了支持跨平臺的特性,java語言採用源代碼編譯成中間字節碼,而後又各平臺的jvm解釋執行的方式。字節碼採用了徹底與平臺無關的方式進行描述,java只給出了字節碼格式的規範,並無規定字節碼最終來源是什麼,它能夠是除了java語言外的其餘語言產生,只要是知足字節碼規範的,均可以在jvm中很好的運行。正由於這個特性,極大的促進了各種語言的發展,在jvm平臺上出現了不少語言,如scala,groovy等
因爲字節碼來源並無作限制,所以jvm必須在字節碼正式使用以前,即在加載過程當中,對字節碼進行檢查驗證,以保證字節碼的可用性和安全性。
1. jvm運行時內存結構劃分
在正式介紹以前,先看看jvm內存結構劃分:
結合垃圾回收機制,將堆細化:
在加載階段主要用到的是方法區:
方法區是可供各條線程共享的運行時內存區域。存儲了每個類的結構信息,例如運行時常量池(Runtime Constant Pool)、字段和方法數據、構造函數和普通方法的字節碼內容、還包括一些在類、實例、接口初始化時用到的特殊方法。
若是把方法的代碼看做它的「靜態」部分,而把一次方法調用須要記錄的臨時數據看作它的「動態」部分,那麼每一個方法的代碼是隻有一份的,存儲於JVM的方法區中;每次某方法被調用,則在該調用所在的線程的的Java棧上新分配一個棧幀,用於存放臨時數據,在方法返回時棧幀自動撤銷。
2. 類加載過程
jvm將類加載過程分紅加載,鏈接,初始化三個階段,其中鏈接階段又細分爲驗證,準備,解析三個階段。
上述三個階段整體上會保持這個順序,可是有些特殊狀況,如加載階段與鏈接階段的部份內容(一部分字節碼的驗證工做)是交叉進行的。再如:解析階段能夠是推遲初次訪問某個類的時候,所以它可能出如今初始化階段以後。
2.1 裝載
裝載階段主要是將java字節碼以二進制的方式讀入到jvm內存中,而後將二進制數據流按照字節碼規範解析成jvm內部的運行時數據結構。java只對字節碼進行了規範,並無對內部運行時數據結構進行規定,不一樣的jvm實現能夠採用不一樣的數據結構,這些運行時數據結構是保存在jvm的方法區中(hotspot jvm的內部數據結構定義能夠參見撒迦的博文藉助HotSpot SA來一窺PermGen上的對象)。當一個類的二進制解析完畢後,jvm最終會在堆上生成一個java.lang.Class類型的實例對象,經過這個對象能夠訪問到該類在方法區的內容。
jvm規範並無規定從二進制字節碼數據應該如何產生,事實上,jvm爲了支持二進制字節碼數據來源的可擴展性,它提供了一個回調接口將經過一個類的全限定名來獲取描述此類的二進制字節碼的動做開放到jvm的外部實現,這就是咱們後面要講到的類加載器,若是有須要,咱們徹底能夠自定義一些類加載器,達到一些特殊應用場景。因爲有了jvm的支持,二進制流的產生的方式能夠是:
(1) 從本地文件系統中讀取
(2) 從網絡上加載(典型應用:java Applet)
(3) 從jar,zip,war等壓縮文件中加載
(4) 經過動態將java源文件動態編譯產生(jsp的動態編譯)
(5) 經過程序直接生成。
2.2 鏈接
鏈接階段主要是作一些加載完成以後的驗證工做,和初始化以前的準備一些工做,它細分爲三個階段。
2.2.1 驗證
驗證是鏈接階段的第一步,它主要是用於保證加載的字節碼符合java語言的規範,而且不會給虛擬機帶來危害。好比驗證這個類是否是符合字節碼的格式、變量與方法是否是有重複、數據類型是否是有效、繼承與實現是否合乎標準等等。按照驗證的內容不一樣又能夠細分爲4個階段:文件格式驗證(這一步會與裝載階段交叉進行),元數據驗證,字節碼驗證,符號引用驗證(這個階段的驗證每每會與解析階段交叉進行)。
2.2.2 準備
準備階段主要是爲類的靜態變量分配內存,並設置jvm默認的初始值。對於非靜態的變量,則不會爲它們分配內存。
在jvm中各種型的初始值以下:
int,byte,char,long,float,double 默認初始值爲0
boolean 爲false(在jvm內部用int表示boolean,所以初始值爲0)
reference類型爲null
對於final static基本類型或者String類型,則直接採用常量值(這其實是在編譯階段就已經處理好了)。
2.2.3 解析
解析過程就是查找類的常量池中的類,字段,方法,接口的符號引用,將他們替換成直接引用的過程。
a.解析過程主要針對於常量池中的CONSTANT_Class_info,CONSTANT_Fieldref_info,CONSTANT_Methodref_info及CONSTANT_InterfaceMethodref_info四種常量。
b. jvm規範並無規定解析階段發生的時間,只是規定了在執行anewarray,checkcast,getfield,getstatic,instanceof,invokeinterface,invokespecial,invokespecial,invokestatic,invokevirtual,multinewaary,new,putfield,putstatic這13個指令應用於符號指令時,先對它們進行解析,獲取它們的直接引用.
c. jvm對於每一個加載的類都會有在內部建立一個運行時常量池(參考上面圖示),在解析以前是以字符串的方式將符號引用保存在運行時常量池中,在程序運行過程當中當須要使用某個符號引用時,就會促發解析的過程,解析過程就是經過符號引用查找對應的類實體,而後用直接引用替換符號引用。因爲符號引用已經被替換成直接引用,所以後面再次訪問時,無需再次解析,直接返回直接引用。
2.3 初始化
初始化階段是根據用戶程序中的初始化語句爲類的靜態變量賦予正確的初始值。這裏初始化執行邏輯最終會體如今類構造器方法<clinit>()方中。該方法由編譯器在編譯階段生成,它封裝了兩部份內容:靜態變量的初始化語句和靜態語句塊。
2.3.1 初始化執行時機
jvm規範明確規定了初始化執行條件,只要知足如下四個條件之一,就會執行初始化工做
(1) 經過new關鍵字實例化對象、讀取或設置類的靜態變量、調用類的靜態方法(對應new,getstatic,putstatic,invokespecial這四條字節碼指令)。
(2) 經過反射方式執行以上行爲時。
(3) 初始化子類的時候,會觸發父類的初始化。
(4) 做爲程序入口直接運行時的主類。
2.3.2 初始化過程
初始化過程包括兩步:
(1) 若是類存在直接父類,而且父類沒有被初始化則對直接父類進行初始化。
(2) 若是類當前存在<clinit>()方法,則執行<clinit>()方法。
須要注意的是接口(interface)的初始化並不要求先初始化它的父接口。(接口不能有static塊)
2.3.3 <clinit>()方法存在的條件
並非每一個類都有<clinit>()方法,以下狀況下不會有<clinit>()方法:
a. 類沒有靜態變量也沒有靜態語句塊
b.類中雖然定義了靜態變量,可是沒有給出明確的初始化語句。
c.若是類中僅包含了final static 的靜態變量的初始化語句,並且初始化語句採用編譯時常量表達時,也不會有<clinit>()方法。
例子:
代碼:
- public class ConstantExample {
-
- public static final int a = 10;
- public static final float b = a * 2.0f;
- }
編譯以後用 javap -verbose ConstantExample查看字節碼,顯示以下:
- {
- public static final int a;
- Constant value: int 10
- public static final float b;
- Constant value: float 20.0f
- public ConstantExample();
- Code:
- Stack=1, Locals=1, Args_size=1
- 0: aload_0
- 1: invokespecial #15;
- 4: return
- LineNumberTable:
- line 12: 0
-
- LocalVariableTable:
- Start Length Slot Name Signature
- 0 5 0 this LConstantExample;
-
- }
這裏因爲編譯器直接10,看成常量來處理,看到是沒有<clinit>()方法存在的。能夠看成常量來處理的類型包括基本類型和String類型
對於其餘類型:
- public class ConstantExample1 {
-
- public static final int a = 10;
- public static final float b = a * 2.0f;
- public static final Date c = new Date();
- }
這裏雖然c被聲明成final,可是仍然會產生<clinit>()方法,以下所示:
- {
- public static final int a;
- Constant value: int 10
- public static final float b;
- Constant value: float 20.0f
- public static final java.util.Date c;
-
- static {};
- Code:
- Stack=2, Locals=0, Args_size=0
- 0: new #17;
- 3: dup
- 4: invokespecial #19;
- 7: putstatic #22;
- 10: return
- LineNumberTable:
- line 19: 0
- line 14: 10
2.3.4 併發性
在同一個類加載器域下,每一個類只會被初始化一次,當多個線程都須要初始化同一個類,這時只容許一個線程執行初始化工做,其餘線程則等待。當初始化執行完後,該線程會通知其餘等待的線程。
2.4 在使用過程當中類,對象在方法區和堆上的分佈狀態
先上代碼
- public class TestThread extends Thread implements Cloneable {
-
- public static void main(String[] args) {
- TestThread t = new TestThread();
- t.start();
- }
- }
上面這代碼中TestThread及相關類在jvm運行的存儲和引用狀況以下圖所示:
其中 t 做爲TestThread對象的一個引用存儲在線程的棧幀空間中,Thread對象及類型數據對應的Class對象實例都存儲在堆上,類型數據存儲在方法區,前面講到了,TestThread的類型數據中的符號引用在解析過程當中會被替換成直接引用,所以TestThread類型數據中會直接引用到它的父類Thread及它實現的接口Cloneable的類型數據。
在同一個類加載器空間中,對於全限定名相同的類,只會存在惟一的一份類的實例及類型數據。實際上類的實例數據和其對應的Class對象是相互引用的。
3. 類加載器
上面已經講到類加載器實際上jvm在類加載過程當中的裝載階段開放給外部使用的一個回調接口,它主要實現的功能就是:將經過一個類的全限定名來獲取描述此類的二進制字節碼。固然類加載器的優點遠不止如此,它是java安全體系的一個重要環節(java安全體系結構,後面會專門寫篇文章討論),同時經過類加載器的雙親委派原則等類加載器和class惟一性標識一個class的方式,能夠給應用程序帶來一些強大的功能,如hotswap。
3.1 雙親委派模型
在jvm中一個類實例的惟一性標識是類的全限定名和該類的加載器,類加載器至關於一個命名空間,將同名class進行了隔離。
從jvm的角度來講,只存在兩類加載器,一類是由c++實現的啓動類加載器,是jvm的一部分,一類是由java語言實現的應用程序加載器,獨立在jvm以外。
jkd中本身定義了一些類加載器:
(1).BootStrap ClassLoader:啓動類加載器,由C++代碼實現,負責加載存放在%JAVA_HOME%\lib目錄中的,或者通被-Xbootclasspath參數所指定的路徑中的,而且被java虛擬機識別的(僅按照文件名識別,如rt.jar,名字不符合的類庫,即便放在指定路徑中也不會被加載)類庫到虛擬機的內存中,啓動類加載器沒法被java程序直接引用。
(2).Extension ClassLoader:擴展類加載器,由sun.misc.Launcher$ExtClassLoader實現,負責加載%JAVA_HOME%\lib\ext目錄中的,或者被java.ext.dirs系統變量所指定的路徑中的全部類庫,開發者能夠直接使用擴展類加載器。
(3).Application ClassLoader:應用程序類加載器,由sun.misc.Launcher$AppClassLoader實現,負責加載用戶類路徑classpath上所指定的類庫,是類加載器ClassLoader中的getSystemClassLoader()方法的返回值,開發者能夠直接使用應用程序類加載器,若是程序中沒有自定義過類加載器,該加載器就是程序中默認的類加載器。
參考ClassLoader源代碼會發現,這些Class之間並非採用繼承的方式實現父子關係,而是採用組合方式。
正常狀況下,每一個類加載在收到類加載請求時,會先調用父加載器進行加載,若父加載器加載失敗,則子加載器進行加載。
3.2 兩種主動加載方式
在java中有兩種辦法能夠在應用程序中主動加載類:
一種是Class類的forName靜態方法
- public static Class<?> forName(String className)
- throws ClassNotFoundException
-
- public static Class<?> forName(String name, boolean initialize, ClassLoader loader) throws ClassNotFoundException
另外一種就是ClassLoader中的loadClass方法
- protected synchronized Class<?> loadClass(String name, boolean resolve)
- throws ClassNotFoundException
-
- public Class<?> loadClass(String name) throws ClassNotFoundException
上面這兩種方式是有區別的,以下例所示
- public class InitialClass {
-
- public static int i;
- static {
- i = 1000;
- System.out.println("InitialClass is init");
- }
-
- }
- public class InitClassTest {
-
- public static void main(String[] args) throws MalformedURLException, ClassNotFoundException {
- Class classFromForName = Class.forName("com.alibaba.china.jianchi.example.InitialClass",
- true,
- new URLClassLoader(
- new URL[] { new URL(
- "file:/home/tanfeng/workspace/springStudy/bin/") },
- InitClassTest.class.getClassLoader()));
-
- Class classFromClassLoader = (new URLClassLoader(
- new URL[] { new URL(
- "file:/home/tanfeng/workspace/springStudy/bin/") },
- InitClassTest.class.getClassLoader())).loadClass("com.alibaba.china.jianchi.example.InitialClass");
-
- }
- }
經過運行能夠考到用Class.forName()方法會將裝載的類初始化,而ClassLoader.loadClass()方法則不會。
咱們常常會看到在數據庫操做時,會用Class.forName()的方式加載驅動類,而不是ClassLoader.loadClass()方法,爲什麼要這樣呢?
來看看mysql的驅動類實現,能夠看到在類的初始化階段,它會將本身註冊到驅動管理器中(static塊)。
- package com.mysql.jdbc;
- public class Driver extends NonRegisteringDriver implements java.sql.Driver {
-
- static {
- try {
- java.sql.DriverManager.registerDriver(new Driver());
- } catch (SQLException E) {
- throw new RuntimeException("Can't register driver!");
- }
- }
- ... ...
- }
3.3 自定義類加載器的應用
3.3.1 Tomcat中類加載器分析
3.3.1.1 tomcat中經過自定義一組類加載器,解決了如下幾個問題:
(1)部署在一個服務器上的兩個Web應用程序自身所使用的Java類庫是相互隔離的。
(2)部署在一個服務器上的兩個Web應用程序能夠共享服務器提供的java共用類庫。
(3)服務器儘量的保證自身安全不受部署的Web應用程序影響。
(4)支持對JSP的HotSwap功能。
3.3.1.2 tomcat的目錄結構
tomcat主要根據根據java類庫的共享範圍,分爲4組目錄:
(1)common目錄:能被Tomcat和全部Web應用程序共享。
(2)server目錄:僅能被Tomcat使用,其餘Web應用程序不可見。
(3)Shared目錄:能夠被全部Web應用程序共享,對Tomcat不可見。
(4)WEB-INF目錄:只能被當前Web應用程序使用,對其餘web應用程序不可見。
3.3.1.3 tomcat自定義類加載器
這幾個類加載器分別對應加載/common/*、/server/*、/shared/*和 /WEB-INF/*類庫, 其中Webapp類加載器和Jsp類加載器會存在多個,每一個Web應用對應一個Webapp類加載器。
CommonClassLoader加載的類能夠被CatalinaClassLoader和ShareClassLoader使用;CatalinaClassLoader加載的類和ShareClassLoader加載的類相互隔離; WebappClassLoader可使用ShareClassLoader加載的類,但各個WebappClassLoader間相互隔離;JspClassLoader僅能用JSP文件編譯的class文件。
29.防止sql注入
嚴格區分權限、使用參數化語句,不要直接嵌入在sql語句中、增強驗證等
30. 大文件交集,利用hash函數或者md5等 把url轉化爲不重複的整數,而後用bloomfilter過濾
31 jdk裏的設計模式
原文出處:
javacodegeeks 譯文出處:
deepinmind。歡迎加入
技術翻譯小組。
這也是篇老文了,相信不少人也看過。前面那些廢話就不翻譯了,直接切入正題吧~
結構型模式:
適配器模式:
用來把一個接口轉化成另外一個接口。
- java.util.Arrays#asList()
- javax.swing.JTable(TableModel)
- java.io.InputStreamReader(InputStream)
- java.io.OutputStreamWriter(OutputStream)
- javax.xml.bind.annotation.adapters.XmlAdapter#marshal()
- javax.xml.bind.annotation.adapters.XmlAdapter#unmarshal()
橋接模式:
這個模式將抽象和抽象操做的實現進行了解耦,這樣使得抽象和實現能夠獨立地變化。
- AWT (It provides an abstraction layer which maps onto the native OS the windowing support.)
- JDBC
組合模式
使得客戶端看來單個對象和對象的組合是同等的。換句話說,某個類型的方法同時也接受自身類型做爲參數。
- javax.swing.JComponent#add(Component)
- java.awt.Container#add(Component)
- java.util.Map#putAll(Map)
- java.util.List#addAll(Collection)
- java.util.Set#addAll(Collection)
裝飾者模式:
動態的給一個對象附加額外的功能,這也是子類的一種替代方式。能夠看到,在建立一個類型的時候,同時也傳入同一類型的對象。這在JDK裏隨處可見,你會發現它無處不在,因此下面這個列表只是一小部分。
- java.io.BufferedInputStream(InputStream)
- java.io.DataInputStream(InputStream)
- java.io.BufferedOutputStream(OutputStream)
- java.util.zip.ZipOutputStream(OutputStream)
- java.util.Collections#checkedList|Map|Set|SortedSet|SortedMap
門面模式:
給一組組件,接口,抽象,或者子系統提供一個簡單的接口。
- java.lang.Class
- javax.faces.webapp.FacesServlet
享元模式
使用緩存來加速大量小對象的訪問時間。
- java.lang.Integer#valueOf(int)
- java.lang.Boolean#valueOf(boolean)
- java.lang.Byte#valueOf(byte)
- java.lang.Character#valueOf(char)
代理模式
代理模式是用一個簡單的對象來代替一個複雜的或者建立耗時的對象。
- java.lang.reflect.Proxy
- RMI
建立模式
抽象工廠模式
抽象工廠模式提供了一個協議來生成一系列的相關或者獨立的對象,而不用指定具體對象的類型。它使得應用程序可以和使用的框架的具體實現進行解耦。這在JDK或者許多框架好比Spring中都隨處可見。它們也很容易識別,一個建立新對象的方法,返回的倒是接口或者抽象類的,就是抽象工廠模式了。
- java.util.Calendar#getInstance()
- java.util.Arrays#asList()
- java.util.ResourceBundle#getBundle()
- java.sql.DriverManager#getConnection()
- java.sql.Connection#createStatement()
- java.sql.Statement#executeQuery()
- java.text.NumberFormat#getInstance()
- javax.xml.transform.TransformerFactory#newInstance()
建造模式(Builder)
定義了一個新的類來構建另外一個類的實例,以簡化複雜對象的建立。建造模式一般也使用方法連接來實現。
- java.lang.StringBuilder#append()
- java.lang.StringBuffer#append()
- java.sql.PreparedStatement
- javax.swing.GroupLayout.Group#addComponent()
工廠方法
就是一個返回具體對象的方法。
- java.lang.Proxy#newProxyInstance()
- java.lang.Object#toString()
- java.lang.Class#newInstance()
- java.lang.reflect.Array#newInstance()
- java.lang.reflect.Constructor#newInstance()
- java.lang.Boolean#valueOf(String)
- java.lang.Class#forName()
原型模式
使得類的實例可以生成自身的拷貝。若是建立一個對象的實例很是複雜且耗時時,就可使用這種模式,而不從新建立一個新的實例,你能夠拷貝一個對象並直接修改它。
- java.lang.Object#clone()
- java.lang.Cloneable
單例模式
用來確保類只有一個實例。Joshua Bloch在Effetive Java中建議到,還有一種方法就是使用枚舉。
- java.lang.Runtime#getRuntime()
- java.awt.Toolkit#getDefaultToolkit()
- java.awt.GraphicsEnvironment#getLocalGraphicsEnvironment()
- java.awt.Desktop#getDesktop()
行爲模式
責任鏈模式
經過把請求從一個對象傳遞到鏈條中下一個對象的方式,直到請求被處理完畢,以實現對象間的解耦。
- java.util.logging.Logger#log()
- javax.servlet.Filter#doFilter()
命令模式
將操做封裝到對象內,以便存儲,傳遞和返回。
- java.lang.Runnable
- javax.swing.Action
解釋器模式
這個模式一般定義了一個語言的語法,而後解析相應語法的語句。
- java.util.Pattern
- java.text.Normalizer
- java.text.Format
迭代器模式
提供一個一致的方法來順序訪問集合中的對象,這個方法與底層的集合的具體實現無關。
- java.util.Iterator
- java.util.Enumeration
中介者模式
經過使用一箇中間對象來進行消息分發以及減小類之間的直接依賴。
- java.util.Timer
- java.util.concurrent.Executor#execute()
- java.util.concurrent.ExecutorService#submit()
- java.lang.reflect.Method#invoke()
備忘錄模式
生成對象狀態的一個快照,以便對象能夠恢復原始狀態而不用暴露自身的內容。Date對象經過自身內部的一個long值來實現備忘錄模式。
- java.util.Date
- java.io.Serializable
空對象模式
這個模式經過一個無心義的對象來代替沒有對象這個狀態。它使得你不用額外對空對象進行處理。
- java.util.Collections#emptyList()
- java.util.Collections#emptyMap()
- java.util.Collections#emptySet()
觀察者模式
它使得一個對象能夠靈活的將消息發送給感興趣的對象。
- java.util.EventListener
- javax.servlet.http.HttpSessionBindingListener
- javax.servlet.http.HttpSessionAttributeListener
- javax.faces.event.PhaseListener
狀態模式
經過改變對象內部的狀態,使得你能夠在運行時動態改變一個對象的行爲。
- java.util.Iterator
- javax.faces.lifecycle.LifeCycle#execute()
策略模式
使用這個模式來將一組算法封裝成一系列對象。經過傳遞這些對象能夠靈活的改變程序的功能。
- java.util.Comparator#compare()
- javax.servlet.http.HttpServlet
- javax.servlet.Filter#doFilter()
模板方法模式
讓子類能夠重寫方法的一部分,而不是整個重寫,你能夠控制子類須要重寫那些操做。
- java.util.Collections#sort()
- java.io.InputStream#skip()
- java.io.InputStream#read()
- java.util.AbstractList#indexOf()
訪問者模式
提供一個方便的可維護的方式來操做一組對象。它使得你在不改變操做的對象前提下,能夠修改或者擴展對象的行爲。
- javax.lang.model.element.Element and javax.lang.model.element.ElementVisitor
- javax.lang.model.type.TypeMirror and javax.lang.model.type.TypeVisitor
譯者注:不少地方可能會存在爭議,是不是某種模式其實並非特別重要,重要的是它們的設計能爲改善咱們的代碼提供一些經驗。