jvm原理解析--不瘋魔不成活

java程序運行

*.java文件-->編譯器-->*.class文件-->線程啓動(main)-->jvm-->操做系統-->硬件java

 

經過上面的流程咱們能夠看出java程序的執行順序,那麼jvm究竟是什麼,class文件究竟是如何在jvm中運行就顯得很重要了。linux

jvm原理

什麼是jvm

openjdk源碼地址http://hg.openjdk.java.net/jdk9算法

JVM是一個計算機模型,JVM對Java可執行代碼,即字節碼(Bytecode)的格式給出了明確的規格。這一規格包括操做碼操做數的語法(也就是cpu指令集)和數值、標識符的數值表示方式、以及Java類文件中的Java對象、常量緩衝池在JVM的存儲映象。windows

JVM的組成

JVM指令系統、JVM寄存器、JVM 棧結構、JVM 碎片回收堆、JVM 存儲區架構

JVM指令

Java指令也是由操做碼和操做數兩部分組成,與RISC CPU採用的編碼方式是一致的,也就是精簡指令集,目前UNIX、Linux、MacOS系統使用RISC,咱們目前知道的x86架構CPU使用的CISC編碼,也就是複雜指令集。app

JVM寄存器

1.pc程序計數器框架

2.optop操做數棧頂指針dom

3.frame當前執行環境指針jvm

4.vars指向當前執行環境中第一個局部變量指針佈局

 

jvm的裝載

windows操做系統裝入JVM是經過jdk中Java.exe來完成,經過下面4步來完成JVM環境。

1.建立JVM裝載環境和配置

2.裝載JVM.dll(C:\Program Files\Java\jre1.8.0_151\bin\server linux在jre/lib/server下)

3.初始化JVM.dll並掛接到JNIENV(JNI調用接口)實例

4.調用JNIEnv實例裝載並處理class類

JVM虛擬機至關於x86計算機系統,Java解釋器至關於x86CPU

JVM運行數據

