自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

交行2面:什么是內(nèi)存溢出和內(nèi)存泄漏?如何解決?

開發(fā) 后端
本文分析了Java的內(nèi)存溢出和內(nèi)存泄漏并且應(yīng)示例展示了它們導(dǎo)致的原因,應(yīng)該說它們是比較常見的內(nèi)存管理問題,如果在生產(chǎn)環(huán)境出現(xiàn)也是比較頭疼的問題。

內(nèi)存溢出和內(nèi)存泄漏是我們經(jīng)常聽到的兩種內(nèi)存管理問題,那么,它們是如何導(dǎo)致的?又該如何解決?這篇文章,我們來聊一聊。

一、內(nèi)存溢出

內(nèi)存溢出(OutOfMemoryError)是指程序在運行時嘗試分配內(nèi)存,但由于沒有足夠的內(nèi)存可用,Java 虛擬機(JVM)拋出了 OutOfMemoryError 錯誤。常見的內(nèi)存溢出區(qū)域包括堆內(nèi)存和永久代(在 Java 8 之后被元空間取代)。

1.導(dǎo)致的原因

導(dǎo)致內(nèi)存溢出主要有以下幾個原因:1. 堆內(nèi)存溢出:創(chuàng)建大量對象,導(dǎo)致堆內(nèi)存耗盡。2. 棧內(nèi)存溢出:遞歸調(diào)用過深,導(dǎo)致棧內(nèi)存耗盡。3. 永久代/元空間溢出:類加載過多,導(dǎo)致永久代/元空間耗盡。

下面我們用三個示例,分別展示了堆內(nèi)存溢出、棧內(nèi)存溢出和永久代/元空間溢出的情況:

(1) 堆內(nèi)存溢出

如下示例代碼,通過不斷向 ArrayList 添加對象來耗盡堆內(nèi)存。

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

public class HeapMemoryOverflow {
    public static void main(String[] args) {
        List<Object> list = new ArrayList<>();
        while (true) {
            list.add(new Object());
        }
    }
}

在運行上述 HeapMemoryOverflow 示例時,可能需要調(diào)整 JVM 參數(shù)以較小的堆大小運行,例如 -Xmx10m,以更快地觀察到 OutOfMemoryError。

(2) 棧內(nèi)存溢出

如下示例代碼,通過遞歸調(diào)用一個沒有終止條件的方法,導(dǎo)致棧內(nèi)存溢出。

public class StackMemoryOverflow {
    public static void main(String[] args) {
        recursiveMethod();
    }

    public static void recursiveMethod() {
        // 沒有終止條件的遞歸調(diào)用
        recursiveMethod();
    }
}

運行StackOverflowError代碼,通常會很快發(fā)生棧內(nèi)存溢出,因為默認的棧大小不大。

(3) 永久代/元空間溢出

在 Java 8 之前,永久代溢出可以通過動態(tài)生成大量類來模擬,Java 8 之后,永久代被元空間取代,以下是一個使用 CGLIB 動態(tài)生成類的示例,可能導(dǎo)致元空間溢出,需要添加 CGLIB 庫依賴。

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class MetaspaceOverflow {
    public static void main(String[] args) {
        while (true) {
            Enhancer enhancer = new Enhancer();
            enhancer.setSuperclass(DummyClass.class);
            enhancer.setUseCache(false);
            enhancer.setCallback(new MethodInterceptor() {
                @Override
                public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
                    return proxy.invokeSuper(obj, args);
                }
            });
            enhancer.create();
        }
    }

    static class DummyClass {
    }
}

運行 MetaspaceOverflow 示例時,可以使用 JVM 參數(shù) -XX:MaxMetaspaceSize=10m 來限制元空間大小,以更快地觀察到溢出。

2.解決方法

