概念内存泄漏:指程序中动态分配内存给一些临时对象,但该对象不会被GC回收,一直占用内存,分配的对象可达但无用。即无用对象继续占用内存或者无用对象的内存不能及时释放,造成内存空间的浪费。可达性分析算法JVM使用可达性分析算法来判断对象是否存活。GCRoot以一系列名为“GCRoots”的对象为起点,从这些节点开始向下搜索。搜索所经过的路径称为“参考链(ReferenceChain)”。当一个对象到达GCRoots而没有任何饮用链连接时,证明这个对象不可用。object4、object5、object6虽然相互判断,但是GCRootd不可达,所以会判断为可回收对象。可以作为GCRoots的对象包括:虚拟机栈中引用的对象(栈帧中的局部变量表);方法区中类静态属性引用的对象;方法区常量引用的对象;LocalmethodstacksJNI引用的对象虽然Java有垃圾收集器帮助组实现自动内存管理,但是GC虽然有效的处理了大部分内存,但是并不能完全保证内存不会泄露。MemoryLeaks内存泄漏是堆内存中不再使用的对象,垃圾收集器无法清除,因此存在不必要的存在。这会导致内存消耗,降低系统性能,最终导致OOM终止进程。内存泄漏的症状:应用程序长时间连续运行时性能严重下降;应用程序中的OutOfMemoryError堆错误;自发和奇怪的应用程序崩溃;有时,应用程序会用完连接对象;内存泄漏的可能原因:1.静态字段引起的内存泄漏大量使用静态字段可能会导致内存泄漏。在Java中,静态字段通常有一个匹配整个应用程序的生命周期。解决方法:尽量减少静态变量的使用;单例模式下,依赖延迟加载对象而不是立即加载(即使用惰性模式而不是饥饿模式)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()方法详解:Stringstr1="abc";Stringstr2="abc";Stringstr3=newString("abc");Stringstr4=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,falseintern()方法查找字符串常量池,if指定字符串存在则返回;否则,将字符串放入常量池并返回。换句话说,intern()方法保证每次都返回相同的字符串对象Stringstr1="abc";Stringstr2="abc";Stringstr3=newString("abcd");Stringstr4=str3.intern();Stringstr5="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);truefalsefalsefalsetrue为什么要使用intern()方法呢?看equals方法的源码:publicbooleanequals(ObjectanObject){if(this==anObject){returntrue;}if(anObjectinstanceofString){StringanotherString=(String)anObject;intn=value.length;if(n==anotherString.value.length){charv1[]=value;charv2[]=anotherString.value;inti=0;while(n--!=0){if(v1[i]!=v2[i])returnfalse;i++;}returntrue;}}returnfalse;}可见,比较两个字符串时,先比较两个字符串对象的地址是否相同,如果不同,再逐一比较字符。这大大加快了比较速度。不然每次都一个一个比较,会很费时间。7、使用ThreadLocal导致内存泄漏使用ThreadLocal时,每个线程只要还活着,就可以保持对其ThreadLocal变量副本的隐式调用,并会保留自己的副本。使用不当会导致内存泄漏。一旦线程不存在,线程的threadLocal对象就应该被垃圾回收,而现在线程是使用线程池创建的,线程池有线程重用的功能,所以线程不会被垃圾回收器回收.因此,当ThreadLocal用于在线程池中保留线程的变量副本时,当ThreadLocal没有被显式删除时,它会一直保留在内存中,不会被垃圾回收。解决方法:当ThreadLocal不再使用时,调用remove()方法,删除该变量的当前线程值。不要使用ThreadLocal.set(null),它只是查找与当前线程关联的Map,keyvalue中threadLocal对象对应的值为null,并没有清除键值对。最后,我会每天分享java相关的技术文章或者行业资讯。欢迎您关注和转发文章!
