從面試題看問題之JVM和內存

原文:http://mp.weixin.qq.com/s/9WW912UrDPRQUG8260mKTA


什麼是Java虛擬機?

爲什麼Java被稱作是“平臺無關的編程語言”?


Java虛擬機是一個可以執行Java字節碼的虛擬機進程。Java源文件被編譯成能被Java虛擬機執行的字節碼文件(.class文件)。


Java被設計成允許應用程序可以運行在任意的平臺,而不需要程序員爲每一個平臺單獨重寫或者是重新編譯。Java虛擬機讓這個變爲可能,因爲它知道底層硬件平臺的指令長度和其他特性。


下圖爲JVM的物理結構:



JAVA中的 JDK,JRE,JVM有什麼區別?



java中被編譯後的.class並不直接與機器的操作系統相對應,而是經過虛擬機間接與操作系統交互,由虛擬機將程序解釋給本地系統執行。   


 只有JVM還不能成class的執行,因爲在解釋class的時候JVM需要調用解釋所需要的類庫lib,而jre包含lib類庫。


Java Runtime Environment(JRE):java運行時環境,通過它,Java的開發者才得以將自己開發的程序發佈到用戶手中,讓用戶使用。它包括java虛擬機,及Java核心,和其他java運行時的必備組件,以及一些其他插件。


JDK(Java Development Kit):java開發工具包:是完整的Java軟件開發包,包含了JRE,編譯器和其他的工具(比如:JavaDoc,Java調試器),可以讓開發者開發、編譯、執行Java應用程序。


簡述jvm的內存模型?



(1) 程序計數器是一塊較小的內存空間,可以看作是當前線程所執行的字節碼的行號指示器。分支、循環、跳轉、異常處理、線程恢復等基礎功能都需要依賴這個計數器來完成;


(2) 通常我們定義一個基本數據類型的變量,一個對象的引用,還有就是函數調用的現場保存都使用JVM中的棧空間,棧可以被分爲虛擬機棧和本地方法棧;


(3) 而通過new關鍵字和構造器創建的對象則放在堆空間,堆是垃圾收集器管理的主要區域;


(4) 方法區和堆都是各個線程共享的內存區域,用於存儲已經被JVM加載的類信息、常量、靜態變量、JIT編譯器編譯後的代碼等數據;


(5) 程序中的字面量(literal)如直接書寫的100、"hello"和常量都是放在常量池中,常量池是方法區的一部分;


(6) 棧空間操作起來最快但是棧很小,通常大量的對象都是放在堆空間,棧和堆的大小都可以通過JVM的啓動參數來進行調整,棧空間用光了會引發StackOverflowError,而堆和常量池空間不足則會引發OutOfMemoryError。


String str = new String("hello");


上面的語句中變量str放在棧上,用new創建出來的字符串對象放在堆上,而"hello"這個字面量是放在方法區的。


補充1:較新版本的Java(從Java 6的某個更新開始)中,由於JIT編譯器的發展和"逃逸分析"技術的逐漸成熟,棧上分配、標量替換等優化技術使得對象一定分配在堆上這件事情已經變得不那麼絕對了。


補充2:運行時常量池相當於Class文件常量池具有動態性,Java語言並不要求常量一定只有編譯期間才能產生,運行期間也可以將新的常量放入池中,String類的intern()方法就是這樣的。 看看下面代碼的執行結果是什麼並且比較一下Java 7以前和以後的運行結果是否一致。


String s1 = new StringBuilder("go") 


.append("od").toString();System.out.println(s1.intern() == s1);String s2 = new 


StringBuilder("ja") .append("va").toString();System.out.println(s2.intern() == s2);


jvm中堆和棧有什麼區別?


◆棧:存放基本類型的數據和對象的引用,但對象本身不存放在棧中,而是存放在堆中。


在函數中定義的一些基本類型的變量數據和對象的引用變量都在函數的棧內存中分配。即存放的是局部變量。


