国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

摘記《深入理解Java虛擬機:JVM高級特性與最佳實踐(第2版)》

zoomdong / 3431人閱讀

摘要:第章內(nèi)存區(qū)域與內(nèi)存溢出異常運行時數(shù)據(jù)區(qū)域虛擬機在執(zhí)行程序的過程中會把它所管理的內(nèi)存劃分為若干個不同的數(shù)據(jù)區(qū)域。即對象指向它的類元數(shù)據(jù)的指針,虛擬機通過這個指針來確定這個對象是哪個類的實例。

第2章 Java內(nèi)存區(qū)域與內(nèi)存溢出異常 2.2 運行時數(shù)據(jù)區(qū)域

Java虛擬機在執(zhí)行Java程序的過程中會把它所管理的內(nèi)存劃分為若干個不同的數(shù)據(jù)區(qū)域。根據(jù)《Java虛擬機規(guī)范(Java SE 7版)》的規(guī)定,Java虛擬機所管理的內(nèi)存將會包括以下幾個運行時數(shù)據(jù)區(qū)域:

2.2.1 程序計數(shù)器(Program Counter Register)

每條線程都需要有一個獨立的程序計數(shù)器,互不影響,獨立存儲

較小的內(nèi)存空間

記錄當前線程所執(zhí)行的代碼的行號指示器

字節(jié)碼解釋器工作時通過改變程序計數(shù)器的值,來選去下一條需要執(zhí)行的字節(jié)碼指令

Java虛擬機規(guī)范沒有規(guī)定此區(qū)域存在OOM

2.2.2 Java虛擬機棧(Java Virtual Machine Stacks)

生命周期與線程相同

描述的是Java方法執(zhí)行的內(nèi)存模型

每個方法在執(zhí)行的同時都會創(chuàng)建一個棧幀(存放局部變量表、操作數(shù)棧、動態(tài)鏈接、方法出口等)

方法調(diào)用即棧幀的出入棧

局部變量表:基本數(shù)據(jù)類型、對象引用、returnAddress類型

64位長度的long和double類型的數(shù)據(jù)會占用2個局部變量空間(Slot)

局部變量空間在編譯期分配完成;運行期間不會改變大小

Java虛擬機規(guī)范規(guī)定2種異常情況:

StackOverflowError:線程請求的棧深度 > 虛擬機所允許的深度

OutOfMemoryError:虛擬機棧動態(tài)擴展時無法申請到足夠內(nèi)存

2.2.3 本地方法棧(Native Method Stack)

為虛擬機調(diào)用Native方法提供服務(wù)(虛擬機棧是為虛擬機調(diào)用Java方法提供服務(wù))

也會拋出StackOverflowError和OutOfMemoryError

2.2.4 Java堆(Java Heap)

所有線程共享

虛擬機啟動時創(chuàng)建

存放對象實例

堆空間可以物理上不連續(xù),邏輯上連續(xù)

OutOfMemoryError:對象實例沒有被分配,且堆無法擴展

2.2.5 方法區(qū)(Method Area)

線程共享

存儲已被虛擬機加載的類信息、常量、靜態(tài)變量、即時編譯器編譯后的代碼等數(shù)據(jù)

永久代:HotSpot在1.7之前把GC分代收集擴展至方法區(qū),即用永久代實現(xiàn)方法區(qū)

好處:可以像管理Heap一樣管理方法區(qū)

壞處:容易遇到內(nèi)存溢出問題,永久代有-XX:MaxPermSize的上限

這區(qū)域的內(nèi)存回收目標主要是針對常量池的回收和對類型的卸載

2.2.6 運行時常量池(Runtime Constant Pool)

方法區(qū)的一部分

用于存放編譯期生成的各種字面量和符號引用,在類加載后進入存放

具有動態(tài)性,除了編譯期,運行期也可以將新的常量存入(例如 String.intern())

受到方法區(qū)內(nèi)存的限制

2.2.7 直接內(nèi)存(Direct Memory)

并不是虛擬機運行時數(shù)據(jù)區(qū)的一部分,也不是Java虛擬機規(guī)范中定義的內(nèi)存區(qū)域

但使用頻繁,可能導(dǎo)致OutOfMemoryError

分配不會受到Java堆大小的限制,但受到本機總內(nèi)存(包括RAM以及SWAP區(qū)或者分頁文件)大小以及處理器尋址空間的限制

NIO使用Native函數(shù)庫直接分配對外內(nèi)存,通過堆內(nèi)的DirectByteBuffer對象引用該內(nèi)存,因為避免了Heap與Native Heap來回復(fù)制數(shù)據(jù),提高了性能

2.3 HotSpot虛擬機對象探秘 2.3.1 對象的創(chuàng)建

先檢查指令參數(shù)是否在常量池中存在該類的符號引用,并檢查該符號引用是否被加載、解析和初始化

若無,則執(zhí)行類加載過程

垃圾收集器帶壓縮功能(Serial、ParNew) -> Heap是連續(xù)的 -> “指針碰撞”(Bump the Pointer)分配內(nèi)存

垃圾收集器不帶壓縮功能(CMS) -> Heap不是連續(xù)的 -> “空閑列表”(Free List)分配內(nèi)存

同步分配內(nèi)存空間2種方式:

虛擬機采用CAS配上失敗重試的方式保證更新操作的原子性

每個線程在Java堆中預(yù)先分配一小塊內(nèi)存,稱為本地線程分配緩沖(Thread Local Allocation Buffer,TLAB)。只有TLAB用完并分配新的TLAB時,才需要同步鎖定;通過-XX:+/-UseTLAB參數(shù)來設(shè)定

