JVM入门到放弃之基本概念
1. 基本概念
jvm 是可運行Java代碼的假想計算機,包括一套字節碼指令集、一組寄存器、一個棧、一個垃圾回收堆和一個存儲方法域。
jvm 是運行在操作系統之上的,屏蔽了與具體操作系統平臺相關的信息,使得Java程序只需生成在 jvm 上運行的字節碼,就可以在多種平臺上不加修改地運行。
Java 語言的一個非常重要的特點就是與平臺的無關性(跨平臺),其得益于 jvm,不是 Java 實現的跨平臺,而是 jvm 的跨平臺性,進而描述 Java 是跨平臺的。
我們知道,每個平臺的 api 肯定是不同的,就好比,android 實現動畫繪制肯定跟 ios 實現動畫繪制不同。jvm 通過 jit 即時編譯器解釋執行 Java 代碼,最終得到相同的字節碼,所以才有了經典的 "writ once,run anywhere"
通俗理解:jvm 運行在操作系統之上,其通過編譯器解釋執行 Java 代碼得到相同的字節碼,實現跨平臺性,進而描述 Java 語言跨平臺。
2. 運行過程
.java 源文件通過編譯器(假裝javac),能夠產生相應的 .class 文件(字節碼文件),
而字節碼文件又通過 jvm 中的解釋器,編譯成計算機真正識別的機器碼(二進制01)。
- java源文件 > 編譯器 > 字節碼文件
- 字節碼文件 > 解釋器 > 機器碼
每一種平臺的解釋器是不同的,但是實現的虛擬機是相同的,這也就是為什么 Java 能夠實現跨平臺的原因,當一個程序從開始運行時,虛擬機就開始實例化了,多個程序啟動,則存在多個虛擬機實例。程序退出或者關閉,則虛擬機實例消亡,多個虛擬機實例之間的數據不能共享。
通俗理解:java源文件通過編譯器得到字節碼文件,字節碼再通過解釋器獲得計算機真正可識別的機器碼。
3. 內存管理
對于 Java 程序員來說,在 jvm 自動內存管理機制幫助下,不需要在為每一個 new 操作去寫配對的 delete/free 代碼,正常情況下是不容易出現內存泄漏和內存溢出的問題。
jvm 在執行 Java 程序的過程中會把它所管理的內存劃分為若干個不同的數據區域,如圖所示。
針對上圖對運行時數據區域組成部分做簡單描述。
1.程序計數器
用于記錄當前線程所執行到的字節碼的行號。
怎么理解?舉個簡單的例子:
每一個線程都是順序執行單元,就如同上圖標記的行號一樣,是向下順序指定的,而程序計數器,就是用于標記行號的,遇到 if/else 則跳過不執行的行號,比如第 16 行是跳過的。
2.java虛擬機棧
虛擬機棧是為虛擬機執行 java 方法服務的,在了解虛擬機棧之前我們先了解一下 棧幀與局部變量表:
棧幀:每個方法執行都會創建一個棧幀,伴隨著方法從創建到執行完成。用于存儲局部變量表,操作數棧,動態鏈接,方法出口等。下圖為方法執行過程:
方法不停地調用,不停地進棧,如果棧內存滿了,就會 Stack Overflow Error 或者 Out of Memory
局部變量表:
在網上我們經常看到有人把 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.給對象分配內存[位于棧中]
對象的創建一般從 new 指令開始的,jvm 首先對符號引用進行解析,如果找不到對應的符號引用,那么這個類還沒有被加載,因此jvm便會進行類加載過程(關于類加載后面單獨文章講解),符號引用解析完畢之后,jvm 會為對象在堆中分配內存。
2.線程安全性問題
描述一種場景:正在給 A 對象分配內存,指針還沒來得及修改,對象 B 又同時使用了原來的指針來分配內存的情況。
兩種解決方案:
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
總結
以上是生活随笔為你收集整理的JVM入门到放弃之基本概念的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 从头认识Spring-1.7 如何通过属
- 下一篇: C语言复变函数PPT,C语言中如何应用复