<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      JVM理解

      1、JVM的基本介紹

      JVM,即 Java Virtual Machine ,是Java 程序的運行環境(Java 二進制字節碼的運行環境)。

      JVM的作用:

      • 一次編寫,到處運行
      • 自動內存管理,垃圾回收功能
      • 數組下標越界檢查
      • 多態

       

      1.1、JVM、JRE、JDK三者的比較

      JVM、JRE、JDK 的關系如下圖所示。

      • JDK(Java Development Kit):java開發工具包,在JRE的基礎上增加編譯工具,如javac
      • JRE(Java Runtime Environment):java的運行時環境,在JVM的基礎上結合一些基礎類庫
      • JVM:java虛擬機, 可以屏蔽java代碼與底層虛擬機之間的關系

       

      1.2、常見的JVM

       

      1.3、JVM的整體架構

       

      2、程序計數器

      Program Counter Register 程序計數器(寄存器),作用是記住下一條jvm指令的執行地址。
      在物理上實現程序計數器是通過一個叫寄存器來實現的,寄存器可以說是整個CPU組件里讀取速度最快的一個單元,因為讀取/寫指令地址這個動作是非常頻繁的。所以Java虛擬機在設計的時候就把CPU中的寄存器當做了程序計數器,用他來存儲地址,將來去讀取這個地址。
      程序計數器的特點:
      • 是線程私有的。每個線程都有自己的程序計數器,隨著線程創建而創建,隨線程銷毀而銷毀
      • 不會存在內存溢出

       

      3、虛擬機棧(線程內存)

       

      3.1、虛擬機棧基本介紹

      Java Virtual Machine Stacks (Java 虛擬機棧),每個線程運行時所需要的內存,稱為虛擬機棧

      每個棧由多個棧幀(Frame)組成,對應著該線程內各個方法調用時所占用的內存,即線程內每個方法的調用都會創建一個新的棧幀(Stack Frame。每個線程只能有一個活動棧幀,對應著當前正在執行的那個方法。

       

      垃圾回收是否涉及棧內存?
      • 不會。棧幀內存 在每次方法調用結束后會自動彈出棧(自動回收),不需要回收(垃圾回收回收堆內存中無用對象,不會回收棧內存)
       
      棧內存分配越大越好嗎?
      • 不是。因為服務器中物理內存是固定大小的,單個棧內存大了,可創建的線程數就少了。雖然棧內可進行更多次方法調用,但由于線程數減少,所以并不會提高效率。
      • 可以通過 -Xss 參數來設置棧內存大小,JDK1.5+ 中默認是 1M,一般來說使用默認值即可
       
      如何判斷方法內的局部變量是否線程安全? 
      • 如果方法內的局部變量沒有逃離方法的作用范圍,那么它是線程安全的
      • 如果是局部變量引用了對象,并逃離了方法的作用范圍,需要考慮線程安全

      如下:

      package JVM;
      
      public class Demo01 {
      
          public static void main(String[] args) {
              StringBuilder sb = new StringBuilder();
              sb.append(4);
              sb.append(5);
              sb.append(6);
          }
      
      
          /**
           * 不會有線程安全問題。因為StringBuilder是線程內局部變量,屬于線程私有,其他線程無法訪問
           */
          public static void m1() {
              StringBuilder stringBuilder = new StringBuilder();
              stringBuilder.append(1);
              stringBuilder.append(2);
              stringBuilder.append(3);
              System.out.println(stringBuilder.toString());
          }
      
          /**
           * 不是線程安全的。StringBuilder作為參數傳入,StringBuilder可能被其他線程共享,不是線程安全
           */
          public static void m2(StringBuilder stringBuilder) {
              stringBuilder.append(1);
              stringBuilder.append(2);
              stringBuilder.append(3);
              System.out.println(stringBuilder.toString());
          }
      
          /**
           * 不是線程安全的。雖然StringBuilder是作為局部變量,但是返回結果為StringBuilder,可能被其他線程修改
           */
          public static StringBuilder m2() {
              StringBuilder stringBuilder = new StringBuilder();
              stringBuilder.append(1);
              stringBuilder.append(2);
              stringBuilder.append(3);
              return stringBuilder;
          }
      }

       

      3.1.1、棧幀代碼演示

      代碼如下:

      /**
       * 演示棧幀
       */
      public class Demo1_1 {
          public static void main(String[] args) throws InterruptedException {
              method1();
          }
      
          private static void method1() {
              method2(1, 2);
          }
      
          private static int method2(int a, int b) {
              int c =  a + b;
              return c;
          }
      }

      開啟 debug 模式,執行 main 主方法,當調試執行到 method2 方法時,可以看到創建了三個棧幀。當方法 main、method1、method2 執行結束后,棧幀依次被銷毀。

       

      3.2、棧內存溢出(StackOverflowError)

      • 棧幀過多導致棧內存溢出。比如遞歸調用方法未正確結束遞歸
      • 棧幀過大導致棧內存溢出。

      如下分別為棧幀過多和棧幀多大的示例圖:

                   

      代碼示例,如下是演示棧幀過多導致棧內存溢出的情況:

      package cn.itcast.jvm.t1.stack;
      
      /**
       * 演示棧內存溢出   報錯信息:java.lang.StackOverflowError
       * 可以通過設置 JVM 參數來設置棧內存,如:-Xss256k
       */
      public class Demo1_2 {
          private static int count;
      
          public static void main(String[] args) {
              try {
                  method1();
              } catch (Throwable e) {
                  e.printStackTrace();
                  System.out.println(count);
              }
          }
      
          private static void method1() {
              count++;
              method1();
          }
      }

      執行以上 main 方法,可以看到報錯如下:

       

      3.3、線程運行診斷

      3.3.1、CPU占用過高

      通過跑一段無限循環代碼來使系統的 CPU 不斷飆升,演示如何通過命令來診斷出導致 CPU 過高的線程。

      代碼示例:

      package cn.itcast.jvm.t1.stack;
      
      /**
       * 演示 cpu 占用過高
       */
      public class Demo1_16 {
      
          public static void main(String[] args) {
              new Thread(null, () -> {
                  System.out.println("1...");
                  while(true) {
      
                  }
              }, "thread1").start();
      
      
              new Thread(null, () -> {
                  System.out.println("2...");
                  try {
                      Thread.sleep(1000000L);
                  } catch (InterruptedException e) {
                      e.printStackTrace();
                  }
              }, "thread2").start();
      
              new Thread(null, () -> {
                  System.out.println("3...");
                  try {
                      Thread.sleep(1000000L);
                  } catch (InterruptedException e) {
                      e.printStackTrace();
                  }
              }, "thread3").start();
          }
      }

      代碼編譯后,傳入 Linux 系統中,通過 java cn.itcast.jvm.t1.stack.Demo1_16 命令來運行該段程序。

      然后通過 TOP 命令可以定位哪個進程對cpu的占用過高,如下:

       

      通過 ps H -eo pid,tid,%cpu | grep 進程id  命令進一步定位是哪個線程引起的cpu占用過高,如下:

      (注意,左邊是進程id,右邊是線程id)

      最后通過 jstack 進程id  命令(jstack命令可以生成JVM當前時刻的線程快照。)打印出 JVM 當前線程,然后根據上面找到的線程 id 進一步定位到該線程內問題代碼的源碼行號。
      注意,通過 jstack 進程打印出的線程顯示的 id 是十六進制的,所以需要將上面占用 CPU 過高的線程 id 26384 轉換為十六進制,即 6710。 

      如上找到 CPU 占用過高的線程,并且可以定位到具體的代碼類名和行數。

       

      3.3.2、程序阻塞運行很久沒有結果

      如下,通過一段代碼演示程序發生線程死鎖。

      代碼如下:

      package cn.itcast.jvm.t1.stack;
      
      /**
       * 演示線程死鎖
       */
      class A{};
      class B{};
      public class Demo1_3 {
          static A a = new A();
          static B b = new B();
      
      
          public static void main(String[] args) throws InterruptedException {
              new Thread(()->{
                  synchronized (a) {
                      try {
                          Thread.sleep(2000);
                      } catch (InterruptedException e) {
                          e.printStackTrace();
                      }
                      synchronized (b) {
                          System.out.println("show a and b");
                      }
                  }
              }).start();
              Thread.sleep(1000);
              new Thread(()->{
                  synchronized (b) {
                      synchronized (a) {
                          System.out.println("show a and b 222");
                      }
                  }
              }).start();
          }
      
      }

      將該代碼放置到 Linux 環境上執行,可以看到很久都沒有輸出結果。

       

      當我們通過 jstack 命令來查看該進程的線程時,可以發現已經發生了死鎖。

       

      4、本地方法棧

      在 java 虛擬機調用一些本地方法時需要給本地方法提供的內存空間。

      • 本地方法:由于java有限制,不可以直接與操作系統底層交互,所以需要一些用c/c++編寫的本地方法與操作系統底層的API交互,java可以間接的通過本地方法來調用底層功能。本地方法是由其它語言編寫的,編譯成和處理器相關的機器代碼。本地方法保存在動態鏈接庫中,即.dll(windows系統)文件中,格式是各個平臺專有的。

      舉例:Object的clone()、hashCode()、notify()、notifyAll()、wait()等,一個Native Method就是一個java調用非java代碼的接口。

       

      5、堆內存(Heap,線程共享)

      5.1、堆內存的基本介紹(新生代、老年代、永久代)

      Java 中的堆是 JVM 所管理的最大的一塊內存空間,主要用于存放各種類的實例對象。通過 new 關鍵字,創建對象都會使用堆內存。

      特點:

      • 它是線程共享的,堆中對象都需要考慮線程安全的問題。堆跟根程序計數器和虛擬機棧不同的是,后兩者都是線程私有的,而堆是線程同享的
      • 有垃圾回收機制。當一個對象不再被使用時,該對象就會被垃圾回收機制回收,即該對象內存會被垃圾回收掉。

       

      堆內存區域介紹:

       

      在jvm的堆內存中有三個區域:

      1. 年輕代:用于存放新產生的對象。
      2. 老年代:用于存放被長期引用的對象。
      3. 持久帶(或元空間):用于存放Class,method元信息(1.8之后改為元空間)。

      詳細介紹如下:

      年輕代:年輕代中包含兩個區:Eden 和survivor,并且用于存儲新產生的對象,其中有兩個survivor區。

      老年代:年輕代在垃圾回收多次都沒有被GC回收的時候就會被放到老年代,以及一些大的對象(比如緩存,這里的緩存是弱引用),這些大對象可以不進入年輕代就直接進入老年代

      持久代:持久代用來存儲class,method元信息,大小配置和項目規模,類和方法的數量有關。

      元空間:JDK1.8之后,取消perm永久代,轉而用元空間代替。元空間的本質和永久代類似,都是對JVM規范中方法區的實現。不過元空間與永久代之間最大的區別在于元空間并不在虛擬機中,而是使用本地內存,并且可以動態擴容。

       

      為什么分代?

      因為不同對象的生命周期是不一樣的。80%-98%的對象都是“朝生夕死”,生命周期很短,大部分新對象都在年輕代,可以很高效地進行回收,不用遍歷所有對象。而老年代對象生命周期一般很長,每次可能只回收一小部分內存,回收效率很低。

      年輕代和老年代的內存回收算法完全不同,因為年輕代存活的對象很少,標記清楚再壓縮的效率很低,所以采用復制算法將存活對象移到survivor區,更高效。而老年代則相反,存活對象的變動很少,所以采用標記清楚壓縮算法更合適。

      5.2、堆內存溢出

      堆內存溢出模擬代碼:

      package cn.itcast.jvm.t1.heap;
      
      import java.util.ArrayList;
      import java.util.List;
      
      /**
       * 演示堆內存溢出 java.lang.OutOfMemoryError: Java heap space
       * 可以通過配置JVM參數:-Xmx8m 來設置最大堆內存
       */
      public class Demo1_5 {
      
          public static void main(String[] args) {
              int i = 0;
              try {
                  List<String> list = new ArrayList<>();
                  String a = "hello";
                  while (true) {
                      list.add(a); // hello, hellohello, hellohellohellohello ...
                      a = a + a;  // hellohellohellohello
                      i++;
                  }
              } catch (Throwable e) {
                  e.printStackTrace();
                  System.out.println(i);
              }
          }
      }

       

      6、方法區(Method Area)

      方法區(Method Area)與Java堆一樣,是各個線程共享的內存區域。方法區存儲類的結構的相關信息,如運行時常量池、成員變量、方法數據、成員方法和構造器的代碼等。

      方法區在虛擬機啟動時創建,其邏輯上是堆的一個組成部分,但在實現時不同的JVM廠商可能會有不同的實現。方法區的大小,跟堆空間一樣,可以選擇固定大小或者可擴展。

      組成如下:以Oracle的HotSpot為例

      • jdk1.6:永久代(PermGen space),占用JVM內存空間

       

      •  jdk1.8:元空間(Metaspace),移出JVM內存(除StringTable),放入操作系統內存

       

      6.1、方法區內存溢出

      通過不斷創建類來演示產生方法區內存溢出,如下:

      package cn.itcast.jvm.t1.metaspace;
      
      import jdk.internal.org.objectweb.asm.ClassWriter;
      import jdk.internal.org.objectweb.asm.Opcodes;
      
      /**
       * 元空間內存溢出 java.lang.OutOfMemoryError: Metaspace
       * 設置元空間大小:-XX:MaxMetaspaceSize=8m
      
       * 永久代內存溢出  java.lang.OutOfMemoryError: PermGen space
       * 設置永久代內存大小:-XX:MaxPermSize=8m
       */
      public class Demo1_8 extends ClassLoader { // 可以用來加載類的二進制字節碼
          public static void main(String[] args) {
              int j = 0;
              try {
                  Demo1_8 test = new Demo1_8();
                  for (int i = 0; i < 10000; i++, j++) {
                      // ClassWriter 作用是生成類的二進制字節碼
                      ClassWriter cw = new ClassWriter(0);
                      // 版本號, public, 類名, 包名, 父類, 接口
                      cw.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, "Class" + i, null, "java/lang/Object", null);
                      // 返回 byte[]
                      byte[] code = cw.toByteArray();
                      // 執行了類的加載
                      test.defineClass("Class" + i, code, 0, code.length); // Class 對象
                  }
              } finally {
                  System.out.println(j);
              }
          }
      }

      當使用 jdk1.8 及之后的版本時,內存溢出報錯提示:java.lang.OutOfMemoryError: Metaspace。當使用 jdk1.8 之前的版本時,內存溢出報錯提示:java.lang.OutOfMemoryError: PermGen space

      (默認的元空間內存大小為操作系統的內存大小,可能沒那么容易產生內存溢出,可以通過設置 jvm 參數限制元空間內存大小來演示內存溢出現象)

       

      posted @ 2022-08-11 21:05  wenxuehai  閱讀(186)  評論(0)    收藏  舉報
      //右下角添加目錄
      主站蜘蛛池模板: 午夜在线不卡| 久久精品99国产精品日本| 97免费人妻在线视频| 亚洲综合国产一区二区三区| 亚洲高清WWW色好看美女| 777奇米四色成人影视色区| 午夜福利国产精品视频| 久久精品第九区免费观看| 久久精品国产99久久美女| 男女性高爱潮免费网站| 亚洲欧美日韩成人综合一区| 国产在线欧美日韩精品一区| 深夜福利资源在线观看| 超碰成人人人做人人爽| 国产成人精品午夜二三区| 午夜国产精品福利一二| 在线看片免费人成视频久网| 国产亚洲天堂另类综合| 日韩av裸体在线播放| 成人国产亚洲精品一区二区| 国产成人精品1024免费下载| 韩国三级在线 中文字幕 无码| 精品久久久久久久中文字幕| 日韩国产成人精品视频| 亚洲av成人无码天堂| 亚洲中文字幕av无码区| 国产精品久久久国产盗摄| 国产精品熟女一区二区不卡| 少妇xxxxx性开放| 麻豆亚洲精品一区二区| 国产精品毛片在线看不卡| 久久中文字幕无码一区二区| 91久久夜色精品国产网站| 好紧好湿太硬了我太爽了视频| 国产成人精品无人区一区| 丁香花成人电影| 日韩老熟女av搜索结果| 久国产精品韩国三级视频| 高潮潮喷奶水飞溅视频无码| 国产无遮挡又黄又大又爽| 国产精品美女一区二区三|