JVM入門到放棄之基本概念

1. 基本概念

jvm 是可運行Java代碼的假想計算機,包括一套字節碼指令集、一組寄存器、一個棧、一個垃圾回收堆和一個存儲方法域。java

jvm 是運行在操做系統之上的,屏蔽了與具體操做系統平臺相關的信息,使得Java程序只需生成在 jvm 上運行的字節碼,就能夠在多種平臺上不加修改地運行。android

Java 語言的一個很是重要的特色就是與平臺的無關性(跨平臺),其得益於 jvm,不是 Java 實現的跨平臺,而是 jvm 的跨平臺性,進而描述 Java 是跨平臺的。ios

咱們知道,每一個平臺的 api 確定是不一樣的,就比如,android 實現動畫繪製確定跟 ios 實現動畫繪製不一樣。jvm 經過 jit 即時編譯器解釋執行 Java 代碼,最終獲得相同的字節碼,因此纔有了經典的 "writ once,run anywhere"程序員

通俗理解:jvm 運行在操做系統之上,其經過編譯器解釋執行 Java 代碼獲得相同的字節碼,實現跨平臺性,進而描述 Java 語言跨平臺。算法

2. 運行過程

.java 源文件經過編譯器(僞裝javac),可以產生相應的 .class 文件(字節碼文件),
而字節碼文件又經過 jvm 中的解釋器,編譯成計算機真正識別的機器碼(二進制01)。編程

  • java源文件 > 編譯器 > 字節碼文件
  • 字節碼文件 > 解釋器 > 機器碼

每一種平臺的解釋器是不一樣的,可是實現的虛擬機是相同的,這也就是爲何 Java 可以實現跨平臺的緣由,當一個程序從開始運行時,虛擬機就開始實例化了,多個程序啓動,則存在多個虛擬機實例。程序退出或者關閉,則虛擬機實例消亡,多個虛擬機實例之間的數據不能共享。api

通俗理解:java源文件經過編譯器獲得字節碼文件,字節碼再經過解釋器得到計算機真正可識別的機器碼。數組

3. 內存管理

對於 Java 程序員來講,在 jvm 自動內存管理機制幫助下,不須要在爲每個 new 操做去寫配對的 delete/free 代碼,正常狀況下是不容易出現內存泄漏和內存溢出的問題。安全

jvm 在執行 Java 程序的過程當中會把它所管理的內存劃分爲若干個不一樣的數據區域,如圖所示。微信

針對上圖對運行時數據區域組成部分作簡單描述。

1.程序計數器

用於記錄當前線程所執行到的字節碼的行號。
怎麼理解?舉個簡單的例子:


每個線程都是順序執行單元,就如同上圖標記的行號同樣,是向下順序指定的,而程序計數器,就是用於標記行號的,遇到 if/else 則跳過不執行的行號,好比第 16 行是跳過的。
2.java虛擬機棧

虛擬機棧是爲虛擬機執行 java 方法服務的,在瞭解虛擬機棧以前咱們先了解一下 棧幀局部變量表

棧幀:每一個方法執行都會建立一個棧幀,伴隨着方法從建立到執行完成。用於存儲局部變量表,操做數棧,動態連接,方法出口等。下圖爲方法執行過程:

方法不停地調用,不停地進棧,若是棧內存滿了,就會 Stack Overflow Error 或者 Out of Memory

局部變量表:

  1. 存放編譯器可知的各類基本數據類型、引用類型。
  2. 局部變量表的內存空間在編譯器完成分配,當進入一個方法時,這個方法須要幀分配多少內存是固定的,在方法運行期間是不會改變局部表量表的大小,局部變量表存放的是對象的引用。

在網上咱們常常看到有人把 java 內存分爲 堆內存棧內存,這種分法是比較廣義的,內存的實際劃分是比較複雜的。這種劃分方式的流行只能說大多數人關注的是:與對象內存分配關係最親密的兩塊內存區域(棧堆)。其中 棧內存 就是上方的虛擬機棧,或者說是虛擬機棧中局部變量表部分。

3.本地方法棧

本地方法棧是爲 native 方法服務的。

native方法:

  • 簡單地講,一個 native 方法就是一個 Java 調用非 Java 代碼的接口。
  • 一個 native 方法是這樣一個 Java 的方法:該方法的實現由非 Java 語言實現,好比 C。
    這個特徵並不是 Java 所特有,不少其它的編程語言都有這一機制,好比在 C++ 中,你能夠用extern "C"告知C++編譯器去調用一個 C 的函數。
4.java堆

Java 堆是 jvm 所管理的內存中最大的一塊,全部的對象實例以及數組都要在堆上分配。

由於分的蛋糕比較大,當然成爲 gc(垃圾回收器)常常光顧的主要區域。

因爲如今的收集器基本都採用分代收集算法,因此 Java 堆能夠細分爲:新生代、老年代、永久代(java8中移除了永久代),這一塊後面會單獨寫一篇關於垃圾回收器的文章,暫時有個印象便可。