內(nèi)存分配完成后,虛擬機需要將分配到的內(nèi)存空間都初始化為零值(不包括對象頭)

設(shè)置對象頭(Object Header)信息。包括:元數(shù)據(jù)信息、hash碼、GC分代年齡信息等

執(zhí)行方法,初始化對象。

2.3.2 對象的內(nèi)存布局

HotSpot VM中,對象在內(nèi)存中的布局:

對象頭(Header)

Mark Word。存儲運行時數(shù)據(jù);如哈希碼(HashCode)、GC分代年齡、鎖狀態(tài)標志、線程持有的鎖、偏向線程ID、偏向時間戳等

類型指針。即對象指向它的類元數(shù)據(jù)的指針,虛擬機通過這個指針來確定這個對象是哪個類的實例。

實例數(shù)據(jù)(Instance Data)。對象真正存儲的有效信息

對齊填充(Padding)。僅起著占位符的作用

2.3.3 對象的訪問定位

以下是Java程序通過棧上的Reference來操作堆上的具體對象。

方式一:使用句柄

優(yōu)勢:reference存放的穩(wěn)定句柄,對象移動不會影響到reference

劣勢:需要在堆上開辟一塊空間存放句柄信息

方式二:使用直接指針

優(yōu)勢:reference存放的對象地址,訪問速度快。

劣勢:對象移動時需要更新reference。

HotSpot使用這種

2.4 實戰(zhàn):OutOfMemoryError異常 2.4.1 Java堆溢出

將堆的最小值-Xms參數(shù)與最大值-Xmx參數(shù)設(shè)置為一樣即可避免堆自動擴展

-XX:+HeapDumpOnOutOfMemoryError可以讓虛擬機在出現(xiàn)內(nèi)存溢出異常時Dump出當前的內(nèi)存堆轉(zhuǎn)儲快照

2.4.2 虛擬機棧和本地方法棧溢出

在HotSpot虛擬機中并不區(qū)分虛擬機棧和本地方法棧

如果線程請求的棧深度大于虛擬機所允許的最大深度,將拋出StackOverflowError異常(單線程下居多)

如果虛擬機在擴展棧時無法申請到足夠的內(nèi)存空間,則拋出OutOfMemoryError異常(多線程下居多)

不考慮虛擬機本身耗費內(nèi)存、程序計數(shù)器內(nèi)存(很小)
虛擬機棧和本地方法棧分配到的內(nèi)存 = 進程內(nèi)存 - 最大堆內(nèi)存(Xmx)- 最大方法區(qū)(MaxPermSize)
所以線程數(shù)越多,單個線程內(nèi)存就越小,成反比
2.4.3 方法區(qū)和運行時常量池溢出

方法區(qū)主要存放Class相關(guān)的信息,當使用例如CGLib字節(jié)碼增強、動態(tài)語言時,容易導(dǎo)致方法區(qū)內(nèi)存溢出

2.4.4 本機直接內(nèi)存溢出

DirectMemory可以通過-XX:MaxDirectMemorySize進行設(shè)置,不設(shè)置則等同于Heap最大值。

Heap Dump文件中不會看見明顯的異常

如果Dump文件很小,但程序有使用NIO,則可能時本機直接內(nèi)存溢出

第3章 垃圾收集器與內(nèi)存分配策略

本章討論Heap內(nèi)存的分配和回收

3.2 對象已死嗎 3.2.1 引用計數(shù)算法
給對象中添加一個引用計數(shù)器,每當有一個地方引用它時,計數(shù)器值就加1;當引用失效時,計數(shù)器值就減1;任何時刻計數(shù)器為0的對象就是不可能再被使用的。

很難解決對象之間相互循環(huán)引用的問題

3.2.2 可達性分析算法
這個算法的基本思路就是通過一系列的稱為"GC Roots"的對象作為起始點,從這些節(jié)點開始向下搜索,搜索所走過的路徑稱為引用鏈(Reference Chain),當一個對象到GC Roots沒有任何引用鏈相連(用圖論的話來說,就是從GC Roots到這個對象不可達)時,則證明此對象是不可用的。

可作為CG Root的對象:

虛擬機棧(棧幀中的本地變量表)中引用的對象。

方法區(qū)中類靜態(tài)屬性引用的對象。

方法區(qū)中常量引用的對象。

本地方法棧中JNI(即一般說的Native方法)引用的對象。

3.2.3 再談引用

強引用:類似Object() obj = new Object();,只要存在引用,便無法進行垃圾收集

軟引用:描述一些有用但非必需的對象;在系統(tǒng)將要內(nèi)存溢出前,進行二次收集,如果還是不足,則拋出內(nèi)存溢出異常。SoftReference

弱引用:描述非必需對象,只能生存到下一次垃圾收集器工作之前,不管內(nèi)存是否不足。WeakReference

虛引用:無法通過其獲取對象實例,作用時當對被垃圾收集時可以獲取一個系統(tǒng)通知。

3.2.4 生存還是死亡

當對象被檢測到?jīng)]有與GC Root可達,則將會被第一次標記,如果對象沒有覆蓋finalize(),或者finalize()已經(jīng)被調(diào)用過,則不會執(zhí)行

對象進入F-Queue,稍后虛擬機自動建立Finalizer線程執(zhí)行它,僅觸發(fā)

GC對F-Queue中的對象進行二次標記,標記前如果對象和GC Root關(guān)聯(lián),則可以逃脫

所以主動調(diào)用finalize()并不能立即觸發(fā)GC,它不是C++中的析構(gòu)函數(shù)
3.2.5 回收方法區(qū)

永久代收集內(nèi)容:

