一区二区三区在线-一区二区三区亚洲视频-一区二区三区亚洲-一区二区三区午夜-一区二区三区四区在线视频-一区二区三区四区在线免费观看

服務器之家:專注于服務器技術及軟件下載分享
分類導航

PHP教程|ASP.NET教程|Java教程|ASP教程|編程技術|正則表達式|C/C++|IOS|C#|Swift|Android|VB|R語言|JavaScript|易語言|vb.net|

服務器之家 - 編程語言 - Java教程 - Java中七個潛在的內存泄露風險,你知道幾個?

Java中七個潛在的內存泄露風險,你知道幾個?

2021-03-23 01:09苦味代碼L Java教程

雖然Java程序員不用像C/C++程序員那樣時刻關注內存的使用情況,JVM會幫我們處理好這些,但并不是說有了GC就可以高枕無憂,內存泄露相關的問題一般在測試的時候很難發現,一旦上線流量起來可能馬上就是一個詭異的線上故障。

雖然Java程序員不用像C/C++程序員那樣時刻關注內存的使用情況,JVM會幫我們處理好這些,但并不是說有了GC就可以高枕無憂,內存泄露相關的問題一般在測試的時候很難發現,一旦上線流量起來可能馬上就是一個詭異的線上故障。

Java中七個潛在的內存泄露風險,你知道幾個?

1. 內存泄露的定義

如果GC無法回收內存中不再使用的對象,則定義為內存有泄露

2. 未關閉的資源類

當我們在程序中打開一個新的流或者是新建一個網絡連接的時候,JVM都會為這些資源類分配內存做緩存,常見的資源類有網絡連接,數據庫連接以及IO流。值得注意的是,如果在業務處理中異常,則有可能導致程序不能執行關閉資源類的代碼,因此最好按照下面的做法處理資源類