5.方法區

方法區與 java 堆同樣,是各個線程共享的內存區域,用於存儲已被虛擬機加載的類信息、常量、靜態變量、即時編譯器編譯後的代碼等數據。

儘管 jvm 規範把方法區描述爲堆的一個邏輯部分,可是它卻有一個別名叫非堆,目的區分堆。

4. 對象的建立

對象的建立過程:

  1. 給對象分配內存
  2. 線程安全性問題
  3. 初始化對象
  4. 執行構造方法
1.給對象分配內存[位於棧中]

對象的建立通常從 new 指令開始的,jvm 首先對符號引用進行解析,若是找不到對應的符號引用,那麼這個類尚未被加載,所以jvm便會進行類加載過程(關於類加載後面單獨文章講解),符號引用解析完畢以後,jvm 會爲對象在堆中分配內存。

2.線程安全性問題

描述一種場景:正在給 A 對象分配內存,指針還沒來得及修改,對象 B 又同時使用了原來的指針來分配內存的狀況。

兩種解決方案:

  1. 對內存分配內存空間的動做進行同步處理。
  2. 把內存分配動做按照線程劃分在不一樣的空間之中進行。
3.初始化對象

對象建立後一般有個默認值。
jvm 爲對象分配完堆內存以後,jvm 會將該內存進行零值初始化,這也就解釋了爲何 Java 的屬性字段無需顯示初始化就能夠被使用,而方法的局部變量卻必需要顯示初始化後才能夠訪問。

4.執行構造方法

執行完上方三步,jvm 會調用對象的構造函數。

至此,一個對象就被建立完畢。

5. 最後總結

jvm 的學習是比較枯燥乏味的,基本都是一些概念性問題,上文對 jvm 的基本概念以及內存區域作了簡單的介紹。
工欲善其事,必先利其器。儘管 jvm 的學習很是無聊,可是卻很是重要,下面我用白話文根據本身的理解針對本文作一個總結。

上邊有提到 "jvm 的跨平臺性,實現了java語言的跨平臺" ,何爲跨平臺?通俗理解就是: 一個操做系統下開發的應用,放到另外一個操做系統下依然能夠運行。舉一個生動一點的例子,用 eclipse/idea 開發的 Java 程序,能夠同時運行在 Linux、Windows 等操做系統上,即所謂的 "Write once, run anywhere(一次編寫,處處運行)" 。

從廣義的層次理解了 jvm 的跨平臺性,接着是上文提到的 jvm 內存管理,jvm 有一套 自動內存管理機制,在該機制的幫助下,一般咱們是不須要去關注對象的 內存分配以及釋放的,而後這套內存管理機制,會把他所管理的內存劃分爲不一樣的運行時數據區域,而運行時數據區域由線程獨立以及線程共享兩部分組成。

說到線程,何爲線程呢?說到線程又不得不提進程….(果真坑越描越大)
這裏所說的線程是指程序執行過程當中的一個線程實體,在講程序計數器時有提到,每個線程都是順序執行單元,就比如一個簡單的main方法,是按照從上到下的順序執行的。線程之間又是有所區分的,好比虛擬機線程、gc線程、編譯器線程等,jvm 容許一個應用併發執行多個線程,就是所謂的多線程。

關於線程、進程、多線程的關係。
咱們將王小工做的車間理解爲進程,而線程則理解爲車間裏的工人,一個車間裏確定有不少工人 ,他們協同完成一個任務,也就是多線程完成一個進程。
咱們結合車間與工人,再來看看上邊提到的 "線程獨立" 與 "線程共享"。
線程共享:車間的空間是工人們共享的,好比許多房間是每一個工人均可以進出的。這表示一個進程的內存空間是共享的,每一個線程均可以使用這些共享內存。
線程獨立:王小所在的車間,只有一個廁所,而廁所的空間最多隻能容納一我的,進入廁所後,是須要上鎖的,這樣別人纔不能進來。裏面有人的時候,其餘人就不能進去,這表明一個線程使用某些共享內存時,其餘線程必須等它結束,才能使用這一塊內存。

瞭解了運行時數據區域劃分的線程概念,接着就是內存的各個組成區域,主要了解到 java堆,堆是用來存放對象實例以及數組的。每當你 new 一個對象,都是要在堆上拉取一塊內存區域的,一般一個程序要 new(實例化)不少對象,無形中帶來了內存負擔,因此就引出了 gc 垃圾回收的概念,咱們能夠將堆中的對象理解爲 gc 的獵物,很顯然,對於富的流油的堆來講,天然成了 gc 主要的光顧對象。在這個地方咱們提到了 新生代、老年代、永久代的概念,下一篇單獨講解。

若是文章有錯的地方歡迎指正,你們互相交流。習慣在微信看技術文章,想要獲取更多的Java資源的同窗,能夠關注微信公衆號:niceyoo

相關文章
相關標籤/搜索