廢棄常量 :常量池中沒有被引用的字面量

無用類:

所有實例都被回收

ClassLoader被回收

Class對象沒有被引用

3.3 垃圾收集算法 3.3.1 標記-清除算法(Mark-Sweep)

首先標記出所有需要回收的對象

在標記完成后統(tǒng)一回收所有被標記的對象

不足:

效率不夠高,標記和清除兩個效率都不高

空間問題,會產(chǎn)生不連續(xù)的碎片內(nèi)存,

3.3.2 復(fù)制算法(Coping)

將可用內(nèi)存按容量劃分為大小相等的兩塊,每次只使用其中的一塊。

一塊內(nèi)存用完,將存活對象復(fù)制到另一塊,然后將已使用的對象清除

不用考慮碎片問題,只要移動堆頂指針,按順序分配即可

空間利用率低

現(xiàn)在的商業(yè)虛擬機都采用這種收集算法來回收新生代

當復(fù)制到另一個Survivor空間不夠用時,需要依賴其他內(nèi)存(這里指老年代)進行分配擔(dān)保(Handle Promotion)

3.3.3 標記-整理算法(Mark-Compact)

先標記需要回收的對象

再移動存活對象到一端

最后清理

3.3.4 分代收集算法(Generational Collection)

當前商業(yè)虛擬機的垃圾收集都采用“分代收集”

根據(jù)對象存活周期進行分代

新生代:復(fù)制法;大量對象存活時間短 (Eden/Survivor0/Survivor1 : 8/1/1)

老年代:標記清除法、標記整理法;存活時間長

3.4 HotSpot的算法實現(xiàn) 3.4.1 枚舉根節(jié)點

可達性分析為保證準確性必須在一個保證一致性的快照中進行,所以導(dǎo)致GC進行時需要停頓所有Java線程 -- Stop The World。

CMS收集器中,枚舉根節(jié)點時也是必須要停頓的。

HotSpot通過內(nèi)部實現(xiàn)的OopMap數(shù)據(jù)結(jié)構(gòu)可以快速且準確地完成GC Roots枚舉,在類加載期和編譯期記錄下對象引用信息,方便GC掃描。

3.4.2 安全點

HotSpot只在特定位置設(shè)置引用信息 -- 安全點

程序只有在安全點才會停下來執(zhí)行GC

選定標準“是否具有讓程序長時間執(zhí)行的特征”,即指令序列復(fù)用,例如:方法調(diào)用、循環(huán)跳轉(zhuǎn)、異常跳轉(zhuǎn)等

安全點位置選定還需考慮GC時讓所有線程都進入此

搶先式中斷:在GC發(fā)生時,首先把所有線程全部中斷,如果發(fā)現(xiàn)有線程中斷的地方不在安全點上,就恢復(fù)線程,讓它“跑”到安全點上。(現(xiàn)在幾乎不采用)

主動式中斷:當GC需要中斷線程的時候,不直接對線程操作,僅僅簡單地設(shè)置一個標志,各個線程執(zhí)行時主動去輪詢這個標志,發(fā)現(xiàn)中斷標志為真時就自己中斷掛起。輪詢標志的地方和安全點是重合的

3.4.3 安全區(qū)域

在線程執(zhí)行到Safe Region中的代碼時,首先標識自己已經(jīng)進入了Safe Region,那樣,當在這段時間里JVM要發(fā)起GC時,就不用管標識自己為Safe Region狀態(tài)的線程了。在線程要離開Safe Region時,它要檢查系統(tǒng)是否已經(jīng)完成了根節(jié)點枚舉(或者是整個GC過程),如果完成了,那線程就繼續(xù)執(zhí)行,否則它就必須等待直到收到可以安全離開Safe Region的信號為止。

3.5 垃圾收集器

3.5.1 Serial

最基本、發(fā)展歷史最悠久的收集器

它只會使用一個CPU或一條收集線程去完成垃圾收集工作,更重要的是在它進行垃圾收集時,必須暫停其他所有的工作線程,直到它收集結(jié)束 -- Stop The World

默認Client模式下新生代收集器

3.5.2 ParNew

Serial的多線程版本

許多Server模式下首選的新生代收集器

除了Serial收集器外,目前只有它能與CMS收集器配合工作

使用-XX:+UseConcMarkSweepGC選項后的默認新生代收集器,也可以使用-XX:+UseParNewGC選項來強制指定它。

默認開啟的收集線程數(shù)與CPU的數(shù)量相同

可以使用-XX:ParallelGCThreads參數(shù)來限制垃圾收集的線程數(shù)。

并行(Parallel):指多條垃圾收集線程并行工作,但此時用戶線程仍然處于等待狀態(tài)。

并發(fā)(Concurrent):指用戶線程與垃圾收集線程同時執(zhí)行(但不一定是并行的,可能會交替執(zhí)行),用戶程序在繼續(xù)運行,而垃圾收集程序運行于另一個CPU上。

3.5.3 Parallel Scavenge

是一個新生代收集器,使用復(fù)制算法,并行的多線程收集器

關(guān)注的維度不同

CMS考慮停頓時間,適合交互多的程序;

Parallel Scavenge考慮吞吐量,適合高效利用CPU時間的后臺程序

吞吐量=運行用戶代碼時間/(運行用戶代碼時間+垃圾收集時間)

虛擬機總共運行了100分鐘,其中垃圾收集花掉1分鐘,那吞吐量就是99%。

-XX:MaxGCPauseMillis控制最大垃圾收集停頓時間,參數(shù)是>0的毫秒數(shù),如果停頓時間減小,吞吐量降低,收集次數(shù)增加。