在這里,我們只是給了一個大的思路,關(guān)于內(nèi)存溢出的排查工作也是一個很重要的知識點,我們會在后面的文章中去詳細介紹。

  • 增加內(nèi)存:調(diào)整 JVM 參數(shù)增加堆內(nèi)存大小,如 -Xmx。
  • 優(yōu)化代碼:減少不必要的對象創(chuàng)建,優(yōu)化數(shù)據(jù)結(jié)構(gòu)。
  • 檢查遞歸:避免過深的遞歸調(diào)用。
  • 監(jiān)控和分析:使用工具如 JVisualVM、JProfiler 分析內(nèi)存使用情況。

二、內(nèi)存泄漏

內(nèi)存泄漏(Memory Leak)是指程序中存在一些對象,它們不再被使用,但由于仍然被引用,垃圾回收器無法回收這些對象。因此,隨著時間的推移,內(nèi)存泄漏會導(dǎo)致可用內(nèi)存逐漸減少,最終可能導(dǎo)致內(nèi)存溢出。

1.導(dǎo)致的原因

導(dǎo)致內(nèi)存泄漏主要有以下幾個原因:

  • 靜態(tài)集合類:使用 static 修飾的集合類持有對象引用,因為靜態(tài)集合的生命周期和 JVM 一致,所以靜態(tài)集合引用的對象不能被釋放。
  • 監(jiān)聽器和回調(diào):注冊的監(jiān)聽器或回調(diào)未被移除。
  • 長生命周期對象持有短生命周期對象:長生命周期對象不當(dāng)持有短生命周期對象的引用。

下面我們用三個示例,分別展示了內(nèi)存泄漏可能發(fā)生的場景:

(1) 靜態(tài)集合類導(dǎo)致的內(nèi)存泄漏

靜態(tài)集合類持有對象引用,導(dǎo)致這些對象無法被垃圾回收。

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

public class StaticCollectionLeak {
    // 靜態(tài)集合持有對象引用
    private static List<Object> objectList = new ArrayList<>();

    public static void main(String[] args) {
        for (int i = 0; i < 10000; i++) {
            // 每次創(chuàng)建一個新對象并添加到靜態(tài)集合中
            objectList.add(new Object());
        }
        // 即使在這里試圖清理掉一些其他的引用
        System.gc();  // 這些對象仍然無法被回收,因為它們被靜態(tài)集合引用
    }
}

(2) 監(jiān)聽器和回調(diào)未被移除

注冊的監(jiān)聽器或回調(diào)未被移除,導(dǎo)致內(nèi)存泄漏。

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

public class ListenerLeak {
    private List<EventListener> listeners = new ArrayList<>();

    public void addListener(EventListener listener) {
        listeners.add(listener);
    }

    public void triggerEvent() {
        for (EventListener listener : listeners) {
            listener.onEvent();
        }
    }

    public static void main(String[] args) {
        ListenerLeak leakExample = new ListenerLeak();
        
        // 匿名類創(chuàng)建的監(jiān)聽器對象
        leakExample.addListener(new EventListener() {
            @Override
            public void onEvent() {
                System.out.println("Event triggered");
            }
        });

        // 假設(shè)在某個時候不再需要監(jiān)聽器,但未移除
        // listeners.remove(listener); // 應(yīng)該移除不需要的監(jiān)聽器
    }
}

interface EventListener {
    void onEvent();
}

(3) 長生命周期對象持有短生命周期對象

長生命周期對象不當(dāng)持有短生命周期對象的引用,導(dǎo)致短生命周期對象無法被回收。

import java.util.HashMap;
import java.util.Map;

public class LongLifeCycleLeak {
    private static Map<String, byte[]> cache = new HashMap<>();

    public static void main(String[] args) {
        while (true) {
            // 短生命周期對象
            byte[] data = new byte[1024 * 1024]; // 1MB

            // 長生命周期對象持有短生命周期對象的引用
            cache.put(String.valueOf(System.nanoTime()), data);

            // 需要定期移除不再需要的數(shù)據(jù),否則會導(dǎo)致內(nèi)存泄漏
            // cache.clear(); // 應(yīng)該在適當(dāng)時機清理緩存
        }
    }
}

2.解決方法

