30道「熱乎乎」的 JVM 典型題目剖析!
- 2019 年 10 月 6 日
- 筆記
問題一
問題
方法走完,引用消失,堆內存還未必消失。好多人在做報表導出的時候,就會在for循環里不斷的創建對象,很容易造成堆溢出,請問這種大文件導出怎麼破?
解答
建議不要在for里創建對象,可以在外面搞一個對象,for循環里對一個對象修改數據即可
問題二
問題
1.Java支持多線程,每個線程有自己的Java虛擬機棧和本地方法棧,是這樣嗎?
2.新建的實例在堆內存,實例變量也是在堆內存? 是這樣嗎?
解答
1、2兩點均理解正確
問題三
問題
您好,我不太看懂入棧和出棧有什麼意義,可以給我解釋一下嗎?謝謝!
解答
入棧的時候,就是你執行一個方法的時候,為這個方法創建一個棧幀入棧
出棧,就是你的方法執行完畢了,就會出棧,其實這個不用急,明天的文章會有詳細的圖解,你會看明白的。
問題四
問題
如果是父類子類的情況是下面哪種呢? 加載父類->加載子類->初始化父類->初始化子類, 加載父類->初始化父類->加載子類->初始化子類
解答
不是的,加載父類就是父類,除非用到子類才會加載子類;但是加載子類要初始化之前,必須先加載父類,初始化父類
問題五
問題
類加載器有三層,如果在第二層的類加載器可以加載這些類的話,就沒有必要往上去找他的父類加載嗎?
既然說類只有用到的時候才加載到內存中,那麼new對象的時候肯定用到,但是是不是先經歷過類的所有過程才將類實例化?
解答
沒錯,必須先加載類,再實例化對象
問題六
問題
第一課內容比較詳細的講解了java程序的執行過程,但是感覺提出的問題並不能在文章中找到答案,也許是一個課後需要自己找尋答案的提問?還是希望可以有一個比較全面的回答的
解答
提出的問題是給大家的思考題,第二天會給出簡單的解釋,但是其實理解了文章的內容,完全可以自己找資料去理解,這是一個小作業,是一個思考的過程
問題七
問題
Object Header(4位元組) + Class Pointer(4位元組)+ Fields(看存放類型),但是jvm內存佔用是8的倍數,所以結果要向上取整到8的倍數
解答
很好,就是這樣
問題八
問題
如果我有一個靜態的成員變量int,那我多線程更改是否會有線程安全問題,為什麼?
解答
靜態成員變量,他在內存里,只有一份,就是屬於類的。你多個線程並發修改,一定會有並發問題,可能導致數據出錯。
問題九
問題
類加載是按需加載,可以一次性加載全部的類嗎?
解答
如果是默認的類加載機制,那麼是你的代碼運行過程中,遇到什麼類加載什麼類。如果你要自己加載類,那麼需要寫自己的類加載器
問題十
問題
為什麼必須要一級一級類加載器的往上找,直接從頂層類加載器開始找不就行了嗎?
解答
其實關於這個問題,不用過於糾結,每一層類加載器對某個類的加載,上推給父類加載器,到頂層類加載器,如果發現自己加載不到,再下推回子類加載器來加載,這樣可以保證絕對不會重複加載某個類。
至於為什麼不直接從頂層類加載器開始找,那是因為類加載器本身就是做的父子關係模型
你想一下Java代碼實現,他最底下的子類加載器,只能通過自己引用的父類加載器去找。如果直接找頂層類加載器,不合適的,那麼頂層類加載器不就必須硬編碼規定了嗎?
這就是一個代碼設計思想,保證代碼的可擴展性。
問題十一
問題
是在執行new replicamanager()這行代碼的時候加載replicamanger類嗎?還是說加載cafka的時候就同時加載了呢?
解答
執行new ReplicaManager的時候加載類
問題十二
問題
還是沒有明白 jvm和平時運行在機器上的系統之間是什麼關係呢
解答
其實很簡單,你運行在機器上的系統,其實就是一個JVM進程,JVM進程會執行你系統里寫好的那些代碼
問題十三
問題
- class文件分配內存是在準備階段,那類的class對象是在準備階段創建的嗎?
- 如果實例變量有初始值,那實例變量是和類變量一同在初始化階段賦值的嗎?
- 初始化之後是不是就有實例了
解答
- 類是在準備階段分配內存空間的
- 實例變量得在你創建類的實例對象時才會初始化
- 類的初始化階段,僅僅是初始化類而已,跟對象無關,用new關鍵字才會構造一個對象出來
問題十四
問題
雙親委派可以解決類重複加載的問題。按照文章中介紹每個類加載器有不同的類加載路徑,這些類加載路徑是否可能重疊?
解答
不同類加載器的路徑,一般是不會重疊的
問題十五
問題
自定義的類加載器本身是由系統加載器加載的,也就是說其本身是沒有加密的,那麼我拿到該類反編譯就可以看到如果解密class文件了,請問老師是這樣么?
解答
是的,所以說對class文件需要做特殊混淆處理,有商用的產品可以用
問題十六
問題
作為一個web容器,既要解決跨應用公共共享問題也要解決獨立應用獨立問題。tomcat必須支持多層級的自定義類加載器
解答
很好的推測,明天會給出答案
問題十七
問題
用戶使用類的時候應該是希望類已經準備好了一些數據,我猜想jvm設計者設計先執行static代碼塊的機制,是希望開發者在這裡把使用類之前需要準備的工作在這裡準備好
- 為什麼類的初始化需要執行靜態代碼塊,給靜態成員變量賦值,是因為這些數據是在方法區嗎?
- 啟動類、擴展類和自定義加載器都已經指定了加載路徑,所以不應該會有重複加載類的問題吧,所以雙親委派是不是沒有必要
解答
- 沒錯,必須有初始化過程,準備好類級別的數據
- 雙親委派,避免重複加載,評論區里多次回復了這個問題,可以看一看
問題十八
問題
其實初始化時機就是對類的主動使用:調用靜態方法時對類的主動使用的一種場景,main方法本質上是個static方法,沒有調用的main方法和沒有調用的static方法沒區別!
有一個問題,包含main方法的類會優先加載,如果一個項目中有多個類中都有main方法,都會加載么?
解答
不會的,你啟動一個jar包,需要指定某個main主類,優先就是加載他
問題十九
問題
tomcat本身是java程序,那麼tomcat的實現程序的class是由應用類加載器加載的,用戶自己的java程序war包,放入tomcat的程序的classpath中
這樣用戶的程序和tomcat的程序都是由應用類加載器加載了,也就是處於一個jvm中了
解答
非常好的回復,明天文章會給出答案
問題二十
問題
有一個問題,包含main方法的類會優先加載,如果一個項目中有多個類中都有main方法,都會加載么?
解答
你啟動一個jar包的時候,會指定是走哪個main方法所在的類,是唯一的
問題二十一
問題
- 為什麼類的初始化需要執行靜態代碼塊,給靜態成員變量賦值,是因為這些數據是在方法區嗎?
- 啟動類、擴展類和自定義加載器都已經指定了加載路徑,所以不應該會有重複加載類的問題吧,所以雙親委派是不是沒有必要
解答
- 沒錯,類在方法區,他在內存里,所以你必須給他初始化,賦值
- 還是有必要,比如啟動類加載器,可以通過一些方式指定加載其他目錄的類,那麼你必須得走雙親委派,如果對那些特殊區域的類加載,走雙親委派,才能上推到啟動類加載器去執行,不會重複加載
問題二十二
問題
老師好請問類加載雙親委派機制 為什麼要先找父加載 而不是自己找?這種設計的好處是?
解答
好處就在於,每個層級的類加載器各司其職,而且不會重複加載一個類。
比如你代碼里用兩個不同層級的類加載器,都去嘗試加載了某個類,如果有雙親委派機制,那麼都會先找父類加載器去加載,如果加載到了,那麼以後就只會是他去加載這個類。
否則如果沒有雙親委派機制,那麼豈不是兩個不同層級的類加載器可以加載同一個類,造成類的重複加載!
問題二十三
問題
自定義類加載器如何實現?
解答
自己寫一個類,繼承ClassLoader類,重寫類加載的方法,然後在代碼裏面可以用自己的類加載器去針對某個路徑下的類加載到內存里來
問題二十四
問題
看到一個詞:動態部署,那麼是否也有對應的靜態部署?如何解釋呢?(謝老師回答)
解答
假設一個背景在Tomcat部署系統的話,那麼動態部署,也成為熱部署
就是直接系統放入Tomcat對應目錄,他自動就重新加載你最新的代碼給你熱部署了,不需要對Tomcat進行停機再重啟;
反之,則是先停止Tomcat,然後部署最新代碼到Tomcat對應目錄里,然後重啟Tomcat
問題二十五
問題
-XX:+TraceClassLoading 可以看加載了哪些類,動手實驗了一下,jrelibrt.jar下的類全部加載了,其他都是用到時候加載。
解答
沒錯,明天更新的第三篇文章里,會講解類加載機制,rt.jar這屬於核心類庫,屬於支撐我們Java系統運行的底層類庫,所以他一定會被加載
我們自己寫的代碼,一般是你代碼運行使用到了哪個類,就會去加載哪個類
問題二十六
問題
老師,類加載器是把jar包里的所有類一次性全部加載進去嗎?
解答
不是的,首先加載包含main方法的主類,接着是運行你寫的代碼的時候,遇到你用了什麼類,再加載什麼類
二十七
問題
通過代碼混淆機制,加大反編譯之後的可讀性!或者是否可以基於二進制加密呢,學生沒用過!
解答
其實現在對於這個一般都是用商業產品的,有很多第三方公司提供加密產品,可以百度一下,class文件加密,就可以看到,直接用他們的產品即可
問題二十八
問題
Class源文件的保護,可以採用代碼混淆技術,方式有很多,如回答區中老師提到的商用加密軟件
解答
非常好,就是這樣
問題二十九
問題
看文中內容,是會加載兩次位元組碼嗎,第一次加載進jvm,然後程序執行的時候再加載。有點不解!
解答
你好,不是加載兩次,是JVM先把「.class」位元組碼文件中的類加載到內存里,然後執行的時候,就直接使用加載好的類即可,不會重複加載
問題三十
問題
class文件通過工具可以反編譯的,請問有沒有方法對class文件進行加密又不影響它的執行。windows桌面程序里一般都是打包成dll文件,java中有沒有比較好的方式?
解答
可以的,比如jvmti小工具就可以實現class文件的加密
另外其實為了保護源代碼安全,有很多商業公司推出了專業級別的class加密產品,可以付費使用。
解密的話一般可以基於自定義的類加載器來實現,在加載類的時候把class給解密,這樣就可以保護自己的源代碼安全了。
最後,附上兩張Tomcat類加載如果按委派模型的加載流程和實際實現的流程(專欄讀者所畫)