<sub id="gqw76"><listing id="gqw76"></listing></sub>
      <sub id="gqw76"><listing id="gqw76"></listing></sub>

    1. <form id="gqw76"><legend id="gqw76"></legend></form>
    2. 聊一聊JVM

      JVM

      什么是JVM?

      ? JVM是java虛擬機的縮寫,本質上是一個程序,能識別.class字節碼文件(.java文件編譯后產生的二進制代碼),并且能夠解析它的指令,最終調用操作系統上的函數,完成我們想要的操作。

      ? 關于java語言的跨平臺性(一次編譯,多次運行),就是應為JVM,可以把它想象出一個抽象層,運行在操作系統之上的,與硬件沒有直接的交互,只要這個抽象層JVM正確執行了.class文件,就能運行在各種操作系統之上了。

      ? 介紹幾個術語:

      • JDK:java開發工具包,JDK=JRE+javac/java/jar等指令工具
      • JRE:java運行環境,JRE=JVM+java基本類庫

      JVM體系結構

      ? java虛擬機主要分為五大模塊:

      • 類加載器
      • 運行時數據區
      • 執行引擎
      • 本地方法接口
      • 垃圾收集模塊

      img

      ? 方法區是一種特殊的堆,棧里面不回有垃圾,用完就彈出了,否則阻塞了main方法。垃圾幾乎都在堆里,所以JVM性能調優%99都針對與堆。

      ? 目前最常用的JVM是Sun公司的HotSpot,此外還有BEA公司的JRockit和IBM公司的J9 VM。

      類加載器

      ? 作用:加載.class字節碼文件。

      new一個對象的過程

      //運行時,JVM將Test的信息放入方法區
      public class Test{
        public static void main(String[] args){
          Student s1 = new Student("Tom");//引用放在棧里,具體的實例放在堆里
          Student s2 = new Student("Jerry");
          Student s3 = new Student("Victor");
          //三個hashCode是不同的,因為是三個不同的對象,對象是具體的
          System.out.println(s1.hashCode());
          System.out.println(s2.hashCode());
          System.out.println(s3.hashCode());
          //class1,class2,class3為同一個對象,因為這是類模版,模版是抽象的
          Class<? extends Stedent> class1 = s1.getClass();
          Class<? extends Stedent> class2 = s2.getClass();
          Class<? extends Stedent> class3 = s3.getClass();
          System.out.println(class1.hashCode());
          System.out.println(class2.hashCode());
          System.out.println(class3.hashCode());
        }
      }
      
      1. 首先Class Loader讀取字節碼文件,加載初始化生成Student模版類。
      2. 通過Student模版類new出三個對象。

      類加載器的類別

      public class Test{
        public static void main(String[] args){
          Student s = new Student("Tom");
          Class<? extends Student> c = s.getClass();
          ClassLoader classLoader = c.getClassLoader();
          System.out.println(classLoader);//APPClassLoader
          System.out.println(classLoader.getParent());//PlatformClassLoader
          System.out.println(classzLader.getParent().getParent());//null,獲取不到(C++寫的)
        }
      }
      

      ? 根據返回結果,級別從高到低有三種加載器:

      1. 啟動類(根)加載器:BootStrapClassLoader。
        • c++編寫的,加載java核心庫,構造拓展類加載器和應用程序加載器
        • 根加載器加載拓展類加載器,并且將拓展類加載器的父加載器設置為根加載器
        • 然后在加載應用程序加載器,應將應用程序的加載器的父加載器設置為拓展類加載器
        • 由于根加載器涉及到虛擬機本地實現的細節,我們無法直接獲取到啟動類加載器的引用,這就是上面第三個結果為null的原因
        • 加載文件存在于/jdk/jdk1.8/jre/lib/rt.jar
      2. 拓展類加載器:PlatformClassLoader
        • java編寫,加載擴展庫,開發者可以直接使用標準擴展類加載器
        • java9之前稱為ExtClassLoader
        • 加載文件存在于.../lib/ext
      3. 應用程序加載器:AppClassLoader
        • Java編寫,加載程序所在的目錄,是java默認的類加載器
      4. 用戶自定義加載器:CustomeClassLoader
        • java編寫,用戶自定義的類加載器,可加載指定路徑的class文件

      ? 實際上,這些加載器的區別就是加載不同范圍或不同路徑的.class文件。

      雙親委派機制

      ? 雙親委派機制是類加載器收到類加載的請求,會將這個請求向上委托給父類加載器去完成,一直向上委托,直到根加載器BootStrapClassLoader。根加載器檢查是否能夠加載當前類,能加載就結束,使用當前類加載器,否則就拋出異常,通知子加載器進行加載。

      ? 舉個例子,我們重寫java.lang包下的String類:

      package java.lang;
      public class String{
        public String toString(){
          return "xing";
        }
        public static void main(String[] args){
          new String().toString;
        }
      }
      
      //Error:(1,1) java:程序包已存在于另一個模塊中:java:base
      

      ? 我們會發現報錯,這就是雙親委派機制起的作用,當類加載器委托到根加載器的時候,String類已經被根加載器加載過一遍了,所以不會再加載,從一定程度上防止了危險代碼的植入。

      作用總結:

      1. 防止重復加載同一個.class,通過不斷委托父加載器直到根加載器,如果父加載器加載過了,就不用再加載一遍,保證數據安全。
      2. 保證系統核心.class不被篡改。通過委托方式,不會去篡改核心.class,即使篡改也不會去加載,即使加載也不會是同一個.class對象了。不同的加載器加載同一個.class也不是同一個class對象,這樣保證了class執行安全。

      沙箱安全機制

      什么是沙箱

      ? java安全模型的核心就是java沙箱(sandbox)。

      ? 沙箱是一個限制程序運行的環境。沙箱機制就是將java代碼限定在虛擬機特定的運行范圍中,并且嚴格限制代碼對本地系統資源的訪問,通過這樣的措施來保證對代碼的有效隔離,防止對本地系統的破壞。

      ? 沙箱主要限制系統資源訪問,包括CPU、內存、文件系統、網絡。不同級別的沙箱對這些資源訪問的限制也不一樣。

      ? 所有的java程序運行都可以指定沙箱,可以定制安全策略。

      java中安全模型的演進

      ? 在java中將執行程序分為本地代碼和遠程代碼兩種:本地代碼可信任,可以訪問一起本地資源。遠程代碼不可信任,在早期的java實現中,安全依賴于沙箱機制。

      ? 如此嚴格的安全機制也給程序的功能擴展帶來障礙,比如當用戶希望遠程代碼訪問本地系統文件的時候,就無法實現。因此在后續的java1.1中,針對安全機制做了改進,增加了安全策略,允許用戶指定代碼對本地資源的訪問權限。

      ? 在java1.2版本中,再次改進了安全機制,增加了代碼簽名。不論本地代碼或者遠程代碼,都會按照用戶的安全策略設定,由類加載器加載到虛擬機中權限不同的運行空間,來實現差異化的代碼執行權限控制。

      ? 當前最新的安全機制實現,則引入了域(Domain)的概念。虛擬機會把所有的代碼加載到不同的系統域和應用域,系統域部分專門負責與關鍵資源進行交互,應用域部分則通過系統域的部分代理來對各種需要的資源進行訪問。虛擬機中不同的受保護域對應不一樣的權限,存在于不同域中的類文件就具有了當前域的全部權限。

      組成沙箱的基本組件

      1. 字節碼校驗器(bytecode verifier)

        確保java類文件遵循java語言規范。這樣可以幫助java程序實現內存保護。但并不是所有的類文件都會經過字節碼校驗,比如核心類。

      2. 類裝載器(class loader)

        類裝載器在3個方面對java沙箱起作用

        • 防止惡意代碼去干涉善意的代碼
        • 守護了被信任的類庫邊界
        • 將代碼歸入保護域,確定了代碼可以進行哪些操作。

        虛擬機為不同的類加載器載入的類提供不同的命名空間,命名空間由一系列唯一的名稱組成,每一個被裝載的類將有一個名字,這個命名空間是由java虛擬機為每一個類裝載器維護的,他們互相之間甚至不可見。

      3. 存取控制器(access controller):存取控制器可以控制核心API對操作系統的存取權限,而這個控制的策略設定可以由用戶指定。

      4. 安全管理器(security manager):是核心API和操作系統之間的主要接口。實現權限控制,比如存取控制器優先級高。

      5. 安全軟件包(security package):java.security下的類和擴展包下的類,允許用戶為自己的應用增加新的安全特性,包括安全提供者、消息摘要、數字簽名、加密、鑒別。

      Native本地方法接口

      ? JNI:java native interface

      ? 本地接口的作用是融合不同的編程語言為java所用,它的初衷是融合C/C++程序。

      ? 凡是帶native關鍵字的,就說明java的作用范圍達不到了,會去調用底層c語言庫,進入本地方法棧,調用本地方法接口JNI,拓展java的使用,融合不同的語言為java所用。

      ? java誕生的時候C/C++橫行,為了立足,必須要能夠調用C/C++程序,于是在內存區域中專門開辟了一塊標記區域:Native Method Stack,登記Native方法,最終在執行引擎上執行的時候通過JNI加載本地方法庫中的方法。目前該方法的使用越來越少了,除非是與硬件有關的應用,比如通過java程序驅動打印機或者java系統管理生產設備,在企業級應用中已經比較少見。因為現在的異構領域間通信很發達,比如可以用Socket通信,也可以使用Web Service等。

      運行時數據區

      PC寄存器(Program Counter Register)

      ? 每個線程都有一個程序計數器,是線程私有的,就是一個指針,指向方法區中的方法字節碼(用來存儲指向像一條指令的地址,也即將要執行的指令代碼),在執行引擎讀取下一條指令,是一個非常小的內存空間,幾乎可以忽略不計。

      方法區(Method Area)

      ? 方法區與java堆一樣,是各個線程共享的內存區域,用于存儲已被虛擬機加載的類信息、常量、靜態變量、即時編譯器遍以后的代碼等數據。雖然java虛擬機規范把方法區描述為堆的一個邏輯部分,但是它卻有一個別名Non-Heap,因此實際上應該和堆區分開。

      方法區中有啥?

      • 靜態變量(static)
      • 常量(final)
      • 類信息(構造方法,接口定義)
      • 運行時的常量池

      創建對象內存分析

      public class Person{
        int age;
        String name = "xing";
        public Person(int age, String name){
          this.age = age;
          this.name = name;
        }
        
        public static void main(String[] agrs){
          Person s1 = new Person(18,"Tom");
        }
      }
      /*
      創建一個對象時,方法區中會生成對應類的抽象模版;還有對應的常量池、靜態變量、類信息、常量。
      我們通過類模版去new對象的時候,堆中存放實例對象,棧中存放對象的引用,每個對象對應一個地址指向堆中相同地址的實例對象。
      */
      
      

      ? 主管程序的運行,生命周期和線程同步。線程結束,棧內存就釋放了,不存在垃圾回收。棧中存放8大基本類型,對象引用,實例的方法。

      棧運行的原理

      ? 棧表示java方法執行的內存模型,每調用一個方法就會為每個方法生成一個棧幀(Stack Frame),每個方法被調用的完成的過程,都對應一個棧幀從虛擬機棧上入棧和出棧的過程。程序正在執行的方法一定在棧的頂部。

      堆棧溢出(StackOverflowError)

      public class Test{
        public static void main(String[] args){
          new Test().a();
        }
        
        public void a(){
          b();
        }
        public void b(){
          a();
        }
      }
      
      //最開始,main()方法壓入棧中,然后執行a(),a()押入棧中,在調用b(),b()押入棧棧中,以此往復,最終導致棧溢出
      

      ? 一個JVM只有一個堆內存(棧是線程級的),堆內存的大小是可以調節的,堆中存放實例化的對象。

      堆內存詳解

      1. 年輕代

        對象的誕生、成長甚至死亡的區

        • Eden Space(伊甸園區):所有對象都是在此new出來的
        • Survivor Space(幸存區)
          • 幸存0區(From Space),動態的From和To會互相交換
          • 幸存1區(To Space)

        Eden區占大容量,Survivor兩個區占小容量,默認比例是8:1:1。

      2. 老年代

      3. Perm元空間

        存儲的是java運行時的一些環境或類信息,這個區域不存在垃圾回收。關閉虛擬機就會釋放這個區域的內存,這個區域常駐內存,用來存放JDK自身攜帶的Class對象、Interface元數據。jdk1.8之前被稱為永久代。

        注意:元空間在邏輯上存在,在物理上不存在。新生代+老年代的內存空間=JVM分配的總內存。

      什么是OOM

      ? 內存溢出,產生原因:

      • 分配的太少
      • 用的太多
      • 用完沒釋放

      GC垃圾回收

      ? 主要在年輕代和老年代。

      ? 首先對象出生在伊甸園區,假設伊甸園區只能存在一定數量的對象,則每當存滿時就會出發一次輕GC(Minor GC)。輕GC清理后,有的對象可能還存在引用,就活下來了,活下來的對象就進入幸存區;有的對象沒用了,就被GC清理掉了;每次輕GC都會使得伊甸園區為空。

      ? 如果幸存區和伊甸園區都滿了,則會進入老年代,如果老年代滿了,就會出發一次重GC(FullGC),年輕代+老年代的對象都會清理一次,活下來的對象都進入老年代。

      ? 如果新生代和老年代都滿了,則OOM。

      • Minor GC:伊甸園區滿時觸發,從年輕代回收內存
      • Full GC:老年代滿時觸發,清理整個堆空間
      • Major GC:清理老年代

      ? 什么情況下永久區會崩?一個啟動類加載了大量的第三方jar包,Tomcat部署了過多應用,或者大量動態生成的反射類,這些東西不斷的被加載,知道內存滿,就會出現OOM。

      堆內存調優

      查看并設置JVM堆內存

      public class Test{
        public static void main(String[] args){
          //返回jvm試圖使用的最大內存
          long max = Runtime.getRuntime().maxMemory();
          //返回jvm的初始化內存
          long total = Runtime.getRuntime().totalMemory();
          //默認情況下:分配的總內存為電腦內存的1/4,初始化內存為電腦內存的1/64
          System.out.println("max=" + max / (double) 1024 / 1024 / 1024 + "G");
          System.out.println("total=" + total / (double) 1024 / 1024 / 1024 + "G");
        }
      }
      

      ? 我們可以手動調整堆內存的大小,在VM options 中可以指定jvm試圖使用的最大內存和jvm初始化內存的大小。

      -Xms1024m -Xmx1024m -Xlog:gc*
      
      • -Xms用來設置jvm試圖使用的最大內存
      • -Xmx用來設置jvm初始化內存
      • -Xlog:gc*用來打印GC垃圾回收信息

      怎么排除OOM錯誤?

      1. 嘗試擴大堆內存看結果

      2. 利用內存快照工具JProfiler

        作用:分析Dump內存文件,快速定位內存泄漏;獲得堆中的文件;獲得大的對象

        Dump文件是進程的內存鏡像,可以把程序的執行狀態通過調試器保存到dump文件中

        import java.util.ArrayList;
        
        public class Test{
          byte[] array = new byte[1024*1024];//1M
          public static void main(String[] args){
            ArrayList<Test> list = new ArrayList<>();
            int count = 0;
            try{
              while(true){
                list.add(new Test());
                count++;
              }
            }catch(Exception e){
              System.out.println("count="+count);
              e.printStackTrace();
            }
          }
        }
        

        運行程序,報錯OOM。

        接下來設置一下堆內存并附加生成dump文件的指令

        -Xms1m -Xmx8m -XX:+HeapDumpOnOutOfMemoryError
        

        -XX:+HeapDumpOnOutOfMemoryError表示當JVM發生OOM時,自動生成DUMP文件。再次點擊運行,下載了對應的Dump文件。

        分析步驟:

        • 右鍵該類,點擊Show in Explorer
        • 一直點擊上級目錄,直到找到.hprof文件

        每次打開dump文件查看完后,建議刪除,打開文件后生成了很多內容,占內存。

      GC垃圾回收

      ? 之前已經堆GC垃圾回收流程進行了大概的講解:JVM在進行GC時,大部分回收都是在年輕代。

      GC算法

      1. 引用計數法(很少使用)

        • 每個對象在創建的時候,就給這個對象綁定一個計數器。
        • 每當有一個引用指向該對象時,計數器加一;每當有一個指向它的引用被刪除時,計數器減一;
        • 這樣,當沒有引用指向該對象時,該對象死亡,計數器為0,這時就應該對這個對象進行垃圾回收操作。
      2. 復制算法

        復制算法主要發生在年輕代(幸存0區和幸存1區)

        • 當Eden區滿的時候,會觸發輕GC,每觸發一次,活的對象就被轉移到幸存區,死的對象就被GC清理掉,所以每次觸發輕GC時,Eden區就會清空
        • 對象被轉移到了幸存區,幸存區又分為From SpaceTo Space,這兩塊區域是動態交換的,誰是空的誰就是To Space,然后From Space就會把全部對象轉移到To Space去;
        • 那如果兩塊區域都不為空呢?這就用到了復制算法,其中一個區域會將存活的對象轉移到另一個區域去,然后將自己區域的內存空間清空,這樣該區域為空,又成為了To Space
        • 所以每次觸發輕GC后,Eden區清空,同時To區也清空了,所有的對象都在From區

        好處:沒有內存碎片

        壞處:浪費內存空間(浪費幸存區一半的空間);對象存活率較高的場景下,需要復制的東西太多,效率會下降。

        最佳使用環境:對象存活率較低的時候,也就是年輕代。

      3. 標記-清除算法

        為每個對象存儲一個標記位,記錄對象的生存狀態。

        • 標記階段:這個階段內,為每個對象更新標記位,檢查對象是否死亡。
        • 清除階段:該階段對死亡的對象進行清除,執行GC操作。

        缺點:兩次掃描嚴重浪費時間;會產生內存碎片

        優點:不需要額外的空間

      4. 標記-整理算法

        這個是標記-清除算法的一個改進版,又叫做標記-清除-壓縮算法。不同的是在第二個階段,該算法并沒有直接對死亡的對象進行清理,而是將所有存貨的對象整理一下,放到另一處空間,然后把剩下的所有對象全部清除。可以進一步優化,在內存碎片不太多的情況下,就繼續標記清除,到達一定量的時候再壓縮。

      有沒有最優的算法?

      ? 沒有最優算法,只有最合適的。

      ? GC也稱為分代收集算法,對于年輕代,對象存活率低用復制算法;對于老年代,區域大,對象存活率高,用標記清除+標記壓縮混合實現。

      posted @ 2021-03-01 17:26  尹瑞星  閱讀(258)  評論(0編輯  收藏
      最新chease0ldman老人|无码亚洲人妻下载|大香蕉在线看好吊妞视频这里有精品www|亚洲色情综合网

        <sub id="gqw76"><listing id="gqw76"></listing></sub>
        <sub id="gqw76"><listing id="gqw76"></listing></sub>

      1. <form id="gqw76"><legend id="gqw76"></legend></form>