-XX:GCTimeRatio直接設(shè)置吞吐量大小,大于0且小于100的整數(shù),垃圾收集時間占總時間的比率,相當于是吞吐量的倒數(shù)

-XX:+UseAdaptiveSizePolicy GC自適應(yīng)調(diào)節(jié)策略,內(nèi)存管理調(diào)優(yōu)過程由虛擬機完成,這是與ParNew最大的區(qū)別

3.5.4 Serial Old

Serial的老年版本

單線程,使用“標記-整理”算法

Client模式下的虛擬機使用

3.5.5 Parallel Old

Parallel Scavenge收集器的老年代版本

使用多線程和“標記-整理”算法

在注重吞吐量以及CPU資源敏感的場合,都可以優(yōu)先考慮Parallel Scavenge加Parallel Old

3.5.6 CMS (Concurrent Mark Sweep)

以獲取最短回收停頓時間為目標

“標記-清除”算法

初始標記(CMS initial mark):Stop The World,僅標記一下GC Roots能直接關(guān)聯(lián)到的對象

并發(fā)標記(CMS concurrent mark):進行GC RootsTracing的過程,可與用戶線程一起工作

重新標記(CMS remark):Stop The World,修正并發(fā)標記期間因用戶程序運作導(dǎo)致標記變動的對象標記記錄,時間稍長于初始標記,遠小于并發(fā)標記

并發(fā)清除(CMS concurrent sweep):可與用戶線程一起工作

缺點:

對CPU資源非常敏感,并發(fā)階段會占用一部分線程導(dǎo)致應(yīng)用變慢,總吞吐量降低

CMS收集器無法處理浮動垃圾(Floating Garbage),可能出現(xiàn)"Concurrent Mode Failure"失敗而導(dǎo)致另一次Full GC的產(chǎn)生。

產(chǎn)生碎片空間可能無法存放當前對象,導(dǎo)致進行Full GC

浮動垃圾:并發(fā)清除時用戶線程還在運行,可能在標記過程后產(chǎn)生部分垃圾,只能留到下次GC時清除。

3.5.7 G1

面向服務(wù)端應(yīng)用的垃圾收集器

并行與并發(fā):使用多CPU來縮短Stop The World

分代收集:可以獨立管理整個GC堆

空間整合:整體是基于“標記—整理”算法,局部(兩個Region之間)是基于“復(fù)制”算法,保證不會產(chǎn)生碎片

可預(yù)測的停頓:它將整個Java堆劃分為多個大小相等的獨立區(qū)域(Region),跟蹤各個Region的垃圾堆積的價值大小(回收所獲得的空間大小以及回收所需時間的經(jīng)驗值),后臺維護一個優(yōu)先列表,每次根據(jù)允許的收集時間,優(yōu)先回收價值最大的Region,

每個Region內(nèi)部維護一個Remmbered Set來記錄對象引用信息,后面可以不用通過全堆掃描來收集垃圾

G1的運作步驟:

初始標記(Initial Marking):標記GC Root到直接關(guān)聯(lián)的對象,修改TAMS(Next Top at Mark Start)的值,讓下一階段用戶程序并發(fā)運行時,能在正確可用的Region中創(chuàng)建新對象,這階段需要停頓線程,但耗時很短。

并發(fā)標記(Concurrent Marking):從GC Root開始對堆中對象進行可達性分析,找出存活的對象,這階段耗時較長,但可與用戶程序并發(fā)執(zhí)行。

最終標記(Final Marking):修正在并發(fā)標記期間因用戶程序繼續(xù)運作而導(dǎo)致標記產(chǎn)生變動的那一部分標記記錄,虛擬機將這段時間對象變化記錄在線程Remembered Set Logs里面,最終標記階段需要把Remembered Set Logs的數(shù)據(jù)合并到Remembered Set中,這階段需要停頓線程,但是可并行執(zhí)行。

篩選回收(Live Data Counting and Evacuation):首先對各個Region的回收價值和成本進行排序,根據(jù)用戶所期望的GC停頓時間來制定回收計劃,從Sun公司透露出來的信息來看,這個階段其實也可以做到與用戶程序一起并發(fā)執(zhí)行,但是因為只回收一部分Region,時間是用戶可控制的,而且停頓用戶線程將大幅提高收集效率。

3.5.9 垃圾收集器參數(shù)總結(jié)

3.6 內(nèi)存分配與回收策略 3.6.1 對象優(yōu)先在Eden分配

大多數(shù)情況下,對象在新生代Eden區(qū)中分配。當Eden區(qū)沒有足夠空間進行分配時,虛擬機將發(fā)起一次Minor GC。

-XX:+PrintGCDetails:在發(fā)生垃圾收集行為時打印內(nèi)存回收日志,并且在進程退出的時候輸出當前的內(nèi)存各區(qū)域分配情況

-Xms20M、-Xmx20M、-Xmn10M這3個參數(shù)限制了Java堆大小為20MB,不可擴展,其中10MB分配給新生代,剩下的10MB分配給老年代

-XX:SurvivorRatio=8決定了新生代中Eden區(qū)與一個Survivor區(qū)的空間比例是8:1

新生代GC(Minor GC):指發(fā)生在新生代的垃圾收集動作,因為Java對象大多都具備朝生夕滅的特性,所以Minor GC非常頻繁,一般回收速度也比較快

老年代GC(Major GC/Full GC):指發(fā)生在老年代的GC,出現(xiàn)了Major GC,經(jīng)常會伴隨至少一次的Minor GC(但非絕對的,在Parallel Scavenge收集器的收集策略里就有直接進行Major GC的策略選擇過程)。Major GC的速度一般會比Minor GC慢10倍以上。

