面试官:小伙子,你给我说一下Java中什么状况会致使内存泄漏呢?

概念

内存泄露:指程序中动态分配内存给一些临时对象,但对象不会被GC回收,它始终占用内存,被分配的对象可达但已无用。即无用对象持续占有内存或无用对象的内存得不到及时释放,从而形成的内存空间浪费。java

可达性分析算法

JVM使用可达性分析算法判断对象是否存活。算法

GC Root

经过一系列名为“GC Roots”的对象做为起点,从这些结点开始向下搜索,搜索所走过的路径称为“引用链(Reference Chain)”,当一个对象到GC Roots没有任何饮用链相连时,则证实此对象是不可用的。
性能

object四、object五、object6虽然有互相判断,可是它们到GC Rootd是不可达的,因此它们将会断定为是可回收对象。this

能够做为GC Roots的对象有:spa

  • 虚拟机栈(栈帧中的本地变量表)中的引用的对象;
  • 方法区中的类静态属性引用的对象;
  • 方法区中的常量引用的对象;
  • 本地方法栈中JNI的引用的对象

虽然Java有垃圾收集器帮组实现内存自动管理,虽然GC有效的处理了大部份内存,可是并不能彻底保证内存的不泄漏。线程

内存泄漏

内存泄漏就是堆内存中再也不使用的对象没法被垃圾收集器清除掉,所以它们会没必要要地存在。这样就致使了内存消耗,下降了系统的性能,最终致使OOM使得进程终止。code

内存泄漏的表现:对象

  • 应用程序长时间连续运行时性能严重降低;
  • 应用程序中的OutOfMemoryError堆错误;
  • 自发且奇怪的应用程序崩溃;
  • 应用程序偶尔会耗尽链接对象;

可能致使内存泄漏的缘由:

1. static字段引发的内存泄漏

大量使用static字段会潜在的致使内存泄漏,在Java中,静态字段一般拥有与整个应用程序相匹配的生命周期。blog

解决办法:最大限度的减小静态变量的使用;单例模式时,依赖于延迟加载对象而不是当即加载的方式(即采用懒汉模式,而不是饿汉模式)生命周期

2. 未关闭的资源致使内存泄漏

每当建立链接或者打开流时,JVM都会为这些资源分配内存。若是没有关闭链接,会致使持续占有内存。在任意状况下,资源留下的开放链接都会消耗内存,若是不处理,就会下降性能,甚至OOM。

解决办法:使用finally块关闭资源;关闭资源的代码,不该该有异常;JDK1.7以后,可使用太try-with-resource块。

3. 不正确的equals()和hashCode()

在HashMap和HashSet这种集合中,经常用到equal()和hashCode()来比较对象,若是重写不合理,将会成为潜在的内存泄漏问题。

解决办法:用最佳的方式重写equals()和hashCode().

4. 引用了外部类的内部类

非静态内部类的初始化,老是须要外部类的实例;默认状况下,每一个非静态内部类都包含对其外部类的隐式引用,若是咱们在应用程序中使用这个内部类对象,那么即便在咱们的外部类对象超出范围后,它也不会被垃圾收集器清除掉。

解决办法:若是内部类不须要访问外部类包含的类成员,能够转换为静态类。

5. finalize方法致使的内存泄漏

重写finalize()方法时,该类的对象不会当即被垃圾收集器收集,若是finalize()方法的代码有问题,那么会潜在的印发OOM;

解决办法:避免重写finalize()方法。

6. 常量字符串形成的内存泄漏

若是咱们读取一个很大的String对象,并调用了intern(),那么它将放到字符串池中,位于PermGen中,只要应用程序运行,该字符串就会保留,这就会占用内存,可能形成OOM。(针对JDK1.6及之前,常量池在PermGen永久代中)

解决办法:增长PermGen的大小,-XX:MaxPermSize=512M;JDK1.7之后字符串池转移到了堆中。

intern()方法详解:

String str1 = "abc";
String str2 = "abc";
String str3 = new String("abc");
String str4 = str3.intern();

System.out.println(str1 == str2);
System.out.println(str2 == str3);

System.out.println(str1 == str4);
System.out.println(str3 == str4);

true, false, true, false

intern()方法搜索字符串常量池,若是存在指定的字符串,就返回之;

不然,就将该字符串放入常量池并返回之。

换言之,intern()方法保证每次返回的都是 同一个字符串对象

String str1 = "abc";
String str2 = "abc";
String str3 = new String("abcd");
String str4 = str3.intern();
String str5 = "abcd";

System.out.println(str1 == str2);
System.out.println(str2 == str3);

System.out.println(str1 == str4);
System.out.println(str3 == str4);

System.out.println(str4 == str5);

true
false
false
false
true

为什么要使用intern()方法?看看equals方法的源码:

public boolean equals(Object anObject) {
    if (this == anObject) {
        return true;
    }
    if (anObject instanceof String) {
        String anotherString = (String)anObject;
        int n = value.length;
        if (n == anotherString.value.length) {
            char v1[] = value;
            char v2[] = anotherString.value;
            int i = 0;
            while (n-- != 0) {
                if (v1[i] != v2[i])
                    return false;
                i++;
            }
            return true;
        }
    }
    return false;
}

能够看到,比较两个字符串的时候,首先比较两个字符串对象是否地址相同,不一样再挨个比较字符。这样就大大加快了比较的速度。不然若每次都挨个比较将是很是耗时的。

7. 使用ThreadLocal形成内存泄漏

使用ThreadLocal时,每一个线程只要处于存活状态就可保留对其ThreadLocal变量副本的隐式调用,且将保留其本身的副本。使用不当,就会引发内存泄漏。

一旦线程再也不存在,该线程的threadLocal对象就应该被垃圾收集,而如今线程的建立都是使用线程池,线程池有线程重用的功能,所以线程就不会被垃圾回收器回收。因此使用到ThreadLocal来保留线程池中的线程的变量副本时,ThreadLocal没有显式地删除时,就会一直保留在内存中,不会被垃圾回收。

解决办法:再也不使用ThreadLocal时,调用remove()方法,该方法删除了此变量的当前线程值。不要使用ThreadLocal.set(null),它只是查找与当前线程关联的Map并将键值中这个threadLocal对象所对应的值为null,并无清除这个键值对。

最后

感谢你看到这里,看完有什么的不懂的能够在评论区问我,以为文章对你有帮助的话记得给我点个赞,天天都会分享java相关技术文章或行业资讯,欢迎你们关注和转发文章!

相关文章
相关标签/搜索