《JVM系列》深入浅出类加载机制中<init>和<Clinit>的区别【一篇即可搞懂初始化机制】
文章目錄
- 前言:init和Clinit怎么產生的?
- 1.init方法
- 1.1.init方法什么時候被調用?用來做什么?
- 1.2.那么實例變量賦值操作、非靜態代碼塊、構造器這三者,哪一個會先執行呢?
- 1.3.那么既然賦值操作和非靜態代碼塊優先級是相同的,那么看下面代碼分析能不能這樣操作?
- 1.4.分析如下代碼,看init方法字節碼指令
- 1.5.分析如下代碼,看你搞懂init方法沒呢~!
- 2.Clinit方法
- 2.1.你會不會有個疑問,為什么需要Clinit方法?
- 2.2.Clinit方法什么時候被調用?
- 2.3.Clinit方法有什么作用呢?
- 2.4.靜態域賦值操作和靜態代碼塊的順序?
- 2.5.那么既然靜態域的賦值操作和靜態代碼塊優先級是相同的,那么看下面代碼分析能不能這樣操作?
- 2.6.你會不會有疑問為什么類變量會被分配在方法區(JDK8開始為元空間)?
- 2.7.從字節碼角度分析以下代碼的初始化流程?
- 2.8.分析如下代碼,看你搞懂Cinit方法沒呢~!
- 2.9.來自我的靈魂拷問:
前言:init和Clinit怎么產生的?
public class ClinitAndInitTest {private Integer num = 2;private static Integer num2 = 3; }
這兩個方法是編譯成字節碼之后生成的(我們只在Java代碼中定義了一個非靜態和靜態的變量),你覺得這兩個方法有什么作用呢?看完你就明白了。
🍛首先從字面意義上看Clinit比init多了一個“C”,是不是可以認為是Class的縮寫
🌀Clinit :類構造器方法【類級別的變量誰初始化呢?比如static修飾的】
🌀init :對象構造器方法【對象級別的實例變量誰初始化呢?】
這里及其下面我說的初始化是顯示初始化,顯示初始化就是賦值操作。比如private int a = 3;(另外一個就是默認初始化,它在變量被分配空間時就會默認初始化為0或null或false)
這就涉及到了類加載機制和實例化對象的過程,下圖僅供了解。后續會講解~
上圖是JVM的整體結構
上圖是類加載的流程圖上圖是對象內存結構圖
1.init方法
1.1.init方法什么時候被調用?用來做什么?
當類的實例被創建的時候會被調用,至于這個方法做什么,你可以理解為JVM幫我們收集了對象的實例變量的顯示賦值操作和非靜態代碼塊,然后全部放到了init方法里(注意構造器中的代碼當然在里面)。
class Init {private int a = 2; //實例變量的顯示賦值操作{a = 3; //非靜態代碼塊}public Init() { //構造器a =4; } }1.2.那么實例變量賦值操作、非靜態代碼塊、構造器這三者,哪一個會先執行呢?
- 實例變量賦值操作和非靜態代碼塊是優先執行的,它們兩者的代碼誰在前面先執行誰。
- 構造器中的代碼當然最后執行
如果你對父類+子類這種引用鏈的各大變量的初始化順序不明白,請移步:
深入淺出Java復用類:https://blog.csdn.net/Kevinnsm/article/details/121392948?spm=1001.2014.3001.5501
1.3.那么既然賦值操作和非靜態代碼塊優先級是相同的,那么看下面代碼分析能不能這樣操作?
public class ClinitAndInitTest {{num = 2;}private Integer num = 5; }如果你思路很清晰,那么恭喜你,你已經了解了初始化機制。首先這樣寫肯定是正確的;這是因為其默認初始化num = 0早已在為該對象分配內存時完成,這說明內存中已經有了num這個變量了,你為其賦值當然可以。
如果你不明白默認初始化和顯示初始化的順序請移步:
深入淺出Java復用類:https://blog.csdn.net/Kevinnsm/article/details/121392948?spm=1001.2014.3001.5501
1.4.分析如下代碼,看init方法字節碼指令
class Init2 {private int a = 3;{a = 4;}public Init2() {a = 5;} }你會不會對上圖中aload_0將引用壓入操作數據棧中有疑問?我不是沒創建對象嘛,哪里的引用呢?
這是因為每個方法內部都隱式地包含了this引用,在方法的局部變量標中都可以看到,它指向的是調用當前方法的對象;不信你可以看下面
- aload_0指令:將局部變量標中索引為0的引用壓入操作數棧中
- iconst_3指令:將常量3壓入操作數棧中
- putfield #2 指令:將常量和引用彈棧,并將#2引用的變量進行修改。(this.a = 2)
你可別認為這是順序的原因嘍,你無論把構造器放哪,它都是最后才會執行(注意我一直分析的是init方法,不包含類級別的變量)
1.5.分析如下代碼,看你搞懂init方法沒呢~!
class Init3 {{a = 40;}private int a = 3;public Init3() {System.out.println(a);a = 100;System.out.println(a);}{a = 200;}public static void main(String[] args) {Init3 it = new Init3();} }結果如下:
請你嘗試著分析它的字節碼哈~
或許你會對3使用iconst指令,100使用bipush指令,200使用sipush指令有疑問?
iconst指令:這個指令的范圍為-1~5,而且這個常量值已經隱含在了指令里面,為什么這樣設計呢?其實是因為經過統計,在底層中-1 ~ 5使用的頻率較高。
bipush指令:接收一個8位數正數作為參數,剛好對應一個字節(8個bit),范圍剛好是-128~127(也就是byte的范圍)
sipush指令:接收一個16位整數–》int范圍
前兩行調用了父類init方法(不懂初始化順序:看深入淺出Java復用類)
2.Clinit方法
2.1.你會不會有個疑問,為什么需要Clinit方法?
眾所周知,init是對對象級別的變量或非靜態代碼塊進行初始化的;那么靜態變量或者靜態代碼塊誰來初始化呢?
沒錯,答案就是Clinit方法。(以下類級別的操作會在類加載階段就完成)
如果你有疑問,請移步:看深入淺出Java復用類
2.2.Clinit方法什么時候被調用?
在類加載的的第三階段初始化階段會被調用
注意在類加載階段,只會涉及類級別代碼的執行,比如static修飾的域與代碼塊。上圖只對類加載的三個過程做簡單解釋!
2.3.Clinit方法有什么作用呢?
該方法不需要我們創建,該方法是JVM幫我們自動收集代碼中的所有的靜態域的賦值操作和靜態代碼塊合并而來。
class Init4 {private static Integer a = 2; //靜態域static { //靜態代碼塊System.out.println(a);a = 3;} }2.4.靜態域賦值操作和靜態代碼塊的順序?
其實這和上面init方法中類似,這兩者是同級的,也就是誰在前面先執行誰。
class Init4 {private static Integer a = 2;static {System.out.println(a);a = 3;}public static void main(String[] args) {System.out.println(Init4.a);} }執行結果:
2.5.那么既然靜態域的賦值操作和靜態代碼塊優先級是相同的,那么看下面代碼分析能不能這樣操作?
class Init5 {static {a = 3;}private static int a = 2; }當然是當然可以,和1.2類似。根據我們上面分析Clinit的執行過程在類加載的初始化階段進行顯示初始化操作。那么默認初始化是在什么時候呢?
通過類比init的流程我們可以知道,當變量被分配空間時會被默認初始化為0或null或false。其實在類加載的第二階段Linking中的準備階段完成的
2.6.你會不會有疑問為什么類變量會被分配在方法區(JDK8開始為元空間)?
注意上圖中有個隱含的問題,在JDK1.6及其以前類變量是被分配到方法區的;在JDK1.7及其以后,類變量和字符串常量池被移植到了堆中。(為了內存考慮?咱也不敢亂發表意見),畢竟JDK1.6及其以前永久代(方法區)和堆容易出現OOM,JDK1.8開始移除了方法區,改名為元空間,直接創建在本地內存中。【也不敢亂發表意見,為什么將類變量移植到堆】
2.7.從字節碼角度分析以下代碼的初始化流程?
2.8.分析如下代碼,看你搞懂Cinit方法沒呢~!
class Clinit7 {static {a = 100;}private static int a = 200;public static void main(String[] args) {System.out.println(a);} }結果:200
請你嘗試著分析它的字節碼哈~
bipush和sipush指令我已經在上文init方法中解釋過。
2.9.來自我的靈魂拷問:
(1)為什么靜態方法中不能使用非靜態變量?
(2)為什么靜態方法中不能定義靜態變量?【好像有點無用】
【這兩個問題應該比較簡單】
🎃個人介紹
🎈簡介:一枚雙非本科科班在讀的學生,普普通通的生活卻夾雜著不該有的夢想;一起學習交流WX:kht808
🎈博客主頁:https://blog.csdn.net/Kevinnsm?spm=1001.2101.3001.5343
🎈熱愛:廣泛涉獵C/C++、JAVA、Python、Go,專注于服務端技術
總結
以上是生活随笔為你收集整理的《JVM系列》深入浅出类加载机制中<init>和<Clinit>的区别【一篇即可搞懂初始化机制】的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【大白话系列】图解TCP三次握手【使用w
- 下一篇: 【多线程高并发】深入浅出原子性