JVM定義了若干個程序執行期間使用的數據區域。這個區域裏的一些數據在JVM啓動的時候建立,在JVM退出的時候銷燬。而其餘的數據依賴於每個線程,在線程建立時建立,在線程退出時銷燬。分別有程序計數器,堆,棧,方法區,運行時常量池

  • 程序計數器:每一個線程一旦被建立就擁有了本身的程序計數器。當線程執行Java方法的時候,它包含該線程正在被執行的指令的地址。可是若線程執行的是一個本地的方法,那麼程序計數器的值就不會被定義。
  • 常量緩衝池和方法區:常量緩衝池用於存儲類名稱、方法和字段名稱以及串常量。方法區則用於存儲Java方法的字節碼。對於這兩種存儲區域具體實現方式在JVM規格中沒有明確規定。這使得Java應用程序的存儲佈局必須在運行過程當中肯定,依賴於具體平臺的實現方式。
  • 棧:Java棧是JVM存儲信息的主要方法。當JVM獲得一個Java字節碼應用程序後,便爲該代碼中一個類的每個方法建立一個棧框架,以保存該方法的狀態信息。每一個棧框架包括如下三類信息:

    局部變量對應vars寄存器指向該變量表中的第一個局部變量用於存儲一個類的方法中所用到的局部變量。

    執行環境:對應frame寄存器的當前執行環境指針用於保存解釋器對Java字節碼進行解釋過程當中所需的信息。它們是:上次調用的方法、局部變量指針和操做數棧的棧頂和棧底指針。執行環境是一個執行一個方法的控制中心。例如:若是解釋器要執行iadd(整數加法),首先要從frame寄存器中找到當前執行環境,然後便從執行環境中找到操做數棧,從棧頂彈出兩個整數進行加法運算,最後將結果壓入棧頂。

    操做數棧:對應optop寄存器的操做數棧頂指針,操做數棧用於存儲運算所需操做數及運算的結果。

  • 堆:JVM中最大的,應用的對象和數據都是存在這個區域,這塊區域也是線程共享的,也是 gc 主要的回收區,一個 JVM 實例只存在一個堆類存,堆內存的大小是能夠調節的。類加載器讀取了類文件後,須要把類、方法、常變量放到堆內存中,以方便執行器執行。

       垃圾回收機制(GC)只發生在線程共享區,也就是堆和方法區,棧不須要回收,線程銷燬則棧也銷燬,         也就是上圖的heap space與method area會發生gc。

        經過上圖能夠發現heap space被分爲兩部分:

  • Young Generation:又分爲Eden space全部的類都是在Eden space被new出來的。From區(Survivor 0 space)和To區(Survivor 1 space)。當Eden space空間用完時,程序又須要建立對象,JVM的垃圾回收器將對Eden space進行垃圾回收(Minor GC),將Eden space中的剩餘對象移動到From區。若From區也滿了,再對該區進行垃圾回收,而後移動到To區。那若是To區也滿了呢,再移動到Old區。
  • Old Generation:若該區也滿了,那麼這個時候將產生Major GC(FullGCC),進行Tenured區的內存清理。若該區執行Full GC 以後發現依然沒法進行對象的保存,產生異常java.lang.OutOfMemoryError: Java heap space
  1. Java虛擬機的堆內存設置不夠,能夠經過參數-Xms、-Xmx來調整。
  2. 代碼中建立了大量大對象,而且長時間不能被垃圾收集器收集(存在被引用)。
  • Permanent Generation:是一個常駐內存區域,用於存放JDK自身所攜帶的 Class,Interface 的元數據,也就是說它存儲的是運行環境必須的類信息,被裝載進此區域的數據是不會被垃圾回收器回收掉的,關閉 JVM 纔會釋放此區域所佔用的內存。產生異常java.lang.OutOfMemoryError: PermGen space jdk1.8以後已經不會再報報這個錯誤了。由於類信息的卸載幾乎不多發生,這樣會影響GC的效率。因而PermGen便被拆分出去了。
  1. 程序啓動須要加載大量的第三方jar包。例如:在一個Tomcat下部署了太多的應用。
  2. 大量動態反射生成的類不斷被加載,最終致使Perm區被佔滿。

jvm的算法

因爲算法篇幅太長具體算法可自行查閱資料,主要介紹gc算法發生在什麼區。

分代蒐集算法:是由複製算法、標記/整理、標記/清除算法共同組成

複製算法發生在Young Generation

標記/整理和標記/清除算法發生在Old Generation和Permanent Generation

java驗證jvm

棧中通常存放的都是對象的指針和基本類型,存取速度比堆要快,僅次於直接位於CPU中的寄存器。但缺點是,存在棧中的數據大小與生存期必須是肯定的,缺少靈活性。

棧數據能夠共享

​
/**
 * Created by liustc on 2018/4/20.
 */
public class JvmTest {

    public static void main(String[] args) {
        int a=0;
        int b=0;
        System.out.print(a==b);
    }
}

​
"C:\Program Files\Java\jdk1.8.0_151\bin\java"
true
Process finished with exit code 0

編譯器先處理int a = 0;首先它會在棧中建立一個變量爲a的引用,而後查找有沒有字面值爲0的地址,沒找到,就開闢一個存放0這個字面值的地址,而後將a指向0的地址。接着處理int b = 0;在建立完b的引用變量後,因爲在棧中已經有0這個字面值,便將b直接指向0的地址。這樣,就出現了a與b同時均指向0的狀況

/**
 * Created by liustc on 2018/4/20.
 */
public class JvmTest {

    public static void main(String[] args) {
        int a=0;
        int b=0;
        a=1;
        System.out.print("a="+a);
        System.out.print("b="+b);
        System.out.print(a==b);
    }
}
"C:\Program Files\Java\jdk1.8.0_151\bin\java" 
a=1b=0false
Process finished with exit code 0

