久違的重新寫了一篇面試匯總的,關于JVM的一篇,一共三篇,今天寫了第一篇,繼續重新學習,重新卷起來,come on baby
1.什么情況下會觸發類的初始化?
(1)首先是類未被初始化時,創建類的實例(new 的方式),訪問某個類或接口的靜態變量,或者對該靜態變量賦值,調用類的靜態方法。
(2)對類進行反射調用的時候,如果類沒有進行過初始化,則需要先觸發其初始化。
(3)如果在初始化某一個類時候,其父類沒有被初始化時候,則會觸發父類的初始化。
(4)當咱們打的jar包,在執行完java -jar命令后,用戶需要指定一個要執行的主類(包含 main() 方法的那個類,例如SpringBoot的那個啟動類的main方法,非SpringBoot的,咱們自己手動打包的一般在MANIFEST.MF文件中指定),虛擬機會先初始化這個主類。
(5)JDK 1.7新增了一種反射方式java.lang.invoke.MethodHandle,通過實MethodHandle同樣是訪問靜態變量,對該靜態變量賦值,調用類的靜態方法,前提仍然是該類未被初始化。
2.談談你對解析與分派的認識。
(1)解析調用是將那些在編譯期就完全確定,在類加載的解析階段就將涉及的符號引用全部轉變為可以確定的直接引用,不會延遲到運行期再去完成。
(2)分派又分為靜態分派和動態分派
(3)靜態分派:同樣是將編譯期確定的調用,重載(Oveload)就是這種類型,在編譯期通過參數的靜態類型(注意不是實際類型)作為判斷依據,找到具體的調用的方法。
public class TestOverLoad {public static void main(String[] args) {//靜態類型都是Parent,實際類型分別是Sun和DaughterParent sun = new Sun();Parent daughter = new Daughter();TestOverLoad test = new TestOverLoad();//輸出結果按照靜態類型執行test.testMethod(sun);test.testMethod(daughter);}static abstract class Parent { }static class Sun extends Parent { }static class Daughter extends Parent { }public void testMethod(Parent parent) {System.out.println("hello, Parent");}public void testMethod(Sun sun) {System.out.println("hello, Sun");}public void testMethod(Daughter daughter) {System.out.println("hello, Daughter");}
}//輸出
hello, Parent
hello, Parent
(4)動態分派:運行期根據實際類型確定方法執行版本的分派過程稱為動態分派。重寫(Override),在運行時期,通過判斷實體的真實類型,判斷具體執行哪一個方法。
public class TestOverride {public static void main(String[] args) {//靜態類型都是Parent,實際類型分別是Sun和DaughterParent sun = new Sun();Parent daughter = new Daughter();//這時候輸出結果按照實際類型找到方法sun.testMethod();daughter.testMethod();}static abstract class Parent {public void testMethod() {System.out.println("hello, Parent");}}static class Sun extends Parent {@Overridepublic void testMethod() {System.out.println("hello, Sun");}}static class Daughter extends Parent {@Overridepublic void testMethod() {System.out.println("hello, Daughter");}}
}
//輸出
hello, Sun
hello, Daughter
3.Java類加載器包括?種?它們之間的??關系是怎么樣的?雙親委派機制是什么意思?有什么好處?
(1)啟動類加載器(Bootstrap ClassLoader),由C語言編寫的。負責把<JAVA_HOME>\lib目錄中的類庫加載到虛擬機內存中。
(2)擴展類加載器(Extension ClassLoader):這個加載器由sun.misc.LauncherApp-ClassLoader實現。由于這個類加載器是ClassLoader中的getSystemClassLoader()方法的返回值,所以一般也稱它為系統類加載器。它負責加載用戶類路徑(ClassPath)上所指定的類庫,開發者可以直接使用這個類加載器,如果應用程序中沒有自定義過自己的類加載器,一般情況下這個就是程序中默認的類加載器。
(4)自定義類加載器。下面是自定義類加載器的方式,這個有幾點注意,TestDemo編譯出來class,把class復制到idea或者eclipse生成target目錄之外,因為需要刪除掉TestDemo.java,這樣target下的class可能也自動沒有了,另外如果不刪除TestDemo.java會導致一直輸出默認的應用程序加載器,因為你運行環境里有,雙親委派的應用程序加載器能找TestDemo,所以默認用父類的了,所以必須刪除掉。
package test;public class TestDemo {private String name;public TestDemo(){}public TestDemo(String name){this.name = name;}public String getName(){return name;}public void setName(String name){this.name = name;}public String toString(){return "Demo name is " + name;}
}
package test;import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.nio.channels.WritableByteChannel;public class TestMyClassLoader extends ClassLoader
{public TestMyClassLoader(){}public TestMyClassLoader(ClassLoader parent){super(parent);}protected Class<?> findClass(String name) throws ClassNotFoundException{File file = getClassFile(name);try{byte[] bytes = getClassBytes(file);Class<?> c = this.defineClass(name, bytes, 0, bytes.length);return c;}catch (Exception e){e.printStackTrace();}return super.findClass(name);}private File getClassFile(String name){//重點是這個路徑,在本地編譯好TestDemo后,把class放在一個其他路徑下//不要默認用idea或者eclipse的target路徑//注意運行這個類之前把代碼的TestDemo.java刪除掉或者注釋掉//否則怎么運行都是默認的加載器AppClassLoaderFile file = new File("/Users/buxuesong/TestDemo.class");return file;}private byte[] getClassBytes(File file) throws Exception{// 這里要讀入.class的字節,因此要使用字節流FileInputStream fis = new FileInputStream(file);FileChannel fc = fis.getChannel();ByteArrayOutputStream baos = new ByteArrayOutputStream();WritableByteChannel wbc = Channels.newChannel(baos);ByteBuffer by = ByteBuffer.allocate(1024);while (true){int i = fc.read(by);if (i == 0 || i == -1)break;by.flip();wbc.write(by);by.clear();}fis.close();return baos.toByteArray();}public static void main(String[] args) throws Exception{TestMyClassLoader mcl = new TestMyClassLoader();Class<?> c1 = Class.forName("test.TestDemo", true, mcl);Object obj = c1.newInstance();System.out.println(obj);System.out.println(obj.getClass().getClassLoader());}
}
//輸出
Demo name is null
test.TestMyClassLoader@5cad8086
//如果沒刪除TestDemo.java輸出
Demo name is null
sun.misc.Launcher$AppClassLoader@18b4aac2
(5)擴展類加載器的父類是啟動類加載器,應用程序類加載器的父類是擴展類加載器,自定義類加載器的父類是應用程序類加載器。
(6)雙親委派機制:除了啟動類加載器,其余加載器都應該有自己的父類加載器,當一個類加載器需要加載某個類時,默認把這個累交給自己的父類去加載,只有當父類無法加載這個類時候(它的搜索范圍中沒有找到所需的類),自己才去加載,按照這個規則,所有的累加載最終都會到啟動類加載器過一遍。
(7)雙親委派實際上保障了Java程序的穩定運作,因為隨著這種父類關系自帶了一種層級關系,按照層級關系來分別加載,如果不按照順序各個加載器自行加載,用戶如果自己寫了一個java. lang.Object的類,系統會出現多個Object類,導致整個java體系無法運轉。
4.如何?定義?個類加載器?你使?過哪些或者你在什么場景下需要?個?定義的類加載器嗎?
(1)這問題問了我上面的,我在寫一遍,自定義類加載器,有幾點注意,TestDemo編譯出來class,把class復制到idea或者eclipse生成target目錄之外,因為需要刪除掉TestDemo.java,這樣target下的class可能也自動沒有了,另外如果不刪除TestDemo.java會導致一直輸出默認的應用程序加載器,因為你運行環境里有,雙親委派的應用程序加載器能找TestDemo,所以默認用父類的了,所以必須刪除掉。
package test;public class TestDemo {private String name;public TestDemo(){}public TestDemo(String name){this.name = name;}public String getName(){return name;}public void setName(String name){this.name = name;}public String toString(){return "Demo name is " + name;}
}
package test;import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.nio.channels.WritableByteChannel;public class TestMyClassLoader extends ClassLoader
{public TestMyClassLoader(){}public TestMyClassLoader(ClassLoader parent){super(parent);}protected Class<?> findClass(String name) throws ClassNotFoundException{File file = getClassFile(name);try{byte[] bytes = getClassBytes(file);Class<?> c = this.defineClass(name, bytes, 0, bytes.length);return c;}catch (Exception e){e.printStackTrace();}return super.findClass(name);}private File getClassFile(String name){//重點是這個路徑,在本地編譯好TestDemo后,把class放在一個其他路徑下//不要默認用idea或者eclipse的target路徑//注意運行這個類之前把代碼的TestDemo.java刪除掉或者注釋掉//否則怎么運行都是默認的加載器AppClassLoaderFile file = new File("/Users/buxuesong/TestDemo.class");return file;}private byte[] getClassBytes(File file) throws Exception{// 這里要讀入.class的字節,因此要使用字節流FileInputStream fis = new FileInputStream(file);FileChannel fc = fis.getChannel();ByteArrayOutputStream baos = new ByteArrayOutputStream();WritableByteChannel wbc = Channels.newChannel(baos);ByteBuffer by = ByteBuffer.allocate(1024);while (true){int i = fc.read(by);if (i == 0 || i == -1)break;by.flip();wbc.write(by);by.clear();}fis.close();return baos.toByteArray();}public static void main(String[] args) throws Exception{TestMyClassLoader mcl = new TestMyClassLoader();Class<?> c1 = Class.forName("test.TestDemo", true, mcl);Object obj = c1.newInstance();System.out.println(obj);System.out.println(obj.getClass().getClassLoader());}
}
//輸出
Demo name is null
test.TestMyClassLoader@5cad8086
//如果沒刪除TestDemo.java輸出
Demo name is null
sun.misc.Launcher$AppClassLoader@18b4aac2
(2)我們之前寫的獲取數據庫連接,通過class.forname去加載數據庫驅動。以及熱加載這種方式,咱們修改了java文件,但是tomcat沒有手動重啟,這個時候有一個能夠監控到java有變化重新編譯了的情況,通過線程出發tomcat重啟,就達到了熱加載的機制。還有就是apk加密的方式,打包時候,源碼-》class-》加密-》打成jar包-》安裝-》運行-》classLoader解密-》classLoader加載-》用戶使用app,這樣只有實現解密方法的classloader才能正常加載,其他的classLoader無法運行。
5.堆內存設置的參數是什么?
(1)-Xms初始堆內存大小
(2)-Xmx最大堆內存大小,生產環境中,JVM的Xms和Xmx建議設置成一樣的,能夠避免GC時還要調整堆大小。
(3)-XX:NewSize=n,設置年輕代大小-XX:NewRatio=n設置年輕代和年老代的比值。如:-XX:NewRatio=3,表示年輕代與年老代比值為1:3,年輕代占整個年輕代年老代和的1/4,默認新生代和老年代的比例=1:2
(4)-XX:SurvivorRatio=n,設置年輕代中Eden區與兩個Survivor區的比值。注意Survivor區有兩個,默認是8,表示Eden:S0:S1=8:1:1如:-XX:SurvivorRatio=3,表示Eden:Survivor=3:2,一個Survivor區占整個年輕代的1/5。
(5)-XX:+HeapDumpOnOutOfMemoryError,-XX:HeapDumpPath,這兩個是設置但程序OOM后,輸出Dump文件以供分析原因的,但是對于目前的K8s的情況,這倆沒什么用,OOM之后,K8s發現服務沒響應,直接kill了,然后重啟一個新的,OOM根本就來不及生成,因為生成文件耗時較多,K8s殺的很快。
(6)-Xss128k 設置每個線程的堆棧大小。
(7)-XX:+PrintGCDetails,輸出GC日志。
感謝各位的閱讀,幫忙點贊,感謝各位。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務

喜歡的朋友記得點贊、收藏、關注哦!!!