Java程式設計師必備基礎:Java代碼是怎麼運行的?

2020-12-06 騰訊網

前言

作為一名Java程式設計師,我們需要知道Java代碼是怎麼運行的。最近複習了深入理解Java虛擬機,做了一下總結,希望對大家有幫助,如果有不正確的地方,歡迎提出,感激不盡。

java 代碼運行主要流程

本文主要講解流程如下:

java源文件編譯為class字節碼

類加載器把字節碼加載到虛擬機的方法區。

運行時創建對象

方法調用,執行引擎解釋為機器碼

CPU執行指令

多線程切換上下文

編譯

我們都知道,java代碼是運行在Java虛擬機上的。但是java是一門面向對象的高級語言,它不僅語法非常複雜,抽象程度也非常高,並不能直接運行在計算機硬體機器上。

Java虛擬機(Java Virtual Machine 簡稱JVM)是運行所有Java程序的抽象計算機,是Java語言的運行環境。

因此,在運行Java程序之前,需要編譯器把代碼編譯成java虛擬機所能識別的指令程序,這就是Java字節碼,即class文件。

所以,Java代碼運行的第一步是:把Java原始碼編譯成.class 字節碼文件。

類加載

在Class文件中描述的各種信息,需要被加載到虛擬機之後才能運行和使用。因此,需要把class字節碼文件加載到Java虛擬機來。

虛擬機把描述類的數據從 Class 文件加載到內存,並對數據進行校驗、轉換解析和初始化,最終形成可以被虛擬機直接使用的 Java 類型,這就是虛擬機的類加載機制。

加載

加載階段,虛擬機需要完成以下3件事情:

通過一個類的全限定名來獲取定義此類的二進位字節流。

將這個字節流所代表的靜態存儲結構轉化為方法區的運行時數據結構。

在內存中生成一個代表這個類的java.lang.Class對象,作為方法區這個類的各種數據的訪問入口

加載階段完成後,這些二進位字節流按照虛擬機所需的格式存儲在方法區之中。

驗證

為了確保Class文件的字節流中包含的信息符合當前虛擬機的要求,不會危害虛擬機的安全,Java虛擬機對輸入的字節流走驗證過程。

驗證階段包括四個階段:文件格式驗證、元數據驗證、字節碼驗證、符號引用驗證。

文件格式驗證:驗證字節流是否符合Class文件格式規範,如:是否以魔數0xCAFEBABE開頭。

元數據驗證:對字節碼描述的信息進行語義分析,如:這個類的父類是否繼承了不允許被繼承的類(被final修飾的類);

字節碼驗證:主要目的是通過數據流和控制流分析,確定程序語義是合法的、符合邏輯的。如:保證跳轉指令不會跳轉到方法體以外的字節碼指令上。

符號引用驗證:發生在虛擬機將符號引用轉化為直接引用的時候,如:校驗符號引用中通過字符串描述的全限定名是否能找到對應的類。

準備

準備階段是正式為類變量分配內存並設置類變量初始值,這些變量所使用的內存都將在方法區中進行分配。如:

變量value在準備階段過後的初始值是0而不是123。

解析

解析階段是虛擬機將常量池內的符號引用替換為直接引用的過程。

比如:com.User類引用com.Tool類,在編譯時,User類不知道Tool類的實際內存地址,因此只能使用符號com.Tool(假設)來表示。而在類加載加載User類的時候,可以通過虛擬機獲取Tool類的實際內存地址,因此便可以將符號com.Tool替換為Tool類的實際內存地址,即直接引用地址。

解析動作主要針對類或接口、欄位、類方法、接口方法、方法類型、方法句柄和調用點限定符 7 類符號引用進行。

初始化

到了初始化階段,才真正開始執行類中定義的Java字節碼。在這個階段,則根據程式設計師通過程序制定的主觀計劃去初始化類變量和其他資源。

創建對象

Java虛擬機是如何執行字節碼的呢?我們先來看一下運行時創建對象。

Java是面向對象的程式語言,程序的運行是以對象為調用單位的。

字節碼文件加載到虛擬機的方法區後,在程序運行過程,通過 class字節碼文件創建與其對應的對象信息 。

創建對象的方式有:new關鍵字,反射等。

Java堆內存是線程共享的區域,創建後的對象信息就保存在Java堆內存中。

方法調用

JVM的調用單位是對象,但是真正執行功能性的代碼還是對象上的方法。

在運行過程中,每當調用進入一個java方法,java虛擬機會在當前線程的java方法棧中生成一個棧幀,用以存放局部變量以及字節碼的操作數。方法棧內存是線程私有的,每個線程都有自己的方法棧。如果對應的方法是本地方法,則對應的就是本地方法棧。

java運行時數據區域如下:

解釋

當調用Java對象的某個方法時,JVM執行引擎會將該方法的字節碼文件翻譯成計算機所能識別的機器碼,機器碼信息保存在方法區中。翻譯有解釋執行和即時編譯兩種方式。

兩種翻譯方式的區別如下:

解釋執行

來一行代碼,解釋一行,大部分不常用的代碼,都是採用這種方式。

即使編譯

