JVM學習隨筆

JVM規範:
    Class文件格式
    數字的內部表示和存儲
        -Byte -128 to 127
    returnAddress數據類型定義:
        -指向操做碼的指針。不對應java數據類型,不能再運行時修改。
    定義PC
    堆
    棧
    方法區

JVM運行機制:

    JVM啓動流程:
        如圖:
    JVM基本結構:
        如圖:
        PC寄存器:
            - 每一個線程擁有一個PC寄存器
            - 在線程建立時 建立
            - 指向嚇一跳指令的地址
            - 執行本地方法時,PC的值爲undefined
        方法區:
            - 保存類的源信息
                -類的常量池
                -字段、方法的信息
                -方法字節碼
            - 一般和永久去(Perm)關聯再一塊兒
        Java堆:
            - 和程序開發密切相關
            - 應用系統對象都保存在java堆中
            - 全部的線程共享java堆
            - 對分代GC來講,堆也是分代的
            - GC的主要工做空間
                -----------------------------------------
                |        |        |        |                |
                |  eden    |    s0    |    s1    |    tenured        
                |        |        |        |                |
                -----------------------------------------
                                複製算法
        java棧:
            - 線程私有的
            - 棧由一系列的幀組成(所以java棧也叫做幀棧)
            - 棧保存一個方法的局部變量、操做數棧、常量池指針
            - 每一次方法調用建立一個幀,並壓棧

        
    內存模型:
        - 每個線程有一個工做內存和主存獨立
        
        - 工做內存存放主存中變量值的拷貝
                ----------------
                | 線程執行引擎    |
                ----------------
                    |        ^
            assign  |        |    use
                    V        |
                ----------------
                | 線程工做內存    |
                ----------------    
                    ^        |
        read,load   |        |    store,write
                    |        V
                ----------------
                |    主內存        |
                ----------------    
            注:當數據從主存複製到工做內存是,必須出現兩個動做:
                    1:與主內存執行讀(read)操做
                    2:由工做內存執行相應的load操做
                當數據從工做內存拷貝到主內存時,也出現兩個操做:
                    1:由工做內存執行的存儲(store)操做
                    2:由主內存執行相應的寫(write)操做
                每個操做都是原子的,即執行期間不會被中斷
                對於普通變量,一個線程中更新的值,不能立刻反應再其餘的變量中
                若是須要在其它線程中當即可見,須要是用volatile關鍵字
        
        可見性:
            - 一個線程修改了變量,其餘線程能夠當即知道
        
            - 保證線程可見性的方法:
                - volatile
                - synchronized(unlock以前,寫變量值會主存)
                - final(一旦初始化完成,其餘線程就可見)
        有序性:
            - 在本線程內,操做都是有序的
            - 在線程外觀察,操做都是無序的
    
JVM經常使用的配置參數:

    Trace跟蹤參數:
        *:-verbose:gc
        *:-XX:+pringGC
        *:能夠打印GC的簡要信息
            ---[GC 4790K->364K(15782K),0.001606 secs]
            ---[GC 4790K->364K(15782K),0.001474 secs]
            ---[GC 4790K->364K(15782K),0.001563 secs]
            ---[GC 4790K->364K(15782K),0.001682 secs]
        *-XX:+PrintGCDetails
            -打印GC詳細信息
        *-XX:+PrintGCTimeStamps
            -打印GC發生的時間戳
        *-Xloggc:log/gc.log
            -指定GC log的位置,以文件輸出
            - 幫助開發人員分析問題
        *-XX:+PringHeapAtGC
            -每一次GC先後,都打印堆信息
        *-XX:+TraceClassLoading
            -監控類的加載
        *-XX:+PrintClassHistogram
            -按下Ctrl + Break之後,打印類的信息
            分別顯示:序號、實例數量、總大小、類型 (這個參數能夠看各個數據類型的使用狀況)
    
    堆的分配參數:
        -Xmx -Xms
            -指定最大堆和最小堆
            -Xmx20m -Xms5m
        -Xmn
            -設置新生代的大小
        -XX:NewRatio
            -新生代(eden + 2*s)和老年代(不包含永久區)的比值
            -4表示 新生代:老年代 = 1:4,即年輕代佔堆的1/5
        -XX:SurvivorRatio
            -設置兩個Survivor區和eden的比
            -8表示 兩個Survivor:eden = 2:8 一個Survivor也就是佔年輕代1/10
        -XX:+HeapDumpOnOutOfMemoryError    
            -OOM時導出到堆
        -XX:+HeapDumpPath
            -導出OOM的路勁
        -XX:OnOutOfMemoryError
            -在oom時,執行一個腳本
            -"-XX:OnOutOfMemoryError=xxxx.bat %P"  %P java進程ID 當程序oom時就會執行bat文件  能夠用來發郵件甚至重啓服務
        
            堆的分配參數總結:
                根據實際狀況調整新生代和心存代的大小
                官方推薦新生代佔堆的3/8
                辛存代佔新生代的1/10
                在oom時,記得Dump出堆,確保能夠排查現場問題
                
        永久區分配參數:
            -XX:PermSize -XX:MaxPermSize
                -設置永久帶的初始空間和最大空間
                -他們表示,一個系統能夠容納多少個類型
    
    棧大小分配:
        -Xss
            -一般只有幾百K
            -決定了函數調用的深度
            -每一個線程都有獨立的棧空間
            -局部變量、參數 分配在棧上
        