3.6.2 大對象直接進入老年代

大對象:需要大量連續(xù)內(nèi)存空間的Java對象;例如:很長的字符串以及數(shù)組

經(jīng)常出現(xiàn)大對象容易導(dǎo)致內(nèi)存還有不少空間時就提前觸發(fā)垃圾收集以獲取足夠的連續(xù)空間

-XX:PretenureSizeThreshold:令大于這個設(shè)置值的對象直接在老年代分配,只對Serial和ParNew兩款收集器有效

3.6.3 長期存活的對象將進入老年代

對象在Eden出生并經(jīng)過第一次Minor GC后仍然存活,并且能被Survivor容納的話,對象年齡設(shè)為1

對象在Survivor區(qū)中每“熬過”一次Minor GC,年齡就增加1歲

當它的年齡增加到一定程度(默認為15歲),就將會被晉升到老年代中

對象晉升老年代的年齡閾值,可以通過參數(shù)-XX:MaxTenuringThreshold設(shè)置

3.6.4 動態(tài)對象年齡判定

如果在Survivor空間中相同年齡所有對象大小的總和大于Survivor空間的一半,年齡大于或等于該年齡的對象就可以直接進入老年代,無須等到MaxTenuringThreshold中要求的年齡

3.6.5 空間分配擔(dān)保

在發(fā)生Minor GC之前,虛擬機會先檢查老年代最大可用的連續(xù)空間是否大于新生代所有對象總空間,如果這個條件成立,那么Minor GC可以確保是安全的。如果不成立,則虛擬機會查看HandlePromotionFailure設(shè)置值是否允許擔(dān)保失敗。如果允許,那么會繼續(xù)檢查老年代最大可用的連續(xù)空間是否大于歷次晉升到老年代對象的平均大小,如果大于,將嘗試著進行一次Minor GC,盡管這次Minor GC是有風(fēng)險的;如果小于,或者HandlePromotionFailure設(shè)置不允許冒險,那這時也要改為進行一次Full GC。

如果出現(xiàn)了HandlePromotionFailure失敗,那就只好在失敗后重新發(fā)起一次Full GC

在JDK 6 Update 24之后,這個測試結(jié)果會有差異,HandlePromotionFailure參數(shù)不會再影響到虛擬機的空間分配擔(dān)保策略

JDK 6 Update 24之后的規(guī)則變?yōu)橹灰夏甏倪B續(xù)空間大于新生代對象總大小或者歷次晉升的平均大小就會進行Minor GC,否則將進行Full GC。

第4章 虛擬機性能監(jiān)控與故障處理工具 jps:虛擬機進程狀況工具

JVM Process Status Tool

使用頻率最高的JDK命令行工具

jps[options][hostid]

jps可以通過RMI協(xié)議查詢開啟了RMI服務(wù)的遠程虛擬機進程狀態(tài),hostid為RMI注冊表中注冊的主機名

jstat:虛擬機統(tǒng)計信息監(jiān)視工具

JVM Statistics Monitoring Tool

可以顯示本地或者遠程虛擬機進程中的類裝載、內(nèi)存、垃圾收集、JIT編譯等運行數(shù)據(jù)

jstat[option vmid[interval[s|ms][count]]]

interval:查詢間隔
count:次數(shù)

#每250毫秒查詢一次進程2764垃圾收集狀況,一共查詢20次
jstat -gc 2764 250 20

jinfo:Java配置信息工具

Configuration Info for Java

實時地查看和調(diào)整虛擬機各項參數(shù)

jinfo[option]pid

# 查詢CMSInitiatingOccupancyFraction參數(shù)值
$ jinfo -flag CMSInitiatingOccupancyFraction 13435
-XX:CMSInitiatingOccupancyFraction=-1
jmap:Java內(nèi)存映像工具

Memory Map for Java

生成堆轉(zhuǎn)儲快照(一般稱為heapdump或dump文件)

其他方式獲得dump文件:

-XX:+HeapDumpOnOutOfMemoryError:OOM異常出現(xiàn)之后自動生成dump文件

-XX:+HeapDumpOnCtrlBreak:使用[Ctrl]+[Break]鍵讓虛擬機生成dump文件

kill -3:發(fā)送進程退出信號“嚇唬”一下虛擬機,也能拿到dump文件

jhat:虛擬機堆轉(zhuǎn)儲快照分析工具

JVM Heap Analysis Tool

與jmap搭配使用,來分析jmap生成的堆轉(zhuǎn)儲快照

功能較簡陋

jstack:Java堆棧跟蹤工具

Stack Trace for Java

生成虛擬機當前時刻的線程快照(一般稱為threaddump或者javacore文件)

定位線程出現(xiàn)長時間停頓的原因,如線程間死鎖、死循環(huán)、請求外部資源導(dǎo)致的長時間等待等

jstack[option]vmid

第5章 調(diào)優(yōu)案例分析與實戰(zhàn) 5.2 案例分析 高性能硬件上的程序部署策略

在大多數(shù)網(wǎng)站形式的應(yīng)用里,主要對象的生存周期都應(yīng)該是請求級或者頁面級的,會話級和全局級的長生命對象相對很少。只要代碼寫得合理,應(yīng)當都能實現(xiàn)在超大堆中正常使用而沒有Full GC,這樣的話,使用超大堆內(nèi)存時,網(wǎng)站響應(yīng)速度才會比較有保證。

堆外內(nèi)存導(dǎo)致的溢出錯誤

