java和jvm_java 和 JVM
C++和Java的區別
指針:java中不存在指針的概念,編程者無法直接通過指針來直接訪問內存,有利于維護java程序的安全
多重繼承:C++支持多重繼承,java不支持多重繼承,但是允許一個類繼承多個接口來實現多重繼承的問題
數據類型和類:java是完全面向對象的語言,所有的函數和變量必須是類的一部分,而C++中允許將函數和變量設置為全局,兼具面向過程和面向對象的特點
內存管理:Java中由系統進行自動的內存管理和回收,C++中需要程序員手動釋放內存資源。當Java中的一個對象不會再用到的時候,無用內存會給它貼上標簽以示刪除,Java的GC過程是以線程的形式在后臺運行,利用空閑時間工作
操作符重載:Java不支持操作符重載,C++支持操作符重載,也是C++一個突出特征
預處理功能:Java不支持預處理,C++有一個預編譯階段,Java沒有預處理器,但它提供了import與C++預處理器具有類似功能
Java不支持缺省函數參數
字符串:字符串變量
類型轉換:C++中有數據類型隱含轉換的機制,Java中需要限時強制類型轉換
異常:Java中異常機制用于捕獲例外事件,增強系統的容錯能力
Java為解釋型語言,程序源代碼通過Java編譯器編譯成字節碼,然后由jvm解釋執行,C++為編譯型語言,源代碼通過編譯鏈接后直接生成可執行的二進制代碼,可直接執行,因此java的執行速度比C++要慢,但是Java可以跨平臺
Java具有平臺無關性,對每種數據類型都分配了固定長度的空間,但是C++不同,在不同的平臺上會分配不同的字節數
Java跨平臺的原因
第一點: 我們通常將CPU處理器和操作系統的整體稱之為平臺,不同的CPU中可能使用不同的指令集(指令集是CPU中用于計算和控制計算機系統的一套指令的集合),而不同操作系統支持不同CPU的指令集
C語言的編譯過程:windows下通過VS編譯成exe文件,Linux下通過gcc編譯成elf文件,但是window所編譯的exe是不能在linux上運行的,因此結論是:編譯器是與平臺相關的,編譯后的文件也是與平臺相關的,我們所說的跨平臺是指編譯后的文件跨平臺,而不是源程序跨平臺,這點要注意。
對于Java而言,源程序為.java文件,通過與平臺無關的編譯器編譯成與平臺無關的中間碼,也就是.class文件,中間碼再由解釋器(也就是jvm)解釋執行,注意解釋器是與平臺相關的,也就是不同平臺需要不同的解釋器。
JVM:Java虛擬機
1. 基本特性
JRE由Java api和JVM組成,JVM通過classloader來加載類應用,通過JavaAPI來執行
基于棧結構的虛擬機
符號引用:除了基本類型外所有的Java類型都是通過符號引用來取得關聯,而不是通過顯式內存地址的引用
垃圾回收機制:顯式創建,通過GC自動回收
明確界定基本類型字節長度保證平臺的無關性
網絡字節序: 基于大端的字節序
2. Java程序的執行過程
類加載將Java字節碼載入到運行時數據區,執行引擎負責Java字節碼執行;
3. 類加載
Java提供了動態加載的特性,只有在運行時第一次遇到類時才會去加載和鏈接,而非在編譯時加載它。JVM的類加載器負責類的動態加載過程。Java類加載器的特點如下:
層次結構:Java的類加載器按是父子關系的層次結構組織的。Boostrap類加載器處于層次結構的頂層,是所有類加載器的父類。
委派模式:基于類加載器的層次組織結構,類加載器之間是可以進行委派的。當一個類需要被加載,會先去請求父加載器判斷該類是否已經被加載。如果父類加器已加載了該類,那它就可以直接使用而無需再次加載。如果尚未加載,才需要當前類加載器來加載此類。
可見性限制:子類加載器可以從父類加載器中獲取類,反之則不行。
不能卸載: 類加載器可以載入類卻不能卸載它。但是可以通過刪除類加載器的方式卸載類。
每個類加載器都有自己的空間,用于存儲其加載的類信息。當類加載器需要加載一個類時,它通過FQCN)(Fully Quanlified Class Name: 全限定類名)的方式先在自己的存儲空間中檢測此類是否已存在。在JVM中,即便具有相同FQCN的類,如果出現在了兩個不同的類加載器空間中,它們也會被認為是不同的。存在于不同的空間意味著類是由不同的加載器加載的。
當JVM請示類加載器加載一個類時,加載器總是按照從類加載器緩存、父類加載器以及自己加載器的順序查找和加載類。也就是說加載器會先從緩存中判斷此類是否已存在,如果不存在就請示父類加載器判斷是否存在,如果直到Bootstrap類加載器都不存在該類,那么當前類加載器就會從文件系統中找到類文件進行加載。
Bootstrap加載器:Bootstrap加載器在運行JVM時創建,用于加載Java APIs,包括Object類。不像其他的類加載器由Java代碼實現,Bootstrap加載器是由native代碼實現的。
擴展加載器(Extension class loader):擴展加載器用于加載除基本Java APIs以外擴展類。也用于加載各種安全擴展功能。
系統加載器(System class loader):如果說Bootstrap和Extension加載器用于加載JVM運行時組件,那么系統加載器加載的則是應用程序相關的類。它會加載用戶指定的CLASSPATH里的類。
用戶自定義加載器:這個是由用戶的程序代碼創建的類加載器。
像Web應用服務器(WAS: Web Application Server)等框架通過使用用戶自定義加載器使Web應用和企業級應用可以隔離開在各自的類加載空間獨自運行。也就是說可以通過類加載器的委派模式來保證應用的獨立性。不同的WAS在自定義類加載器時會有略微不同,但都不外乎使用加載器的層次結構原理。
如果一個類加載器發現了一個未加載的類,則該類的加載和鏈接過程如下圖
每一步的具體描述如下:
加載(Loading): 從文件中獲取類并載入到JVM內存空間。
驗證(Verifying): 驗證載入的類是否符合Java語言規范和JVM規范。在類加載流程的測試過程中,這一步是最為復雜且耗時最長的部分。大部分JVM TCK的測試用例都用于檢測對于給定的錯誤的類文件是否能得到相應的驗證錯誤信息。
準備(Preparing): 根據內存需求準備相應的數據結構,并分別描述出類中定義的字段、方法以及實現的接口信息。
解析(Resolving): 把類常量池中所有的符號引用轉為直接引用。
初始化(Initializing): 為類的變量初始化合適的值。執行靜態初始化域,并為靜態字段初始化相應的值。
4. 運行時數據區
運行時數據區是JVM運行時操作系統分配的內存區域,運行時數據區可分為6部分,即:為每個線程分別創建的PC寄存器,JVM棧,本地方法棧,和被所有線程共用的數據堆,方法區,和運行時常量池。
PC寄存器:每一個線程都會有一個Program counter寄存器,隨著線程啟動而創建,其中存放要執行的JVM指令地址;
JVM棧:每一個線程都會有一個JVM棧,隨著線程啟動而創建,其中存儲的數據元素為棧幀,在JVM中一旦有方法執行,JVM都會為之創建一個棧幀,并添加到當前現成的JVM棧中,當方法運行結束后,棧幀也會隨之移除。棧幀中保存著對本地變量數組,操作數棧和屬于當前運行方法的運行時常量池的引用。
本地方法棧:為非Java編寫的本地程序定義的棧空間,也就是說它基本上是用于通過JNI(Java Native Interface)方式調用和執行的C/C++代碼。根據具體情況,C棧或C++棧將會被創建。
方法區:方法區是被所有線程共用的內存空間,在JVM啟動時創建。它存儲了運行時常量池、字段和方法信息、靜態變量以及被JVM載入的所有類和接口的方法的字節碼。不同的JVM提供者在實現方法區時會通常有不同的形式。在Oracle的Hotspot JVM里方法區被稱為Permanent Area(永久區)或Permanent Generation(PermGen, 永久代)。JVM規范并對方法區的垃圾回收未做強制限定,因此對于JVM實現者來說,方法區的垃圾回收是可選操作。
運行時常量池:一個存儲了類文件格式中的常量池表的內存空間。這部分空間雖然存在于方法區內,但卻在JVM操作中扮演著舉足輕重的角色,因此JVM規范單獨把這一部分拿出來描述。除了每個類或接口中定義的常量,它還包含了所有對方法和字段的引用。因此當需要一個方法或字段時,JVM通過運行時常量池中的信息從內存空間中來查找其相應的實際地址。
數據堆:堆中存儲著所有的類實例或對象,并且也是垃圾回收的目標場所。當涉及到JVM性能優化時,通常也會提及到數據堆空間的大小設置。JVM提供者可以決定劃分堆空間或者不執行垃圾回收。
5. 執行引擎
JVM通過類加載器把字節碼載入運行時數據區是由執行引擎執行的。執行引擎以指令為單位讀入Java字節碼,就像CPU一個接一個的執行機器命令一樣。每個字節碼命令包含一字節的操作碼和可選的操作數。執行引擎讀取一個指令并執行相應的操作數,然后去讀取并執行下一條指令。
盡管如此,Java字節碼還是以一種可以理解的語言編寫的,而不像那些機器直接執行的無法讀懂的語言。所以JVM的執行引擎必須要把字節碼轉換為能被機器執行的語言指令。執行引擎有兩種常用的方法來完成這一工作:
解釋器(Interpreter):讀取、解釋并逐一執行每一條字節碼指令。因為解釋器逐一解釋和執行指令,因此它能夠快速的解釋每一個字節碼,但對解釋結果的執行速度較慢。所有的解釋性語言都有類似的缺點。叫做字節碼的語言人本質上就像一個解釋器一樣運行。
即時編譯器(JIT: Just-In-Time):即時編譯器的引入用來彌補解釋器的不足。執行引擎先以解釋器的方式運行,然后在合適的時機,即時編譯器把整修字節碼編譯成本地代碼。然后執行引擎就不再解釋方法的執行而是通過使用本地代碼直接執行。執行本地代碼較逐一解釋執行每條指令在速度上有較大的提升,并且通過對本地代碼的緩存,編譯后的代碼能具有更快的執行速度。
然而,即時編譯器在編譯代碼時比逐一解釋和執行每條指令更耗時,所以如果代碼只會被執行一次,解釋執行可能會具有更好的性能。所以JVM通過檢查方法的執行頻率,然后只對達到一定頻率的方法才會做即時編譯。
Java垃圾回收機制
1. 哪些內存需要回收?
引用計數法:判斷是否還需要使用,最簡單方法是通過目前是否有引用指向這個對象,如果沒有說明這個對象就不會再使用了,這種通過引用是否存在的方法叫做引用計數法,但是存在一個問題無法解決就是對象循環引用問題。
可達性分析:這個算法的基本思路就是通過一系列的稱為“GC Roots”的對象作為起始點,從這些節點開始向下搜索,搜索所走過的路徑稱為引用鏈(Reference Chain),當一個對象到GC Roots沒有任何引用鏈相連(用圖論的話來說,就是從GC Roots到這個對象不可達)時,則證明此對象是不可用的。
在Java語言中,可作為GC Roots的對象包括下面幾種:
虛擬機棧(棧幀中的本地變量表)中引用的對象。
方法區中類靜態屬性引用的對象。
方法區中常量引用的對象。
本地方法棧中JNI(即一般說的Native方法)引用的對象。
2 如何回收?
step 1: marking 標記
第一步是標記,也就是找到那些需要回收的對象和確定哪些對象不需要回收,所有堆中的對象都會掃描一遍,這通常是一個很耗時的過程。
step2 : normal deletion
垃圾收集器清除掉標記出來的對象區域,簡單的清除帶來的問題是產生大量的不連續的內存碎片,空間碎片太多可能會導致在程序運行過程中需要分配較大對象時,無法找到足夠的連續內存而提前觸發一次垃圾回收
step 2 :improve -- deletion with compacting 壓縮整理
由于簡單的清除可能會存在碎片的問題,所以又出現了壓縮清除的方法,也就是先清除需要回收的對象,然后再對內存進行壓縮操作,將內存分成可用和不可用兩大部分。
3 分代回收
為什么需要分代回收:一個程序中大部分對象都是短命的,因此為了增大GC的效率,將JVM堆分代,分為新生代,老年代和永久代。
新生代:所有new出來的新對象都在新生代,新生代這部分內存滿了之后,就會發起一次GC事件,這種發生在新生代的垃圾回收稱為Minor collections,這種收集相對比較快。
老年代:老年代來存儲存活事件比較長的對象,一般來說,我們會給新生代的對象限定一個存活的時間,當達到這個時間還沒有被收集的時候就會被移動到老年代中。老年代區域的垃圾收集叫做major garbage collection。通常Major garbage collection都相對比較慢,因為老年代的收集包括了對所有對象的收集,也就是同時需要收集新生代和老年代的對象。
永久代:The Permanent generation contains metadata required by the JVM to describe the classes and methods used in the application. The permanent generation is populated by the JVM at runtime based on classes in use by the application. In addition, Java SE library classes and methods may be stored here.
5 分代回收的過程
第一步 所有new出來的對象都會最先分配到新生代區域中,兩個survivor區域初始化是為空的
第二步,當eden區域滿了之后,就引發一次 minor garbage collection
第三步,當在minor garbage collection,存活下來的對象就會被移動到S0survivor區域
第四步,然后當eden區域又填滿的時候,又會發生下一次的垃圾回收,存活的對象會被移動到survivor區域而未存活對象會被直接刪除。但是,不同的是,在這次的垃圾回收中,存活對象和之前的survivor中的對象都會被移動到s1中。一旦所有對象都被移動到s1中,那么s2中的對象就會被清除,仔細觀察圖中的對象,數字表示經歷的垃圾收集的次數。目前我們已經有不同的年齡對象了。
第五步,下一次垃圾回收的時候,又會重復上次的步驟,清除需要回收的對象,并且又切換一次survivor區域,所有存活的對象都被移動至s0。eden和s1區域被清除。
第六步,重復以上步驟,并記錄對象的年齡,當有對象的年齡到達一定的閾值的時候,就將新生代中的對象移動到老年代中。在本例中,這個閾值為8.
第七步,接下來垃圾收集器就會重復以上步驟,不斷的進行對象的清除和年代的移動
最后,我們觀察上述過程可以發現,大部分的垃圾收集過程都是在新生代進行的,直到老年代中的內存不夠用了才會發起一次 major GC,會進行標記和整理壓縮。
Java中對象的生命周期
創建階段(created)
為對象分配存儲空間,開始構造對象,從父類到子類對static成員進行初始化,父類成員變量按照順序初始化,遞歸調用父類的構造方法,子類成員變量按照順序初始化,子類構造方法調用,一旦對象被創建,并有某個引用指向它,這個對象的狀態就切換到了應用階段(In Use)
應用階段(in use)
對象至少被一個強引用持有并且對象在作用域內
不可見階段(Invisible)
程序本身不再持有該對象的任何強引用,但是這些引用可能還存在著;一般具體是指程序的執行已經超過該對象的作用域了
不可達階段(Unreachable)
對象處于不可達階段是指該對象不再被任何強引用所持有。
與“不可見階段”相比,“不可見階段”是指程序不再持有該對象的任何強引用,這種情況下,該對象仍可能被JVM等系統下的某些已裝載的靜態變量或線程或JNI等強引用持有著,這些特殊的強引用被稱為”GC root”。存在著這些GC root會導致對象的內存泄露情況,無法被回收。
收集階段(Collected)
當垃圾回收器發現該對象已經處于“不可達階段”并且垃圾回收器已經對該對象的內存空間重新分配做好準備時,則對象進入了“收集階段”。如果該對象已經重寫了finalize()方法,則會去執行該方法的終端操作。
這里要特別說明一下:不要重載finazlie()方法!原因有兩點:
會影響JVM的對象分配與回收速度
在分配該對象時,JVM需要在垃圾回收器上注冊該對象,以便在回收時能夠執行該重載方法;在該方法的執行時需要消耗CPU時間且在執行完該方法后才會重新執行回收操作,即至少需要垃圾回收器對該對象執行兩次GC。
可能造成該對象的再次“復活”
在finalize()方法中,如果有其它的強引用再次持有該對象,則會導致對象的狀態由“收集階段”又重新變為“應用階段”。這個已經破壞了Java對象的生命周期進程,且“復活”的對象不利用后續的代碼管理。
終結階段
當對象執行完finalize()方法后仍然處于不可達狀態時,則該對象進入終結階段。在該階段是等待垃圾回收器對該對象空間進行回收。
對象空間的重新分配
垃圾回收器對該對象的所占用的內存空間進行回收或者再分配了,則該對象徹底消失了,稱之為“對象空間重新分配階段”。
創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎總結
以上是生活随笔為你收集整理的java和jvm_java 和 JVM的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: java corepoolsize_理解
- 下一篇: java 7 update 17_Jav