當在一段代碼塊定義一個變量時,Java就在棧中 爲這個變量分配內存空間,當該變量退出該作用域後,Java會自動釋放掉爲該變量所分配的內存空間,該內存空間可以立即被另作他用。


◆堆:存放用new產生的數據。


堆內存用來存放由new創建的對象和數組。 在堆中分配的內存,由Java虛擬機的自動垃圾回收器來管理。


◆小例子


在函數中定義一個數組

int[] arr = new int[3];


內存中:



特點一:在堆中,每一個new出來的對象都有一個地址值(一般是一個16進制的對象),堆再把這個值賦值給棧中的局部變量。堆和棧就通過這個地址聯繫起來。


所以我們直接打印arr,輸出的是地址。


System.out.println(arr);


輸出結果: [I@15db9742


特點二:堆中每個變量都有默認值


比如數組:arr[0],arr[1],arr[2] 默認值爲0


特點三:堆中使用完畢就變成了垃圾,但是沒有立即回收,會在垃圾回收器中回收 點:棧內存中的數據使用完就釋放(脫離作用域後,比如函數結束)


描述一下JVM加載class文件的原理機制?


VM中類的裝載是由類加載器(ClassLoader)和它的子類來實現的,Java中的類加載器是一個重要的Java運行時系統組件,它負責在運行時查找和裝入類文件中的類。


由於Java的跨平臺性,經過編譯的Java源程序並不是一個可執行程序,而是一個或多個類文件。


當Java程序需要使用某個類時,JVM會確保這個類已經被加載、連接(驗證、準備和解析)和初始化。


類的加載是指把類的.class文件中的數據讀入到內存中,通常是創建一個字節數組讀入.class文件,然後產生與所加載類對應的Class對象。


加載完成後,Class對象還不完整,所以此時的類還不可用。當類被加載後就進入連接階段,這一階段包括驗證、準備(爲靜態變量分配內存並設置默認的初始值)和解析(將符號引用替換爲直接引用)三個步驟。


最後JVM對類進行初始化,包括:1)如果類存在直接的父類並且這個類還沒有被初始化,那麼就先初始化父類;2)如果類中存在初始化語句,就依次執行這些初始化語句。


類的加載是由類加載器完成的,類加載器包括:根加載器(BootStrap)、擴展加載器(Extension)、系統加載器(System)和用戶自定義類加載器(java.lang.ClassLoader的子類)。


從Java 2(JDK 1.2)開始,類加載過程採取了父親委託機制(PDM)。PDM更好的保證了Java平臺的安全性,在該機制中,JVM自帶的Bootstrap是根加載器,其他的加載器都有且僅有一個父類加載器。類的加載首先請求父類加載器加載,父類加載器無能爲力時才由其子類加載器自行加載。JVM不會向Java程序提供對Bootstrap的引用。



下面是關於幾個類加載器的說明:


Bootstrap:一般用本地代碼實現,負責加載JVM基礎核心類庫(rt.jar); Extension:從java.ext.dirs系統屬性所指定的目錄中加載類庫,它的父加載器是Bootstrap;


System:又叫應用類加載器,其父類是Extension。它是應用最廣泛的類加載器。它從環境變量classpath或者系統屬性java.class.path所指定的目錄中記載類,是用戶自定義加載器的默認父加載器。


64 位 JVM 中,int 的長度是多數?


Java 中,int 類型變量的長度是一個固定值,與平臺無關,都是 32 位。意思就是說,在 32 位 和 64 位 的Java 虛擬機中,int 類型的長度是相同的。


32 位 JVM 和 64 位 JVM 的最大堆內存分別是多數?


理論上說上 32 位的 JVM 堆內存可以到達 2^32,即 4GB,但實際上會比這個小很多。


不同操作系統之間不同,如 Windows 系統大約 1.5 GB,Solaris 大約 3GB。64 位 JVM允許指定最大的堆內存,理論上可以達到 2^64,這是一個非常大的數字,實際上你可以指定堆內存大小到 100GB。甚至有的 JVM,如 Azul,堆內存到 1000G 都是可能的。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章