GC算法與種類:

    GC的概念:垃圾收集,在java中,GC的對象是堆空間和永久區。
    
    GC算法:
        引用計數法:
            引用計數法的概念
                對於一個對象A,只要有任何一個對象引用了A,則A的引用計數器就加1,當引用失效時,引用計數器就減1,只要對象A的引用計數器的值爲0,
                則A對象就不可能再被使用。
            引用計數法的問題:
                -引用計數法伴隨着加法和減法,影響性能。
                -很難處理循環引用。
        
        標記-清除算法:
            概念:
                標記-清除算法分爲兩個階段:標記階段和清除階段。一種可行的實現是,在標記階段,首先經過根節點,標記全部從根節點開始的可達對象。
                所以,從未被標記的對象就是未被引用的垃圾對象。而後在清除階段,清除全部未被標記的對象。
        
        標記-壓縮算法:
            概念:
                標記-壓縮算法適合用於存活對象較多的場合,如老年代。它在標記-清除算法的基礎上作了一些優化。和標記清除算法同樣。標記壓縮也須要從根節點開始。
                對全部可達對象作一次標記。但以後,它並不簡單的清除未標記對象,而是將全部的存活對象壓縮到內存的一端以後,清理邊界外全部空間。
        
        複製算法:
            -與標記-清除算法相比,複製算法是一種相對高效的回收方法
            -不適用於存活對象比較多的場合
            -將原有內存空間分爲兩塊,每次只使用其中一塊,在垃圾收回時,將正在使用的內存中存活的對象複製到未使用的內存塊中,以後,清除真的使用的內存塊
                中的全部對象,交換兩個內存的角色,完成垃圾回收。
            複製算法的問題:
                空間浪費,需預留一半的空間。
            
    分代思想:
        -根據對象的存活週期進行分代,短命對象歸爲新生代,長命對象歸爲老年代。
        -根據不一樣代的特色,選取合適的收集算法:
            -少許對象存活適合複製算法
            -大量對象存活適合標記清理或者標記壓縮
            
    GC算法總結:全部的算法,須要一個識別的垃圾對象,所以須要給出一個可觸及性的定義。
        引用計數
            -沒有被java採用
        標記-清除
        標記-壓縮
        複製算法
            -新生代
            
    可觸及性:(什麼是根:棧中的對象,全局對象,JNI方法棧中引用的對象)
        可觸及:
            -從根節點能夠觸及到這個對象
        可復活:
            -一旦全部引用被釋放,就可復活的狀態
            -由於在finallize()中可能復活該對象
        不可觸及的:
            -在finallze()後,可能進入不可觸及的狀態
            -不可觸及的對象不可能復活
            -能夠回收
            
    Stop-The-World
        是什麼:
            -java中一種全局暫停的現象
            -全局停頓,全部java代碼中止,native代碼能夠執行,但不能和jvm交互。
            -多半因爲GC引發
                -Dump線程
                -死鎖檢查
                -堆Dump
                
        爲何:
            -中止製造垃圾才能打掃乾淨。
        
        危害:
            -長時間服務中止,沒有響應。
            -遇到HA系統,可能引發主備切換,嚴重危害生產環境。
            
