一次内存泄露问题的排查

2019-03-27 01:05|来源: 网路

      系统对外提供的Solr查询接口,在来自外部调用的压力加大之后,就会出现solr查询报Read Timed Out的异常,从表面现象上看是此时solr核压力过大,无法响应过多的查询请求。

      但实际上此时并发查询压力并不是很大,那么为何solr核会无法及时响应查询请求呢?首先用top查看了下load average,也是很低,也佐证了系统本身压力并不大。

      然后,用jstack –l <pid> 查看那些cpu使用率过高的线程,发现全都是GC线程,说明GC过于频繁,而且耗时过长,导致应用线程被挂起,无法响应客户端发来的请求,这种情况就应该是有存在内存泄露的问题咯。

 

      于是,就用jmap将进程的堆转储文件dump出来到heap.bin文件中

JMap -dump:format=b,file=/tmp/heap.bin <pid>

然后用Eclipse Memory AnalyzerMAT)打开堆转储文件进行分析

通常我们都会采用下面的三步曲来分析内存泄露问题:

首先,对问题发生时刻的系统内存状态获取一个整体印象。

第二步,找到最有可能导致内存泄露的元凶,通常也就是消耗内存最多的对象

    接下来,进一步去查看这个内存消耗大户的具体情况,看看是否有什么异常的行为。

下面来展示如何采用三步曲来查看生产的分析报告。

查看报告之一:内存消耗的整体状况

 

如上图所示,在报告上最醒目的就是一张简洁明了的饼图,从图上我们可以清晰地看到一个可疑对象消耗了系统75% 的内存。

现在,让我们开始真正的寻找内存泄露之旅,点击“Leak  Suspects”链接,可以看到如下图所示对可疑对象的详细分析报告。

 

   我们查看下从 GC 根元素到内存消耗聚集点的最短路径 

 

我们可以很清楚的看到整个引用链,内存聚集点是一个拥有大量对象的列表,如果你对代码比较熟悉的话,相信这些信息应该能给你提供一些找到内存泄露的思路了。

接下来,我们再继续看看,这个对象集合里到底存放了什么,为什么会消耗掉如此多的内存。

     在这张图上,我们可以清楚的看到,这个列表中保存了大量 HashMap 对象的引用,就是它导致的内存泄露。

至此,我们已经拥有了足够的信息去寻找泄露点,回到代码中就发现,List没有clear或者设置为null,导致其包含的强引用的各个HashMap没有得到释放。至此,问题得到解决。

下面我们来继续深入研究java的内存泄露问题。Java的一个重要优点就是通过垃圾收集器(Garbage CollectionGC)自动管理内存的回收,程序员不需要通过调用函数来释放内存。因此,很多程序员认为Java不存在内存泄漏问题,或者认为即使有内存泄漏也不是程序的责任,而是GCJVM的问题。其实,这种想法是不正确的,因为Java也存在内存泄露,但它的表现与C++不同。

随着越来越多的服务器程序采用Java技术,例如JSPServlet EJB等,服务器程序往往长期运行。另外,在很多嵌入式系统中,内存的总量非常有限。内存泄露问题也就变得十分关键,即使每次运行少量泄漏,长期运行之后,系统也是面临崩溃的危险。

为了判断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中的内存泄漏,这些对象不会被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就支持这一特性。

   下面给出了一个简单的内存泄露的例子。在这个例子中,我们循环申请Object对象,并将所申请的对象放入一个Vector中,如果我们仅仅释放引用本身,那么Vector仍然引用该对象,所以这个对象对GC来说是不可回收的。因此,如果对象加入到Vector后,还必须从Vector中删除,最简单的方法就是将Vector对象设置为null 

Vector v= new Vector(10); 

for (int i=1;i<100; i++)
{
    Object o=new Object();
    v.add(o);
    o=null;    
}


转自:http://www.cnblogs.com/phinecos/archive/2011/11/14/2247733

相关问答