再令a=1;那麼,b不會等於1,仍是等於0。在編譯器內部,遇到a=1;時,它就會從新搜索棧中是否有1的字面值,若是沒有,從新開闢地址存放1的值;若是已經有了,則直接將a指向這個地址。所以a值的改變不會影響到b的值。 

String str = "abc"的工做原理

/**
 * Created by liustc on 2018/4/20.
 */
public class JvmTest {

    public static void main(String[] args) {
        String str1 = "abc";
        String str2 = "abc";
        System.out.println(str1==str2);
    }
}
"C:\Program Files\Java\jdk1.8.0_151\bin\java"
true

Process finished with exit code 0
/**
 * Created by liustc on 2018/4/20.
 */
public class JvmTest {

    public static void main(String[] args) {
        String str1 = "abc";
        String str2 = "abc";
        str1 = "bcd";
        System.out.println(str1 + "," + str2);
        System.out.println(str1==str2);
    }
}
"C:\Program Files\Java\jdk1.8.0_151\bin\java" 
bcd,abc
false

Process finished with exit code 0

賦值的變化致使了類對象引用的變化,str1指向了另一個新對象!而str2仍舊指向原來的對象。上例中,當咱們將str1的值改成"bcd"時,JVM發如今棧中沒有存放該值的地址,便開闢了這個地址,並建立了一個新的對象,其字符串的值指向這個地址。

/**
 * Created by liustc on 2018/4/20.
 */
public class JvmTest {

    public static void main(String[] args) {
        String str1 = "abc";
        String str2 = "abc";

        str1 = "bcd";

        String str3 = str1;
        System.out.println(str3);

        String str4 = "bcd";
        System.out.println(str1 == str4);
    }
}
"C:\Program Files\Java\jdk1.8.0_151\bin\java"
bcd
true

Process finished with exit code 0

str3這個對象的引用直接指向str1所指向的對象(注意,str3並無建立新對象)。當str1改完其值後,再建立一個String的引用 str4,並指向因str1修改值而建立的新的對象。能夠發現,這回str4也沒有建立新的對象,從而再次實現棧中數據的共享。

堆驗證

String類 

/**
 * Created by liustc on 2018/4/20.
 */
public class JvmTest {

    public static void main(String[] args) {
        String str1 = new String("abc");
        String str2 = "abc";
        System.out.println(str1==str2);
    }
}
"C:\Program Files\Java\jdk1.8.0_151\bin\java"
false

Process finished with exit code 0

 以上代碼說明,只要是用new()來新建對象的,都會在堆中建立,並且其字符串是單獨存值的,即便與棧中的數據相同,也不會與棧中的數據共享。

使用String str = "abc";的方式,能夠在必定程度上提升程序的運行速度,由於JVM會自動根據棧中數據的實際狀況來決定是否有必要建立新對象。而對於String str = new String("abc");的代碼,則一律在堆中建立新對象,而無論其字符串值是否相等,是否有必要建立新對象,從而加劇了程序的負擔。這個思想應該是 享元模式的思想。

因爲String類的性質,當String變量須要常常變換其值時,應該考慮使用StringBuffer類,以提升程序效率。

執行時間上寄存器 < 堆棧 < 堆 

/**
 * Created by liustc on 2018/4/20.
 */
public class JvmTest {

    public static void main(String[] args) {
        String s1 = "ja";
        String s2 = "va";
        String s3 = "java";
        String s4 = s1 + s2;
        System.out.println(s3 == s4);
        System.out.println(s3.equals(s4));
    }
}
"C:\Program Files\Java\jdk1.8.0_151\bin\java"
false
true

Process finished with exit code 0

是否是很矛盾啊!是否是又懵逼了?

打印false的緣由是,java 重載了「+」,查看java字節碼能夠發現「+」實際上是調用了StringBuilder 因此使用了「+」實際上是生成了一個新的對象。因此(s3 == s4)打印false

