如何处置 什么是内存溢出和内存走漏 交行2面
内存溢出和内存走漏是咱们经常听到的两种内存治理疑问,那么,它们是如何造成的?又该如何处置?这篇文章,咱们来聊一聊。
一、内存溢出
内存溢出(OutOfMemoryError)是指程序在运转时尝试调配内存,但由于没有足够的内存可用,Java 虚构机(JVM)抛出了OutOfMemoryError失误。经常出现的内存溢出区域包含堆内存和终身代(在 Java 8 之后被元空间取代)。
1.造成的要素
造成内存溢出关键有以下几个要素:1. 堆内存溢出:创立少量对象,造成堆内存耗尽。2.栈内存溢出:递归调用过深,造成栈内存耗尽。3.终身代/元空间溢出:类加载过多,造成终身代/元空间耗尽。
上方咱们用三个示例,区分展现了堆内存溢出、栈内存溢出和终身代/元空间溢出的状况:
(1) 堆内存溢出
如下示例代码,经过始终向ArrayList参与对象来耗尽堆内存。
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示例时,或者须要调整 JVM 参数以较小的堆大小运转,例如-Xmx10m,以更快地观察到OutOfMemoryError。
(2) 栈内存溢出
如下示例代码,经过递归调用一个没有中断条件的方法,造成栈内存溢出。
public class StackMemoryOverflow {public static void main(String[] args) {recursiveMethod();}public static void recursiveMethod() {// 没有中断条件的递归调用recursiveMethod();}}
运转StackOverflowError代码,通常会很快出现栈内存溢出,由于自动的栈大小不大。
(3) 终身代/元空间溢出
在 Java 8 之前,终身代溢出可以经过灵活生成少量类来模拟,Java 8 之后,终身代被元空间取代,以下是一个经常使用 CGLIB 灵活生成类的示例,或者造成元空间溢出,须要参与 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() {@Overridepublic Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {return proxy.invokeSuper(obj, args);}});enhancer.create();}}static class DummyClass {}}
运转MetaspaceOverflow示例时,可以经常使用 JVM 参数-XX:MaxMetaspaceSize=10m来限度元空间大小,以更快地观察到溢出。
2.处置方法
在这里,咱们只是给了一个大的思绪,关于内存溢出的排查上班也是一个很关键的常识点,咱们会在前面的文章中去具体引见。
二、内存走漏
内存走漏(Memory Leak)是指程序中存在一些对象,它们不再被经常使用,但由于依然被援用,渣滓回收器无法回收这些对象。因此,随着期间的推移,内存走漏会造成可用内存逐渐缩小,最终或者造成内存溢出。
1.造成的要素
造成内存走漏关键有以下几个要素:
上方咱们用三个示例,区分展现了内存走漏或者出现的场景:
(1) 静态汇合类造成的内存走漏
静态汇合类持有对象援用,造成这些对象无法被渣滓回收。
import java.util.ArrayList;import java.util.List;public class StaticCollectionLeak {// 静态汇合持有对象援用private static List<Object> objectList = new ArrayList<>();public static void main(String[] args) {for (int i = 0; i < 10000; i++) {// 每次创立一个新对象并参与到静态汇合中objectList.add(new Object());}// 即使在这里试图清算掉一些其余的援用System.gc();// 这些对象依然无法被回收,由于它们被静态汇合援用}}
(2) 监听器和回调未被移除
注册的监听器或回调未被移除,造成内存走漏。
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();// 匿名类创立的监听器对象leakExample.addListener(new EventListener() {@Overridepublic void onEvent() {System.out.println("Event triggered");}});// 假定在某个时刻不再须要监听器,但未移除// listeners.remove(listener); // 应该移除不须要的监听器}}interface EventListener {void onEvent();}
(3) 长生命周期对象持有短生命周期对象
长生命周期对象不当持有短生命周期对象的援用,造成短生命周期对象无法被回收。
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[]>
2.处置方法
在这里,咱们只是给了一个大的思绪,关于内存走漏的排查上班也是一个很关键的常识点,咱们会在前面的文章中去具体引见。
3.示例代码
上方示例代码,用于测试内存走漏。
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是一个常年存在的静态变量,并且没有及时清算,则或者造成内存走漏。
三、对比
关于内存溢出和内存走漏的比拟如下:
四、总结
本文,咱们剖析了Java的内存溢出和内存走漏并且应示例展现了它们造成的要素,应该说它们是比拟经常出现的内存治理疑问,假设在消费环境出现也是比拟头疼的疑问。所以在日常开发中,咱们必定要留意自己的代码格调和代码品质,尽量防止这些疑问的出现。