更多
  • Qt有没有内存泄露检测工具 vue没用过 其他两个的话 AngularJS比较好学 1 资料多 因为AngularJS 出来挺久了,所以进行AngularJS开发的人也多,中文的都很多,甚至有类似W3c那样的教程网站 2 插件也全 相对来说 ReactJS出来的时间不长
  • 使用crosswalk 具体使用 1)在布局文件中 2)在activity import android.app.Activity; import android.os.Bundle; import org.xwalk.core.XWalkView; public class MyActivity extends Activity { private XWalkView mXWalkView; @Override protected void onCreate(Bundle savedInstanceSta ...
  • 当我们使用父对象来创建一个对象的时候 ,父对象会把这个对象添加到自己的子对象列表中。当这个父对象被删除的时候,它会遍历它的子对象类表并且删除每一个子对象,然后子对象们自己再删除它们自己的子对象,这样递归调用直到所有对象都被删除。 这种父子对象机制会在很大程度上简化我们的内存管理工作,减少内存泄露的风险。我们需要显试删除(就是用DELETE删除)的对象是那些使用NEW创建的并且没有父对象的对象(切记是new的才要delete,通过成员函数获得的对象,没有特殊说明的,千万不要随便delete.)。如果我们在删除 ...
  • 如果用户有一些经验,也许他可以转储你的内存配置文件,这样你就可以查看分配。 有关说明,请参见http://android-developers.blogspot.de/2011/03/memory-analysis-for-android.html 。 您还可以尝试使用Debug#dumpHprofData()从应用程序中转储内存配置文件,并要求用户向您发送该文件。 If the user has some experience maybe he can dump you a memory profile ...
  • 垃圾收集者只是没有机会收集东西。 我把它包装在这样的程序中... public class Program { static void Main(string[] args) { new MoqTests().Memory_leak_test2().GetAwaiter().GetResult(); } } 并观察内存使用情况,就像你说的那样,对于test1,内存使用情况保持绝对平坦。 对于test2,它只是继续攀升。 但是,如果添加GC.Collect(); 在t ...
  • 它在那里告诉你: 期望不正确的结果,断言和崩溃。 如果你仍然想运行它,打印关于虚假泄漏的详细信息( --leak-check=full ),并用它来抑制有关它们的消息。 It tells you right there: Expect incorrect results, assertions and crashes. If you still want to run it, print detailed information about spurious leaks (--leak-check=full ...
  • 如果你退回它并不是泄漏(好吧,这不是你的泄漏)。 您需要考虑资源所有权。 如果从函数返回已分配的缓冲区,则函数的调用者现在负责它。 API应该清楚地表明它需要在完成它时被释放。 无论是自己释放它还是将它传递给你的另一个函数来释放它(封装以防止需要完成而不仅仅是释放内存)是API的另一个问题。 It's not a leak if you return it (well, it's not your leak). You need to think in terms of resource ownership ...
  • 我不是Ruby,但知道一些Gtk +。 在C中,你必须自己处理内存分配,你需要取消pixbuf。 从GtkImage文档 : GtkImage不承担对pixbuf的引用; 如果您拥有参考资料,您仍然需要取消它。 所以,如果Ruby没有实现ARC(在GObjects上自动引用计数),那么您最好在@image.pixbuf = newPixbuf之后执行一些类似newPixbuf.unref (不确定Ruby语法)的@image.pixbuf = newPixbuf 。 希望能帮助到你。 Apparently ...
  • @ iharob在他的回答中写道, 您使用了EVP_MD_CTX_init()但未使用EVP_MD_CTX_cleanup(&ctx)释放它分配的数据,或者 - 取决于您的openssl版本 - EVP_MD_CTX_free(&ctx) 。 如果使用debug信息进行编译(在gcc / clang中使用-g), valgrind会显示负责泄漏的源代码行。 您也可以使用EVP_MD_CTX_create()而不是使用静态EVP_MD_CTX 。 这里是更新后的代码: #include