1.打开/tomcat_home/bin/catalina.bat文件
创新互联建站服务项目包括靖安网站建设、靖安网站制作、靖安网页制作以及靖安网络营销策划等。多年来,我们专注于互联网行业,利用自身积累的技术优势、行业经验、深度合作伙伴关系等,向广大中小型企业、政府机构等提供互联网行业的解决方案,靖安网站推广取得了明显的社会效益与经济效益。目前,我们服务的客户以成都为中心已经辐射到靖安省份的部分城市,未来相信会继续扩大服务区域并继续获得客户的支持与信任!
2.加上:set JAVA_OPTS=%JAVA_OPTS% -server -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=D:\heapdump,这样当内存溢出是就会在对应路径下生成dump文件
运行程序打开jdk bin 文件夹下再带的 jvisualvm.exe
选择tomcat线程,打开实时监控页面可以看到对应的堆栈线程和装在类,内存的实时情况
运行程序打开jdk bin 文件夹下再带的 jconsole.exe,可以概览堆栈线程和装在类,内存的全部运行时间情况
下载安装mat,dump分析软件,安装后,点击file open 导入dump文件
打开后,灰色区域为可能发生内存溢出的区域,下带问题描述
选择Histogram,点击shallow列进行排序,找出实例最多的
右键实例最的选择list objects-with incoming references,可在根据正则表达式输入自己想查的类,搜索,后右键打开 path to gc root-exclude phantom/weak/soft etc. references 就可以查出 调用为回收的相关信息。
一、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; i100; 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)
{
SetPerson set = new HashSetPerson();
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内存泄露
一般来说内存泄漏有两种情况。一种情况如在C/C++语言中的,在堆中的分配的内存,在没有将其释放掉的时候,就将所有能访问这块内存的方式都删掉(如指针重新赋值);另一种情况则是在内存对象明明已经不需要的时候,还仍然保留着这块内存和它的访问方式(引用)。第一种情况,在Java中已经由于垃圾回收机制的引入,得到了很好的解决。所以,Java中的内存泄漏,主要指的是第二种情况。
可能光说概念太抽象了,大家可以看一下这样的例子:
1 Vector v=new Vector(10);
2 for (int i=1;i100; i++){
3 Object o=new Object();
4 v.add(o);
5 o=null;
6 }
在这个例子中,代码栈中存在Vector对象的引用v和Object对象的引用o。在For循环中,不断的生成新的对象,然后将其添加到Vector对象中,之后将o引用置空。问题是当o引用被置空后,如果发生GC,创建的Object对象是否能够被GC回收呢?答案是否定的。因为,GC在跟踪代码栈中的引用时,会发现v引用,而继续往下跟踪,就会发现v引用指向的内存空间中又存在指向Object对象的引用。也就是说尽管o引用已经被置空,但是Object对象仍然存在其他的引用,是可以被访问到的,所以GC无法将其释放掉。如果在此循环之后,Object对象对程序已经没有任何作用,那么就认为此Java程序发生了内存泄漏。
尽管对于C/C++中的内存泄露情况来说,Java内存泄露导致的破坏性小,除了少数情况会出现程序崩溃的情况外,大多数情况下程序仍然能正常运行。但是,在移动设备对于内存和CPU都有较严格的限制的情况下,Java的内存溢出会导致程序效率低下、占用大量不需要的内存等问题。这将导致整个机器性能变差,严重的也会引起抛出OutOfMemoryError,导致程序崩溃。
一般情况下内存泄漏的避免
在不涉及复杂数据结构的一般情况下,Java的内存泄露表现为一个内存对象的生命周期超出了程序需要它的时间长度。有时也将其称为“对象游离”。
例如:
1 public class FileSearch{
2
3 private byte[] content;
4 private File mFile;
5
6 public FileSearch(File file){
7 mFile = file;
8 }
9
10 public boolean hasString(String str){
11 int size = getFileSize(mFile);
12 content = new byte[size];
13 loadFile(mFile, content);
14
15 String s = new String(content);
16 return s.contains(str);
17 }
18 }
在这段代码中,FileSearch类中有一个函数hasString,用来判断文档中是否含有指定的字符串。流程是先将mFile加载到内存中,然后进行判断。但是,这里的问题是,将content声明为了实例变量,而不是本地变量。于是,在此函数返回之后,内存中仍然存在整个文件的数据。而很明显,这些数据后续是不再需要的,这就造成了内存的无故浪费。
要避免这种情况下的内存泄露,要求以C/C++的内存管理思维来管理自己分配的内存。第一,是在声明对象引用之前,明确内存对象的有效作用域。在一个函数内有效的内存对象,应该声明为local变量,与类实例生命周期相同的要声明为实例变量……以此类推。第二,在内存对象不再需要时,记得手动将其引用置空。
复杂数据结构中的内存泄露问题
在实际的项目中,经常用到一些较为复杂的数据结构用于缓存程序运行过程中需要的数据信息。有时,由于数据结构过于复杂,或者存在一些特殊的需求(例如,在内存允许的情况下,尽可能多的缓存信息来提高程序的运行速度等情况),很难对数据结构中数据的生命周期作出明确的界定。这个时候,可以使用Java中一种特殊的机制来达到防止内存泄露的目的。
之前介绍过,Java的GC机制是建立在跟踪内存的引用机制上的。而在此之前,所使用的引用都只是定义一个“Object o;”这样形式的。事实上,这只是Java引用机制中的一种默认情况,除此之外,还有其他的一些引用方式。通过使用这些特殊的引用机制,配合GC机制,就可以达到一些需要的效果。