一.請你談談實際的項目中在Java虛擬機會拋出哪些異常,每一個異常都是怎麼產生的?java
1.java.lang.StackOverflowError 棧空間滿了vim
public static void stackOverFlow(){ // 遞歸調用以後,把棧空間塞滿了,當程序出現遞歸調用沒有終止的時候,就會出現此類錯誤 // Exception in thread "main" java.lang.StackOverflowError stackOverFlow(); }
運行結果:緩存
2.java.lang.OutOfMemoryError: Java heap space 堆空間滿了服務器
public static void outOfMemoryHeap() { // 經過不停的生成新的string對象,把堆空間塞滿,堆中沒有在存放新的對象,此時會出現該錯誤 // Exception in thread "main" java.lang.OutOfMemoryError: Java heap space String str = "itheima"; while (true){ str += str + new Random().nextInt(111111)+new Random().nextInt(22222222); str.intern(); } }
運行結果:多線程
3.java.lang.OutOfMemoryError: GC overhead limit exceeded GC回收時間過長併發
public static void outOfMemoryGC() { /* * GC回收時間過長時候會拋出OutOfMemoryError,過長的定義是指,超過98%的時間用來作GC而且回收了不到 * 2%的堆內存,連續屢次GC都回收不到2%的極端狀況下才會拋出.假如不拋出GC overflow limit會發生什麼 * 呢?那就是GC清理的內存很快就會被再次填滿,迫使GC再次執行,致使CPU的使用率一直在100%,可是GC卻沒有 * 任何成果. * 配置JVM參數 -Xms10m -Xmx10m -XX:MaxDirectMemorySize=5m -XX:+PrintGCDetails * * java.lang.OutOfMemoryError: GC overhead limit exceeded * */ int i = 0; List<String> list = new ArrayList<>(); try { while (true){ list.add(String.valueOf(++i).intern()); } }catch (Throwable e){ e.printStackTrace(); System.out.println("i = "+i); throw e; } }
運做結果:dom
4.java.lang.OutOfMemoryError: Direct buffer memory 外部內存滿了ide
public static void outOfMemoryDbm() { /* 寫NIO的程序進場使用ByteBuffer來讀取或寫入數據,這是一種基於通道和緩衝區的I/O方式,它可使用 Native函數庫直接分配堆外內存,而後經過一個存儲在Java堆裏面的DirectByteBuffer對象做爲這塊的引用 進行操做.這樣能夠在一些場景中顯著提升性能,由於避免了Java堆和Native堆中來回複製內存 ByteBuffer.allocate(capability) 第一種方式是分配JVM堆內存,屬於GC的範圍,因爲須要拷貝,全部 速度相對較慢 ByteBuffer.allocateDirect(capability)第一種方式是分配OS本地內存,不屬於GC管轄的範圍,因此 因爲不須要進行內存拷貝因此速度相對較快 但若是不斷分配本地內存,堆內存不多使用,那麼JVM就不須要執行GCDirectByteBuffer對象們就不會被回收 這時候堆內存充足,可是本地內存已經使用光,再次嘗試使用本地內存就會拋出OOM錯誤,JVM崩潰 JVM配置 -Xms10m -Xmx10m -XX:MaxDirectMemorySize=5m -XX:+PrintGCDetails * java.lang.OutOfMemoryError: Direct buffer memory nio程序常常出現 */ System.out.println("JVM最大可用內存: "+ (sun.misc.VM.maxDirectMemory() / (double)1024 / 1024 )+"MB"); try { TimeUnit.MICROSECONDS.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); } ByteBuffer byteBuffer = ByteBuffer.allocateDirect(6 * 1024 *1024); }
運行結果:函數
5.java.lang.OutOfMemoryError: unable to create new native thread 沒法建立新的本地的線程高併發
public static void outOfMemoryUcnt(){ /* * 高併發請求服務器時候,會出現以下的錯誤,準確的來講該異常與對應的平臺有關 * 致使緣由: * 1.應用建立了太多線程,一個應用程序建立了多個線程,超過了系統的承載 * 2.服務器並不容許應用程序建立過多的線程,Linux默認容許單個進程能夠建立的線程數爲1024個 * 應用程序建立的線程超過1024,就會拋出java.lang.OutOfMemoryError: unable to create new native thread * 解決辦法: * 1.想辦法下降應用程序建立的線程數量,分析應用程序是否真的須要建立這麼多線程,若是不是,修改代碼下降線程數 * 2.對於有的應用,確實須要建立不少線程,遠超過Linux系統默認的1024個上限,能夠經過修改Linux服務器配置 * 擴大Linux的默認限制 root用戶沒有限制 * ulimit -u 查看當前的用戶下能夠容許建立的線程數 * vim /etc/security/limits.d/90-nproc.conf 文件 * */ for (int i = 1; ; i++) { System.out.println("i = "+i); new Thread(()->{ try { Thread.sleep(Integer.MAX_VALUE); } catch (InterruptedException e) { e.printStackTrace(); } },String.valueOf(i)).start(); } }
運行結果:Windows沒有測出來
6.java.lang.OutOfMemoryError: Metaspace 元空間溢出
static class OOMTest{}; 內部類 public static void outOfMemoryMetaSpace(){ /* * MetaSpace是方法區在HotSpot中的實現,它和持久區最大的區別在於:MetaSpace並不在虛擬機內存而使用本地內存 * 在JDK1.8中,class Metadata 被存儲在MetaSpace的native memory * 永久代(元空間)存放了如下信息 * 1. 虛擬機加載的類信息 * 2. 常量池 * 3. 靜態變量 * 4. 即時編譯後的代碼 * 配置JVM: -XX:MetaspaceSize=8m -XX:MaxMetaspaceSize=9m * * */ int i = 0; try { while (true){ i++; Enhancer e = new Enhancer(); e.setSuperClass(OOMTest); e.setUseCache(false); e.setCallBack(new MethodInterceptor(){ @Override public Object intercept(Object o,Method method,Object[] objects,MethodProxy methodProxy) throws Throwable{ return methodProxy.invokeSuper(o,args); } }); e.create(); } }catch (Throwable e){ System.out.println("第" + i+"次元空間溢出"); e.printStackTrace(); } }
運行結果:Windows沒有出現!!!
二.請你能談一談Java的有哪些引用?JVM分別對不一樣的引用時怎樣進行回收的?有什麼做用?
1.強引用
當內存不足時,JVM會進行垃圾回收,對於強引用對象,就算出現OOM錯誤也不會對該對象進行垃圾回收,強引用是最多見的普通對象的引用,
只要還有強引用指向一個對象,就說明這個對象還活着,垃圾回收機制就不會回收這個對象,在Java中,最多見的引用就是強引用,把一個對象賦值
給一個引用變量,這個引用變量也是強引用當一個變量是強引用時,它處於可達的狀態,不被垃圾回收機制回收的.即便該對象之後永遠也用不到,JVM
也不會對其進行回收,所以強引用也是引起Java內存泄漏的主要緣由之一.
對於一個普通的對象,若是沒有其它得我引用關係,只要超過了引用的做用域,或者顯式的把強引用賦值爲null,通常就能夠被垃圾回收機制回收.
// 強引用 public static void show01(){ Object o1 = new Object(); // new 出來的對象通常默認是強引用 Object o2 = o1; // o2 引用賦值 o2也是強引用 o1 = null; // 置空 System.gc(); // 手動GC垃圾回收 System.out.println(o1); System.out.println(o2); }
運行結果: o2沒有被垃圾回收機制清除
2.軟引用
軟引用是相對於強引用弱化了一些的引用,須要使用java.lang.ref.SoftReference類來實現,可讓對象豁免一些垃圾回收,
對於軟引用的對象來講,當系統的內存充足時,不會被垃圾回收機制回收,當系統的內存不足時,會被垃圾回收機制回收.
軟引用一般用在一些對內存敏感的系統中,好比高速緩存就有用到軟引用
//軟引用 public static void show02(boolean flag){ if(flag){ // 內存充足 Object o1 = new Object(); SoftReference<Object> softReference = new SoftReference<>(o1); System.out.println(o1); System.out.println(softReference.get()); o1 = null; System.gc(); System.out.println(o1); System.out.println(softReference.get()); }else{ // 內存不充足設置JVM -Xms5m -Xmx5m -XX:+PrintGCDetails Object o1 = new Object(); SoftReference<Object> softReference = new SoftReference<>(o1); System.out.println(o1); System.out.println(softReference.get()); o1 = null; System.gc(); try { // 形成OOM,故意製造大對象 byte[] bytes = new byte[30 * 1024 * 1024]; }catch (Throwable e){ e.printStackTrace(); }finally { System.out.println(o1); System.out.println(softReference.get()); } } }
運行結果:
1.內存充足,o1被置空,但o2沒有被回收
2.內存不足 o1,o2都被置爲空
3.弱引用
須要使用java.lang.ref.WeakReference類來實現,它比軟引用的生命週期更短
對於只有軟引用的對象來講,只要垃圾回收機制一運行,無論JVM的內存空間是否充足,都會回收該對象所佔的內存
用處:假若有一個應用,須要讀取大量的本地圖片,若是每次都從硬盤中讀取,嚴重影響速度,若是所有加載到內存,又可能引起OOM
使用軟引用/弱引用能夠解決此類問題
使用一個HashMap來保存圖片的路徑和相應圖片對象關聯的軟引用之間的映射關係,在內存不足時,
JVM會自動回收這些緩存圖片所佔用的空間,從而有效避免OOM問題
Map<String,SoftReference<BitMap>> imageCache = new HashMap<String,SoftReference<BitMap>>();
public static void show03(){ Object o1 = new Object(); WeakReference<Object> weakReference = new WeakReference<>(o1); System.out.println(o1); System.out.println(weakReference.get()); o1 = null; System.gc(); System.out.println(o1); System.out.println(weakReference.get()); }
運行結果: 只要一運行垃圾回收,內存就會被釋放
4.虛引用
虛引用主要是經過java.lang.ref.PhantomReference類來實現的,與其餘的引用類型不一樣,虛引用並不會決定對象的生命週期
若是一個對象僅持有虛引用,那麼它就和沒有任何引用同樣,在任什麼時候候都有可能被垃圾回收機制回收,它不能單獨使用也不能
經過它訪問對象,虛引用必須和引用隊列來聯合使用虛引用的主要做用是跟蹤對象被垃圾回收的狀態,就是這個對象被收集器回收
的時候收到一個系統的通知或者後續添加作進一步的處理
public static void show04(){ Object o1 = new Object(); ReferenceQueue<Object> referenceQueue = new ReferenceQueue<>(); PhantomReference<Object> phantomReference = new PhantomReference<>(o1,referenceQueue); System.out.println(o1); System.out.println(referenceQueue.poll()); System.out.println(phantomReference.get()); System.out.println("---------------------"); o1 = null; System.gc(); System.out.println(o1); System.out.println(referenceQueue.poll()); System.out.println(phantomReference.get()); }
運行結果:
1.虛引用的get()方法永遠只返回null
2.在進行垃圾回收以後,在引用隊列中能夠看到此對象,主要就是在對象銷燬前作出一些通知,相似於Spring的後置AOP