JVM系列-第一節:JVM簡介、運行時數據區、內存分代模型

{"type":"doc","content":[{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"一、什麼是JVM?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"JVM是Java Virtual Machine(Java虛擬機))的縮寫,JVM是一種用於計算設備的規範,它是一個虛構出來的計算機,是通過在實際的計算機上仿真模擬各種計算機功能來實現的。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"JVM是一種規範,有很多種實現,比如Oracle/Sun JDK、OpenJDK等,用的都是相同的JVM:"},{"type":"text","marks":[{"type":"strong"}],"text":"HotSpot VM"},{"type":"text","text":";IBM開發的一個高度模塊化的JVM:"},{"type":"text","marks":[{"type":"strong"}],"text":"J9"},{"type":"text","text":"。除此之外,還有很多其他的JVM實現。通常大家說起“Java性能如何如何”、“Java有多少種GC”、“JVM如何調優”等問題,默認說的就是HotSpot VM,所以HotSpot VM是絕對的主流。下文提到的JVM都是指HotSpot。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Java虛擬機本質上就是一個程序,當它在命令行上啓動的時候,就開始執行保存在字節碼文件中的指令。JVM有兩個重要作用,1.機器碼翻譯。JVM保證“一次編譯,多次運行”,原因是不同平臺有不同的JVM,比如HotSpot有windows版和linux版本,不同的平臺使用不同的版本,對於程序員來說,只需要關注些代碼,不用考慮代碼的移植性,因爲不同平臺的JVM已經屏蔽了系統的差異了。2.內存管理。程序員需要使用一個對象,只需要new出來,不用關心具體是如何new出來的,不用關心對象的生命週期是怎樣的,也不用關心什麼時候回收對象。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/ad/ade0188866dc11e6aa4f09f35d69ab27.png","alt":"JVM分區","title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Java虛擬機主要分爲五大模塊:類加載器、運行時數據區、執行引擎、本地方法接口和垃圾收集模塊。下面的內容主要講其中兩塊,運行時數據區、垃圾收集器。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"二、運行時數據區"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"當一個線程運行前,會把需要執行的代碼的不同部分,放到運行時數據區的不同區域,當線程運行時會從不同的位置拿數據。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"2.1程序計數器"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"程序計數器存儲當前線程正在執行的字節碼指令的地址和行號。爲什麼要記錄一個線程正在執行的字節碼指令的地址和行號呢?線程是java的最小執行單元,因爲CUP同時執行多個線程的時候,會涉及到線程的切換,當CUP切換線程時,要記錄這些信息,以便CUP再次切換到當前線程時,該線程知道從什麼位置開始繼續執行。每個線程都會有自己的程序計數器。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"2.1棧"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"虛擬機棧存儲"},{"type":"text","marks":[{"type":"strong"}],"text":"當前線程運行的方法"},{"type":"text","text":"所需要的數據、指令、返回地址。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"舉一個簡單的代碼示例:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"package com.wuxiaolong.jvm;\n\n/**\n * Description:\n *\n * @author 諸葛小猿\n * @date 2020-09-06\n */\npublic class TestJVM {\n\n public static final int AGE = 30;\n\n public static void test () {\n int a = 1;\n int b = 2;\n int c = a + b;\n Object objc= new Object();\n }\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"找到上面TestJVM.java編譯後的TestJVM.class文件,通過"},{"type":"codeinline","content":[{"type":"text","text":"javap"}]},{"type":"text","text":"命令查看字節碼的每一條指令,將指令存入TestJVM.txt文件。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"bash"},"content":[{"type":"text","text":"$ javap -c -v ./TestJVM.class > TestJVM.txt"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"指令文件TestJVM.txt:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"Classfile /C:/Users/WuXiaoLong/Desktop/java-summary/target/classes/com/wuxiaolong/jvm/TestJVM.class\n Last modified 2020-9-6; size 497 bytes\n MD5 checksum e2bee1c0136645a123ea37b4c6aba4a2\n Compiled from \"TestJVM.java\"\npublic class com.wuxiaolong.jvm.TestJVM\n minor version: 0\n major version: 52\n flags: ACC_PUBLIC, ACC_SUPER\n// 這裏是常量池的描述 \nConstant pool:\n #1 = Methodref #2.#23 // java/lang/Object.\"\":()V\n #2 = Class #24 // java/lang/Object\n #3 = Class #25 // com/wuxiaolong/jvm/TestJVM\n #4 = Utf8 AGE\n #5 = Utf8 I\n #6 = Utf8 ConstantValue\n #7 = Integer 30\n #8 = Utf8 \n #9 = Utf8 ()V\n #10 = Utf8 Code\n #11 = Utf8 LineNumberTable\n #12 = Utf8 LocalVariableTable\n #13 = Utf8 this\n #14 = Utf8 Lcom/wuxiaolong/jvm/TestJVM;\n #15 = Utf8 test\n #16 = Utf8 a\n #17 = Utf8 b\n #18 = Utf8 c\n #19 = Utf8 objc\n #20 = Utf8 Ljava/lang/Object;\n #21 = Utf8 SourceFile\n #22 = Utf8 TestJVM.java\n #23 = NameAndType #8:#9 // \"\":()V\n #24 = Utf8 java/lang/Object\n #25 = Utf8 com/wuxiaolong/jvm/TestJVM\n{\n // 靜態常量AGE的描述 \n public static final int AGE;\n descriptor: I\n flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL\n ConstantValue: int 30\n // 這裏是TestJVM類的描述\n public com.wuxiaolong.jvm.TestJVM();\n descriptor: ()V\n flags: ACC_PUBLIC\n Code:\n stack=1, locals=1, args_size=1\n 0: aload_0\n 1: invokespecial #1 // Method java/lang/Object.\"\":()V\n 4: return\n LineNumberTable:\n line 9: 0\n LocalVariableTable:\n Start Length Slot Name Signature\n 0 5 0 this Lcom/wuxiaolong/jvm/TestJVM;\n // 這裏是test方法的描述\n public static void test();\n descriptor: ()V\n flags: ACC_PUBLIC, ACC_STATIC\n Code: // test方法的指令\n stack=2, locals=4, args_size=0\n 0: iconst_1\n 1: istore_0\n 2: iconst_2\n 3: istore_1\n 4: iload_0\n 5: iload_1\n 6: iadd\n 7: istore_2\n 8: new #2 // class java/lang/Object //創建一個對象 在堆上分配了內存並在棧頂壓入了指向這段內存的地址\n 11: dup\n 12: invokespecial #1 // Method java/lang/Object.\"\":()V //調用構造函數、實例化方法\n 15: astore_3\n 16: return\n LineNumberTable: // test方法在java代碼中的行號 \n line 14: 0\n line 15: 2\n line 16: 4\n line 17: 8\n line 18: 16\n LocalVariableTable: // test方法的本地局部變量表\n Start Length Slot Name Signature\n 2 15 0 a I\n 4 13 1 b I\n 8 9 2 c I\n 16 1 3 objc Ljava/lang/Object;\n}\nSourceFile: \"TestJVM.java\""}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"通過上面的指令描述文件,可以看出,TestJVM這個類的所有指令的描述。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"JVM中的每一個線程擁有一個運行時棧。JVM會爲每一個線程執行的方法在運行時棧中開闢一塊空間,這塊空間叫"},{"type":"text","marks":[{"type":"strong"}],"text":"棧幀"},{"type":"text","text":"。每一個棧幀又分爲幾塊,比如方法的局部變量表、操作數棧、動態鏈接、方法出口(返回地址)等:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/38/3875d0cf5087219a6451d05074196d80.png","alt":"虛擬機棧","title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在記錄test方法的棧幀中,"},{"type":"text","marks":[{"type":"strong"}],"text":"局部變量表"},{"type":"text","text":"中存在的是test方法中的四個本地變量:a/b/c/objc,對應TestJVM.txt指令描述文件的81-86行;"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"操作數棧"},{"type":"text","text":"中存的是局部變量對應的操作數,比如TestJVM.txt指令描述文件的62-74行指令中,第一個指令iconst_1:int型常量值1進棧,表示將"},{"type":"codeinline","content":[{"type":"text","text":"int a = 1"}]},{"type":"text","text":"這一句代碼中的操作數1放入(壓棧)操作數棧裏(這時棧裏只有一個數1);第二個指令istore"},{"type":"text","marks":[{"type":"italic"}],"text":"0:將棧頂int型數值存入第0個局部變量,第0個局部變量是誰?TestJVM.txt指令描述文件的83行,表明第0個局部變量a,操作數1出站存入局部變量a。其實iconst"},{"type":"text","text":"1和istore_0就是"},{"type":"codeinline","content":[{"type":"text","text":"int a = 1"}]},{"type":"text","text":"這句代碼執行的指令。其他的指令分析就不詳細說了,具體每個指令什麼意思可以自行網上查閱。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"需要注意TestJVM.java的test方法第17行"},{"type":"codeinline","content":[{"type":"text","text":"Object objc= new Object();"}]},{"type":"text","text":",這是一個對象,和上面本地變量a/b/c不同,這一句涉及到的指令有new、dup、invokespecial、astore_3四個指令。其中new指的是創建一個對象,具體是在堆上分配內存,並在棧頂壓入指向這段內存的地址。"},{"type":"text","marks":[{"type":"strong"}],"text":"對象是存在堆裏的,棧裏只存對象在堆中的地址"},{"type":"text","text":"。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"動態鏈接"},{"type":"text","text":"指的是如果被調用的方法或對象在編譯期無法被確定下來,也就是說,只能夠在程序運行期將調用方法的符號引用轉換爲直接引用,由於這種引用轉換過程具備動態性,因此也就被稱之爲動態鏈接。類似於TestJVM.txt的第70行和第72行中的#2和#1,對應TestJVM.txt的第12和11兩行的Constant pool,動態鏈接的作用就是爲了將這些符號引用(#)最終轉換爲調用方法的直接引用。簡單舉一個例子,通常在Controller層調用Service層時,通過@Autowired注入一個Service,通常使用的時一個Service的接口而不是實現類,在Controller的一個方法中通過Service的接口中的方法調用具體的Service實現,如果Service接口有多個實現,程序在編譯期並不知道具體使用哪個實現類,這個時候就會在字節碼Constant pool部分生成"},{"type":"text","marks":[{"type":"strong"}],"text":"動態鏈接"},{"type":"text","text":"(#),在程序的運行期最終轉換爲調用方法的直接引用。通過字節碼指令文件可以看出Constant pool翻譯成”常量池“並不準確,Constant pool中除了有常量,還有符號引用(包括類、方法、字段等的描述符)。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"方法出口"},{"type":"text","text":"(返回地址)指的是,當一個方法執行完成後,要出棧,那麼出棧後要去哪,正常執行的方法出棧和異常執行的方法的出棧也不一樣。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"注意,在線程中遞歸調用某個方法時,方法的每次調用都會有一個棧幀,所以線程請求的棧深度大於虛擬機允許的棧深度,將拋出StackOverflowError。雖然棧的大小可以自動擴展,但動態擴展時無法申請到更大的空間時,任然會報出OutOfMemory。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"棧幀大小確定的時間在編譯期,不受運行期數據影響。所以局部變量表所需要的內存空間在編譯期完成分配,當進入一個方法時,這個方法在棧中需要分配多大的局部變量空間是完全確定的,在方法運行期間不會改變局部變量表大小。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"2.3本地方法棧"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"本地方法棧和虛擬機棧類似,只是它描述的是本地方法在執行是的情況。什麼是本地方法?本地方法是指方法被native關鍵字修飾的方法,在JDK中沒有具體實現類的,具體的實現在JVM的代碼中,"},{"type":"link","attrs":{"href":"http://hg.openjdk.java.net/","title":""},"content":[{"type":"text","text":"這裏"}]},{"type":"text","text":"就可以找到各種版本的Hotspot源碼,源碼是C或C++寫的。之前文章中在分析CAS時就使用了Hotspot源碼。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"2.4方法區"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"方法區中存儲類字節碼的類信息、常量、靜態變量、JIT等信息。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"TestJVM.txt指令文件的第10-35行是常量池,這部分的內容就放在方法區的常量池中。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/05/055fdacab1f720a0341b9e54610b630e.png","alt":null,"title":"","style":[{"key":"width","value":"25%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這裏可以思考一下,靜態變量和常量爲什麼不放在堆中?我感覺是因爲常量和靜態變量一般都是不變的,只要存儲一份就可以了,放在堆中那麼每次new對象都會存在相同的數據,造成空間浪費。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"2.5堆"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"對於大多數應用來說,"},{"type":"text","marks":[{"type":"strong"}],"text":"堆是java虛擬機管理內存最大的一塊內存區域,因爲堆存放的對象是線程共享的,所以多線程的時候也需要同步機制"},{"type":"text","text":"。因此需要重點了解下。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"堆裏存儲的都是new出來的對象,棧裏存儲該對象的引用會指向堆裏該對象的內存地址。所有對象實例及數組都要在堆上分配內存,但隨着JIT編譯器的發展和逃逸分析技術的成熟("},{"type":"link","attrs":{"href":"https://www.jianshu.com/p/20bd2e9b1f03","title":""},"content":[{"type":"text","text":"淺談HotSpot逃逸分析"}]},{"type":"text","text":"),這個說法也不是那麼絕對,但是大多數情況都是這樣的。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"三、JVM內存分代模型"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在JDK1.8之前,JVM的內存分三大塊,新生代、老年代、永久代。其中前兩塊在堆中,後一塊在方法區中。在JDK1.8及以後,移除了永久代使用了Meta Space。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/34/3464613096fcd59e010b022add82c3cf.png","alt":null,"title":"","style":[{"key":"width","value":"100%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"爲什麼要分代"},{"type":"text","text":"?因爲不同的對象的生命週期是不一樣的,將不同生命週期的對象放在不同的代,使用不同的垃圾回收算法進行回收。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"3.1新生代"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"一般來說,對象剛new出來會放在新生代,新生代中的對象一般是生命週期比較短,一次回收能回收(Minor GC)98%以上的對象。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"新生代內存分爲三塊Eden區、s0區、s1區,"},{"type":"text","marks":[{"type":"strong"}],"text":"爲什麼是分三塊"},{"type":"text","text":"?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"新生代分三塊,主要的原因是新生代使用的垃圾回收算法使用的是複製算法。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/8a/8a788fb2a6288c8810b6bacd2f1eaa97.png","alt":"Minor GC","title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"S0和S1是兩塊大小相同、功能相同的區域,但是一次GC只能有一塊區域起作用。當eden區第一次滿了時,會觸發第一次minor gc,回收eden區的對象,gc後,還有一個對象是可達的,那麼就屬於存活的對象,這個對象會被放到s0區域。當eden區第二次滿了時,會觸發第二次minor gc,這時會回收eden區和s0區域,如果這時對象b依然可達,並且對象j也可達,那麼這兩個對象就會進入s1區域。可以看出,每次gc時存活的對象會在s0和s1區域來回複製,這就是複製算法。每次gc後存活的對象,年齡都會加1,多次gc後年齡達到固定的閾值(默認15)後,對象會進入老年代。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"新生代內存分爲三塊Eden區、s0區、s1區,比例是:8:1:1。"},{"type":"text","marks":[{"type":"strong"}],"text":"爲什麼比例是8:1:1"},{"type":"text","text":"?因爲複製算法中s0和s1只能一塊區域起作用,另一塊是空的,所以並不是所有的新生代都是有效的存儲空間,s0和s1過大會導致可用內存變小並且eden區過小,minor gc會變得更頻繁;s0和s1過小,導致較少的gc次數時,s0和s1就會滿了,從而導致年齡較小的對象進入老年代。8:1:1可以看成是二八原則。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"3.2老年代"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"新生代和老年代的內存比例是1:2。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"新生代中多次回收後依然存在的對象會進入老年代。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"3.3永久代和Meta Space"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"爲了避免永久代的溢出,在JDK1.8及之後,去掉了永久代,使用了Meta Space。Meta Space這塊內存屬於代外分配的內存,使用的是機器的直接內存。Meta Space可以自動擴容,雖然可以自動擴容,但Meta Space也並不是越大越好,因爲機器的總內存是固定的,Meta Space變大會擠壓其他的內存空間的使用。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"後續將繼續介紹java內存模型、四種引用、GC回收算法、GC回收器、JVM優化等內容。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"關注公衆號,輸入“"},{"type":"text","marks":[{"type":"strong"}],"text":"java-summary"},{"type":"text","text":"”即可獲得源碼。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"完成,收工!"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/77/77e2a53cabfaaac7d98ee0a860144e29.gif","alt":null,"title":"","style":[{"key":"width","value":"50%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"【"},{"type":"text","marks":[{"type":"strong"}],"text":"傳播知識,共享價值"},{"type":"text","text":"】,感謝小夥伴們的關注和支持,我是【"},{"type":"text","marks":[{"type":"strong"}],"text":"諸葛小猿"},{"type":"text","text":"】,一個彷徨中奮鬥的互聯網民工。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/6a/6acafea3f4c9b96373b3f566ec7078e2.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章