Java 內部類有坑。。100 % 內存泄露!

來源:https://knife.blog.csdn.net/article/details/124946774

今天給大家分享一種,Java內部類使用不當導致的內存泄露問題,最終導致內存溢出!希望能夠幫助到大家!

簡介

「說明」

本文介紹 Java 內部類持有外部類導致內存泄露的原因以及其解決方案。

「爲什麼內部類持有外部類會導致內存泄露?」

非靜態內部類會持有外部類,如果有地方引用了這個非靜態內部類,會導致外部類也被引用,垃圾回收時無法回收這個外部類(即使外部類已經沒有其他地方在使用了)。

「解決方案」

  1. 不要讓其他的地方持有這個非靜態內部類的引用,直接在這個非靜態內部類執行業務。
  2. 將非靜態內部類改爲靜態內部類。內部類改爲靜態的之後,它所引用的對象或屬性也必須是靜態的,所以靜態內部類無法獲得外部對象的引用,只能從 JVM 的 Method Area(方法區)獲取到static類型的引用。

爲什麼要持有外部類

Java 語言中,非靜態內部類的主要作用有兩個:

  1. 當內部類只在外部類中使用時,匿名內部類可以讓外部不知道它的存在,從而減少了代碼的維護工作。
  2. 當內部類持有外部類時,它就可以直接使用外部類中的變量了,這樣可以很方便的完成調用,如下代碼所示:
package org.example.a;

class Outer{
    private String outerName = "Tony";

    class Inner{
        private String name;

        public Inner() {
            this.name = outerName;
        }
    }

    Inner createInner() {
        return new Inner();
    }
}

public class Demo {
    public static void main(String[] args) {
        Outer.Inner inner = new Outer().createInner();
        System.out.println(inner);
    }
}

但是,靜態內部類就無法持有外部類和其非靜態字段了。另外,最新 Java 面試題整理:https://www.javastack.cn/mst/

比如下邊這樣就會報錯

package org.example.a;

class Outer{
    private String outerName = "Tony";

    static class Inner{
        private String name;

        public Inner() {
            this.name = outerName;
        }
    }

    Inner createInner() {
        return new Inner();
    }
}

public class Demo {
    public static void main(String[] args) {
        Outer.Inner inner = new Outer().createInner();
        System.out.println(inner);
    }
}

報錯:

實例:持有外部類

「代碼」

package org.example.a;

class Outer{
    class Inner {

    }

    Inner createInner() {
        return new Inner();
    }
}

public class Demo {
    public static void main(String[] args) {
        Outer.Inner inner = new Outer().createInner();
        System.out.println(inner);
    }
}

「斷點調試」

可以看到:內部類持有外部類的對象的引用,是以“this$0”這個字段來保存的。

實例:不持有外部類

「代碼」

package org.example.a;

class Outer{
    static class Inner {

    }

    Inner createInner() {
        return new Inner();
    }
}

public class Demo {
    public static void main(String[] args) {
        Outer.Inner inner = new Outer().createInner();
        System.out.println(inner);
    }
}

更多 Java 教程及示例:https://github.com/javastacks/javastack

「斷點調試」

可以發現:內部類不再持有外部類了。

實例:內存泄露

「簡介」

若內部類持有外部類的引用,對內部類的使用很多時,會導致外部類數目很多。此時,就算是外部類的數據沒有被用到,外部類的數據所佔空間也不會被釋放。

本處在外部類存放大量的數據來模擬。

「代碼」

package org.example.a;

import java.util.ArrayList;
import java.util.List;

class Outer{
    private int[] data;

    public Outer(int size) {
        this.data = new int[size];
    }

    class Innner{

    }

    Innner createInner() {
        return new Innner();
    }
}

public class Demo {
    public static void main(String[] args) {
        List<Object> list = new ArrayList<>();
        int counter = 0;
        while (true) {
            list.add(new Outer(100000).createInner());
            System.out.println(counter++);
        }
    }
}

「測試」

可以看到:運行了八千多次的時候就內存溢出了。

我換了一臺 mac 電腦,4000 多就內存溢出了。

不會內存泄露的方案

「簡介」

內部類改爲靜態的之後,它所引用的對象或屬性也必須是靜態的,所以靜態內部類無法獲得外部對象的引用,只能從 JVM 的 Method Area(方法區)獲取到 static 類型的引用。

「代碼」

package org.example.a;

import java.util.ArrayList;
import java.util.List;

class Outer{
    private int[] data;

    public Outer(int size) {
        this.data = new int[size];
    }

    static class Inner {

    }

    Inner createInner() {
        return new Inner();
    }
}

public class Demo {
    public static void main(String[] args) {
        List<Object> list = new ArrayList<>();
        int counter = 0;
        while (true) {
            list.add(new Outer(100000).createInner());
            System.out.println(counter++);
        }
    }
}

「測試」

可以發現:循環了四十多萬次都沒有內存溢出。

近期熱文推薦:

1.1,000+ 道 Java面試題及答案整理(2022最新版)

2.勁爆!Java 協程要來了。。。

3.Spring Boot 2.x 教程,太全了!

4.別再寫滿屏的爆爆爆炸類了,試試裝飾器模式,這纔是優雅的方式!!

5.《Java開發手冊(嵩山版)》最新發布,速速下載!

覺得不錯,別忘了隨手點贊+轉發哦!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章