public void handleResource() { 

    try { 

        // open connection 

        // handle business 

    } catch (Throwable t) { 

        // log stack 

    } finally { 

        // close connection 

    } 

3. 未正確實現equals()和hashCode()

假如有下面的這個類

public class Person { 

    public String name

     

    public Person(String name) { 

        this.name = name

    } 

并且如果在程序中有下面的操作

@Test 

public void givenMapWhenEqualsAndHashCodeNotOverriddenThenMemoryLeak() { 

    Map<Person, Integer> map = new HashMap<>(); 

    for(int i=0; i<100; i++) { 

        map.put(new Person("jon"), 1); 

    } 

    Assert.assertFalse(map.size() == 1); 

可以預見,這個單元測試并不能通過,原因是Person類沒有實現equals方法,因此使用Object的equals方法,直接比較實體對象的地址,所以map.size() == 100

如果我們改寫Person類的代碼如下所示:

public class Person { 

    public String name

     

    public Person(String name) { 

        this.name = name

    } 

     

    @Override 

    public boolean equals(Object o) { 

        if (o == this) return true

        if (!(o instanceof Person)) { 

            return false

        } 

        Person person = (Person) o; 

        return person.name.equals(name); 

    } 

     

    @Override 

    public int hashCode() { 

        int result = 17; 

        result = 31 * result + name.hashCode(); 

        return result; 

    } 

則上文中的單元測試就可以順利通過了,需要注意的是這個場景比較隱蔽,一定要在平時的代碼中注意。

4. 非靜態內部類

要知道,所有的非靜態類別類都持有外部類的引用,因此某些情況如果引用內部類可能延長外部類的生命周期,甚至持續到進程結束都不能回收外部類的空間,這類內存溢出一般在Android程序中比較多,只要MyAsyncTask處于運行狀態MainActivity的內存就釋放不了,很多時候安卓開發者這樣做只是為了在內部類中拿到外部類的屬性,殊不知,此時內存已經泄露了。

public class MainActivity extends Activity { 

    @Override 

    protected void onCreate(Bundle savedInstanceState) { 

        super.onCreate(savedInstanceState); 

        setContentView(R.layout.main); 

        new MyAsyncTask().execute(); 

    } 

 

    private class MyAsyncTask extends AsyncTask { 

        @Override 

        protected Object doInBackground(Object[] params) { 

            return doSomeStuff(); 

        } 

        private Object doSomeStuff() { 

            //do something to get result 

            return new MyObject(); 

        } 

    } 

5. 重寫了finalize()的類

如果運行下面的這個例子,則最終程序會因為OOM的原因崩潰

public class Finalizer { 

    @Override 

    protected void finalize() throws Throwable { 

    while (true) { 

           Thread.yield(); 

      } 

  } 

 

public static void main(String str[]) { 

  while (true) { 

        for (int i = 0; i < 100000; i++) { 

            Finalizer force = new Finalizer(); 

        } 

   } 

 } 

JVM對重寫了finalize()的類的處理稍微不同,首先會針對這個類創建一個java.lang.ref.Finalizer類,并讓java.lang.ref.Finalizer持有這個類的引用,在上文中的例子中,因為Finalizer類的引用被java.lang.ref.Finalizer持有,所以他的實例并不能被Young GC清理,反而會轉入到老年代。在老年代中,JVM GC的時候會發現Finalizer類只被java.lang.ref.Finalizer引用,因此將其標記為可GC狀態,并放入到java.lang.ref.Finalizer.ReferenceQueue這個隊列中。等到所有的Finalizer類都加到隊列之后,JVM會起一個后臺線程去清理java.lang.ref.Finalizer.ReferenceQueue中的對象,之后這個后臺線程就專門負責清理java.lang.ref.Finalizer.ReferenceQueue中的對象了。這個設計看起來是沒什么問題的,但其實有個坑,那就是負責清理java.lang.ref.Finalizer.ReferenceQueue的后臺線程優先級是比較低的,并且系統沒有提供可以調節這個線程優先級的接口或者配置。因此當我們在使用使用重寫finalize()方法的對象時,千萬不要瞬間產生大量的對象,要時刻謹記,JVM對此類對象的處理有特殊邏輯。

6. 針對長字符串調用String.intern()

如果提前在src/test/resources/large.txt中寫入大量字符串,并且在Java 1.6及以下的版本運行下面程序,也將得到一個OOM

@Test 

public void givenLengthString_whenIntern_thenOutOfMemory() 

  throws IOException, InterruptedException { 

    String str  

      = new Scanner(new File("src/test/resources/large.txt"), "UTF-8"

      .useDelimiter("\\A").next(); 

    str.intern(); 

     

    System.gc();  

    Thread.sleep(15000); 

原因是在Java 1.6及以下,字符串常量池是處于JVM的PermGen區的,并且在程序運行期間不會GC,因此產生了OOM。在Java 1.7以及之后字符串常量池轉移到了HeapSpace此類問題也就無需再關注了

7. ThreadLocal的誤用

ThreadLocal一定要列在Java內存泄露的榜首,總能在不知不覺中將內存泄露掉,一個常見的例子是:

@Test 

public void testThreadLocalMemoryLeaks() { 

    ThreadLocal<List<Integer>> localCache = new ThreadLocal<>(); 

   List<Integer> cacheInstance = new ArrayList<>(10000); 

    localCache.set(cacheInstance); 

    localCache = new ThreadLocal<>(); 

當localCache的值被重置之后cacheInstance被ThreadLocalMap中的value引用,無法被GC,但是其key對ThreadLocal實例的引用是一個弱引用,本來ThreadLocal的實例被localCache和ThreadLocalMap的key同時引用,但是當localCache的引用被重置之后,則ThreadLocal的實例只有ThreadLocalMap的key這樣一個弱引用了,此時這個實例在GC的時候能夠被清理。

Java中七個潛在的內存泄露風險,你知道幾個?

img

其實看過ThreadLocal源碼的同學會知道,ThreadLocal本身對于key為null的Entity有自清理的過程,但是這個過程是依賴于后續對ThreadLocal的繼續使用,假如上面的這段代碼是處于一個秒殺場景下,會有一個瞬間的流量峰值,這個流量峰值也會將集群的內存打到高位(或者運氣不好的話直接將集群內存打滿導致故障),后面由于峰值流量已過,對ThreadLocal的調用也下降,會使得ThreadLocal的自清理能力下降,造成內存泄露。ThreadLocal的自清理實現是錦上添花,千萬不要指望它雪中送碳。

8. 類的靜態變量

Tomcat對在網絡容器中使用ThreadLocal引起的內存泄露做了一個總結,詳見:https://cwiki.apache.org/confluence/display/tomcat/MemoryLeakProtection,這里我們列舉其中的一個例子。

熟悉Tomcat的同學知道,Tomcat中的web應用由webapp classloader這個類加載器的,并且webapp classloader是破壞雙親委派機制實現的,即所有的web應用先由webapp classloader加載,這樣的好處就是可以讓同一個容器中的web應用以及依賴隔離。

下面我們看具體的內存泄露的例子:

public class MyCounter { 

 private int count = 0; 

 

 public void increment() { 

  count++; 

 } 

 

 public int getCount() { 

  return count

 } 

 

public class MyThreadLocal extends ThreadLocal<MyCounter> { 

 

public class LeakingServlet extends HttpServlet { 

 private static MyThreadLocal myThreadLocal = new MyThreadLocal(); 

 

 protected void doGet(HttpServletRequest request, 

   HttpServletResponse response) throws ServletException, IOException { 

 

  MyCounter counter = myThreadLocal.get(); 

  if (counter == null) { 

   counter = new MyCounter(); 

   myThreadLocal.set(counter); 

  } 

 

  response.getWriter().println( 

    "The current thread served this servlet " + counter.getCount() 

      + " times"); 

  counter.increment(); 

 } 

需要注意這個例子中的兩個非常關鍵的點:

  • MyCounter以及MyThreadLocal必須放到web應用的路徑中,保被webapp classloader加載
  • ThreadLocal類一定得是ThreadLocal的繼承類,比如例子中的MyThreadLocal,因為ThreadLocal本來被common classloader加載,其生命周期與tomcat容器一致。ThreadLocal的繼承類包括比較常見的NamedThreadLocal,注意不要踩坑。

假如LeakingServlet所在的web應用啟動,MyThreadLocal類也會被webapp classloader加載,如果此時web應用下線,而線程的生命周期未結束(比如為LeakingServlet提供服務的線程是一個線程池中的線程),那會導致myThreadLocal的實例仍然被這個線程引用,而不能被GC,期初看來這個帶來的問題也不大,因為myThreadLocal所引用的對象占用的內存空間不太多,問題在于myThreadLocal間接持有加載web應用的webapp classloader的引用(通過myThreadLocal.getClass().getClassLoader()可以引用到),而加載web應用的webapp classloader有持有它加載的所有類的引用,這就引起了classloader泄露,它泄露的內存就非常可觀了。

原文地址:https://mp.weixin.qq.com/s?__biz=Mzk0NjExMjU3Mg==&mid=2247484269&idx=1&sn=3c2ffe355bd2f0890411e27ac3f84e90&chksm=c30a523ef47ddb284eb54872d5fd10b96f41762e049ae451daa649e7960bcdfdb15d44117035&mpshare=1&

延伸 · 閱讀

精彩推薦
主站蜘蛛池模板: 日本欧美强乱视频在线 | 日韩黄色影视 | 免费国产一级 | 亚洲精品日韩专区在线观看 | 桃色导航 | 羞羞麻豆国产精品1区2区3区 | 5555kkkk香蕉在线观看 | 特级淫片大乳女子高清视频 | 91视频a | 精品久久国产 | 国产一区二区三区欧美 | 亚洲六月丁香六月婷婷色伊人 | 校园春色偷拍自拍 | 免费看欧美一级特黄a大片一 | 欧洲vodafonewi精品 | 精品一区二区三区免费观看 | 亚洲天堂2016 | 亚洲成人看片 | 极品久久 | 四影虎库最新2021 | 日本高清va不卡视频在线观看 | 成人国产在线观看 | 惩罚狠h调教灌满 | 午夜理论电影在线观看亚洲 | 日本护士xxxx视频免费 | 美女脱得一二净无内裤全身的照片 | 亚洲AV 日韩 国产 有码 | 亚洲视频在线一区二区三区 | 欧美专区在线视频 | 骚虎最新网址 | 跪趴好紧h | 久久人妻少妇嫩草AV无码 | ai换脸杨颖被啪在线观看 | 国产精品网站在线观看 | 久久精品一区 | 草莓丝瓜芭乐樱桃榴莲色多黄 | 欧美日韩国产一区二区三区不卡 | 好逼365| 天天干天天日天天射天天操毛片 | 天天插在线视频 | 亚洲羞羞裸色私人影院 |