美文网首页
java内存溢出问题调试

java内存溢出问题调试

作者: 奔跑的Robi | 来源:发表于2019-08-16 00:25 被阅读0次

    写java程序时大家一定对一下两条异常并不陌生:

    java.lang.OutOfMemoryError: Java heap space
    java.lang.OutOfMemoryError: PermGen space
    尤其当应用服务器(Java容器)出现上述情况更是让人有一种天塌下来的感觉。

    好的编码实践可能会大大降低内存溢出的产生。
    本文并不是写如何规避内存溢出,但是我还是要介绍一下如何能够尽量规避内存溢出:
    1. 编码规范认真执行。找几个资深程序猿(或者整个项目组讨论后)写一个Java编码规范,让项目组成员尽量遵守。一目了然的代码更容易定位问题,当然也更能让人写出好的代码。
    2. 单元测试要覆盖所有分支与边界条件。不要拿某种情况不会出现做借口。有句老话说常在河边站哪有不湿鞋(学名墨菲定律)。
    3. 代码审查要走。代码写完了,找资深程序猿扫扫代码没有坏处。
    4. 有条件的项目组要充分利用测试人员的能动性。
    5. 如果项目的期望较高,就把上面的尽量、可能等词汇改成一定要。

    以上五条建议对非性命攸关型项目足够了。

    下面说正题:

    对于java.lang.OutOfMemoryError: PermGen space 这种情况更多的是靠程序猿的经验来解决:

    PermGen space的全称是Permanent Generation space,是指内存的永久保存区域, 这块内存主要是被JVM存放Class和Meta信息的,Class在被Load时就会被放到PermGen space中, 它和存放类实例(Instance)的Heap区域不同,GC(Garbage Collection)不会在主程序运行期对 PermGen space进行清理,所以如果你的应用中有很多CLASS的话,就很可能出现PermGen space错误。

    通过上面的描述就可以得出:如果要加载的class与jar文件大小超过-XX:MaxPermSize就有可能会产生java.lang.OutOfMemoryError: PermGen space 。

    换句话说-XX:MaxPermSize的大小要超过class与jar的大小。通常-XX:MaxPermSize为-Xmx的1/8。

    对于java.lang.OutOfMemoryError: Java heap space 可能定位的过程就需要折腾一翻了:

    虽然各种java虚拟机的实现机制不一,但是heap space内存溢出产生的原因相同:那就是堆内存不够虚拟机分配了。

    我对java虚拟机的实现不感兴趣,对各种虚拟机的内存分配机制与gc的交互关系也不了解。但是我大致认为内存分配机制与gc是有联系的,也就是说内存不够分配时gc肯定也释放不了堆内存。从这一点出发,我们就需要找为什么gc释放不了堆内存。通常来说释放不了是因为内存还在使用。对于java对象产生的堆内存占用,只要其不再被继续引用gc是能够顺利回收的(对于通过本地方法调用,即JNI调用产生内存泄露的情况暂不考虑)。

    问题的关键就找到了,当产生heap space内存溢出时,堆内存中对象数量过多的就可能是问题的根源了。例外的情况是,程序确实需要那么多内存,这时就要考虑增大堆内存。

    例外的情况在本文中就不再多说了,下面介绍jdk自带的两个可视化工具来定位问题。

    jdk/jconsole.exe jdk/jvisualvm.exe

    jconsole.exe可以查看本地以及远程主机上的java虚拟机的当前状况,这对服务器健康检查情况非常有用。如下图:


    image

    jvisualvm.exe可以用来查看分析内存转储文件;也可以用其做java虚拟机当前状况查看,但是jvisualvm.exe的侵入性非常强,一旦使用会严重影响应用性能。如下图:

    image

    下面写些代码来演示一下内存溢出的产生,堆转储文件的生成,堆内存的分析。

    首先创建数据持有对象类:

    <pre style="box-sizing: border-box; outline: 0px; margin: 0px 0px 24px; padding: 8px; position: relative; font-family: Consolas, Inconsolata, Courier, monospace; white-space: pre-wrap; overflow-wrap: break-word; overflow-x: auto; font-size: 14px; line-height: 22px; color: rgb(0, 0, 0); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial; text-align: left;">package com.zas.jvm.om;

    /**

    • 数据对象

    • @author zas
      */
      public class DataObject {
      //数据对象ID
      private String id;
      //数据对象内容
      private String des;

      public DataObject(String id, String des) {
      super();
      this.id = id;
      this.des = des;
      }

      public String getId() {
      return id;
      }

      public void setId(String id) {
      this.id = id;
      }

      public String getDes() {
      return des;
      }

      public void setDes(String des) {
      this.des = des;
      }

      @Override
      public String toString() {
      return "DataObject [id=" + id + ", des=" + des + "]";
      }
      /**

      • @param args
        */
        public static void main(String[] args) {

      }

    }
    </pre>

    溢出演示代码

    <pre style="box-sizing: border-box; outline: 0px; margin: 0px 0px 24px; padding: 8px; position: relative; font-family: Consolas, Inconsolata, Courier, monospace; white-space: pre-wrap; overflow-wrap: break-word; overflow-x: auto; font-size: 14px; line-height: 22px; color: rgb(0, 0, 0); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial; text-align: left;">package com.zas.jvm.om;

    import java.util.ArrayList;
    import java.util.List;

    public class OutMemeryTest {

    List<DataObject> list = new ArrayList<DataObject>();
    
    public void testOm(){
        for (int i = 0; i < 100000; i++) {
            DataObject data = new DataObject("id&"+i, "des:"+i);
            list.add(data);
        }
    }
    /**
     * @param args
     */
    public static void main(String[] args) {
        OutMemeryTest omt = new OutMemeryTest();
        for (int i = 0; i < 2; i++) {
            omt.testOm();
        }
        System.out.println("DOne!");
        try {
            Thread.sleep(100000000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    

    }
    </pre>

    运行参数设置如下:-Xms64m -Xmx64m -XX:PermSize=8m -XX:MaxPermSize=8m
    -XX:-HeapDumpOnOutOfMemoryError
    见下图:

    image

    jvisualvm分析效果图:


    image

    从上图结合代码明显得出:com.zas.jvm.om.DataObject这个类的对象出了问题。

    以上是一个演示问题产生及定位过程,生产环境的问题千奇百怪需要具体问题具体分析。

    当堆内存巨大时可能要调整jdk\lib\visualvm\etc\visualvm.conf文件中的-xms -xmx大小来导入转储文件。

    生产环境为linux的较多,可以借助jdk自带的jmap来转储堆内存文件来分析。


    java 内存溢出 栈溢出的原因与排查方法

    ** 1、 内存溢出的原因是什么?**

    内存溢出是由于没被引用的对象(垃圾)过多造成JVM没有及时回收,造成的内存溢出。如果出现这种现象可行代码排查:

    一)是否App中的类中和引用变量过多使用了Static修饰 如public staitc Student s;在类中的属性中使用 static修饰的最好只用基本类型或字符串。如public static int i = 0; //public static String str;
    

    二)是否App中使用了大量的递归或无限递归(递归中用到了大量的建新的对象)

    三)是否App中使用了大量循环或死循环(循环中用到了大量的新建的对象)

    四)检查App中是否使用了向数据库查询所有记录的方法。即一次性全部查询的方法,如果数据量超过10万多条了,就可能会造成内存溢出。所以在查询时应采用“分页查询”。

    五)检查是否有数组,List,Map中存放的是对象的引用而不是对象,因为这些引用会让对应的对象不能被释放。会大量存储在内存中。

    六)检查是否使用了“非字面量字符串进行+”的操作。因为String类的内容是不可变的,每次运行"+"就会产生新的对象,如果过多会造成新String对象过多,从而导致JVM没有及时回收而出现内存溢出。

    如String s1 = "My name";

       String s2 = "is";
    
       String s3 = "xuwei";
    
      String str = s1 + s2 + s3 +.........;这是会容易造成内存溢出的
    
     但是String str =  "My name" + " is " + " xuwei" + " nice " + " to " + " meet you"; //但是这种就不会造成内存溢出。因为这是”字面量字符串“,在运行"+"时就会在编译期间运行好。不会按照JVM来执行的。
    

    在使用String,StringBuffer,StringBuilder时,如果是字面量字符串进行"+"时,应选用String性能更好;如果是String类进行"+"时,在不考虑线程安全时,应选用StringBuilder性能更好。

    七)使用 DDMS工具进行查找内存溢出的大概位置

    2、栈溢出的原因

    一)、是否有递归调用

    二)、是否有大量循环或死循环
    

    三)、全局变量是否过多

    四)、 数组、List、map数据是否过大

    五)使用DDMS工具进行查找大概出现栈溢出的位置

    相关文章

      网友评论

          本文标题:java内存溢出问题调试

          本文链接:https://www.haomeiwen.com/subject/ocsxsctx.html