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爲解釋型語言,程序源代碼經過Java編譯器編譯成字節碼,而後由jvm解釋執行,C++爲編譯型語言,源代碼經過編譯連接後直接生成可執行的二進制代碼,可直接執行,所以java的執行速度比C++要慢,可是Java能夠跨平臺
  • Java具備平臺無關性,對每種數據類型都分配了固定長度的空間,可是C++不一樣,在不一樣的平臺上會分配不一樣的字節數linux

Java跨平臺的緣由

  • 第一點: 咱們一般將CPU處理器和操做系統的總體稱之爲平臺,不一樣的CPU中可能使用不一樣的指令集(指令集是CPU中用於計算和控制計算機系統的一套指令的集合),而不一樣操做系統支持不一樣CPU的指令集
  • C語言的編譯過程:windows下經過VS編譯成exe文件,Linux下經過gcc編譯成elf文件,可是window所編譯的exe是不能在linux上運行的,所以結論是:編譯器是與平臺相關的,編譯後的文件也是與平臺相關的,咱們所說的跨平臺是指編譯後的文件跨平臺,而不是源程序跨平臺,這點要注意。
  • 對於Java而言,源程序爲.java文件,經過與平臺無關的編譯器編譯成與平臺無關的中間碼,也就是.class文件,中間碼再由解釋器(也就是jvm)解釋執行,注意解釋器是與平臺相關的,也就是不一樣平臺須要不一樣的解釋器

JVM:Java虛擬機

http://www.javashuo.com/article/p-nhfhibef-cq.html程序員

1. 基本特性

  • JRE由Java api和JVM組成,JVM經過classloader來加載類應用,經過JavaAPI來執行
  • 基於棧結構的虛擬機
  • 符號引用:除了基本類型外全部的Java類型都是經過符號引用來取得關聯,而不是經過顯式內存地址的引用
  • 垃圾回收機制:顯式建立,經過GC自動回收
  • 明確界定基本類型字節長度保證平臺的無關性
  • 網絡字節序: 基於大端的字節序

2. Java程序的執行過程


類加載將Java字節碼載入到運行時數據區,執行引擎負責Java字節碼執行;算法

3. 類加載

Java提供了動態加載的特性,只有在運行時第一次遇到類時纔會去加載和連接,而非在編譯時加載它。JVM的類加載器負責類的動態加載過程。Java類加載器的特色以下:編程

  • 層次結構:Java的類加載器按是父子關係的層次結構組織的。Boostrap類加載器處於層次結構的頂層,是全部類加載器的父類。segmentfault

  • 委派模式:基於類加載器的層次組織結構,類加載器之間是能夠進行委派的。當一個類須要被加載,會先去請求父加載器判斷該類是否已經被加載。若是父類加器已加載了該類,那它就能夠直接使用而無需再次加載。若是還沒有加載,才須要當前類加載器來加載此類。
  • 可見性限制:子類加載器能夠從父類加載器中獲取類,反之則不行。
  • 不能卸載: 類加載器能夠載入類卻不能卸載它。可是能夠經過刪除類加載器的方式卸載類。windows

每一個類加載器都有本身的空間,用於存儲其加載的類信息。當類加載器須要加載一個類時,它經過FQCN)(Fully Quanlified Class Name: 全限定類名)的方式先在本身的存儲空間中檢測此類是否已存在。在JVM中,即使具備相同FQCN的類,若是出如今了兩個不一樣的類加載器空間中,它們也會被認爲是不一樣的。存在於不一樣的空間意味着類是由不一樣的加載器加載的。
279698375-5700c7e49cda5_articlex.pngapi

當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在自定義類加載器時會有略微不一樣,但都不外乎使用加載器的層次結構原理。緩存

若是一個類加載器發現了一個未加載的類,則該類的加載和連接過程以下圖

類加載過程.png

每一步的具體描述以下:

  • 加載(Loading): 從文件中獲取類並載入到JVM內存空間。
  • 驗證(Verifying): 驗證載入的類是否符合Java語言規範和JVM規範。在類加載流程的測試過程當中,這一步是最爲複雜且耗時最長的部分。大部分JVM TCK的測試用例都用於檢測對於給定的錯誤的類文件是否能獲得相應的驗證錯誤信息。
  • 準備(Preparing): 根據內存需求準備相應的數據結構,並分別描述出類中定義的字段、方法以及實現的接口信息。
  • 解析(Resolving): 把類常量池中全部的符號引用轉爲直接引用。
  • 初始化(Initializing): 爲類的變量初始化合適的值。執行靜態初始化域,併爲靜態字段初始化相應的值。

4. 運行時數據區

運行時數據區.png

運行時數據區是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經過檢查方法的執行頻率,而後只對達到必定頻率的方法纔會作即時編譯。

執行引擎.png

Java垃圾回收機制

垃圾回收算法和JVM垃圾回收綜述

深刻解析Java垃圾回收機制

1. 哪些內存須要回收?

  • 引用計數法:判斷是否還須要使用,最簡單方法是經過目前是否有引用指向這個對象,若是沒有說明這個對象就不會再使用了,這種經過引用是否存在的方法叫作引用計數法,可是存在一個問題沒法解決就是對象循環引用問題
  • 可達性分析:這個算法的基本思路就是經過一系列的稱爲「GC Roots」的對象做爲起始點,從這些節點開始向下搜索,搜索所走過的路徑稱爲引用鏈(Reference Chain),當一個對象到GC Roots沒有任何引用鏈相連(用圖論的話來講,就是從GC Roots到這個對象不可達)時,則證實此對象是不可用的。

    在Java語言中,可做爲GC Roots的對象包括下面幾種:

    • 虛擬機棧(棧幀中的本地變量表)中引用的對象。
    • 方法區中類靜態屬性引用的對象。
    • 方法區中常量引用的對象。
    • 本地方法棧中JNI(即通常說的Native方法)引用的對象。

