专栏首页JAVA乐园JVM各区溢出分析

JVM各区溢出分析

阅读文本大概需要3分钟。

0x01:Java虚拟机栈和本地方法栈溢出

由于在Hotspot虚拟机中中不区分虚拟机栈和本地方法栈,因此通过-Xoss修改参数是无效的,可以通过修改-Xss设定。

  • 如果线程请求的栈深度大于虚拟机允许的最大深度,将抛出StackOverflowError异常。
  • 如果虚拟机在扩展栈时无法申请到足够的内存空间,将抛出OutOfMemoryError异常。

这两种异常有一些重叠的部分:当栈空间无法继续分配时,到底是内存太小,还是已经使用的栈空间过大,其本质只是对同一件事情的两种不同描述。

可以通过以下方法验证:

  • 在使用-Xss参数减少栈内存容量,结果抛出Stack OverflowError异常,异常出现时输出的堆栈深度相应缩小。
  • 定义了大量的本地变量,增大此方法栈中本地变量表的长度,结果抛出Stack OverflowError异常时输出的堆栈深度相应缩小。

可以通过递归调用的方式进行测试:

public void stackLeak() {
    stackLeak();
}

通过不断建立线程的方式可以生产内存异常异常,但是产生的内存异常异常和栈空间是否足够大并不存在任何关联,在这种情况下,为每个线程的栈分配的内存越大,反而越容易产生内存溢出。

操作系统为虚拟机分配的内存是有限制的,如果虚拟机进程本身消耗的内存计算在内,剩余的内存就由虚拟机栈和本地方法栈瓜分了,每个线程分配到的栈容量越大,可以建立的线程数量自然就越少,建立线程时就越容易把剩下的内存耗尽。

如果是建立线程过多导致内存溢出,在不能减少线程数量或者更换64位虚拟机的情况下,就只能通过减少最大堆和减少栈容量来换取更多的线程。

可以通过死循环创建线程的方式模拟“由于线程过多导致的内存溢出”:

while(true){
  Thread t = new Thread(new Runable(){
      ......
  });
}

0x02:Java堆内存溢出

可以通过不停的创建对象来造成堆内存溢出

public static void main(String[] args) {
    List list = new ArrayList<>();
    while(true) {
       list.add(new ObjectBIg())
    }
}

使用-XX:+HeapDumpOnOutOfMemoryError可以在虚拟机在出现内存溢出异常时Dump出当前的内存堆转存储快照以便后续进行分析。

对Dump快照进行分析,需要区分出到底是内存泄漏Memory Leak还是内存异常Memory Overflow。

如果是内存泄漏,进一步通过工具对GC Root的引用链进行分析。

如果不是内存泄漏,就是内存中的对象确实都还必须存活,那就应该修改虚拟机参数Xmx Xms,同时判断是否可以通过调大物理内存的方式解决。然后从代码角度检测是否存在某些对象生命周期过长、持有状态时间过长的情况,尝试减少程序运行期的消耗。

0x03: 方法区和运行时常量池溢出

由于运行时常量池属于方法区的一部分,因此两个区域放在一块执行。

String.intern()是一个Native方法,它的作用是如果字符串常量池中已经包含了此String对象的字符串,则返回代表池中这个字符串的String对象;否则将此String对象包含的字符串添加到常量池中,并且返回此String对象的引用。

可以通过以下代码测试运行时常量池溢出:

public class Test {
 public static void main(String[] args) {
      int i =0;
      List<String> list = new ArrayList();
      while(true) {
         list.add(String.valueOf(i++).intern());
      }
   }
}

可以在抛出的异常后面发现“Perm space”信息。

可以使用String.intern()测试运行时常量池:

public class Test1 {
 public static void main(String[] args) {
      String str1 = new StringBuilder("111").append("-222").toString();
      System.out.println(str1.intern()==str1);
 String str2= new 
       StringBuilder("jav").append("a").toString();;
      System.out.println(str2.intern()==str2);
 }
}
结果:
true
false

