class文件
可以被JVM
識別,加載並執行的文件格式,記錄了一個類文件的全部信息
。html
生成一個class文件,能夠經過兩種方式:java
- 經過IDE自動幫咱們build
- 手動經過javac去生成class文件
class文件格式詳解:android
- 一種8位字節的二進制流文件
- 各個數據順序緊密的排序,無間隙
- 每一個類或接口都單獨佔據一個class文件
javap -verbose TestClass
查看字節碼內容程序員
class文件弊端:shell
- 內存佔用大,不適合移動端
- 堆棧的加載模式,加載速度慢
- 文件IO操做多,類查找慢
dex文件
可以被DVM
識別,加載並執行的文件格式,記錄整個工程
中全部類文件的信息。安全
如何生成一個dex文件:數據結構
- 經過IDE自動幫咱們build生成
- 手動經過dx命名去生成dex文件
如何運行dex文件:jvm
//javac -target 1.6 -source 1.6 Hello.java //dx --dex --output Hell.dex Hello.class //adb push Hello.dex /storage/emulated/0 //adb shell進入手機控制檯 //dalvikvm -cp /sdcard/Hello.dex Hello
dex文件格式詳解
- 一種8位字節的二進制流文件
- 各個流按順序緊密的排列
- 整個應用中全部Java源文件都放在一個dex中
dex和class文件的區別
- dvm執行的是.dex格式文件 jvm執行的是.class文件 android程序編譯完以後生產.class文件,而後,dex工具會把.class文件處理成.dex文件,而後把資源文件和.dex文件等打包成.apk文件。apk就是android package的意思。 jvm執行的是.class文件。
- .class文件存在不少的冗餘信息,dex工具會去除冗餘信息,並把全部的.class文件整合到.dex文件中。減小了I/O操做,提升了類的查找速度
- dvm是基於寄存器的虛擬機 而jvm執行是基於虛擬棧的虛擬機。寄存器存取速度比棧快的多,dvm能夠根據硬件實現最大的優化,比較適合移動設備。
JVM內存結構
四大模塊須要掌握的: 1.內存空間劃分 2.內存管理 3.gc 4.classloader
內存空間劃分
程序計數器(Program Counter Register)
一塊較小的內存空間,能夠看做當前線程所執行的字節碼的行號指示器。若是線程正在執行的是一個Java方法,這個計數器記錄的是正在執行的虛擬機字節碼指令的地址;若是正在執行的是Native方法,這個計數器值則爲空。
Java虛擬機棧(Java Virtual Machine Stacks)
與程序計數器同樣,Java虛擬機棧也是線程私有的,它的生命週期與線程相同。虛擬機棧描述的是Java方法執行的內存模型:每一個方法在執行的同時都會建立一個棧幀用於存儲局部變量表、操做數棧、動態連接、方法出口等信息。每個方法從調用直至執行完成的過程,就對應着一個棧幀在虛擬機棧中入棧到出棧的過程。
本地方法棧(Native Method Stack)
本地方法棧與虛擬機棧所發揮的做用是很是類似的,它們之間的區別不過是虛擬機棧爲虛擬機執行Java方法(也就是字節碼)服務,而本地方法棧則爲虛擬機使用到的Native方法服務。
Java堆(Java Heap)
對大多數應用來講,Java堆是Java虛擬機所管理的內存中最大的一塊。Java堆是被全部線程共享的一塊內存區域,在虛擬機啓動時建立。此內存區域的惟一目的就是存放對象實例,幾乎全部的對象實例都在這裏分配內存。
GC要回收的部分
方法區(Method Area)
與Java堆同樣,是各個線程共享的內存區域,它用於存儲已被虛擬機加載的類信息、常量、靜態變量、即時編譯器編譯後的代碼等數據。方法區是JVM規範中定義的一個概念,具體放在哪裏,不一樣的實現能夠放在不一樣的地方。
運行時常量(Runtime Constant Pool)
運行時常量池是方法區的一部分。Class文件中除了有類的版本、字段、方法、接口等描述信息外,還有一項信息是常量池,用於存放編譯期生成的各類字面量和符號引用,這部份內容將在類加載後進入方法區的運行時常量池中存放。
內存管理
何時觸發垃圾回收?
- java虛擬機沒法再爲新的對象分配內存空間了
- 手動調用System.gc()方法(強烈不推薦)
手動調用,加大虛擬機壓力
低優先級的GC線程,被運行時就會執行GC
怎麼判斷對象是否已經「死去」?
常見的斷定方法有兩種:引用計數法和可達性分析算法,HotSpot中採用的是可達性分析算法。
引用計數法
給對象中添加一個引用計數器,每當有一個地方引用它時,計數器值就加1;當引用失效時,計數器值就減1;任什麼時候刻計數器爲0的對象就是不可能再被使用的。
客觀地說,引用計數算法的實現簡單,斷定效率也很高,在大部分狀況下它都是一個不錯的算法,可是主流的Java虛擬機裏面沒有選用引用計數算法來管理內存,其中最主要的緣由是它很難解決對象之間相互循環引用的問題。
可達性分析算法
這個算法的基本思路就是經過一系列的稱爲「GC Roots」的對象做爲起始點,從這些節點開始向下搜索,搜索所走過的路徑稱爲引用鏈,當一個對象到GC Roots沒有任何引用鏈相連(用圖論的話來講,就是從GC Roots到這個對象不可達)時,則證實此對象是不可用的。以下圖所示,對象object 五、object 六、object 7雖然互相有關聯,可是它們到GC Roots是不可達的,因此它們將會被斷定爲是可回收的對象。
引用記數算法 jdk1.2以前使用
可達性算法 jdk1.2以後使用
垃圾收集有哪些算法,各自的特色?
標記 - 清除算法
首先標記出全部須要回收的對象,在標記完成後統一回收全部被標記的對象。它的主要不足有兩個:一個是效率問題,標記和清除兩個過程的效率都不高;另外一個是空間問題,標記清除以後會產生大量不連續的內存碎片,空間碎片太多可能會致使之後在程序運行過程當中須要分配較大對象時,沒法找到足夠的連續內存而不得不提早觸發另外一次垃圾收集動做。
複製算法
爲了解決效率問題,一種稱爲「複製」(Copying)的收集算法出現了,它將可用內存按容量劃分爲大小相等的兩塊,每次只使用其中的一塊。當這一塊的內存用完了,就將還存活着的對象複製到另一塊上面,而後再把已使用過的內存空間一次清理掉。這樣使得每次都是對整個半區進行內存回收,內存分配時也就不用考慮內存碎片等複雜狀況,只要移動堆頂指針,按順序分配內存便可,實現簡單,運行高效。只是這種算法的代價是將內存縮小爲了原來的一半,未免過高了一點。
標記 - 整理算法
複製收集算法在對象存活率較高時就要進行較多的複製操做,效率將會變低。更關鍵的是,若是不想浪費50%的空間,就須要有額外的空間進行分配擔保,以應對被使用的內存中全部對象都100%存活的極端狀況,因此在老年代通常不能直接選用這種算法。
根據老年代的特色,有人提出了另一種「標記-整理」(Mark-Compact)算法,標記過程仍然與「標記-清除」算法同樣,但後續步驟不是直接對可回收對象進行清理,而是讓全部存活的對象都向一端移動,而後直接清理掉端邊界之外的內存。
更多:
Dalvik和JVM不一樣
- 執行的文件不一樣,一個是class,一個是dex
- 類加載的系統與JVM區別較大
- 能夠同時存在多個DVM
- Dalvik是基於寄存器,而JVM是基於棧的
Dalvik和ART的區別
- DVM使用JIT來將字節碼轉換成機器碼,效率低
- ART採用了AOT預編譯技術,執行速度更快
- ART會佔用更多的應用安裝時間和存儲空間
AOT安裝時候就會將字節碼轉化爲機器碼,不須要每次運行時候轉化,執行速度更快
可是所須要空間更大
四大引用
強引用
,軟引用
,弱引用
,虛引用
,最經常使用的就是強引用
和弱引用
**強引用:**在程序代碼之中廣泛存在的,相似「Object obj=new Object()」這類的引用,只要強引用還存在,垃圾收集器永遠不會回收掉被引用的對象。
**弱引用:**用來描述一些還有用但並不是必需的對象,使用SoftReference類來實現軟引用,在系統將要發生內存溢出異常以前,將會把這些對象列進回收範圍之中進行第二次回收。
**弱引用:**用來描述非必需對象的,使用WeakReference類來實現弱引用,被弱引用關聯的對象只能生存到下一次垃圾收集發生以前。
**虛引用:**是最弱的一種引用關係,使用PhantomReference類來實現虛引用,一個對象是否有虛引用的存在,徹底不會對其生存時間構成影響,也沒法經過虛引用來取得一個對象實例。爲一個對象設置虛引用關聯的惟一目的就是能在這個對象被收集器回收時收到一個系統通知。
java代碼編譯過程
class字節碼加載過程
類從被加載到虛擬機內存中開始,到卸載出內存爲止,它的整個生命週期包括:加載、驗證、準備、解析、初始化、使用和卸載7個階段。其中驗證、準備、解析3個部分統稱爲鏈接。
加載:
「類加載」過程的一個階段,在加載階段,虛擬機須要完成如下3件事情:
- 經過一個類的全限定名來獲取定義此類的二進制字節流。
- 將這個字節流所表明的靜態存儲結構轉化爲方法區的運行時數據結構。
- 在內存中生成一個表明這個類的java.lang.Class對象,做爲方法區這個類的各類數據的訪問入口。
驗證:
鏈接階段的第一步,這一階段的目的是爲了確保Class文件的字節流中包含的信息符合當前虛擬機的要求,而且不會危害虛擬機自身的安全。從總體上看,驗證階段大體上會完成下面4個階段的檢驗動做:文件格式驗證、元數據驗證、字節碼驗證、符號引用驗證。
準備:
該階段是正式爲類變量(static修飾的變量)分配內存並設置類變量初始值的階段,這些變量所使用的內存都將在方法區中進行分配。這裏所說的初始值「一般狀況」下是數據類型的零值,下表列出了Java中全部基本數據類型的零值。
解析:
該階段是虛擬機將常量池內的符號引用替換爲直接引用的過程。
初始化:
初始化階段是執行類構造器<clinit>()
方法的過程。<clinit>()
方法是由編譯器自動收集類中的全部類變量(static
修飾的變量)的賦值動做和靜態語句塊(static{}
塊)中的語句合併產生的,編譯器收集的順序是由語句在源文件中出現的順序所決定的。若是該類存在父類,則虛擬機會保證在執行子類的<clinit>()
方法前,父類的<clinit>()
方法已經執行完畢。所以在虛擬機中第一個被執行<clinit>()
方法的類確定是java.lang.Object
。
類加載器
java經常使用類加載器
從Java虛擬機的角度來說,只存在兩種不一樣的類加載器:一種是啓動類加載器
(Bootstrap ClassLoader
),這個類加載器使用C++語言實現,是虛擬機自身的一部分;另外一種就是全部其餘的類加載器
,這些類加載器都由Java語言實現,獨立於虛擬機外部,而且全都繼承自抽象類java.lang.ClassLoader
。
從Java開發人員的角度來看,絕大部分Java程序都會使用到如下3種系統提供的類加載器。
啓動類加載器(Bootstrap ClassLoader):
這個類加載器負責將存放在<JAVA_HOME>\lib
目錄中的,或者被-Xbootclasspath參數所指定的路徑中的,而且是虛擬機識別的(僅按照文件名識別,如rt.jar,名字不符合的類庫即便放在lib目錄中也不會被加載)類庫加載到虛擬機內存中。
擴展類加載器(Extension ClassLoader):
這個加載器由sun.misc.Launcher$ExtClassLoader實現,它負責加載<JAVA_HOME>\lib\ext
目錄中的,或者被java.ext.dirs
系統變量所指定的路徑中的全部類庫,開發者能夠直接使用擴展類加載器。
應用程序類加載器(Application ClassLoader):
這個類加載器由sun.misc.Launcher$AppClassLoader
實現。因爲這個類加載器是ClassLoader中的getSystemClassLoader()方法的返回值,因此通常也稱它爲系統類加載器。它負責加載用戶類路徑(ClassPath)上所指定的類庫,開發者能夠直接使用這個類加載器,若是應用程序中沒有自定義過本身的類加載器,通常狀況下這個就是程序中默認的類加載器。
通常都是由這3種類加載器相關配合進行加載,若是有必要,還能夠加入本身定義的類加載器.
雙親委派模型
若是一個類加載器收到了類加載的請求,它首先不會本身去嘗試加載這個類,而是把這個請求委派給父類加載器去完成,每個層次的類加載器都是如此,所以全部的加載請求最終都應該傳送到頂層的啓動類加載器中,只有當父加載器反饋本身沒法完成這個加載請求(它的搜索範圍中沒有找到所需的類)時,子加載器纔會嘗試本身去加載。
雙親委派模型好處
使用雙親委派模型來組織類加載器之間的關係,有一個顯而易見的好處就是Java類隨着它的類加載器一塊兒具有了一種帶有優先級的層次關係。例如類java.lang.Object,它存放在rt.jar之中,不管哪個類加載器要加載這個類,最終都是委派給處於模型最頂端的啓動類加載器進行加載,所以Object類在程序的各類類加載器環境中都是同一個類。相反,若是沒有使用雙親委派模型,由各個類加載器自行去加載的話,若是用戶本身編寫了一個稱爲java.lang.Object的類,並放在程序的ClassPath中,那系統中將會出現多個不一樣的Object類,Java 類型體系中最基礎的行爲也就沒法保證,應用程序也將會變得一片混亂。
Android中的ClassLoader
BootClassLoader加載Framework class文件
PathClassLoader加載已經安裝到系統APK的class文件
DexClassLoader加載指定目錄中的class字節碼文件
BaseDexClassLoader前面兩個的父類
//獲取應用中須要加載的classLoader ClassLoader classLoader = getClassLoader(); if(classLoader != null){ Log.e("TAG", "classLoader:" + classLoader.toString()); while (classLoader.getParent() != null){ classLoader = classLoader.getParent(); Log.e("TAG", "classLoader:" + classLoader.toString()); } }
參考
4.Java中new一個對象是一個怎樣的過程?JVM中發生了什麼?