您好,登錄后才能下訂單哦!
這篇文章給大家分享的是有關Java中內存安全問題的注意事項有哪些的內容。小編覺得挺實用的,因此分享給大家做個參考,一起跟隨小編過來看看吧。
Java在內存管理方面是要比C/C++更方便的,不需要為每一個對象編寫釋放內存的代碼,JVM虛擬機將為我們選擇合適的時間釋放內存空間,使得程序不容易出現內存泄漏和溢出的問題
不過,也正是因為Java把內存控制的權利交給了Java虛擬機,一旦出現內存泄漏和溢出方面的問題,如果不了解虛擬機是怎么使用內存的,那排查錯誤將會成為一項異常艱難的工作
下面先看看JVM如何管理內存的
根據Java虛擬機規范(第3版) 的規定,Java虛擬機所管理的內存將會包括以下幾個運行內存數據區域:
線程隔離數據區:
程序計數器: 當前線程所執行字節碼的行號指示器
虛擬機棧: 里面的元素叫棧幀,存儲局部變量表、操作棧、動態鏈接、方法出口等,方法被調用到執行完成的過程對應一個棧幀在虛擬機棧中入棧到出棧的過程。
本地方法棧: 和虛擬機棧的區別在于虛擬機棧為虛擬機執行Java方法,本地方法棧為虛擬機使用到的本地Native方法服務。
線程共享數據區:
方法區: 可以描述為堆的一個邏輯部分,或者說使用永久代來實現方法區。存儲已被虛擬機加載的類信息、常量、靜態變量、即時編譯器編譯后的代碼等數據。
堆: 唯一目的就是存放對象的實例,是垃圾回收管理器的主要區域,分為Eden、From/To Survivor空間。
下圖中永久代理解為堆的邏輯區域,移除永久代的工作從JDK7就已經開始了,部分永久代中的數據(常量池)在JDK7中就已經轉移到了堆中,JDK8中直接去除了永久代,方法區中的數據大部分被移到堆里面,還剩下一些元數據被保存在元空間里
內存泄露Memory Leak: 申請的內存空間沒有及時釋放,導致后續程序里這塊內容永遠被占用。
內存溢出Out Of Memory: 要求的內存超過了系統所能提供的
運行時數據區域的常見異常
在JVM中,除了程序計數器外,虛擬機內存的其他幾個運行時數據區域都有發生OOM異常的可能。
不斷的創建對象,并且保證GC Roots到對象之間有可達路徑來避免垃圾回收機制清除這些對象。
public class HeapOOM { static class ObjectInHeap{ } public static void main(String[] args) { List<ObjectInHeap> list = new ArrayList(); while (true) { list.add(new ObjectInHeap()); } } }
單個線程下不斷擴大棧的深度引起棧溢出。
public class StackSOF { private int stackLength = 1; public void stackLeak() { stackLength++; stackLeak(); } public static void main(String[] args) { StackSOF sof = new StackSOF(); try { sof.stackLeak(); } catch (Throwable e) { System.out.println("Stack Length: " + sof.stackLength); throw e; } } }
循環的創建線程,達到最大棧容量。
public class StackOOM { private void dontStop() { while (true) { } } public void stackLeadByThread() { while (true) { Thread thread = new Thread(new Runnable() { @Override public void run() { dontStop(); } }); thread.start(); } } public static void main(String[] args) { StackOOM stackOOM = new StackOOM(); stackOOM.stackLeadByThread(); } }
不斷的在常量池中新建String,并且保持引用不釋放。
public class RuntimeConstantPoolOOM { public static void main(String[] args) { // 使用List保持著常量池的引用,避免Full GC回收常量池 List<String> list = new ArrayList<String>(); int i = 0; while (true) { // intern()方法使String放入常量池 list.add(String.valueOf(i++).intern()); } } }
借助CGLib直接操作字節碼運行時產生大量的動態類,最終撐爆內存導致方法區溢出。
public class MethodAreaOOM { static class ObjectInMethod { } public static void main(final String[] args) { // 借助CGLib實現 while (true) { Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(ObjectInMethod.class); enhancer.setUseCache(false); enhancer.setCallback(new MethodInterceptor() { @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { return methodProxy.invokeSuper(o, objects); } }); enhancer.create(); } } }
助CG Lib運行時產生大量動態類,唯一的區別在于運行環境修改為Java 1.8,設置-XX:MaxMetaspaceSize參數,便可以收獲java.lang.OutOfMemoryError: Metaspace這一報錯
直接申請分配內存(實際上并沒有真正向操作系統申請分配內存,而是通過計算得知內存無法分配,于是拋出異常)
public class DirectMemoryOOM { private static final int _1MB = 1024 * 1024; public static void main(String[] args) throws IllegalAccessException { Field unsafeField = Unsafe.class.getDeclaredFields()[0]; unsafeField.setAccessible(true); Unsafe unsafe = (Unsafe) unsafeField.get(null); while (true) { unsafe.allocateMemory(_1MB); } } }
在工作中一般會遇到有以下幾種情況導致內存問題
傳輸數據量過大
因為傳輸數量過大、或一些極端情況導致代碼中間結果對象數據量過大,過大的數據量撐爆內存
查詢出大量對象
這個多為SQL語句設置問題,SQL未設置分頁,用戶一次查詢數據量過大、頻繁查詢SQL導致內存堆積、或是未作判空處理導致WHERE條件為空查詢出超大數據量等
接口性能問題導致
這類為外部接口性能較慢,占用內存較大,并且短時間內高QPS導致的,導致服務內存不足,線程堆積或掛起進而出現FullGC
元空間問題
使用了大量的反射代碼,Java字節碼存取器生成的類不斷生成
使用jmap分析內存泄漏
1.生成dump文件
jmap -dump:format=b,file=/xx/xx/xx.hprof pid
2.dump文件下載到本地
3.dump文件分析
可以使用MAT,MAT可作為Eclipse插件或一個獨立軟件使用,MAT是一個高性能、具備豐富功能的Java堆內存分析工具,主要用來排查內存泄漏和內存浪費的問題。
使用MAT打開上一部后綴名.hprof的dump文件
Histogram:直方圖,各個類的實例,包括個數和大小,可以查看類引用和被引用的路徑。
Dominator Tree:支配圖,列出所有線程和線程下面的那些對象占用的空間。
Top Consumers:通過圖形列出消耗內存多的實例。
Leak Suspects:MAT自動分析的內存泄漏報表
可以用這個工具分析出什么對象什么線程占用內存空間較大,對象是被什么引用的,線程內有哪些資源占用很高
以運行時常量池溢出為例
打開Histogram類實例表
Objects是類的對象的數量;Shallow是對象本身占用內存大小、不包含其他引用;
Retained是對象自己的Shallow加上直接或間接訪問到對象的Shallow之和,也可以說是GC之后可以回收的內存總和
從圖中可以看出運行時常量池溢出的情況,產生了大量的String和char[]實例
在char[]上右鍵可以得到上圖所有char[]對象的被引用路徑,可以看出這些char數組都是以String的形式存在ArrayList中,并且是由main這個線程運行的
可以看出是main線程中新建了一個數組,其中存了32w+個長度為6的char數組組成的String造成的內存溢出
感謝各位的閱讀!關于“Java中內存安全問題的注意事項有哪些”這篇文章就分享到這里了,希望以上內容可以對大家有一定的幫助,讓大家可以學到更多知識,如果覺得文章不錯,可以把它分享出去讓更多的人看到吧!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。