GC回收器:
    GC串行回收器:
        -最古老,最穩定
        -效率高
        -可能會產生較長的停頓
        -XX:+UseSerialGC
            -新生代、老年代使用串行回收
            -新生代複製算法
            -老年代標記-壓縮算法
            
    GC並行回收器:
        ParNew收集器:
            -XX:+UseOarNewGC
                -新生代並行
                -老年代串行
            -Serial收集器新生代的並行版本
            -複製算法
            -多線程,須要多核支持
            - -XX:ParallelGCThreads 限制線程數量
        
        Parallel收集器:
            -相似ParNew
            -新生代複製算法
            -老年代 標記-壓縮
            -更加關注吞吐量
            - -XX:+UseParallelGC
                -使用parallel收集器+老年代串行
            - -XX:+UserParallelOldGC
                -使用Parallel收集器+並行老年代

        並行回收器的參數:
            -XX:MaxGCPauseMills
                -最大停頓時間,單位毫秒
                -GC盡力保證回收時間不超過設定值
            -XX:GCTimeRatio
                -0-100的取值範圍
                -垃圾收集時間佔總時間的比
                -默認99,即最大容許1%時間作GC
            這兩個參數是矛盾的。由於停頓時間和吞吐量不可能同時調優

    CMS收集器:
        -Concurrent Mark Sweep 併發標記清除
        -與標記-清除算法
        -與標記-壓縮相比
        -併發階段會下降吞吐量
        -老年代收集器(新生代使用ParNew)
        - -XX:+UseConcMarkSweepGC
        
        CMS的運行過程:
            -初始標記
                -根能夠直接關聯到的對象
                -速度快
            -併發標記(和用戶線程一塊兒)
                -主要標記過程,標記所有對象
            -從新標記
                因爲併發標記時,用戶線程任然運行,所以在正式清理前,再作修正
            -併發清除(和用戶線程一塊兒)
                基於標記結果,直接清理對象
        
        CMS的特色:
            -儘量的下降停頓
            -會影響系統總體吞吐量和性能
                -好比在用戶線程運行過程當中,分一半CPU去作GC,反應速度就降低一半
            -清理不完全
                -由於在清理階段,用戶線程還在運行,會產生新的垃圾,沒法清理
            -由於和用戶線程一塊兒運行,不能在空間快滿時再清理
                - -XX:CMSInitiatingOccupancyFraction設置觸發GC的閾值
                -若是不幸內存預留空間不夠,就會引發concurrent failure

        GC參數 – CMS收集器
            -XX:+ UseCMSCompactAtFullCollection Full GC後,進行一次整理
                整理過程是獨佔的,會引發停頓時間變長
            -XX:+CMSFullGCsBeforeCompaction 
                設置進行幾回Full GC後,進行一次碎片整理
            -XX:ParallelCMSThreads
                設定CMS的線程數量

    GC參數整理:
        -XX:+UseSerialGC:在新生代和老年代使用串行收集器
        -XX:SurvivorRatio:設置eden區大小和survivior區大小的比例
        -XX:NewRatio:新生代和老年代的比
        -XX:+UseParNewGC:在新生代使用並行收集器
        -XX:+UseParallelGC :新生代使用並行回收收集器
        -XX:+UseParallelOldGC:老年代使用並行回收收集器
        -XX:ParallelGCThreads:設置用於垃圾回收的線程數
        -XX:+UseConcMarkSweepGC:新生代使用並行收集器,老年代使用CMS+串行收集器
        -XX:ParallelCMSThreads:設定CMS的線程數量
        -XX:CMSInitiatingOccupancyFraction:設置CMS收集器在老年代空間被使用多少後觸發
        -XX:+UseCMSCompactAtFullCollection:設置CMS收集器在完成垃圾收集後是否要進行一次內存碎片的整理
        -XX:CMSFullGCsBeforeCompaction:設定進行多少次CMS垃圾回收後,進行一次內存壓縮
        -XX:+CMSClassUnloadingEnabled:容許對類元數據進行回收
        -XX:CMSInitiatingPermOccupancyFraction:當永久區佔用率達到這一百分比時,啓動CMS回收
        -XX:UseCMSInitiatingOccupancyOnly:表示只在到達閥值的時候,才進行CMS回收

    GC參數-Tomcat實例:
        環境:
            Tomcat7
            jsp網站
            測試網站吞吐和延時
        工具:
            JMeter
        目的:
            讓tomcat有一個不錯的吞吐量