對於部分熱點代碼,將一個方法包含的所有字節碼翻譯成機器指令,以提高java虛擬機的運行效率。

即時編譯是建立經典的二八定律上,即20%代碼佔據了80%的計算資源。

執行指令

Java程序被加載入內存後,指令也在內存中了。

指令的指令寄存器IP,指向下一條待執行指令的地址。

CPU的控制單元根據IP寄存器的指向,將主存中的指令裝載到指令寄存器,這些加載的指令就是一串二進位碼,還需要解碼器進行解碼。

解碼後,如果需要獲取操作數,則從內存中取數據,調用運算單元進行計算。

多線程上下文切換

CPU一通上電,就會周而復始從內存中獲取指令、解碼、執行。

為了支持多任務,CPU 將執行時間這個資源劃分成時間片,每個程序執行一段時間。

java虛擬機的多線程是通過線程輪流切換分配處理執行時間的方式來實現的,在任何一個確定的時刻,一個處理器(對於多核處理器來說是一個內核)都只會執行一條程序中的指令。

假設當前線程在運行中,CPU分配的時間執行完了,總得保存運行過的結果信息吧,要不然白白浪費之前的工作了,因此,程序計數器(PC寄存器)作用體現出來了,它是一塊較小的內存空間,線程私有,可以看作當前線程執行的字節碼的行號指示器。當CPU又給它分配時間跑的時候,可以把數據恢復,接著上一次執行到的位置繼續執行就可以了。