垃圾收集進行時,虛擬機雖然會對Direct Memory進行回收,但是Direct Memory卻不能像新生代、老年代那樣,發(fā)現(xiàn)空間不足了就通知收集器進行垃圾回收,它只能等待老年代滿了后Full GC,然后“順便地”幫它清理掉內(nèi)存的廢棄對象。否則它只能一直等到拋出內(nèi)存溢出異常時,先catch掉,再在catch塊里面“大喊”一聲:"System.gc()!"。要是虛擬機還是不聽(譬如打開了-XX:+DisableExplicitGC開關(guān)),那就只能眼睜睜地看著堆中還有許多空閑內(nèi)存,自己卻不得不拋出內(nèi)存溢出異常了。而本案例中使用的CometD 1.1.1框架,正好有大量的NIO操作需要使用到Direct Memory內(nèi)存。

從實踐經(jīng)驗的角度出發(fā),除了Java堆和永久代之外,我們注意到下面這些區(qū)域還會占用較多的內(nèi)存,這里所有的內(nèi)存總和受到操作系統(tǒng)進程最大內(nèi)存的限制。

Direct Memory:可通過-XX:MaxDirectMemorySize調(diào)整大小,內(nèi)存不足時拋出OutOfMemoryError或者OutOfMemoryError:Direct buffer memory。

線程堆棧:可通過-Xss調(diào)整大小,內(nèi)存不足時拋出StackOverflowError(縱向無法分配,即無法分配新的棧幀)或者OutOfMemoryError:unable to create new native thread(橫向無法分配,即無法建立新的線程)。

Socket緩存區(qū):每個Socket連接都Receive和Send兩個緩存區(qū),分別占大約37KB和25KB內(nèi)存,連接多的話這塊內(nèi)存占用也比較可觀。如果無法分配,則可能會拋出IOException:Too many open files異常。

JNI代碼:如果代碼中使用JNI調(diào)用本地庫,那本地庫使用的內(nèi)存也不在堆中。

虛擬機和GC:虛擬機、GC的代碼執(zhí)行也要消耗一定的內(nèi)存。

外部命令導(dǎo)致系統(tǒng)緩慢

Java的Runtime.getRuntime().exec()方法,首先克隆一個和當前虛擬機擁有一樣環(huán)境變量的進程,再用這個新的進程去執(zhí)行外部命令,最后再退出這個進程。如果頻繁執(zhí)行這個操作,系統(tǒng)的消耗會很大,不僅是CPU,內(nèi)存負擔(dān)也很重

第7章 虛擬機類加載機制 7.2 類加載的時機

加載、驗證、準備、初始化和卸載這5個階段的順序是確定的

解析可以在初始化之后,為了支持Java的運行時綁定(動態(tài)綁定)

因為各個階段都是相互交叉地混合式進行,所以不一定按順序完成

虛擬機規(guī)范則是嚴格規(guī)定了有且只有5種情況必須立即對類進行“初始化”(而加載、驗證、準備自然需要在此之前開始):

遇到new、getstatic、putstatic或invokestatic這4條字節(jié)碼指令時,如果類沒有進行過初始化,則需要先觸發(fā)其初始化。生成這4條指令的最常見的Java代碼場景是:使用new關(guān)鍵字實例化對象的時候、讀取或設(shè)置一個類的靜態(tài)字段(被final修飾、已在編譯期把結(jié)果放入常量池的靜態(tài)字段除外)的時候,以及調(diào)用一個類的靜態(tài)方法的時候。

使用java.lang.reflect包的方法對類進行反射調(diào)用的時候,如果類沒有進行過初始化,則需要先觸發(fā)其初始化。

當初始化一個類的時候,如果發(fā)現(xiàn)其父類還沒有進行過初始化,則需要先觸發(fā)其父類的初始化。

當虛擬機啟動時,用戶需要指定一個要執(zhí)行的主類(包含main()方法的那個類),虛擬機會先初始化這個主類。

當使用JDK 1.7的動態(tài)語言支持時,如果一個java.lang.invoke.MethodHandle實例最后的解析結(jié)果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且這個方法句柄所對應(yīng)的類沒有進行過初始化,則需要先觸發(fā)其初始化。

7.3 類加載過程 7.3.1 加載

通過一個類的全限定名來獲取定義此類的二進制字節(jié)流。

將這個字節(jié)流所代表的靜態(tài)存儲結(jié)構(gòu)轉(zhuǎn)化為方法區(qū)的運行時數(shù)據(jù)結(jié)構(gòu)。

在內(nèi)存中生成一個代表這個類的java.lang.Class對象(并沒有明確規(guī)定是在Java堆中,對于HotSpot虛擬機而言,Class對象比較特殊,它雖然是對象,但是存放在方法區(qū)里面),作為方法區(qū)這個類的各種數(shù)據(jù)的訪問入口。

7.3.2 驗證

目的是為了確保Class文件的字節(jié)流中包含的信息符合當前虛擬機的要求,并且不會危害虛擬機自身的安全。

驗證的4個階段:

文件格式驗證:驗證字節(jié)流是否符合Class文件格式的規(guī)范,并且能被當前版本的虛擬機處理。主要目的是保證輸入的字節(jié)流能正確地解析并存儲于方法區(qū)之內(nèi),格式上符合描述一個Java類型信息的要求。這階段的驗證是基于二進制字節(jié)流進行的,只有通過了這個階段的驗證后,字節(jié)流才會進入內(nèi)存的方法區(qū)中進行存儲,所以后面的3個驗證階段全部是基于方法區(qū)的存儲結(jié)構(gòu)進行的,不會再直接操作字節(jié)流。