類裝載器:
    class裝載驗證流程:
        加載:
            裝載類的第一個階段
            1:取得累的二進制流
            2:轉爲方法區數據結構
            3:在java堆中生成對應的java.lang.Class對象
            
        連接--> 驗證:
            目的:保證Class流的格式是正確的
                -文件格式的驗證
                    -是否以0xCAFEBABE開頭
                    -版本號是否合理
                -元數據驗證
                    -是否有父類
                    -繼承了final類?
                    -非抽象類實現了全部的抽象方法
                -字節碼驗證 (很複雜)
                    -運行檢查
                    -棧數據類型和操做碼數據參數吻合
                    -跳轉指令指定到合理的位置
                -符號引用驗證
                    -常量池中描述類是否存在
                    -訪問的方法或字段是否存在且有足夠的權限
        
        連接--> 準備:
            -分配內存,併爲類設置初始值 (方法區中)
                public static int v=1;
                在準備階段中,v會被設置爲0
                在初始化的<clinit>中才會被設置爲1
                對於static final類型,在準備階段就會被賦上正確的值
                public static final  int v=1;

        連接--> 解析:
            -符號引用替換爲直接引用
            
        初始化:
            執行類構造器<clinit>
                static變量 賦值語句
                static{}語句
            子類的<clinit>調用前保證父類的<clinit>被調用
            <clinit>是線程安全的

什麼是類裝載器ClassLoader:
    ClassLoader是一個抽象類
    ClassLoader的實例將讀入Java字節碼將類裝載到JVM中
    ClassLoader能夠定製,知足不一樣的字節碼流獲取方式
    ClassLoader負責類裝載過程當中的加載階段


系統性能監控
    性能監控 - linux:
        uptime:
            系統時間
            運行時間
            鏈接數
            1,5,15分鐘內的系統平均負載
        top:
            同uptime
            CPU
            內存
            每一個進程佔CPU的狀況
        vmstat:
            能夠統計系統的CPU,內存,swap,io等狀況
            CPU佔用率很高,上下文切換頻繁,說明系統有線程正在頻繁切換
        pidstat:
            細緻觀察進程
            須要安裝
                sudo apt-get install sysstat
            監控CPU
            監控IO
            監控內存
            ps:pidstat -p 2962 -u 1 3 -t (2962/進程號 -u/監控CUP 每秒一次 一共三次 -t/顯示線程)
        
    性能監控 - windows:
        pslist
            -命令行工具
            -可用於自動化數據收集
            -顯示java程序的運行狀況

    Java自帶的工具:
        jps:
            -列出java進程,相似於ps命令
            -參數-q能夠指定jps只輸出進程ID ,不輸出類的短名稱
            -參數-m能夠用於輸出傳遞給Java進程(主函數)的參數
            -參數-l能夠用於輸出主函數的完整路徑
            -參數-v能夠顯示傳遞給JVM的參數
            
        jinfo:
            能夠用來查看正在運行的Java應用程序的擴展參數,甚至支持在運行時,修改部分參數
            -flag <name>:打印指定JVM的參數值
            -flag [+|-]<name>:設置指定JVM參數的布爾值
            -flag <name>=<value>:設置指定JVM參數的值
            
        jmap:
            -生成Java應用程序的堆快照和對象的統計信息
            -jmap -histo 2972 >c:\s.txt
            
        Dump堆:
            jmap -dump:format=b,file=c:\heap.hprof 2972
        
        jstack:
            打印線程dump
            -l 打印鎖信息
            -m 打印java和native的幀信息
            -F 強制dump,當jstack沒有響應時使用

        JConsole:
            圖形化監控工具
            能夠查看Java應用程序的運行概況,監控堆信息、永久區使用狀況、類加載狀況等
    
        - Visual VM:
            Visual VM是一個功能強大的多合一故障診斷和性能監控的可視化工具
    

內存溢出(OOM)的緣由:
    堆溢出:
        佔用大量堆空間,直接溢出,Exception in thread "main" java.lang.OutOfMemoryError: 
    永久區:
        生成大量的類,沒法回收,Caused by: java.lang.OutOfMemoryError: PermGen space 解決方法:增大Perm區 容許Class回收
    Java棧溢出:
        這裏的棧溢出指,在建立線程的時候,須要爲線程分配棧空間,這個棧空間是向操做系統請求的,
        若是操做系統沒法給出足夠的空間,就會拋出OOM。解決方法:減小堆內存 減小線程棧大小
    直接內存溢出:
        ByteBuffer.allocateDirect()沒法從操做系統得到足夠的空間,解決方法:減小堆內存 有意觸發GC

