轉行學java以前,老是聽着大佬們說着java像個渣男同樣能夠跨平臺,一次編譯處處運行,瞬間,我就堅決了學java的信念,哎呀媽呀,得勁。真的學java以後,好像渣男也不是那麼好學的,尤爲這貨的必殺技,各操做系統(年齡段)通殺太難了,這可激起了個人小暴脾氣,還有能難倒小灝哥的,終於在我兢兢業業的努力下,發現了一個道理,還真有。隨着慢慢攻克,看書,看享學老師的視頻課,也瞭解了些皮毛,寫出來當作個筆記吧。java
學習的第一步,就是去oracle官網下載,下載完打開發現有倆目錄jdk和jre,這就尷尬了,我要學的不是jvm虛擬機嗎,我要學的不是他那到處可留情的必殺技嗎,咋越整越多了,這三個東西困擾了我好久,背過,半個月就忘,這可咋整,我但是要當海賊王的男人,怎麼能被區區jvm,jdk,jre難倒呢,耳邊響起了某位大佬的二字真言:理解。背是行不通了,再花點時間鑽研鑽研吧。先百度瞅瞅:安全
jvm:Java Virtual Machine(java虛擬機)數據結構
jre:Java Runtime Environment(java運行環境)多線程
jdk:java development kit (java開發工具包)oracle
仍是沒太搞懂是啥,先看一下java下載下來的jdk和jre有啥吧,先打開jdk,jvm
裏面有一些目錄和文件,其中一個好像很熟悉的樣子-----jre,咦,這個不是在外層目錄嗎,難道他會瞬移,退回上級目錄:函數
還好,他還在,點開兩個jre:工具
基本上是同樣的,我就猜這jdk是否是就是包含jre的呢,否則這oracle官網下載下來的東西,還能有問題?學習
這時候,大牛們就該讀源碼了,而我則繼續百度,發現了一張圖:開發工具
這張圖應該是比較全面了,咱們一般說的jdk是包含jre的,而一般說的jre是包含jvm的,jvm運行在os操做系統之上,而像一些開發工具,則是在這jdk基礎之上,作了更多的完善,讓咱們更方便的去開發代碼。
瞭解了他們的關係以後,再逐一瞭解他們的功能:
你們應該都知道,jvm是個渣男,他可不僅是java的jvm,不少語言均可以在jvm上運行,只要你能夠編譯成class文件,我均可以任你在個人肉體上運行,它的最大功能也就是解釋執行class文件。
jre比起jvm來講,稍微收斂一點,他包含了運行class文件須要的類庫,他雖然也渣,可是好歹是知道老婆仍是java,只不過背地裏在什麼系統上鬼混就說不許了。
jdk對於java開發來講,最直接面對的,也是最熟悉的,jdk包含了jre,是咱們開發用的,其實沒有jdk也能運行程序了,可是做爲一個開發,沒有jdk,你寫的代碼不能編譯成class也沒用。
咱們如今也算是知道這三個渣男有啥關係,分別有啥用,對於學習java虛擬機來講,也算是打過招呼了,那麼接下來,就先深刻了解一下jvm的內存是怎麼劃分的:
線程私有區域:本地方法站,虛擬機棧,程序計數器
線程共享區域:方法區,堆,運行時常量池
直觀點的能夠看下上面的內存區域圖,細心點的會發現,這個圖比我上面寫的多了一個叫直接內存的灰色地帶,他但是不被外界認可的備胎,他不屬於運行時數據區,可是卻又常常會用到他,備胎功底雄厚,後面介紹完了這些正牌,也介紹一下他。
虛擬機棧:
首先來聊一下虛擬機棧,你們都知道,棧的數據結構是先進後出,每一個線程都會有本身的一塊虛擬機棧,虛擬機棧裏面是一個個的棧幀,在每次方法調用時,就會建立一個棧幀並將對應的棧幀壓棧,方法執行完出棧,就這樣跟着方法調用的節奏頻繁的一進一出,在設計的時候,也是考慮了方法調用的模型。
棧幀裏面主要包含局部變量表,操做數棧,動態鏈接和返回地址:
局部變量表: 用來存放八大基本類型和局部變量對象引用的地方,咱們在方法執行的時候,傳參、定義的局部變量都會存放到這裏,供計算時,操做數棧存取,它是一個32位的長度,通常的32位也能夠放的下,放不下的,佔64位的高低位存放。
操做數棧:在執行計算操做時,會從局部變量表裏面獲取數據(可使任意java數據類型)放到操做數棧,而後計算完成再從操做數棧移除,並存入局部變量表。
動態鏈接:這貨有點牛,java從入門到放棄都知道這個-->多態,後面聊動態分派時會詳細聊到。
返回地址:正常調用程序計數器的返回地址做爲返回,具體操做:
1.恢復局部變量表和操做數棧
2.返回值壓入調用者操做數棧
3.調整程序計數器指向下一個命令處
其實這一塊理解不夠透徹的能夠想象一下方法的調用,好比我在一個方法裏面調用另外一個方法,我在方法調用完成出來以前首先要將這個棧幀的數據清掉,返回值天然是外層方法要用到的了,而後呢,就要調用下一步了,天然要調整程序計數器的指向。
異常返回經過異常處理器表來肯定。
程序計數器:
上面說了,程序計數器是線程私有的,他是記錄了當前線程執行的字節碼行號地址,因爲 Java 是多線程語言,當執行的線程數量超過 CPU 核數時,線程之間會根據時間片輪詢爭奪 CPU 資源。若是一個線程的時間片用完了,或者是其它緣由致使這個線程的 CPU 資源被提早搶奪,那麼這個退出的線程就須要單獨的一個程序計數器,來記錄下一條運行的指令,若是沒有程序計數器的保證,線程沒法保證當前程序順序執行。
相比於普通方法,native方法是特殊的,他的執行是否是jvm具體管理的,因此jvm的程序計數器在執行native方法的時候,一直保持爲空,它的調用是操做系統層面的程序計數器來記錄的。
本地方法棧:
跟虛擬機棧沒啥區別,虛擬機棧執行普通方法,本地方法棧執行native方法
上面介紹完了線程私有的jvm內存區域劃分,其實對於線程私有的對於天生多線程的java,是否也是一個天生線程安全的方式呢,由jvm自行處理,又不用加鎖就能夠實現線程安全,我以爲也是咱們在設計線程安全須要考慮的方向吧。
接下來開始介紹線程共享的,首先來介紹方法區:
方法區:
方法區是線程共享的,他主要存儲類的結構信息,運行時常量池(1.8之後放到堆裏面了,但其實他的定位應該仍是屬於方法區的範疇),構造函數,方法數據等,總的來講其實就是存放初始化時須要用到的數據。
方法區和永久代以及元空間是很容易搞混的,尤爲是永久代和方法區,其實方法區至關因而jvm對內存的邏輯劃分,而永久代和元空間是jdk1.7之前和jdk1.81之後分別對方法區的不一樣實現,咱們之前用過jdk1.6版本,公司很摳門,都是2G的內存,使用默認的配置常常會出現 java.lang.OutOfMemoryError: PermGen異常,相信這個異常不少人都很熟悉,其實這個就是永久代內存溢出,而如今,咱們通常都用16g內存,並且jdk1.8以後的元空間不設置參數只受本機內存的限制,想要體驗一下的同窗能夠在vm參數加上-XX:MaxMetaspaceSize=1M,就能很快看到OutOfMemoryError: Metaspace,可見二者實現仍是不同的。
看到這裏,oracle官方爲啥要將永久代棄掉而改用元空間呢,首先按官方來講,是爲了融合HotSpot和JRockit,由於JRockit沒有永久代,其次的話,永久代必需要配置分配空間,而過小很容易內存溢出,太大又太佔空間,爲其分配多少內存很難肯定(永久代的大小依賴因素太多,如常量池大小,方法的大小,class等)永久代在每次FullGc均可能唄回收,而回收率很低,而元空間存儲在本地內存,不須要考慮這麼多。
運行時常量池:
運行時常量池就是用來將class常量池的符號引用轉爲直接引用,具體後面分析常量池會細講。
堆:
堆是jvm上最大的一塊區域,也是咱們後面聊到的垃圾回收器GC主要光顧的地方,由於咱們幾乎全部的對象(幾乎是由於會存在棧上分配以及八大基本對象等,但這些畢竟是小部分),堆給人的感受就是個沙盤,承載了那麼多的對象就像是一個個沙兵,而這些對象的引用就像是一根根鏈接沙兵的線都放在棧上(結合上面說的其實就是放到局部變量表裏,這時候就有問題了,局部變量表是線程私有的,那咱們常說的線程安全還須要考慮嗎,其實就是由於這邊存放的是引用,別的線程也拿到一個引用指向這個對象,那還不是隨便改),棧就像是個軍師同樣,在沙盤上指點着幹掉這個引用,垃圾回收器回收的時候就會把這些對象給幹掉(固然不止這麼簡單,垃圾回收後面也會細講)。
直接內存:
直接內存又叫堆外內存,jvm運行時會首先申請一塊塊內存,分配給堆、棧、方法區等,而jvm沒有申請的內存,就是直接內存,其實就是jvm管不到的地方,很神奇哈,jvm管不到的地方,爲啥要了解呢,由於隨着java生態的發展,咱們不只僅要用好申請的資源,沒申請的有機會也要用好,提高資源的利用率,好比說使用NIO的話,就會頻繁用到這一塊,能夠用java的directByteBuffer 對象操做,也能夠用-XX:MaxDirectMemorySize來設置它的大小。
這一篇簡單介紹了一下jvm的輪廓,相信看完以後,對jvm大致上有一個全面的瞭解了吧,jvm是是比較基礎的知識,枯燥無味,不少東西看不見,摸不着,平時可能還用不到,但學好java,仍是必需要有這方面的基礎的,但願看到我文章的有緣人一塊兒學習,共同進步吧。