元數(shù)據(jù)驗證:對字節(jié)碼描述的信息進行語義分析,以保證其描述的信息符合Java語言規(guī)范的要求

字節(jié)碼驗證:第三階段是整個驗證過程中最復(fù)雜的一個階段,主要目的是通過數(shù)據(jù)流和控制流分析,確定程序語義是合法的、符合邏輯的。在第二階段對元數(shù)據(jù)信息中的數(shù)據(jù)類型做完校驗后,這個階段將對類的方法體進行校驗分析,保證被校驗類的方法在運行時不會做出危害虛擬機安全的事件

符號引用驗證:校驗發(fā)生在虛擬機將符號引用轉(zhuǎn)化為直接引用的時候,這個轉(zhuǎn)化動作將在連接的第三階段——解析階段中發(fā)生。符號引用驗證可以看做是對類自身以外(常量池中的各種符號引用)的信息進行匹配性校驗

7.3.3 準備

準備階段是正式為類變量分配內(nèi)存并設(shè)置類變量初始值的階段,這些變量所使用的內(nèi)存都將在方法區(qū)中進行分配。

這時候進行內(nèi)存分配的僅包括類變量(被static修飾的變量),而不包括實例變量,實例變量將會在對象實例化時隨著對象一起分配在Java堆中

7.3.4 解析

解析階段是虛擬機將常量池內(nèi)的符號引用替換為直接引用的過程

解析動作主要針對類或接口、字段、類方法、接口方法、方法類型、方法句柄和調(diào)用點限定符7類符號引用進行

符號引用(Symbolic References):符號引用以一組符號來描述所引用的目標,符號可以是任何形式的字面量,只要使用時能無歧義地定位到目標即可。

直接引用(Direct References):直接引用可以是直接指向目標的指針、相對偏移量或是一個能間接定位到目標的句柄。

7.3.5 初始化

真正開始執(zhí)行類中定義的Java程序代碼(或者說是字節(jié)碼)。

初始化階段是執(zhí)行類構(gòu)造器<clinit>()方法的過程,初始化類變量和其他資源

7.4 類加載器

類加載器在虛擬機外部

7.4.1 類與類加載器

每一個類加載器,都擁有一個獨立的類名稱空間;例如:兩個類來源于同一個Class文件,被同一個虛擬機加載,只要加載它們的類加載器不同,那這兩個類就必定不相等。

7.4.2 雙親委派模型(Parents Delegation Model)

從Java虛擬機的角度來講,只存在兩種不同的類加載器:一種是啟動類加載器(Bootstrap ClassLoader),這個類加載器使用C++語言實現(xiàn)[1],是虛擬機自身的一部分;另一種就是所有其他的類加載器,這些類加載器都由Java語言實現(xiàn),獨立于虛擬機外部,并且全都繼承自抽象類java.lang.ClassLoader。

從Java開發(fā)人員角度可以大致細分程3種:

啟動類加載器(Bootstrap ClassLoader)[不能直接使用]

<JAVA_HOME>lib

-Xbootclasspath指定目錄

虛擬機識別的類庫

擴展類加載器(Extension ClassLoader),[可直接使用]

<JAVA_HOME>libext

java.ext.dirs系統(tǒng)變量指定的類庫

應(yīng)用程序類加載器(Application ClassLoader),[可直接使用]

ClassLoader中的getSystemClassLoader()方法的返回值

加載用戶類路徑(ClassPath)上所指定的類庫

程序中默認的類加載器

雙親委派模型要求除了頂層的啟動類加載器外,其余的類加載器都應(yīng)當有自己的父類加載器。這里類加載器之間的父子關(guān)系一般不會以繼承(Inheritance)的關(guān)系來實現(xiàn),而是都使用組合(Composition)關(guān)系來復(fù)用父加載器的代碼。

雙親委派模型的工作過程是:如果一個類加載器收到了類加載的請求,它首先不會自己去嘗試加載這個類,而是把這個請求委派給父類加載器去完成,每一個層次的類加載器都是如此,因此所有的加載請求最終都應(yīng)該傳送到頂層的啟動類加載器中,只有當父加載器反饋自己無法完成這個加載請求(它的搜索范圍中沒有找到所需的類)時,子加載器才會嘗試自己去加載。

好處就是Java類隨著它的類加載器一起具備了一種帶有優(yōu)先級的層次關(guān)系。例如類java.lang.Object,它存放在rt.jar之中,無論哪一個類加載器要加載這個類,最終都是委派給處于模型最頂端的啟動類加載器進行加載,因此Object類在程序的各種類加載器環(huán)境中都是同一個類。相反,如果沒有使用雙親委派模型,由各個類加載器自行去加載的話,如果用戶自己編寫了一個稱為java.lang.Object的類,并放在程序的ClassPath中,那系統(tǒng)中將會出現(xiàn)多個不同的Object類,Java類型體系中最基礎(chǔ)的行為也就無法保證,應(yīng)用程序也將會變得一片混亂。

實現(xiàn)雙親委派的代碼都集中在java.lang.ClassLoader的loadClass()方法之中,先檢查是否已經(jīng)被加載過,若沒有加載則調(diào)用父加載器的loadClass()方法,若父加載器為空則默認使用啟動類加載器作為父加載器。如果父類加載失敗,拋出ClassNotFoundException異常后,再調(diào)用自己的findClass()方法進行加載。

  protected synchronized Class loadClass(String name, boolean resolve)
      throws ClassNotFoundException {
    // 首先判斷該類型是否已經(jīng)被加載
    Class c = findLoadedClass(name);
    if (c == null) {
      // 如果沒有被加載,就委托給父類加載或者委派給啟動類加載器加載
      try {
        if (parent != null) {
          // 如果存在父類加載器,就委派給父類加載器加載
          c = parent.loadClass(name, false);
        } else {
          // 如果不存在父類加載器,就檢查是否是由啟動類加載器加載的類,通過調(diào)用本地方法native Class findBootstrapClass(String name)
          c = findBootstrapClass0(name);
        }
      } catch (ClassNotFoundException e) {
        // 如果父類加載器和啟動類加載器都不能完成加載任務(wù),才調(diào)用自身的加載功能
        c = findClass(name);
      }
    }
    if (resolve) {
      resolveClass(c);
    }
    return c;
  }
