-
2021-03-08 16:17:26
一、Java内存回收机制 不论哪种语言的内存分配方式,都需要返回所分配内存的真实地址,也就是返回一个指针到内存块的首地址。Java中对象是采用new或者反射的方法创建的,这些对象的创建都是在堆(Heap)中分配的,所有对象的回收都是由Java虚拟机通过垃圾回收机制完成的。GC为了能够正确释放对象,会监控每个对象的运行状况,对他们的申请、引用、被引用、赋值等状况进行监控,Java会使用有向图的方法进行管理内存,实时监控对象是否可以达到,如果不可到达,则就将其回收,这样也可以消除引用循环的问题。在Java语言中,判断一个内存空间是否符合垃圾收集标准有两个:一个是给对象赋予了空值null,以下再没有调用过,另一个是给对象赋予了新值,这样重新分配了内存空间。 二、Java内存泄露引起原因 首先,什么是内存泄露?经常听人谈起内存泄露,但要问什么是内存泄露,没几个说得清楚。内存泄露是指无用对象(不再使用的对象)持续占有内存或无用对象的内存得不到及时释放,从而造成的内存空间的浪费称为内存泄露。内存泄露有时不严重且不易察觉,这样开发者就不知道存在内存泄露,但有时也会很严重,会提示你Out of memory。 那么,
Java内存泄露根本原因是什么呢?
长生命周期的对象持有短生命周期对象的引用就很可能发生内存泄露,尽管短生命周期对象已经不再需要,但是因为长生命周期对象持有它的引用而导致不能被回收,这就是java中内存泄露的发生场景。
具体主要有如下几大类:
1、静态集合类引起内存泄露: 像HashMap、Vector等的使用最容易出现内存泄露,这些静态变量的生命周期和应用程序一致,他们所引用的所有的对象Object也不能被释放,因为他们也将一直被Vector等引用着。 例: Static Vector v = new Vector(10); for (int i = 1; i<100; i++) { Object o = new Object(); v.add(o); o = null; }// 在这个例子中,循环申请Object 对象,并将所申请的对象放入一个Vector 中,如果仅仅释放引用本身(o=null),那么Vector 仍然引用该对象,所以这个对象对GC 来说是不可回收的。因此,如果对象加入到Vector 后,还必须从Vector 中删除,最简单的方法就是将Vector对象设置为null。
2、当集合里面的对象属性被修改后,再调用remove()方法时不起作用。 例: public static void main(String[] args) { Set set = new HashSet(); Person p1 = new Person("唐僧","pwd1",25); Person p2 = new Person("孙悟空","pwd2",26); Person p3 = new Person("猪八戒","pwd3",27); set.add(p1); set.add(p2); set.add(p3); System.out.println("总共有:"+set.size()+" 个元素!"); //结果:总共有:3 个元素! p3.setAge(2); //修改p3的年龄,此时p3元素对应的hashcode值发生改变 set.remove(p3); //此时remove不掉,造成内存泄漏 set.add(p3); //重新添加,居然添加成功 System.out.println("总共有:"+set.size()+" 个元素!"); //结果:总共有:4 个元素! for (Person person : set) { System.out.println(person); } }
3、监听器 在java 编程中,我们都需要和监听器打交道,通常一个应用当中会用到很多监听器,我们会调用一个控件的诸如addXXXListener()等方法来增加监听器,但往往在释放对象的时候却没有记住去删除这些监听器,从而增加了内存泄漏的机会。
4、各种连接 比如数据库连接(dataSourse.getConnection()),网络连接(socket)和io连接,除非其显式的调用了其close()方法将其连接关闭,否则是不会自动被GC 回收的。对于Resultset 和Statement 对象可以不进行显式回收,但Connection 一定要显式回收,因为Connection 在任何时候都无法自动回收,而Connection一旦回收,Resultset 和Statement 对象就会立即为NULL。但是如果使用连接池,情况就不一样了,除了要显式地关闭连接,还必须显式地关闭Resultset Statement 对象(关闭其中一个,另外一个也会关闭),否则就会造成大量的Statement 对象无法释放,从而引起内存泄漏。这种情况下一般都会在try里面去的连接,在finally里面释放连接。
5、内部类和外部模块等的引用 内部类的引用是比较容易遗忘的一种,而且一旦没释放可能导致一系列的后继类对象没有释放。此外程序员还要小心外部模块不经意的引用,例如程序员A 负责A 模块,调用了B 模块的一个方法如: public void registerMsg(Object b); 这种调用就要非常小心了,传入了一个对象,很可能模块B就保持了对该对象的引用,这时候就需要注意模块B 是否提供相应的操作去除引用。
6、单例模式 不正确使用单例模式是引起内存泄露的一个常见问题,单例对象在被初始化后将在JVM的整个生命周期中存在(以静态变量的方式),如果单例对象持有外部对象的引用,那么这个外部对象将不能被jvm正常回收,导致内存泄露,考虑下面的例子: class A{ public A(){ B.getInstance().setA(this); } .... } //B类采用单例模式 class B{ private A a; private static B instance=new B(); public B(){} public static B getInstance(){ return instance; } public void setA(A a){ this.a=a; } //getter... } 显然B采用singleton模式,它持有一个A对象的引用,而这个A类的对象将不能被回收。想象下如果A是个比较复杂的对象或者集合类型会发生
更多相关内容 -
面试官:小伙子先来说一下可能引起Java内存泄露的场景吧
2021-03-06 16:53:52本文分析一下可能引起java内存泄露的场景:通过 finalize() 方法终结器finalizers的使用是潜在内存泄漏问题的另一个来源。每当类的 finalize() 方法被重写时,该类的对象不会立即被垃圾回收。相反,GC将它们排队等待...本文分析一下可能引起java内存泄露的场景:
通过 finalize() 方法
终结器finalizers的使用是潜在内存泄漏问题的另一个来源。每当类的 finalize() 方法被重写时,该类的对象不会立即被垃圾回收。相反,GC将它们排队等待最后确定,这将在稍后的时间点发生。
另外,如果我们的应用程序不能更快地完成或最终处理一个错误,那么如果我们的应用程序不能更快地完成一个错误,那么我们的应用程序就不能完成。
为了证明这一点,让我们考虑一下,我们已经为一个类重写了 finalize() 方法,并且该方法需要一点时间来执行。当此类的大量对象被垃圾回收时,在VisualVM中,它看起来像:
但是,如果我们只删除重写的 finalize() 方法,则同一个程序会给出以下响应:
如何预防?
我们应该避免使用终结器
内部字符串
当Java 7从 PermGen转移到 HeapSpace 时,Java字符串池经历了一次重大变化。但是对于在版本6及以下运行的应用程序,我们在处理大字符串时应该更加注意。
如果我们读取一个巨大的字符串对象,并在该对象上调用 intern() ,那么它将进入字符串池,该池位于PermGen(永久内存)中,只要我们的应用程序运行,它就会一直留在那里。这会阻塞内存并在我们的应用程序中造成内存泄漏。
JVM 1.6中这个例子的PermGen在VisualVM中如下所示:
与此相反,在一个方法中,如果我们只是从文件中读取一个字符串,而不是对其进行内接,那么PermGen看起来像:
如何预防?
解决这个问题最简单的方法是升级到最新的Java版本,因为从JavaVersion7开始,字符串池被移到HeapSpace如果要处理大型字符串,请增大PermGen空间的大小,以避免任何潜在的 OutOfMemoryError :-XX:MaxPermSize=512m使用ThreadLocals
ThreadLocal是一种构造,它使我们能够将状态隔离到特定线程,从而允许我们实现线程安全。
当使用这个结构时,每个线程都将持有一个对其ThreadLocal变量副本的隐式引用,并将维护自己的副本,而不是在多个线程之间共享资源,只要线程是活动的。
尽管 ThreadLocal变量有很多优点,但是它的使用还是有争议的,因为如果使用不当,它们会导致内存泄漏。Joshua Bloch曾经评论过线程本地用法:
线程池的草率使用与线程局部变量的草率使用可能会导致意外的对象保留,正如在许多地方所指出的那样。但把责任推到线程本地上是没有道理的。
threadlocal导致内存泄漏
一旦保持线程不再活动, threadlocal 就应该被垃圾回收。但是当 threadlocal 与现代应用服务器一起使用时,问题就出现了。
现代应用服务器使用一个线程池来处理请求,而不是创建新的请求(例如apache tomcat中的 Executor )。此外,它们还使用单独的类加载器。
由于应用程序服务器中的线程池遵循线程重用的概念,因此它们永远不会被垃圾回收,而是被重用以服务于另一个请求。
现在,如果任何类创建了一个 ThreadLocal 变量,但没有显式地删除它,那么即使在web应用程序停止之后,该对象的副本也将保留在工作线程中,从而防止对象被垃圾回收。
如何预防?
remove()ThreadLocals.set(null)try { threadLocal.set(System.nanoTime()); //... further processing}finally { threadLocal.remove();}解决内存泄漏的其他方法
虽然在处理内存泄漏时没有一刀切的解决方案,但是我们可以通过一些方法将这些泄漏最小化。
启用分析
Java探查器是监视和诊断应用程序内存泄漏的工具。它们分析应用程序内部发生的事情—例如,如何分配内存。
使用探查器,我们可以比较不同的方法,并找到可以最佳利用资源的领域。
在本教程我们一直在使用javavisualvm。请查看我们的Java探查器指南,了解不同类型的探查器,如任务控制、JProfiler、YourKit、Java VisualVM和Netbeans探查器。
详细的垃圾收集
通过启用详细的垃圾收集,我们可以跟踪GC的详细跟踪。要实现这一点,我们需要在JVM配置中添加以下内容:
-verbose:gc通过添加此参数,我们可以看到GC内部发生的详细情况:
使用引用对象以避免内存泄漏
我们还可以使用Java中内置的引用对象 java.lang.ref处理内存泄漏的包。使用 java.lang.ref包,而不是直接引用对象,我们使用对对象的特殊引用,以便于对它们进行垃圾回收。
引用队列旨在让我们知道垃圾回收器执行的操作。
Eclipse内存泄漏警告
对于jdk1.5及更高版本的项目,每当遇到明显的内存泄漏情况时,Eclipse都会显示警告和错误。因此,在Eclipse中开发时,我们可以定期访问“Problems”选项卡,并对内存泄漏警告(如果有)保持警惕:
Benchmarking基准测试
我们可以通过执行基准测试来测量和分析Java代码的性能。这样,我们就可以比较不同方法的性能来完成相同的任务。这可以帮助我们选择更好的方法,也可以帮助我们保存记忆。
有关基准测试的更多信息,请访问我们的Java微基准标记教程。
代码评审
最后,我们总是采用经典的、老派的方法来完成简单的代码遍历。
在某些情况下,即使是这种看起来微不足道的方法也有助于消除一些常见的内存泄漏问题。
结论
通俗地说,我们可以认为内存泄漏是一种通过阻塞重要内存资源而降低应用程序性能的疾病。而且,与所有其他疾病一样,如果得不到治愈,随着时间的推移,它会导致致命的应用程序崩溃。
内存泄漏很难解决,找到它们需要对Java语言进行复杂的掌握。在处理内存泄漏时,没有一刀切的解决方案,因为泄漏可以通过各种各样的事件发生。
但是,如果我们求助于最佳实践并定期执行严格的代码遍历和分析,那么我们就可以将应用程序中内存泄漏的风险降到最低。
原文链接:http://javakk.com/911.html
如果觉得本文对你有帮助,可以转发关注支持一下
-
java内存泄漏的几种情况
2021-02-28 08:09:58转载于http://blog.csdn.net/wtt945482445/article/details/52483944Java 内存分配策略Java 程序运行时的内存分配策略有三种,分别是静态分配,栈式分配,和堆式分配,对应的,三种存储策略使用的内存空间主要分别是...转载于http://blog.csdn.net/wtt945482445/article/details/52483944
Java 内存分配策略
Java 程序运行时的内存分配策略有三种,分别是静态分配,栈式分配,和堆式分配,对应的,三种存储策略使用的内存空间主要分别是静态存储区(也称方法区)、栈区和堆区。
静态存储区(方法区):主要存放静态数据、全局 static 数据和常量。这块内存在程序编译时就已经分配好,并且在程序整个运行期间都存在。
栈区 :当方法被执行时,方法体内的局部变量(其中包括基础数据类型、对象的引用)都在栈上创建,并在方法执行结束时这些局部变量所持有的内存将会自动被释放。因为栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。
堆区 : 又称动态内存分配,通常就是指在程序运行时直接 new 出来的内存,也就是对象的实例。这部分内存在不使用时将会由 Java 垃圾回收器来负责回收。
栈与堆的区别:
在方法体内定义的(局部变量)一些基本类型的变量和对象的引用变量都是在方法的栈内存中分配的。当在一段方法块中定义一个变量时,Java 就会在栈中为该变量分配内存空间,当超过该变量的作用域后,该变量也就无效了,分配给它的内存空间也将被释放掉,该内存空间可以被重新使用。
堆内存用来存放所有由 new 创建的对象(包括该对象其中的所有成员变量)和数组。在堆中分配的内存,将由 Java 垃圾回收器来自动管理。在堆中产生了一个数组或者对象后,还可以在栈中定义一个特殊的变量,这个变量的取值等于数组或者对象在堆内存中的首地址,这个特殊的变量就是我们上面说的引用变量。我们可以通过这个引用变量来访问堆中的对象或者数组。
举个例子:
1 public classSample {2 int s1 = 0;3 Sample mSample1 = newSample();4
5 public voidmethod() {6 int s2 = 1;7 Sample mSample2 = newSample();8 }9 }10
11 Sample mSample3 = new Sample();
Sample 类的局部变量 s2 和引用变量 mSample2 都是存在于栈中,但 mSample2 指向的对象是存在于堆上的。
mSample3 指向的对象实体存放在堆上,包括这个对象的所有成员变量 s1 和 mSample1,而它自己存在于栈中。
结论:
局部变量的基本数据类型和引用存储于栈中,引用的对象实体存储于堆中。—— 因为它们属于方法中的变量,生命周期随方法而结束。
成员变量全部存储与堆中(包括基本数据类型,引用和引用的对象实体)—— 因为它们属于类,类对象终究是要被new出来使用的。
了解了 Java 的内存分配之后,我们再来看看 Java 是怎么管理内存的。
Java是如何管理内存
Java的内存管理就是对象的分配和释放问题。在 Java 中,程序员需要通过关键字 new 为每个对象申请内存空间 (基本类型除外),所有的对象都在堆 (Heap)中分配空间。另外,对象的释放是由 GC 决定和执行的。在 Java 中,内存的分配是由程序完成的,而内存的释放是由 GC 完成的,这种收支两条线的方法确实简化了程序员的工作。但同时,它也加重了JVM的工作。这也是 Java 程序运行速度较慢的原因之一。因为,GC 为了能够正确释放对象,GC 必须监控每一个对象的运行状态,包括对象的申请、引用、被引用、赋值等,GC 都需要进行监控。
监视对象状态是为了更加准确地、及时地释放对象,而释放对象的根本原则就是该对象不再被引用。
为了更好理解 GC 的工作原理,我们可以将对象考虑为有向图的顶点,将引用关系考虑为图的有向边,有向边从引用者指向被引对象。另外,每个线程对象可以作为一个图的起始顶点,例如大多程序从 main 进程开始执行,那么该图就是以 main 进程顶点开始的一棵根树。在这个有向图中,根顶点可达的对象都是有效对象,GC将不回收这些对象。如果某个对象 (连通子图)与这个根顶点不可达(注意,该图为有向图),那么我们认为这个(这些)对象不再被引用,可以被 GC 回收。
以下,我们举一个例子说明如何用有向图表示内存管理。对于程序的每一个时刻,我们都有一个有向图表示JVM的内存分配情况。以下右图,就是左边程序运行到第6行的示意图。
Java使用有向图的方式进行内存管理,可以消除引用循环的问题,例如有三个对象,相互引用,只要它们和根进程不可达的,那么GC也是可以回收它们的。这种方式的优点是管理内存的精度很高,但是效率较低。另外一种常用的内存管理技术是使用计数器,例如COM模型采用计数器方式管理构件,它与有向图相比,精度行低(很难处理循环引用的问题),但执行效率很高。
什么是Java中的内存泄露
在Java中,内存泄漏就是存在一些被分配的对象,这些对象有下面两个特点,首先,这些对象是可达的,即在有向图中,存在通路可以与其相连;其次,这些对象是无用的,即程序以后不会再使用这些对象。如果对象满足这两个条件,这些对象就可以判定为Java中的内存泄漏,这些对象不会被GC所回收,然而它却占用内存。
在C++中,内存泄漏的范围更大一些。有些对象被分配了内存空间,然后却不可达,由于C++中没有GC,这些内存将永远收不回来。在Java中,这些不可达的对象都由GC负责回收,因此程序员不需要考虑这部分的内存泄露。
通过分析,我们得知,对于C++,程序员需要自己管理边和顶点,而对于Java程序员只需要管理边就可以了(不需要管理顶点的释放)。通过这种方式,Java提高了编程的效率。
因此,通过以上分析,我们知道在Java中也有内存泄漏,但范围比C++要小一些。因为Java从语言上保证,任何对象都是可达的,所有的不可达对象都由GC管理。
对于程序员来说,GC基本是透明的,不可见的。虽然,我们只有几个函数可以访问GC,例如运行GC的函数System.gc(),但是根据Java语言规范定义, 该函数不保证JVM的垃圾收集器一定会执行。因为,不同的JVM实现者可能使用不同的算法管理GC。通常,GC的线程的优先级别较低。JVM调用GC的策略也有很多种,有的是内存使用到达一定程度时,GC才开始工作,也有定时执行的,有的是平缓执行GC,有的是中断式执行GC。但通常来说,我们不需要关心这些。除非在一些特定的场合,GC的执行影响应用程序的性能,例如对于基于Web的实时系统,如网络游戏等,用户不希望GC突然中断应用程序执行而进行垃圾回收,那么我们需要调整GC的参数,让GC能够通过平缓的方式释放内存,例如将垃圾回收分解为一系列的小步骤执行,Sun提供的HotSpot JVM就支持这一特性。
同样给出一个 Java 内存泄漏的典型例子,
1 Vector v = new Vector(10);2 for (int i = 1; i < 100; i++) {3 Object o = newObject();4 v.add(o);5 o = null;6 }
在这个例子中,我们循环申请Object对象,并将所申请的对象放入一个 Vector 中,如果我们仅仅释放引用本身,那么 Vector 仍然引用该对象,所以这个对象对 GC 来说是不可回收的。因此,如果对象加入到Vector 后,还必须从 Vector 中删除,最简单的方法就是将 Vector 对象设置为 null。
详细Java中的内存泄漏
1.Java内存回收机制
不论哪种语言的内存分配方式,都需要返回所分配内存的真实地址,也就是返回一个指针到内存块的首地址。Java中对象是采用new或者反射的方法创建的,这些对象的创建都是在堆(Heap)中分配的,所有对象的回收都是由Java虚拟机通过垃圾回收机制完成的。GC为了能够正确释放对象,会监控每个对象的运行状况,对他们的申请、引用、被引用、赋值等状况进行监控,Java会使用有向图的方法进行管理内存,实时监控对象是否可以达到,如果不可到达,则就将其回收,这样也可以消除引用循环的问题。在Java语言中,判断一个内存空间是否符合垃圾收集标准有两个:一个是给对象赋予了空值null,以下再没有调用过,另一个是给对象赋予了新值,这样重新分配了内存空间。
2.Java内存泄漏引起的原因
内存泄漏是指无用对象(不再使用的对象)持续占有内存或无用对象的内存得不到及时释放,从而造成内存空间的浪费称为内存泄漏。内存泄露有时不严重且不易察觉,这样开发者就不知道存在内存泄露,但有时也会很严重,会提示你Out of memory。j
Java内存泄漏的根本原因是什么呢?长生命周期的对象持有短生命周期对象的引用就很可能发生内存泄漏,尽管短生命周期对象已经不再需要,但是因为长生命周期持有它的引用而导致不能被回收,这就是Java中内存泄漏的发生场景。具体主要有如下几大类:
1、静态集合类引起内存泄漏:
像HashMap、Vector等的使用最容易出现内存泄露,这些静态变量的生命周期和应用程序一致,他们所引用的所有的对象Object也不能被释放,因为他们也将一直被Vector等引用着。
例如
Static Vector v = new Vector(10);
for (int i = 1; i<100; i++)
{
Object o = new Object();
v.add(o);
o = null;
}
在这个例子中,循环申请Object 对象,并将所申请的对象放入一个Vector 中,如果仅仅释放引用本身(o=null),那么Vector 仍然引用该对象,所以这个对象对GC 来说是不可回收的。因此,如果对象加入到Vector 后,还必须从Vector 中删除,最简单的方法就是将Vector对象设置为null。
2、当集合里面的对象属性被修改后,再调用remove()方法时不起作用。
例如:
1 public static voidmain(String[] args)2 {3 Set set = new HashSet();4 Person p1 = new Person("唐僧","pwd1",25);5 Person p2 = new Person("孙悟空","pwd2",26);6 Person p3 = new Person("猪八戒","pwd3",27);7 set.add(p1);8 set.add(p2);9 set.add(p3);10 System.out.println("总共有:"+set.size()+" 个元素!"); //结果:总共有:3 个元素!
11 p3.setAge(2); //修改p3的年龄,此时p3元素对应的hashcode值发生改变
12
13 set.remove(p3); //此时remove不掉,造成内存泄漏
14
15 set.add(p3); //重新添加,居然添加成功
16 System.out.println("总共有:"+set.size()+" 个元素!"); //结果:总共有:4 个元素!
17 for(Person person : set)18 {19 System.out.println(person);20 }21 }
3、监听器
在java 编程中,我们都需要和监听器打交道,通常一个应用当中会用到很多监听器,我们会调用一个控件的诸如addXXXListener()等方法来增加监听器,但往往在释放对象的时候却没有记住去删除这些监听器,从而增加了内存泄漏的机会。
4、各种连接
比如数据库连接(dataSourse.getConnection()),网络连接(socket)和io连接,除非其显式的调用了其close()方法将其连接关闭,否则是不会自动被GC 回收的。对于Resultset 和Statement 对象可以不进行显式回收,但Connection 一定要显式回收,因为Connection 在任何时候都无法自动回收,而Connection一旦回收,Resultset 和Statement 对象就会立即为NULL。但是如果使用连接池,情况就不一样了,除了要显式地关闭连接,还必须显式地关闭Resultset Statement 对象(关闭其中一个,另外一个也会关闭),否则就会造成大量的Statement 对象无法释放,从而引起内存泄漏。这种情况下一般都会在try里面去的连接,在finally里面释放连接。
5、内部类和外部模块的引用
内部类的引用是比较容易遗忘的一种,而且一旦没释放可能导致一系列的后继类对象没有释放。此外程序员还要小心外部模块不经意的引用,例如程序员A 负责A 模块,调用了B 模块的一个方法如:
public void registerMsg(Object b);
这种调用就要非常小心了,传入了一个对象,很可能模块B就保持了对该对象的引用,这时候就需要注意模块B 是否提供相应的操作去除引用。
6、单例模式
不正确使用单例模式是引起内存泄漏的一个常见问题,单例对象在初始化后将在JVM的整个生命周期中存在(以静态变量的方式),如果单例对象持有外部的引用,那么这个对象将不能被JVM正常回收,导致内存泄漏,考虑下面的例子:
1 classA{2 publicA(){3 B.getInstance().setA(this);4 }5 ....6 }7 //B类采用单例模式
8 classB{9 privateA a;10 private static B instance=newB();11 publicB(){}12 public staticB getInstance(){13 returninstance;14 }15 public voidsetA(A a){16 this.a=a;17 }18 //getter...
19 }
显然B采用singleton模式,它持有一个A对象的引用,而这个A类的对象将不能被回收。想象下如果A是个比较复杂的对象或者集合类型会发生什么情况
-
java中内存泄露情况总结 以及解决办法
2021-08-20 09:54:53java中内存泄露8种情况的总结 由于java的JVM引入了垃圾回收机制,垃圾回收器会自动回收不再使用的对象,了解JVM回收机制的都知道JVM是使用引用计数法和可达性分析算法来判断对象是否是不再使用的对象,本质都是判断...java中内存泄露8种情况的总结
由于java的JVM引入了垃圾回收机制,垃圾回收器会自动回收不再使用的对象,了解JVM回收机制的都知道JVM是使用引用计数法和可达性分析算法来判断对象是否是不再使用的对象,本质都是判断一个对象是否还被引用。那么对于这种情况下,由于代码的实现不同就会出现很多种内存泄漏问题(让JVM误以为此对象还在引用中,无法回收,造成内存泄漏)。
1. 静态集合类
如HashMap、LinkedList等等。如果这些容器为静态的,那么它们的生命周期与程序一致,则容器中的对象在程序结束之前将不能被释放,从而造成内存泄漏。简单而言,长生命周期的对象持有短生命周期对象的引用,尽管短生命周期的对象不再使用,但是因为长生命周期对象持有它的引用而导致不能被回收。
2. 各种连接,如数据库连接、网络连接和IO连接等
在对数据库进行操作的过程中,首先需要建立与数据库的连接,当不再使用时,需要调用close方法来释放与数据库的连接。只有连接被关闭后,垃圾回收器才会回收对应的对象。否则,如果在访问数据库的过程中,对Connection、Statement或ResultSet不显性地关闭,将会造成大量的对象无法被回收,从而引起内存泄漏。
3. 变量不合理的作用域
一般而言,一个变量的定义的作用范围大于其使用范围,很有可能会造成内存泄漏。另一方面,如果没有及时地把对象设置为null,很有可能导致内存泄漏的发生。
public class UsingRandom { private String msg; public void receiveMsg(){ readFromNet();// 从网络中接受数据保存到msg中 saveDB();// 把msg保存到数据库中 } }
如上面这个伪代码,通过readFromNet方法把接受的消息保存在变量msg中,然后调用saveDB方法把msg的内容保存到数据库中,此时msg已经就没用了,由于msg的生命周期与对象的生命周期相同,此时msg还不能回收,因此造成了内存泄漏。
实际上这个msg变量可以放在receiveMsg方法内部,当方法使用完,那么msg的生命周期也就结束,此时就可以回收了。还有一种方法,在使用完msg后,把msg设置为null,这样垃圾回收器也会回收msg的内存空间。
4. 内部类持有外部类
如果一个外部类的实例对象的方法返回了一个内部类的实例对象,这个内部类对象被长期引用了,即使那个外部类实例对象不再被使用,但由于内部类持有外部类的实例对象,这个外部类对象将不会被垃圾回收,这也会造成内存泄露。
5. 改变哈希值
当一个对象被存储进HashSet集合中以后,就不能修改这个对象中的那些参与计算哈希值的字段了,否则,对象修改后的哈希值与最初存储进HashSet集合中时的哈希值就不同了,在这种情况下,即使在contains方法使用该对象的当前引用作为的参数去HashSet集合中检索对象,也将返回找不到对象的结果,这也会导致无法从HashSet集合中单独删除当前对象,造成内存泄露
6. 举个例子-看你能否找出内存泄漏
import java.util.Arrays; public class Stack { private Object[] elements; private int size = 0; private static final int DEFAULT_INITIAL_CAPACITY = 16; public Stack() { elements = new Object[DEFAULT_INITIAL_CAPACITY]; } public void push(Object e) { ensureCapacity(); elements[size++] = e; } public Object pop() { if (size == 0) throw new EmptyStackException(); return elements[--size]; } private void ensureCapacity() { if (elements.length == size) elements = Arrays.copyOf(elements, 2 * size + 1); } }
6.1原因分析
上述程序并没有明显的错误,但是这段程序有一个内存泄漏,随着GC活动的增加,或者内存占用的不断增加,程序性能的降低就会表现出来,严重时可导致内存泄漏,但是这种失败情况相对较少。
代码的主要问题在pop函数,下面通过这张图示展现
假设这个栈一直增长,增长后如下图所示
当进行大量的pop操作时,由于引用未进行置空,gc是不会释放的,如下图所示
从上图中看以看出,如果栈先增长,在收缩,那么从栈中弹出的对象将不会被当作垃圾回收,即使程序不再使用栈中的这些队象,他们也不会回收,因为栈中仍然保存这对象的引用,俗称过期引用,这个内存泄露很隐蔽。
1.2解决方法
public Object pop() { if (size == 0) throw new EmptyStackException(); Object result = elements[--size]; elements[size] = null; return result; }
一旦引用过期,清空这些引用,将引用置空。
7. 缓存泄漏
内存泄漏的另一个常见来源是缓存,一旦你把对象引用放入到缓存中,他就很容易遗忘,对于这个问题,可以使用WeakHashMap代表缓存,此种Map的特点是,当除了自身有对key的引用外,此key没有其他引用那么此map会自动丢弃此值
7.1代码示例
package com.ratel.test; /** * @业务描述: * @package_name: com.ratel.test * @project_name: ssm * @author: ratelfu@qq.com * @create_time: 2019-04-18 20:20 * @copyright (c) ratelfu 版权所有 */ import java.util.HashMap; import java.util.Map; import java.util.WeakHashMap; import java.util.concurrent.TimeUnit; public class MapTest { static Map wMap = new WeakHashMap(); static Map map = new HashMap(); public static void main(String[] args) { init(); testWeakHashMap(); testHashMap(); }
public static void init(){ String ref1= new String("obejct1"); String ref2 = new String("obejct2"); String ref3 = new String ("obejct3"); String ref4 = new String ("obejct4"); wMap.put(ref1, "chaheObject1"); wMap.put(ref2, "chaheObject2"); map.put(ref3, "chaheObject3"); map.put(ref4, "chaheObject4"); System.out.println("String引用ref1,ref2,ref3,ref4 消失"); } public static void testWeakHashMap(){ System.out.println("WeakHashMap GC之前"); for (Object o : wMap.entrySet()) { System.out.println(o); } try { System.gc(); TimeUnit.SECONDS.sleep(20); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println("WeakHashMap GC之后"); for (Object o : wMap.entrySet()) { System.out.println(o); } } public static void testHashMap(){ System.out.println("HashMap GC之前"); for (Object o : map.entrySet()) { System.out.println(o); } try { System.gc(); TimeUnit.SECONDS.sleep(20); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println("HashMap GC之后"); for (Object o : map.entrySet()) { System.out.println(o); } } } /** 结果 String引用ref1,ref2,ref3,ref4 消失 WeakHashMap GC之前 obejct2=chaheObject2 obejct1=chaheObject1 WeakHashMap GC之后 HashMap GC之前 obejct4=chaheObject4 obejct3=chaheObject3 Disconnected from the target VM, address: '127.0.0.1:51628', transport: 'socket' HashMap GC之后 obejct4=chaheObject4 obejct3=chaheObject3 **/
上面代码和图示主演演示WeakHashMap如何自动释放缓存对象,当init函数执行完成后,局部变量字符串引用weakd1,weakd2,d1,d2都会消失,此时只有静态map中保存中对字符串对象的引用,可以看到,调用gc之后,hashmap的没有被回收,而WeakHashmap里面的缓存被回收了。8.监听器和回调
内存泄漏第三个常见来源是监听器和其他回调,如果客户端在你实现的API中注册回调,却没有显示的取消,那么就会积聚。需要确保回调立即被当作垃圾回收的最佳方法是只保存他的若引用,例如将他们保存成为WeakHashMap中的键。
-
Java内存泄露8种情况的总结
2020-08-26 18:17:47由于java的JVM引入了垃圾回收机制,垃圾...那么对于这种情况下,由于代码的实现不同就会出现很多种内存泄漏问题(让JVM误以为此对象还在引用中,无法回收,造成内存泄漏)。 1、静态集合类,如HashMap、LinkedList -
浅谈内存泄漏
2021-11-24 09:51:36引子 我们写的程序是如何一步一步运行起来的? 为什么虚拟内存大小可以比实际物理内存大 cpu是如何管理物理内存和映射内存的 ...我们都知道,内存泄漏指的是内存被分配出去后一直没有被释放,导致这部分内存. -
jvm之内存泄露
2021-11-21 21:12:12那么对于这种情况下,由于代码的实现不同就会出现很多种内存泄漏问题(让™误以为此对象还在引用中,无法回收,造成内存泄漏)。 内存泄漏(memory leak)的理解严格来说,只有对象不会再被程序用到了,但是GC又不能... -
jvm故障 内存泄露和内存溢出总结
2022-01-24 22:41:30引起Java中内存泄露8种场景归纳,一定要避开这些!_公众号:肉眼品世界的博客-CSDN博客 内存泄漏memory leak 当某些对象不再被应用程序所使用,但是由于仍然被引用,而导致垃圾收集器不能释放(Remove,移除)他们。... -
C语言内存泄露很严重,如何应对?
2021-05-23 11:38:291. 前言最近部门不同产品接连出现内存泄漏导致的网上问题,具体表现为单板在现网运行数月以后,因为内存耗尽而导致单板复位现象。一方面,内存泄漏问题属于低级错误,此类问题遗漏到现网,影响很坏;另一方面,由于... -
什么是内存泄漏?
2022-01-11 11:03:12内存泄漏并非指内存在物理上的消失,而是应用程序分配某段内存后,由于设计错误,导致在释放该段内存之前就失去了对该段内存的控制,从而造成了内存的浪费。 内存泄漏通常情况下只能由获得程序源代码的程序员才能... -
如何排查Java内存泄漏?看完我给跪了!
2021-07-29 00:12:26但并非所有OutOfMemoryErrors都意味着内存泄漏,并非所有内存泄漏都表现为OutOfMemoryErrors。 为什么这些泄漏如此糟糕?除此之外,程序执行期间泄漏的内存块通常会降低系统性能,因为分配但未使用的内存块必须在... -
内存泄漏的8种情况
2021-08-29 16:28:43如果这些容器为静态的,那么它们的生命周期与JVM程序一致,则容器中的对象在程序结束之前不能被释放,从而造成内存泄漏。简单而言,长生命周期对象持有短生命周期对象的引用,尽管短生命周期的对象不再使用,但是... -
Android 内存泄漏的八种情况与防范
2021-06-09 01:35:46内存泄漏(Memory Leak)当一个对象已经不需要使用了,本该被回收时,而有另外一个正在使用的对象持有它的引用,从而导致了对象不能被GC回收。内存溢出(OOM- Out Of Memory)当应用程序的堆(heap)资源超过了 Dalvik... -
java中内存泄露8种情况的总结
2019-04-18 20:41:36由于java的JVM引入了垃圾...那么对于这种情况下,由于代码的实现不同就会出现很多种内存泄漏问题(让JVM误以为此对象还在引用中,无法回收,造成内存泄漏)。 1、静态集合类,如HashMap、LinkedList等等。如果这些... -
Python内存泄漏和内存溢出的解决方法
2021-01-29 08:13:58Python内存泄漏和内存溢出的解决方法发布时间:2020-10-30 23:08:34来源:亿速云阅读:92作者:Leah这篇文章将为大家详细讲解有关Python内存泄漏和内存溢出的解决方法,文章内容质量较高,因此小编分享给大家做个... -
Mat内存泄漏分析
2022-02-06 10:09:26Mat内存泄漏分析 1、分析背景 1.1、什么是内存泄漏 内存泄漏是我们经常听见的一个词,其定义是指程序中已动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢设置系统崩溃... -
Java内存泄漏的8种情况
2021-04-20 17:21:19刚看完尚硅谷JVM视频,记录...如果这些容器为静态的,那么它们的生命周期与JVM程序一致,则容器中的对象在程序结束之前将不能被释放,从而造成内存泄漏。简单而言,长生命周期的对象持有短生命周期对象的引用,尽... -
c语言内存泄漏严重的解决方法
2021-05-23 08:59:011. 前言最近部门不同产品接连出现内存泄漏导致的网上问题,具体表现为单板在现网运行数月以后,因为内存耗尽而导致单板复位现象。一方面,内存泄漏问题属于低级错误,此类问题遗漏到现网,影响很坏;另一方面,由于... -
内存泄露的最直接表现
2022-06-24 12:44:47内存泄露的最直接表现 -
前端常见内存泄漏及识别方法
2021-04-13 17:19:081. 什么是内存泄漏 程序的运行需要内存。只要程序提出要求,操作系统或者运行时(runtime)就必须供给内存。 对于持续运行的服务进程(daemon),必须及时释放不再用到的内存。否则,内存占用越来越高,轻则影响系统... -
性能测试总结之内存泄露和内存溢出
2019-11-23 23:27:01刚刚做完了一个项目的性能测试,“有幸”也遇到了内存泄露的案例,所以在此和大家分享一下。 主要从以下几部分来说明,关于内存和内存泄露、溢出的概念,区分内存泄露和内存溢出;内存的区域划分,了解GC回收机制;... -
Java程序里的内存泄漏是如何表现的
2012-08-28 13:25:04大多数程序员都知道使用...这个过程意味着 Java 已经解决了困扰其他编程语言的一个棘手的问题 -- 可怕的内存泄漏。果真是这样的吗? 在进行深入讨论之前,让我们先回顾一下垃圾收集是如何进行实际工作的。垃圾收集 -
Python内存泄漏和内存溢出的解决方案
2020-12-04 01:44:24一、内存泄漏像Java程序...1、内存泄露的原因对于 python 这种支持垃圾回收的语言来说,怎么还会有内存泄露? 概括来说,有以下三种原因:所用到的用 C 语言开发的底层模块中出现了内存泄露。代码中用到了全局的 ... -
【Java】Java中内存泄露的场景
2021-11-01 15:54:22首先 ,内存泄漏是指一个不再需要被程序使用的对象或者变量还在内存中占有着它的空间, 在c或者c++语言中, 内存的分配和释放是由开发人员手动操作的, 如果程序员忘记了释放, 那么就会造成内存泄露。 但是呢, ... -
记一次服务端内存泄漏排查之旅(第二篇)
2022-03-13 20:39:08本次线上内存泄漏表现出来(接到报警通知)是在上上周末,当大家都沉浸在周末的闲暇时光的时候 ,突然工作群接到运维通知说监控到我们的服务爆出一些500的错误,一时间,小组哗然,大家忙着看各种监控指标是否正常,... -
java内存泄露的原因
2021-02-28 18:51:53内存泄漏大家都不陌生了,那么java内存泄露的原因是什么呢?下面是学习啦小编精心为你整理的java内存泄露的原因,一起来看看。java内存泄露的原因java内存泄露典型特征现象一:堆/Perm 区不断增长, 没有下降趋势(回收... -
Android 内存泄露常见场景汇总
2021-11-12 15:32:51什么是内存泄露? 该被GC回收的内存没有被回收。讲人话:短生命周期的引用被长生命周期的对象持有,导致短生命周期的对象的内存无法被GC回收。举个例子:非静态内部类默认持有外部类的引用,不幸的是,我们在外部类...