在傳統的Java應用中,bean的生命週期很簡單。使用Java 關鍵字 new 進行bean 實例化,而後該 bean 就可使用了。一旦該bean 再也不被使用,則由 java 自動進行垃圾回收。html
相比之下,Spring 容器中的 bean 的生命週期就顯得相對複雜多了。正確理解Spring bean 的生命週期很是重要,由於你或許要利用 Spring 提供的擴展點來自定義bean 的建立過程。圖1.5展現了 bean 裝載到 spring 應用上下文中的一個典型的生命週期過程。(手機拍攝的。。)java
1. Spring 對 bean 進行實例化;算法
2. Spring 將值和 bean 的引用注入到bean 對應的屬性中;spring
3. 若是 bean 實現了 BeanNameAware 接口,Spring 將 bean 的 ID 傳遞給 setBeanName()方法;數組
4. 若是 bean 實現了 BeanFactoryAware 接口,Spring 將調用 setBeanFactory ()方法,將 BeanFactory 容器實例傳入;安全
5. 若是 bean 實現了 ApplicationContextAware 接口,Spring 將調用 set-ApplicationContext () 方法,將 bean 所在的應用上下文的引用傳入進來;服務器
6. 若是 bean 實現了 BeanPostProcessor 接口,Spring 將調用他們的 post-Process-Before-Initialization () 方法;微信
7. 若是 bean 實現了 InitializingBean 接口,Spring 將調用它們的 after-Properties-Set () 方法。相似的,若是 bean 使用 init -method 聲明瞭初始化方法,該方法也會被調用;
8. 若是 bean 實現了 BeanPostProcessor 接口, Spring 將調用它們的 post-Process-After-Initialization () 方法;
9. 此時,bean 已經準備就緒,能夠被應用程序使用了,它們將一直駐留在應用上下文中,直到該應用上下文被銷燬;
10. 若是 bean 實現了 Disposable-Bean 接口,Spring 將調用它的 destory () 接口方法。一樣,若是 bean 使用 destory-method 聲明瞭銷燬方法,該方法也會被調用。
使用場景:須要在將數組轉換爲List後,對List進行增刪改查操做,在List的數據量不大的狀況下,可使用。
三.經過集合工具類Collections.addAll()方法(推薦)
經過Collections.addAll(arrayList, strArray)方式轉換,根據數組的長度建立一個長度相同的List,而後經過Collections.addAll()方法,將數組中的元素轉爲二進制,而後添加到List中。
關鍵代碼:
ArrayList< String> arrayList = new ArrayList<String>(strArray.length);
Collections.addAll(arrayList, strArray);
使用場景:須要在將數組轉換爲List後,對List進行增刪改查操做,在List的數據量巨大的狀況下,優先使用,能夠提升操做速度。
---轉換String三種方式比較:toString()、String.valueOf()、(String)---
簡單介紹:
一、toString,須要保證調用這個方法的類、方法、變量不爲null,不然會報空指針。
二、String.valueOf。這個方法在使用的時候是有些特殊的。通常狀況下,若是是肯定類型的null傳入,返回的是字符串「null」,而若是直接傳入null,則會發生錯誤。
三、(String) 字符串類型強轉。須要保證的是類型能夠轉成String類型。
總結:
這三者的使用,我的以爲應該使用String.valueOf()的方式。這樣的使用安全可靠,不會帶來異常,但須要注意null;
對於開發人員來講,若是不瞭解Java的JVM,那真的是很難寫得一手好代碼,很難查得一手好bug。同時,JVM也是面試環節的中重災區。今天開始,《JVM詳解》系列開啓,帶你們深刻了解JVM相關知識。
咱們不能爲了面試而面試,可是學習會這些核心知識你一定會成爲面試與工做中「最亮的一顆星」。本系列首發於微信公衆號「程序新視界」。下面,開啓咱們的第一篇文章《JVM以內存結構詳解》。
學習也是要講究方式方法的,本系列學習過程當中會引導你們經過《費曼學習法》來學習,同時儘可能採用圖文方式來進行講解。正所謂一圖勝千言。
思考一下
學習一項知識總該知道爲何學習吧。有人會說,這些寫代碼好像又用不上,貌似全部的事情JVM都替咱們作好了。那就,思考一下爲何要學習JVM虛擬機結構。
那你是否遇到這樣的困惑:堆內存該設置多大?OutOfMemoryError異常究竟是怎麼引發的?如何進行JVM調優?JVM的垃圾回收是如何?甚至建立一個String對象,JVM都作了些什麼?
這些疑問隨着學習的深刻都會慢慢獲得解答,而要解決這些問題的第一步,就是先了解JVM的構成。
JVM內存結構
java虛擬機在執行程序的過程當中會將內存劃分爲不一樣的數據區域,看一下下圖。
若是理解了上圖,JVM的內存結構基本上掌握了一半。經過上圖咱們能夠看到什麼?外行看熱鬧,內行看門道。從圖中能夠獲得以下信息。
第一,JVM分爲五個區域:虛擬機棧、本地方法棧、方法區、堆、程序計數器。PS:你們不要排斥英語,此處用英文記憶反而更容易理解。
第二,JVM五個區中虛擬機棧、本地方法棧、程序計數器爲線程私有,方法區和堆爲線程共享區。圖中已經用顏色區分,綠色表示「通行」,橘黃色表示停一停(需等待)。
第三,JVM不一樣區域的佔用內存大小不一樣,通常狀況下堆最大,程序計數器較小。那麼最大的區域會放什麼?固然就是Java中最多的「對象」了。
學習延伸:若是你記住了這張圖,是否是就能夠說出關於JVM的內存結構了呢?能夠嘗試一下,切記不用死記硬背,發揮你的想象。
堆(Heap)
上面已經得出結論,堆內存最大,堆是被線程共享,堆的目的就是存放對象。幾乎全部的對象實例都在此分配。固然,隨着優化技術的更新,某些數據也會被放在棧上等。
槍打出頭鳥,樹大招風。由於堆佔用內存空間最大,堆也是Java垃圾回收的主要區域(重點對象),所以也稱做「GC堆」(Garbage Collected Heap)。
關於GC的操做,咱們後面章節會詳細講,但正由於GC的存在,而現代收集器基本都採用分代收集算法,堆又被細化了。
一樣,對上圖呈現內容彙總分析。
第一,堆的GC操做採用分代收集算法。
第二,堆區分了新生代和老年代;
第三,新生代又分爲:Eden空間、From Survivor(S0)空間、To Survivor(S1)空間。
Java虛擬機規範規定,Java堆能夠處於物理上不連續的內存空間中,只要邏輯上是連續的便可。也就是說堆的內存是一塊塊拼湊起來的。要增長堆空間時,往上「拼湊」(可擴展性)便可,但當堆中沒有內存完成實例分配,而且堆也沒法再擴展時,將會拋出OutOfMemoryError異常。
方法區(Method Area)
方法區與堆有不少共性:線程共享、內存不連續、可擴展、可垃圾回收,一樣當沒法再擴展時會拋出OutOfMemoryError異常。
正由於如此相像,Java虛擬機規範把方法區描述爲堆的一個邏輯部分,但目前其實是與Java堆分開的(Non-Heap)。
方法區個性化的是,它存儲的是已被虛擬機加載的類信息、常量、靜態變量、即時編譯器編譯後的代碼等數據。
方法區的內存回收目標主要是針對常量池的回收和對類型的卸載,通常來講這個區域的回收「成績」比較難以使人滿意,尤爲是類型的卸載,條件至關苛刻,可是回收確實是有必要的。
程序計數器(Program Counter Register)
關於程序計數器咱們已經得知:佔用內存較小,現成私有。它是惟一沒有OutOfMemoryError異常的區域。
程序計數器的做用能夠看作是當前線程所執行的字節碼的行號指示器,字節碼解釋器工做時就是經過改變計數器的值來選取下一條字節碼指令。其中,分支、循環、跳轉、異常處理、線程恢復等基礎功能都須要依賴計數器來完成。
Java虛擬機的多線程是經過線程輪流切換並分配處理器執行時間的方式來實現的,在任何一個肯定的時刻,一個處理器(對於多核處理器來講是一個內核)只會執行一條線程中的指令。
所以,爲了線程切換後能恢復到正確的執行位置,每條線程都須要有一個獨立的程序計數器,各條線程之間的計數器互不影響,獨立存儲,咱們稱這類內存區域爲「線程私有」的內存。
若是線程正在執行的是一個Java方法,這個計數器記錄的是正在執行的虛擬機字節碼指令的地址;若是正在執行的是Natvie方法,這個計數器值則爲空(Undefined)。
虛擬機棧(JVM Stacks)
虛擬機棧線程私有,生命週期與線程相同。
棧幀(Stack Frame)是用於支持虛擬機進行方法調用和方法執行的數據結構。棧幀存儲了方法的局部變量表、操做數棧、動態鏈接和方法返回地址等信息。每個方法從調用至執行完成的過程,都對應着一個棧幀在虛擬機棧裏從入棧到出棧的過程。
局部變量表(Local Variable Table)是一組變量值存儲空間,用於存放方法參數和方法內定義的局部變量。包括8種基本數據類型、對象引用(reference類型)和returnAddress類型(指向一條字節碼指令的地址)。
其中64位長度的long和double類型的數據會佔用2個局部變量空間(Slot),其他的數據類型只佔用1個。
若是線程請求的棧深度大於虛擬機所容許的深度,將拋出StackOverflowError異常;若是虛擬機棧動態擴展時沒法申請到足夠的內存時會拋出OutOfMemoryError異常。
操做數棧(Operand Stack)也稱做操做棧,是一個後入先出棧(LIFO)。隨着方法執行和字節碼指令的執行,會從局部變量表或對象實例的字段中複製常量或變量寫入到操做數棧,再隨着計算的進行將棧中元素出棧到局部變量表或者返回給方法調用者,也就是出棧/入棧操做。
動態連接:Java虛擬機棧中,每一個棧幀都包含一個指向運行時常量池中該棧所屬方法的符號引用,持有這個引用的目的是爲了支持方法調用過程當中的動態連接(Dynamic Linking)。
方法返回:不管方法是否正常完成,都須要返回到方法被調用的位置,程序才能繼續進行。
本地方法棧(Native Method Stacks)
本地方法棧(Native Method Stacks)與虛擬機棧做用類似,也會拋出StackOverflowError和OutOfMemoryError異常。
區別在於虛擬機棧爲虛擬機執行Java方法(字節碼)服務,而本地方法棧是爲虛擬機使用到的Native方法服務。
小結
通過上面的講解,想必你們已經瞭解到JVM內存結構的基本狀況。下面對照腦圖,概括總結一下,看你能說出來多少。
JVM內存結構補充
在上篇《JVM以內存結構詳解》中有些內容咱們沒有講,本篇結合垃圾回收機制來一塊兒學習。還記得JVM中堆的結構圖嗎?
圖中展現了堆中三個區域:Eden、From Survivor、To Survivor。從圖中能夠也能夠看到它們的大小比例,準確來講是:8:1:1。爲何要這樣設計呢,本篇文章後續會給出解答,仍是根據垃圾回收的具體狀況來設計的。
還記得在設置JVM時,經常使用的相似-Xms和-Xmx等參數嗎?對的它們就是用來講設置堆中各區域的大小的。
(圖片來源於網絡)
控制參數詳解:
- -Xms設置堆的最小空間大小。
- -Xmx設置堆的最大空間大小。
- -Xmn堆中新生代初始及最大大小(NewSize和MaxNewSize爲其細化)。
- -XX:NewSize設置新生代最小空間大小。
- -XX:MaxNewSize設置新生代最大空間大小。
- -XX:PermSize設置永久代最小空間大小。
- -XX:MaxPermSize設置永久代最大空間大小。
- -Xss設置每一個線程的堆棧大小。
對照上面兩個圖,再來看這些參數是否是沒有以前那麼枯燥了,它們在圖中都有了對應的位置。
有沒有發現沒有直接設置老年代空間大小的參數?咱們經過簡單的計算得到。
對上面參數當即了,但記憶有困難?那麼,如下幾個助記詞可能更好的幫你記憶和理解參數的含義。
Xmx(memory maximum), Xms(memory startup), Xmn(memory nursery/new), Xss(stack size)。
對於參數的格式能夠這樣理解:
- -: 標準VM選項,VM規範的選項。
- -X: 非標準VM選項,不保證全部VM支持。
- -XX: 高級選項,高級特性,但屬於不穩定的選項。
GC概述
垃圾收集(Garbage Collection)一般被稱爲「GC」,由虛擬機「自動化」完成垃圾回收工做。
思考一個問題,既然GC會自動回收,開發人員爲何要學習GC和內存分配呢?爲了可以配置上面的參數配置?參數配置又是爲了什麼?
「當須要排查各類內存溢出,內存泄露問題時,當垃圾成爲系統達到更高併發量的瓶頸時,咱們就須要對GC的自動回收實施必要的監控和調節。」
JVM中程序計數器、虛擬機棧、本地方法棧3個區域隨線程而生隨線程而滅。棧幀隨着方法的進入和退出作入棧和出棧操做,實現了自動的內存清理。它們的內存分配和回收都具備肯定性。
所以,GC垃圾回收主要集中在堆和方法區,在程序運行期間,這部份內存的分配和使用都是動態的。
下面經過概念和具體的算法來了解GC垃圾回收的過程。
如何判斷對象存活
判斷對象常規有兩種方法:引用計數算法和可達性分析算法(Reachability Analysis)。
引用計數算法:給對象添加一個引用計數器,每當有一個地方引用它時計數器加1,引用釋放時計數減1,當計數器爲0時能夠回收。
引用計數算法實現簡單,判斷高效,在微軟COM和Python語言等被普遍使用,但在主流的Java虛擬機中沒有使用該方法,主要是由於沒法解決對象相互循環引用的問題。
可達性分析算法:基本思想是經過一系列稱爲「GC Root」的對象(如系統類加載器、棧中的對象、處於激活狀態的線程等)做爲起點,基於對象引用關係,開始向下搜索,所走過的路徑稱爲引用鏈,當一個對象到GC Root沒有任何引用鏈相連,證實對象是不可用的。
上圖中中綠色部分爲存活對象,灰色部分爲可回收對象。雖然灰色部份內部依舊有關聯,但它們到GC Root是不可達的。
面試問題
面試官,說說Java GC都用了哪些算法?分別應用在什麼地方?
答:複製算法、標記清除、標記整理……
你還在單純的死記硬背麼?繼續往下看,你會豁然開朗,不再用死記硬背了。
標記清除算法
標記清除(Mark-Sweep)算法,包含「標記」和「清除」兩個階段:首先標記出全部須要回收的對象,在標記完成後統一回收掉全部被標記的對象。
標記清除算法是最基礎的收集算法,後續的收集算法都是基於該思路並對其缺點進行改進而獲得的。
主要缺點:一個是效率問題,標記和清除過程的效率都不高;另外是空間問題,標記清除以後會產生大量不連續的內存碎片,空間碎片太多可能會致使,當程序在之後的運行過程當中須要分配較大對象時沒法找到足夠的連續內存而不得不提早觸發另外一次垃圾收集動做。
複製算法
複製(Copying)算法:將可用內存按容量劃分爲大小相等的兩塊,每次只使用其中的一塊。當一塊內存用完了,就將還存活着的對象複製到另一塊上,而後清理掉前一塊。
每次對半區內存回收時、內存分配時就不用考慮內存碎片等複雜狀況,只要移動堆頂指針,按順序分配內存便可,實現簡單,運行高效。
缺點:將內存縮小爲一半,性價比低,持續複製長生存期的對象則致使效率低下。
JVM堆中新生代便採用複製算法。回到最初推分配結構圖。
在GC回收過程當中,當Eden區滿時,還存活的對象會被複制到其中一個Survivor區;當回收時,會將Eden和使用的Survivor區還存活的對象,複製到另一個Survivor區,而後對Eden和用過的Survivor區進行清理。
若是另一個Survivor區沒有足夠的內存存儲時,則會進入老年代。
這裏針對哪些對象會進入老年代有這樣的機制:對象每經歷一次複製,年齡加1,達到晉升年齡閾值後,轉移到老年代。
在這整個過程當中,因爲Eden中的對象屬於像浮萍同樣「瞬生瞬滅」的對象,因此並不須要1:1的比例來分配內存,而是採用了8:1:1的比例來分配。
而針對那些像「水熊蟲」同樣,歷經屢次清理依舊存活的對象,則會進入老年代,而老年的清理算法則採用下面要講到的「標記整理算法」。
標記整理算法
標記整理(Mark-Compact)算法:標記過程與「標記-清除」算法同樣,但後續步驟不是直接對可回收對象進行清理,而是讓全部存活的對象都向一端移動,而後直接清理掉端邊界之外的內存。
這種算法不既不用浪費50%的內存,也解決了複製算法在對象存活率較高時的效率低下問題。
分代收集算法
分代收集算法,基本思路:將Java的堆內存邏輯上分紅兩塊,新生代和老年代,針對不一樣存活週期、不一樣大小的對象採起不一樣的垃圾回收策略。
而在新生代中大多數對象都是瞬間對象,只有少許對象存活,複製較少對象便可完成清理,所以採用複製算法。而針對老年代中的對象,存活率較高,又沒有額外的擔保內存,所以採用標記整理算法。
其實,回頭看,分代收集算法就是對新生代和老年代算法從策略維度的規劃而已。
Servlet 生命週期
過程:加載 --> 實例化 --> 服務 --> 銷燬
init():在Servlet生命週期中,init()方法只執行一次,不管有多少客戶端訪問,都不會重複執行。它是在服務器裝入Servlet時執行的,負載初始化Servlet對象。
service():當Servlet容器接收到一個請求時,Servlet容器會針對這個請求建立ServletRequest ServletResponse對象。而後調用service()方法。並把這兩個參數傳遞給service()方法。service()方法經過ServletRequest對象得到請求的信息。並處理該請求。再經過ServletResponse對象生成這個請求的響應結果。
destroy():在Servlet生命週期中,destroy()方法只會被執行一次。當Servlet對象結束生命週期時,負責釋放資源。
Servlet 工做原理
|
web服務器接受到一個http請求後,web服務器會將請求移交給servlet容器 servlet容器首先對所請求的URL進行解析並根據web.xml 配置文件找到相應的處理servlet 同時將request、response對象傳遞給它,servlet經過request對象可知道客戶端的請求者、請求信息以及其餘的信息等 servlet在處理完請求後會把全部須要返回的信息放入response對象中並返回到客戶端 servlet一旦處理完請求,servlet容器就會刷新response對象,並把控制權從新返回給web服務器。 |