第9章 類加載及執(zhí)行子系統(tǒng)的案例與實戰(zhàn) 9.2.1 Tomcat:正統(tǒng)的類加載器架構(gòu)

主流Java Web服務(wù)器要解決的問題:

部署在同一個服務(wù)器上的兩個Web應(yīng)用程序所使用的Java類庫可以實現(xiàn)相互隔離

部署在同一個服務(wù)器上的兩個Web應(yīng)用程序所使用的Java類庫可以互相共享,如果類庫不能共享,虛擬機的方法區(qū)就會很容易出現(xiàn)過度膨脹的風(fēng)險。

服務(wù)器需要盡可能地保證自身的安全不受部署的Web應(yīng)用程序影響。基于安全考慮,服務(wù)器所使用的類庫應(yīng)該與應(yīng)用程序的類庫互相獨立。

Tomcat的目錄結(jié)構(gòu):

/common/*:類庫可被Tomcat和所有的Web應(yīng)用程序共同使用。

/server/*:類庫可被Tomcat使用,對所有的Web應(yīng)用程序都不可見。

/shared/*:類庫可被所有的Web應(yīng)用程序共同使用,但對Tomcat自己不可見。

/WebApp/WEB-INF/*:類庫僅僅可以被此Web應(yīng)用程序使用,對Tomcat和其他Web應(yīng)用程序都不可見。

灰色:JDK默認加載器

每一個Web應(yīng)用程序?qū)?yīng)一個WebApp類加載器,每一個JSP文件對應(yīng)一個Jsp類加載器

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/73623.html

相關(guān)文章

  • 學(xué)習(xí)JVM必看書籍

    學(xué)習(xí)JVM的相關(guān)資料 《深入理解Java虛擬機——JVM高級特性與最佳實踐(第2版)》 showImg(https://segmentfault.com/img/bVbsqF5?w=200&h=200); 基于最新JDK1.7,圍繞內(nèi)存管理、執(zhí)行子系統(tǒng)、程序編譯與優(yōu)化、高效并發(fā)等核心主題對JVM進行全面而深入的分析,深刻揭示JVM的工作原理。以實踐為導(dǎo)向,通過大量與實際生產(chǎn)環(huán)境相結(jié)合的案例展示了解...

    shaonbean 評論0 收藏0
  • 從小白程序員一路晉升為大廠高級技術(shù)專家我看過哪些書籍?(建議收藏)

    摘要:大家好,我是冰河有句話叫做投資啥都不如投資自己的回報率高。馬上就十一國慶假期了,給小伙伴們分享下,從小白程序員到大廠高級技術(shù)專家我看過哪些技術(shù)類書籍。 大家好,我是...

    sf_wangchong 評論0 收藏0
  • 深入理解Java虛擬》(一)Java虛擬發(fā)展史

    摘要:虛擬機發(fā)展史注本文大部分摘自深入理解虛擬機第二版作為一名開發(fā)人員,不能局限于語言規(guī)范,更需要對虛擬機規(guī)范有所了解。虛擬機規(guī)范有多種實現(xiàn),其中是和中所帶的虛擬機,也是目前使用范圍最廣的虛擬機。世界第一款商用虛擬機。號稱世界上最快的虛擬機。 Java虛擬機發(fā)展史 注:本文大部分摘自《深入理解Java虛擬機(第二版)》 作為一名Java開發(fā)人員,不能局限于Java語言規(guī)范,更需要對Java虛...

    張春雷 評論0 收藏0
  • 我的2016年Java書單

    摘要:相對于電子書,我更喜歡紙質(zhì)版的書籍。過去的年一共閱讀過本技術(shù)書,下面對這些書做一個小結(jié)。源碼深度解析這本書是年購買的,年是第四次閱讀。必知必會數(shù)據(jù)庫的復(fù)習(xí)書籍,內(nèi)容淺顯易懂。 相對于電子書,我更喜歡紙質(zhì)版的書籍。我喜歡在拿到新書時記錄購買時間、地點、開始閱讀的時間、第一次看完的時間,算是一種學(xué)習(xí)的記錄。過去的2016年一共閱讀過15本技術(shù)書,下面對這些書做一個小結(jié)。 《深入理解Java...

    Scholer 評論0 收藏0
  • 報道帖——給 Segmentfault 朋友們的電子書

    摘要:一直都挺喜歡這個社區(qū)的,給人的第一感覺就是比較的專業(yè)正式,社區(qū)內(nèi)氛圍不錯,各種文章的質(zhì)量也很好,并且?guī)椭宋液芏唷:荛_心能夠來到這里,記錄自己的成長,希望自己能夠多活躍一下,無論是在問答上面還是寫作上面。 一直都挺喜歡 Segmentfault 這個社區(qū)的,給人的第一感覺就是比較的專業(yè)正式,社區(qū)內(nèi)氛圍不錯,各種文章的質(zhì)量也很好,并且?guī)椭宋液芏唷:荛_心能夠來到這里,記錄自己的成長,希望...

    cnsworder 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<