JDK1.7中的intern实现不会复制实例,只是在常量池中记录首次出现的实例引用,因此intern()返回的引用和由StringBuilder创建的那个字符串实例是同一个。

对str2比较返回false是因为“java”字符串在执行StringBuilder.toString()之前已经出现过了,字符串常量池中已经有它的引用了,不符合“首次出现”的原则。

方法区用于存放Class相关的信息,如类名、访问修饰符、常量池、字段描述、方法描述等,对于这些区域的测试,基本的思路是运行时产生大量的类填充方法区,直到溢出。

可以借助GCLib直接操作字节码运行时产生大量的动态类:

public class Test1 {
     public static void main(final String[] args) {
          while(true){
             Enhancer enhancer = new Enhancer();
             enhancer.setSuperclass(OOMOBject.class);
             enhancer.setUseCache(false);
             enhancer.setCallback(new MethodInterceptor() {
                @Override
                public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                    return methodProxy.invoke(objects,args);
              }
            });
            enhancer.create();
       }
   }
     static class OOMOBject{

     }
}

除了GCLib字节码增强和动态语言之外,常见的还有大量JSP或者动态生成JSP文件的应用、基于OSGi的应用等

另外:程序计数器是JVM唯一不会发生内存溢出的区域。

本文分享自微信公众号 - JAVA乐园(happyhuangjinjin88)

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-05-28

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • JAVA爬数据也是杠杠的,看我爬下杜大哥

    上篇分析的网站是国家级,没有真正编写代码爬取对应的数据,今天以“1药网”为例来爬一爬药品数据

    java乐园
  • Java爬虫可以非常溜

    xxl-crawler是 许雪里 大佬开源的一个java爬虫,熟悉java语言的用起来可以非常顺手。

    java乐园
  • Java图形验证码支持gif、中文、算术等

    图形验证码是最经典,也是最常用的验证方式。今天介绍一个非常不错的类库:Java图形验证码,支持gif、中文、算术等类型,可用于Java Web、JavaSE等项...

    java乐园
  • Redis高级客户端Lettuce详解

    Lettuce是一个Redis的Java驱动包,初识她的时候是使用RedisTemplate的时候遇到点问题Debug到底层的一些源码,发现spring-dat...

    猿天地
  • 【java微信支付】微信支付之扫码支付相关代码

    最近开发网站过程,需要引入支付过程,第三方支付中最火的莫过于支付宝支付和微信支付,下边借助微信支付官网上的文档,写一下接入微信支付之扫码支付...

    用户5640963
  • 剖析更高级的Redis客户端Lettuce

    Lettuce是一个Redis的Java驱动包,初识她的时候是使用RedisTemplate的时候遇到点问题Debug到底层的一些源码,发现spring-dat...

    黄泽杰
  • Android 中Volley二次封装并实现网络请求缓存

    Android目前很多同学使用Volley请求网络数据,但是Volley没有对请求过得数据进行缓存,因此需要我们自己手动缓存。 一下就是我的一种思路,仅供参考

    砸漏
  • 50亿加密手机号md5快速存储及检索,rocksDB、redis等探索

    首先需求比较简单,将所有的号码段(如130、131、132)的全部手机号的md5和其对应的手机号存起来,将来传入一批手机号的md5,能迅速给出对应的明文手机号。...

    天涯泪小武
  • 通过数据库生成实体类

    ​ 这只是一个简单的解析数据库建表语句之后,根据解析结果生成java文件的一个简单工具。写的原因有两个。

    何白白
  • 使用java编程实现明文和密文之间的互转

    现在你就可以自定义key,增加难度,但是这个还是不安全,密钥还是可能被套取,所以加入当前时间,这样每次生成的密钥都不一样

    Erwin

扫码关注云+社区

领取腾讯云代金券