2 如何回收?

  • step 1: marking 標記
    第一步是標記,也就是找到那些須要回收的對象和肯定哪些對象不須要回收,全部堆中的對象都會掃描一遍,這一般是一個很耗時的過程

Mark.PNG

  • step2 : normal deletion
    垃圾收集器清除掉標記出來的對象區域,簡單的清除帶來的問題是產生大量的不連續的內存碎片,空間碎片太多可能會致使在程序運行過程當中須要分配較大對象時,沒法找到足夠的連續內存而提早觸發一次垃圾回收

  • step 2 :improve -- deletion with compacting 壓縮整理
    因爲簡單的清除可能會存在碎片的問題,因此又出現了壓縮清除的方法,也就是先清除須要回收的對象,而後再對內存進行壓縮操做,將內存分紅可用和不可用兩大部分。
    compacting.PNG

3 分代回收

  • 爲何須要分代回收:一個程序中大部分對象都是短命的,所以爲了增大GC的效率,將JVM堆分代,分爲新生代,老年代和永久代。

分代.PNG

  • 新生代:全部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.
    分代2.PNG

5 分代回收的過程

  • 第一步 全部new出來的對象都會最早分配到新生代區域中,兩個survivor區域初始化是爲空的

第1步.PNG

  • 第二步,當eden區域滿了以後,就引起一次 minor garbage collection

第2步.PNG

  • 第三步,當在minor garbage collection,存活下來的對象就會被移動到S0survivor區域

第3步.PNG

  • 第四步,而後當eden區域又填滿的時候,又會發生下一次的垃圾回收,存活的對象會被移動到survivor區域而未存活對象會被直接刪除。可是,不一樣的是,在此次的垃圾回收中,存活對象和以前的survivor中的對象都會被移動到s1中。一旦全部對象都被移動到s1中,那麼s2中的對象就會被清除,仔細觀察圖中的對象,數字表示經歷的垃圾收集的次數。目前咱們已經有不一樣的年齡對象了。

第4步.PNG

  • 第五步,下一次垃圾回收的時候,又會重複上次的步驟,清除須要回收的對象,而且又切換一次survivor區域,全部存活的對象都被移動至s0。eden和s1區域被清除。

第5步.PNG

  • 第六步,重複以上步驟,並記錄對象的年齡,當有對象的年齡到達必定的閾值的時候,就將新生代中的對象移動到老年代中。在本例中,這個閾值爲8.

第6步.PNG

  • 第七步,接下來垃圾收集器就會重複以上步驟,不斷的進行對象的清除和年代的移動

  • 最後,咱們觀察上述過程能夠發現,大部分的垃圾收集過程都是在新生代進行的,直到老年代中的內存不夠用了纔會發起一次 major GC,會進行標記和整理壓縮。

    Java中對象的生命週期

  1. 建立階段(created)
    爲對象分配存儲空間,開始構造對象,從父類到子類對static成員進行初始化,父類成員變量按照順序初始化,遞歸調用父類的構造方法,子類成員變量按照順序初始化,子類構造方法調用,一旦對象被建立,並有某個引用指向它,這個對象的狀態就切換到了應用階段(In Use)

  2. 應用階段(in use)
    對象至少被一個強引用持有而且對象在做用域內
  3. 不可見階段(Invisible)
    程序自己再也不持有該對象的任何強引用,可是這些引用可能還存在着;通常具體是指程序的執行已經超過該對象的做用域了
  4. 不可達階段(Unreachable)
    對象處於不可達階段是指該對象再也不被任何強引用所持有。
    與「不可見階段」相比,「不可見階段」是指程序再也不持有該對象的任何強引用,這種狀況下,該對象仍可能被JVM等系統下的某些已裝載的靜態變量或線程或JNI等強引用持有着,這些特殊的強引用被稱爲」GC root」。存在着這些GC root會致使對象的內存泄露狀況,沒法被回收。
  5. 收集階段(Collected)
    當垃圾回收器發現該對象已經處於「不可達階段」而且垃圾回收器已經對該對象的內存空間從新分配作好準備時,則對象進入了「收集階段」。若是該對象已經重寫了finalize()方法,則會去執行該方法的終端操做。
    這裏要特別說明一下:不要重載finazlie()方法!緣由有兩點:
    • 會影響JVM的對象分配與回收速度
      在分配該對象時,JVM須要在垃圾回收器上註冊該對象,以便在回收時可以執行該重載方法;在該方法的執行時須要消耗CPU時間且在執行完該方法後纔會從新執行回收操做,即至少須要垃圾回收器對該對象執行兩次GC。
    • 可能形成該對象的再次「復活」
      在finalize()方法中,若是有其它的強引用再次持有該對象,則會致使對象的狀態由「收集階段」又從新變爲「應用階段」。這個已經破壞了Java對象的生命週期進程,且「復活」的對象不利用後續的代碼管理。
  6. 終結階段
    當對象執行完finalize()方法後仍然處於不可達狀態時,則該對象進入終結階段。在該階段是等待垃圾回收器對該對象空間進行回收。
  7. 對象空間的從新分配 垃圾回收器對該對象的所佔用的內存空間進行回收或者再分配了,則該對象完全消失了,稱之爲「對象空間從新分配階段」。

相關文章
相關標籤/搜索