鎖:
    對象頭Mark:
        -Mark Word,對象頭的標記,32位
        -描述對象的hash、鎖信息,垃圾回收標記,年齡
            -指向鎖記錄的指針
            -指向monitor的指針
            -GC標記
            -偏向鎖線程ID
    
    偏向鎖:
        大部分狀況是沒有競爭的,因此能夠經過偏向來提升性能
        所謂的偏向,就是偏愛,即鎖會偏向於當前已經佔有鎖的線程
        將對象頭Mark的標記設置爲偏向,並將線程ID寫入對象頭Mark
        只要沒有競爭,得到偏向鎖的線程,在未來進入同步塊,不須要作同步
        當其餘線程請求相同的鎖時,偏向模式結束
        -XX:+UseBiasedLocking
            -默認啓用
        在競爭激烈的場合,偏向鎖會增長系統負擔
    
    輕量級鎖:BasicObjectLock    
        -普通的鎖處理性能不夠理想,輕量級鎖是一種快速的鎖定方法。
        -若是對象沒有被鎖定
            -將對象頭的Mark指針保存到鎖對象中
            -將對象頭設置爲指向鎖的指針(在線程棧空間中)
        -若是輕量級鎖失敗,表示存在競爭,升級爲重量級鎖(常規鎖)
        -在沒有鎖競爭的前提下,減小傳統鎖使用OS互斥量產生的性能損耗
        -在競爭激烈時,輕量級鎖會多作不少額外操做,致使性能降低
    
    自旋鎖:
        -當競爭存在時,若是線程能夠很快得到鎖,那麼能夠不在OS層掛起線程,讓線程作幾個空操做(自旋)
        -JDK1.6中-XX:+UseSpinning開啓
        -JDK1.7中,去掉此參數,改成內置實現
        -若是同步塊很長,自旋失敗,會下降系統性能
        -若是同步塊很短,自旋成功,節省線程掛起切換時間,提高系統性能

    偏向鎖,輕量級鎖,自旋鎖總結:
        -不是Java語言層面的鎖優化方法
        -內置於JVM中的獲取鎖的優化方法和獲取鎖的步驟
            -偏向鎖可用會先嚐試偏向鎖
            -輕量級鎖可用會先嚐試輕量級鎖
            -以上都失敗,嘗試自旋鎖
            -再失敗,嘗試普通鎖,使用OS互斥量在操做系統層掛起

    基於java代碼層面鎖的優化:
        減小鎖持有時間,也就是不必作同步的方法就儘可能不要去作同步,能在方法上別在類上
        減少鎖粒度:
            -將大對象,拆成小對象,大大增長並行度,下降鎖競爭
            -偏向鎖,輕量級鎖成功率提升
            -ConcurrentHashMap
            -HashMap的同步實現
                -Collections.synchronizedMap(Map<K,V> m)
                -返回SynchronizedMap對象
        鎖分離:
            根據功能進行鎖分離
            ReadWriteLock
            讀多寫少的狀況,能夠提升性能
            
        鎖粗化:
            一般狀況下,爲了保證多線程間的有效併發,會要求每一個線程持有鎖的時間儘可能短,即在使用完公共資源後,應該當即釋放鎖。
            只有這樣,等待在這個鎖上的其餘線程才能儘早的得到資源執行任務。可是,凡事都有一個度,若是對同一個鎖不停的進行請求、同步和釋放,
            其自己也會消耗系統寶貴的資源,反而不利於性能的優化
        
        鎖消除:
            在即時編譯器時,若是發現不可能被共享的對象,則能夠消除這些對象的鎖操做
    
        無鎖:
            -鎖是悲觀的操做
            -無鎖是樂觀的操做
            -無鎖的一種實現方式
                -CAS(Compare And Swap)
                -非阻塞的同步
                -CAS(V,E,N)
                    CAS算法的過程是這樣:它包含3個參數CAS(V,E,N)。V表示要更新的變量,E表示預期值,N表示新值。僅當V值等於E值時,
                    纔會將V的值設爲N,若是V值和E值不一樣,則說明已經有其餘線程作了更新,則當前線程什麼都不作。最後,CAS返回當前V的真實值。
                    CAS操做是抱着樂觀的態度進行的,它老是認爲本身能夠成功完成操做。當多個線程同時使用CAS操做一個變量時,只有一個會勝出,
                    併成功更新,其他均會失敗。失敗的線程不會被掛起,僅是被告知失敗,而且容許再次嘗試,固然也容許失敗的線程放棄操做。基於這樣的原理,
                    CAS操做即時沒有鎖,也能夠發現其餘線程對當前線程的干擾,並進行恰當的處理。
            -在應用層面判斷多線程的干擾,若是有干擾,則通知線程重試














            
            
相關文章
相關標籤/搜索