/**
 * Created by liustc on 2018/4/20.
 */
public class JvmTest {

    public static void main(String[] args){
        long maxMemory = Runtime.getRuntime().maxMemory();//返回Java虛擬機試圖使用的最大內存量。
        Long totalMemory = Runtime. getRuntime().totalMemory();//返回jvm實例佔用的內存。
        System.out.println("MAX_MEMORY ="+maxMemory +"(字節)、"+(maxMemory/(double)1024/1024) + "MB");
        System.out.println("TOTAL_ MEMORY = "+totalMemory +"(字節)"+(totalMemory/(double)1024/1024) + "MB");
    }
}
"C:\Program Files\Java\jdk1.8.0_151\bin\java" -XX:+PrintGCDetails
MAX_MEMORY =1868038144(字節)、1781.5MB
TOTAL_ MEMORY = 126877696(字節)121.0MB
Heap
 PSYoungGen      total 37888K, used 3932K [0x00000000d6400000, 0x00000000d8e00000, 0x0000000100000000)
  eden space 32768K, 12% used [0x00000000d6400000,0x00000000d67d7320,0x00000000d8400000)
  from space 5120K, 0% used [0x00000000d8900000,0x00000000d8900000,0x00000000d8e00000)
  to   space 5120K, 0% used [0x00000000d8400000,0x00000000d8400000,0x00000000d8900000)
 ParOldGen       total 86016K, used 0K [0x0000000082c00000, 0x0000000088000000, 0x00000000d6400000)
  object space 86016K, 0% used [0x0000000082c00000,0x0000000082c00000,0x0000000088000000)
 Metaspace       used 3325K, capacity 4494K, committed 4864K, reserved 1056768K
  class space    used 363K, capacity 386K, committed 512K, reserved 1048576K

Process finished with exit code 0

將jvm堆初始值改小,觸發gc回收

import java.util.Random;

/**
 * Created by liustc on 2018/4/20.
 */
public class JvmTest {

    public static void main(String[] args){
        long maxMemory = Runtime.getRuntime().maxMemory();//返回jvm試圖使用的最大內存量。
        Long totalMemory = Runtime. getRuntime().totalMemory();//返回jvm實例的內存大小。
        System.out.println("MAX_MEMORY ="+maxMemory +"(字節)、"+(maxMemory/(double)1024/1024) + "MB");
        System.out.println("TOTAL_ MEMORY = "+totalMemory +"(字節)"+(totalMemory/(double)1024/1024) + "MB");
        String str = "www.baidu.com";
        while(true){
            str += str + new Random().nextInt(88888888) + new Random().nextInt(99999999);
        }
    }}