在這里,我們只是給了一個大的思路,關(guān)于內(nèi)存泄漏的排查工作也是一個很重要的知識點,我們會在后面的文章中去詳細介紹。

  • 及時釋放引用:確保不再使用的對象引用被清除。
  • 使用弱引用:對緩存或非關(guān)鍵對象使用 WeakReference。比如 ThreadLocal 的弱引用會導(dǎo)致內(nèi)存泄漏,因此使用完 ThreadLocal 一定要記得使用 remove 方法來進行清除。
  • 正確管理生命周期:特別是監(jiān)聽器和回調(diào),確保在不需要時移除。

3.示例代碼

下面示例代碼,用于測試內(nèi)存泄漏。

import java.util.HashMap;
import java.util.Map;

public class MemoryLeakExample {
    private static Map<Integer, String> map = new HashMap<>();

    public static void main(String[] args) {
        for (int i = 0; i < 100000; i++) {
            map.put(i, "value" + i);
        }
    }
}

在上面的代碼中,如果 map 是一個長期存在的靜態(tài)變量,并且沒有及時清理,則可能導(dǎo)致內(nèi)存泄漏。

三、對比

關(guān)于內(nèi)存溢出和內(nèi)存泄漏的比較如下:

  • 觸發(fā)時機:內(nèi)存溢出通常在內(nèi)存耗盡時立即觸發(fā),而內(nèi)存泄漏可能在一段時間后逐漸顯現(xiàn)。
  • 影響范圍:內(nèi)存溢出會立即影響程序的可用性,而內(nèi)存泄漏通常是一個逐步積累的問題。
  • 檢測難度:內(nèi)存溢出較容易檢測,而內(nèi)存泄漏往往需要深入分析和調(diào)試。
  • 解決復(fù)雜度:內(nèi)存溢出的解決相對簡單,通常通過優(yōu)化內(nèi)存使用或增加內(nèi)存即可。而內(nèi)存泄漏的解決需要識別并清理不必要的引用,可能涉及更復(fù)雜的代碼重構(gòu)。

四、總結(jié)

本文,我們分析了Java的內(nèi)存溢出和內(nèi)存泄漏并且應(yīng)示例展示了它們導(dǎo)致的原因,應(yīng)該說它們是比較常見的內(nèi)存管理問題,如果在生產(chǎn)環(huán)境出現(xiàn)也是比較頭疼的問題。所以在日常開發(fā)中,我們一定要注意自己的代碼風(fēng)格和代碼質(zhì)量,盡量避免這些問題的發(fā)生。

責(zé)任編輯:趙寧寧 來源: 猿java
相關(guān)推薦

2024-09-09 09:41:03

內(nèi)存溢出golang開發(fā)者

2021-03-04 17:21:49

內(nèi)存檢測泄漏

2025-04-01 05:22:00

JavaThread變量

2024-03-11 08:22:40

Java內(nèi)存泄漏

2015-03-30 11:18:50

內(nèi)存管理Android

2020-01-14 10:57:39

內(nèi)存泄漏虛擬機

2024-03-25 12:38:00

MySQL內(nèi)存參數(shù)

2019-06-24 19:00:09

JavaScript內(nèi)存泄漏垃圾回收

2014-04-03 09:36:37

內(nèi)存溢出內(nèi)存原理

2024-10-15 09:25:08

JDBCMybatis數(shù)據(jù)庫

2024-01-30 10:12:00

Java內(nèi)存泄漏

2013-06-26 16:14:26

Android加載圖片內(nèi)存溢出

2023-12-18 10:45:23

內(nèi)存泄漏計算機服務(wù)器

2021-08-05 15:28:22

JS內(nèi)存泄漏

2021-08-09 09:54:37

內(nèi)存泄漏JS 阿里云

2021-06-28 06:45:06

內(nèi)存溢出內(nèi)存泄露JavaScript

2024-03-22 13:31:00

線程策略線程池

2019-09-24 08:56:00

內(nèi)存Redis使用

2013-12-17 15:46:04

iOS開發(fā)iOS 內(nèi)存泄漏

2021-08-10 09:58:59

ThreadLocal內(nèi)存泄漏
點贊
收藏

51CTO技術(shù)棧公眾號