相關焦點

  • Android被指抄襲Java代碼引爭議
    Mueller仔細檢查了Android的代碼,除了甲骨文在訴訟中提到的一個文件之外,他還發現了六個與Java文件非常相似的文件。這些文件是在Android 2.2版和2.3版中發現的。此外,Mueller指出,在Android的代碼中有三十七個文件包含一些提示,稱這個代碼是Sun專有的代碼。
  • 零基礎java入門教程函數function實例化格式案例void返回值說明
    java基礎自學入門:函數:定義在類中的具有特定功能的一段獨立小程序有時候我們函數也稱為方法,平時我們聽到的函數也就是方法,方法也是函數,每個人的叫法不同,所以這裡要切記。老程式設計師別露餡喲。{執行語句;return 返回值;}返回值:函數運行後的結果的返回數據類型
  • Java 反射:框架設計的靈魂
    解釋型語言:不需要編譯,在運行的時候逐行翻譯解釋;修改代碼時可以直接修改,可以快速部署,不過性能上會比編譯型語言稍差;比如 JavaScript、Python ;編譯型語言:需要通過編譯器將原始碼編譯成機器碼才能執行;編譯之後如果需要修改代碼,在執行之前就需要重新編譯。
  • 打工人打工魂,打工的必會java調用python的幾種用法
    本文轉載自【微信公眾號:五角錢的程式設計師,ID:xianglin965】,經微信公眾號授權轉載,如需轉載與原文作者聯繫圖丨pexelsjava調用python的幾種用法(看這篇就夠了)在java類中直接執行python語句準備工作:創建maven工程,結構如下:到官網https://www.jython.org
  • 世界排行第一的程式語言:java迎來25歲生日
    面向對象的Java以其「一次編寫,隨處運行」的可移植性而聞名,因為Java虛擬機支持多種硬體平臺和作業系統以及Java applet可以從網頁上運行。Java小程序多年來提供號稱優於JavaScript的性能,但後者最終受到瀏覽器製造商的青睞,並於 2018 年將Java從瀏覽器中刪除。
  • (提高Java代碼質量)|25個優化Java代碼的小技巧
    反例:4.集合初始化儘量指定大小java 的集合類用起來十分方便,但是看源碼可知,集合也是有大小限制的。每次擴容的時間複雜度很有可能是 O(n) ,所以儘量指定可預知的集合大小,能減少集合的擴容次數。反例:正例:5.字符串拼接使用 StringBuilder一般的字符串拼接在編譯期 java 會進行優化,但是在循環中字符串拼接,java 編譯期無法做到優化,所以需要使用 StringBuilder 進行替換。
  • 跟我學java編程—認識java語言的字符類型
    用記事本打開「CharSample.java」文件,輸入以下代碼:編譯「CharSample.java」文件,在命令行窗口輸入「javac CharSample.java」並執行命令,編譯通過後,在命令行窗口輸入「java CharSample」運行Java程序,命令行窗口顯示如下信息:
  • 開發崗位這麼多,為什麼選Java?你學Java了嗎-開課吧
    富者越富,市場的正反饋讓Java被更多的公司採用,從而需要更多的Java程式設計師。軟體開發可以使用的語法是非常多,但是為什麼Java被廣泛的使用呢?其他程式語言與Java相比,Java語法相對簡單,並且是很多計算機語言的基礎。
  • 用經典案例來幫助初學者解析Java的「多態」
    概念:Java中這種相同類型的對象(或說是「變量」)、調用了相同的方法、執行的具體代碼卻不同、運行的結果也不同的現象,我們稱之為「多態」!這裡理論上的東西咱就先往後放一放,咱們先看看案例中的具體代碼、品一品、悟一悟、回味回味,可能就已經透徹了很多!
  • Java常見內存溢出異常分析
    編譯運行上述代碼後, 會有如下輸出:   》》》java -Xms10m - Xms10m-XX:HeapDumpOnOutOfMemoryError com.test.OutOfMemoryErrorTest 16-10-12 10:28   java.lang.OutOfMemoryError:Java heap
  • Java NIO 基礎知識
    在 Java 領域,一般性的文件操作確實只需要和 java.io 包打交道就可以了,尤其對於寫業務代碼的程式設計師來說。不過,當你寫了兩三年代碼後,你的業務代碼可能已經寫得很溜了,蒙著眼睛也能寫增刪改查了。這個時候,也許你會想要開始了解更多的底層內容,包括並發、JVM、分布式系統、各個開源框架源碼實現等,處於這個階段的程式設計師會開始認識到 NIO 的用處,因為系統間通訊無處不在。
  • Java面試高頻考點:反射機制使用大全
    例如Spring的IOC實現機制,其底層都是依賴於java的反射機制,因此,這是一個非常重要的知識點。對於初學java的同學來說,掌握其使用方法很有必要。什麼是java中的反射?在JVM運行狀態下,對於任意一個類,都能夠知道這個類的所有屬性和方法;對於任意一個對象,都能夠訪問其屬性、調用其方法,並可操作類或對象的內部屬性;這種動態獲取、動態調用對象的方法稱為Java的反射機制。讀過生硬的文字解釋,讓我們用代碼來解讀反射機制到底是怎麼一回事。首先看常規方法如何操作一個類。
  • 跟我學java編程—認識java的整數類型
    示例1: 整型常量聲明:當整型數據數值大小超出了可以表示的範圍,而程序中又沒有做數值範圍的檢查時,這個整型變量所輸出的值將發生紊亂,且不是預期的運行結果,這種現象稱為溢出。示例2:int類型的溢出在D盤Java目錄下,新建「OverFlow.java」文件。
  • Java8 lambda表達式
    匿名內部類是為了讓java程式設計師傳遞行為和傳遞數據一樣容易,不幸的是,他們並不容易,為了調用處理邏輯的代碼仍然有四行模板代碼,重複的模板代碼並不是唯一的問題,這種代碼也難以閱讀,我們並不想傳遞一個對象,而僅僅只需要傳遞某種行為,在java8中我們可以寫得更簡潔不同於傳遞一個實現某個接口的對象,我們傳遞了一段沒有命名函數的代碼
  • Java基礎教程:java反射機制教程
    Java反射說的是在運行狀態中,對於任何一個類,我們都能夠知道這個類有哪些方法和屬性。很多動力節點的學員在面試中都會被問到Java反射機制這個問題,為了幫助大家更好的掌握這個知識點,小編整理了一些資料分享給大家。
  • Java編程中基礎反射詳細解析
    反射機制允許程序在運行時取得任何一個已知名稱的class的內部信息,包括包括其modifiers(修飾符),fields(屬性),methods(方法)等,並可於運行時改變fields內容或調用methods。那麼我們便可以更靈活的編寫代碼,代碼可以在運行時裝配,無需在組件之間進行原始碼連結,降低代碼的耦合度;還有動態代理的實現等等。
  • 提升java編程性能優化知識 程式設計師必看這幾點
    對於學習java的學子也是如此,那麼java程式設計師如何提高編程性能呢,有哪些小知識或者技巧呢,怎麼樣才能在編程性能優化方面有所提升呢?  1.儘量在合適的場合使用單例  使用單例可以減輕加載的負擔,縮短加載的時間,提高加載的效率,但並不是所有地方都適用於單例,簡單來說,單例主要適用於以下三個方面:
  • 為什麼不推薦使用try-catch-finally處理java異常?
    這篇文章是我近期看了《Effective java》一書中總結的,來自其中第九條。為了對其理解的更加透徹,因此重新分析了一下,並加入了一些其他點。「本文的所有例子均在本地代碼運行完畢基於JDK版本1.8,運行環境eclipse本文類名:TryWithResources,下文的堆棧信息也以此為基礎」在java開發中,一些網絡連結或者是文件資源都需要程式設計師去手動調用close方法關閉,比如InputStream、OutputStream和java.sql.Connection
  • 如何用java判斷一個數是不是質數?
    哈嘍大家好,這是java小白成長記!昨天分享了怎麼判斷一個數是不是迴文數,目的是為了鞏固一下if選擇語句和求餘數運算符,今天分享一下怎麼判斷一個數是不是質數,可以鞏固for循環、if選擇語句、還有沒怎麼使用過的基本數據類型Boolean。
  • Springmvc框架對json的支持 Java程式設計師必看
    但是在寫json的時候還是出現了許多問題,用eclipse寫js代碼經常除了bug卻不知道問題出來哪裡。我們可以使用alert()或console.info()函數來進行判斷問題出在哪一行上。解決了發送json的問題後,又遇到一個bug,發送的json字符串返回的確實一個http415錯誤代碼,大概是說後端接收的參數類型不正確的意思,下面請看java代碼。