"C:\Program Files\Java\jdk1.8.0_151\bin\java" -XX:+PrintGCDetails
MAX_MEMORY =1868038144(字節)、1781.5MB
TOTAL_ MEMORY = 126877696(字節)121.0MB
[GC (Allocation Failure) [PSYoungGen: 32247K->2729K(37888K)] 32247K->10124K(123904K), 0.0045031 secs] [Times: user=0.01 sys=0.03, real=0.00 secs] 
[GC (Allocation Failure) [PSYoungGen: 32912K->4469K(70656K)] 40307K->26638K(156672K), 0.0121112 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
[GC (Allocation Failure) [PSYoungGen: 66160K->776K(70656K)] 88329K->59879K(156672K), 0.0141096 secs] [Times: user=0.03 sys=0.02, real=0.01 secs] 
[Full GC (Ergonomics) [PSYoungGen: 776K->0K(70656K)] [ParOldGen: 59103K->37630K(116224K)] 59879K->37630K(186880K), [Metaspace: 3408K->3408K(1056768K)], 0.0143902 secs] [Times: user=0.03 sys=0.00, real=0.01 secs] 
[Full GC (Ergonomics) [PSYoungGen: 60370K->0K(70656K)] [ParOldGen: 96726K->74565K(172032K)] 157096K->74565K(242688K), [Metaspace: 3409K->3409K(1056768K)], 0.0598124 secs] [Times: user=0.08 sys=0.00, real=0.06 secs] 
[GC (Allocation Failure) [PSYoungGen: 60382K->32K(95744K)] 1257771K->1226968K(1463808K), 0.0227293 secs] [Times: user=0.06 sys=0.01, real=0.02 secs] 
[GC (Allocation Failure) [PSYoungGen: 32K->32K(131584K)] 1226968K->1226968K(1499648K), 0.0037586 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (Allocation Failure) [PSYoungGen: 32K->0K(131584K)] [ParOldGen: 1226936K->355271K(483840K)] 1226968K->355271K(615424K), [Metaspace: 3409K->3409K(1056768K)], 0.1616835 secs] [Times: user=0.19 sys=0.09, real=0.16 secs] 
[GC (Allocation Failure) [PSYoungGen: 2499K->32K(158208K)] 1303306K->1300838K(1526272K), 0.0037952 secs] [Times: user=0.06 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [PSYoungGen: 32K->32K(158208K)] 1300838K->1300838K(1526272K), 0.0036491 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (Allocation Failure) [PSYoungGen: 32K->0K(158208K)] [ParOldGen: 1300806K->473463K(622080K)] 1300838K->473463K(780288K), [Metaspace: 3409K->3409K(1056768K)], 0.1641897 secs] [Times: user=0.30 sys=0.06, real=0.16 secs] 
[GC (Allocation Failure) [PSYoungGen: 0K->0K(250880K)] 946230K->946230K(1618944K), 0.0027229 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [PSYoungGen: 0K->0K(258560K)] 946230K->946230K(1626624K), 0.0027747 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (Allocation Failure) [PSYoungGen: 0K->0K(258560K)] [ParOldGen: 946230K->709846K(879104K)] 946230K->709846K(1137664K), [Metaspace: 3409K->3409K(1056768K)], 0.1013768 secs] [Times: user=0.28 sys=0.02, real=0.10 secs] 
[GC (Allocation Failure) [PSYoungGen: 0K->0K(353280K)] 709846K->709846K(1721344K), 0.0049384 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
[Full GC (Allocation Failure) Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
[PSYoungGen: 0K->0K(353280K)] [ParOldGen: 709846K->709816K(900608K)] 709846K->709816K(1253888K), [Metaspace: 3409K->3409K(1056768K)], 0.1792920 secs] [Times: user=0.39 sys=0.00, real=0.18 secs] 
Heap
	at java.util.Arrays.copyOf(Arrays.java:3332)
 PSYoungGen      total 353280K, used 14028K [0x00000000d6400000, 0x00000000ec700000, 0x0000000100000000)
	at java.lang.AbstractStringBuilder.ensureCapacityInternal(AbstractStringBuilder.java:124)
  eden space 352768K, 3% used [0x00000000d6400000,0x00000000d71b3070,0x00000000ebc80000)
	at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:674)
  from space 512K, 0% used [0x00000000ec680000,0x00000000ec680000,0x00000000ec700000)
	at java.lang.StringBuilder.append(StringBuilder.java:208)
  to   space 4608K, 0% used [0x00000000ebe00000,0x00000000ebe00000,0x00000000ec280000)
	at JvmTest.main(JvmTest.java:15)
 ParOldGen       total 1368064K, used 709816K [0x0000000082c00000, 0x00000000d6400000, 0x00000000d6400000)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
  object space 1368064K, 51% used [0x0000000082c00000,0x00000000ae12e1e8,0x00000000d6400000)
 Metaspace       used 3440K, capacity 4494K, committed 4864K, reserved 1056768K
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
  class space    used 377K, capacity 386K, committed 512K, reserved 1048576K
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at com.intellij.rt.execution.application.AppMain.main(AppMain.java:140)

Process finished with exit code 1
相關文章
相關標籤/搜索