如果大家喜欢这个系列可以关注我的专栏

java提升专栏里面有更多的文章和资料想学习更多东西的朋友关注一下哦!!!

java提升​zhuanlan.zhihu.com

实战内存溢出异常

大家好,相信大部分Javaer在code时经常会遇到本地代码运行正常,但在生产环境偶尔会莫名其妙的报一些关于内存的异常,StackOverFlowError,OutOfMemoryError异常是最常见的。今天就基于上篇文章【JVM成长之路】Java内存结构详解讲解的各个内存区域重点实战分析下内存溢出的情况。在此之前,我还是想多余累赘一些其他关于对象的问题,具体内容如下:

文章结构
1、对象的创建过程
2、对象的内存布局
3、对象的访问定位
4、实战内存异常

一. 对象的创建过程

关于对象的创建,第一反应是new关键字,那么本文就主要讲解new关键字创建对象的过程。
Student stu =new Student("张三","18");复制代码
就拿上面这句代码来说,虚拟机首先会去检查Student这个类有没有被加载,如果没有,首先去加载这个类到方法区,然后根据加载的Class类对象创建stu实例对象,需要注意的是,stu对象所需的内存大小在Student类加载完成后便可完全确定。内存分配完成后,虚拟机需要将分配到的内存空间的实例数据部分初始化为零值,这也就是为什么我们在编写Java代码时创建一个变量不需要初始化。紧接着,虚拟机会对对象的对象头进行必要的设置,如这个对象属于哪个类,如何找到类的元数据(Class对象),对象的锁信息,GC分代年龄等。设置完对象头信息后,调用类的构造函数。
其实讲实话,虚拟机创建对象的过程远不止这么简单,我这里只是把大致的脉络讲解了一下,方便大家理解。

二 . 对象的内存布局

刚刚提到的实例数据,对象头,有些小伙伴也许有点陌生,这一小节就详细讲解一下对象的内存布局,对象创建完成后大致可以分为以下几个部分:

  • 对象头
  • 实例数据
  • 对齐填充

对象头: 对象头中包含了对象运行时一些必要的信息,如GC分代信息,锁信息,哈希码,指向Class类元信息的指针等,其中对Javaer比较有用的是锁信息与指向Class对象的指针,关于锁信息,后期有机会讲解并发编程JUC时再扩展,关于指向Class对象的指针其实很好理解。比如上面那个Student的例子,当我们拿到stu对象时,调用Class stuClass=stu.getClass();的时候,其实就是根据这个指针去拿到了stu对象所属的Student类在方法区存放的Class类对象。虽然说的有点拗口,但这句话我反复琢磨了好几遍,应该是说清楚了。^_^实例数据:实例数据部分是对象真正存储的有效信息,就是程序代码中所定义的各种类型的字段内容。对齐填充:虚拟机规范要求对象大小必须是8字节的整数倍。对齐填充其实就是来补全对象大小的。

三 . 对象的访问定位

谈到对象的访问,还拿上面学生的例子来说,当我们拿到stu对象时,直接调用stu.getName();时,其实就完成了对对象的访问。但这里要累赘说一下的是,stu虽然通常被认为是一个对象,其实准确来说是不准确的,stu只是一个变量,变量里存储的是指向对象的指针,(如果干过C或者C++的小伙伴应该比较清楚指针这个概念),当我们调用stu.getName()时,虚拟机会根据指针找到堆里面的对象然后拿到实例数据name.需要注意的是,当我们调用stu.getClass()时,虚拟机会首先根据stu指针定位到堆里面的对象,然后根据对象头里面存储的指向Class类元信息的指针再次到方法区拿到Class对象,进行了两次指针寻找。具体讲解图如下:

四 .实战内存异常

内存异常是我们工作当中经常会遇到问题,但如果仅仅会通过加大内存参数来解决问题显然是不够的,应该通过一定的手段定位问题,到底是因为参数问题,还是程序问题(无限创建,内存泄露)。定位问题后才能采取合适的解决方案,而不是一内存溢出就查找相关参数加大。
概念

  • 内存泄露:代码中的某个对象本应该被虚拟机回收,但因为拥有GCRoot引用而没有被回收。关于GCRoot概念,下一篇文章讲解。
  • 内存溢出: 虚拟机由于堆中拥有太多不可回收对象没有回收,导致无法继续创建新对象。

在分析问题之前先给大家讲一讲排查内存溢出问题的方法,内存溢出时JVM虚拟机会退出,那么我们怎么知道JVM运行时的各种信息呢,Dump机制会帮助我们,可以通过加上VM参数-XX:+HeapDumpOnOutOfMemoryError让虚拟机在出现内存溢出异常时生成dump文件,然后通过外部工具(作者使用的是VisualVM)来具体分析异常的原因。
下面从以下几个方面来配合代码实战演示内存溢出及如何定位:

  • Java堆内存异常
  • Java栈内存异常
  • 方法区内存异常

Java堆内存异常

/**VM Args://这两个参数保证了堆中的可分配内存固定为20M-Xms20m-Xmx20m//文件生成的位置,作则生成在桌面的一个目录-XX:+HeapDumpOnOutOfMemoryError //文件生成的位置,作则生成在桌面的一个目录//文件生成的位置,作则生成在桌面的一个目录-XX:HeapDumpPath=/Users/zdy/Desktop/dump/*/
public class HeapOOM {public static void main(String[] args) {List<OOMObject> list = new ArrayList<OOMObject>();//无限创建对象,在堆中while (true) {list.add(new OOMObject());}}//创建一个内部类用于创建对象使用static class OOMObject {}
}

Run起来代码后爆出异常如下:java.lang.OutOfMemoryError: Java heap space
Dumping heap to /Users/zdy/Desktop/dump/java_pid1099.hprof ...

可以看到生成了dump文件到指定目录。并且爆出了OutOfMemoryError,还告诉了你是哪一片区域出的问题:heap space
打开VisualVM工具导入对应的heapDump文件(如何使用请读者自行查阅相关资料),相应的说明见图:

切换到"实例数"标签页

分析dump文件后,我们可以知道,OOMObject这个类创建了810326个实例。所以它能不溢出吗?接下来就在代码里找这个类在哪new的。排查问题。(我们的样例代码就不用排查了,While循环太凶猛了)
Java栈内存异常
老实说,在栈中出现异常(StackOverFlowError)的概率小到和去苹果专卖店买手机,买回来后发现是Android系统的概率是一样的。因为作者确实没有在生产环境中遇到过,除了自己作死写样例代码测试。先说一下异常出现的情况,前面讲到过,方法调用的过程就是方法帧进虚拟机栈和出虚拟机栈的过程,那么有两种情况可以导致StackOverFlowError,当一个方法帧(比如需要2M内存)进入到虚拟机栈(比如还剩下1M内存)的时候,就会报出StackOverFlow.这里先说一个概念,栈深度:指目前虚拟机栈中没有出栈的方法帧。虚拟机栈容量通过参数-Xss来控制,下面通过一段代码,把栈容量人为的调小一点,然后通过递归调用触发异常。

/**

结果如下:stack length:751Exception in thread "main" java.lang.StackOverflowError
可以看到,递归调用了751次,栈容量不够用了。
默认的栈容量在正常的方法调用时,栈深度可以达到1000-2000深度,所以,一般的递归是可以承受的住的。如果你的代码出现了StackOverflowError,首先检查代码,而不是改参数。
这里顺带提一下,很多人在做多线程开发时,当创建很多线程时,容易出现OOM(OutOfMemoryError),这时可以通过具体情况,减少最大堆容量,或者栈容量来解决问题,这是为什么呢。请看下面的公式:线程数*(最大栈容量)+最大堆值+其他内存(忽略不计或者一般不改动)=机器最大内存
当线程数比较多时,且无法通过业务上削减线程数,那么再不换机器的情况下,你只能把最大栈容量设置小一点,或者把最大堆值设置小一点。
方法区内存异常
写到这里时,作者本来想写一个无限创建动态代理对象的例子来演示方法区溢出,避开谈论JDK7与JDK8的内存区域变更的过渡,但细想一想,还是把这一块从始致终的说清楚。在上一篇文章中【JVM成长之路】Java内存结构详解讲到方法区时提到,JDK7环境下方法区包括了(运行时常量池),其实这么说是不准确的。因为从JDK7开始,HotSpot团队就想到开始去"永久代",大家首先明确一个概念,方法区和"永久代"(PermGen space)是两个概念,方法区是JVM虚拟机规范,任何虚拟机实现(J9等)都不能少这个区间,而"永久代"只是HotSpot对方法区的一个实现。为了把知识点列清楚,我还是才用列表的形式:

  • JDK7之前(包括JDK7)拥有"永久代"(PermGen space),用来实现方法区。但在JDK7中已经逐渐在实现中把永久代中把很多东西移了出来,比如:符号引用(Symbols)转移到了native heap,运行时常量池(interned strings)转移到了java heap;类的静态变量(class statics)转移到了java heap.
    所以这就是为什么我说上一篇文章中说方法区中包含运行时常量池是不正确的,因为已经移动到了java heap;
  • 在JDK7之前(包括7)可以通过-XX:PermSize -XX:MaxPermSize来控制永久代的大小.
  • JDK8正式去除"永久代",换成Metaspace(元空间)作为JVM虚拟机规范中方法区的实现。
  • 元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。因此,默认情况下,元空间的大小仅受本地内存限制,但仍可以通过参数控制:-XX:MetaspaceSize与-XX:MaxMetaspaceSize来控制大小。

下面作者还是通过一段代码,来不停的创建Class对象,在JDK8中可以看到metaSpace内存溢出:

/**作者准备在JDK8下测试方法区,所以设置了Metaspace的大小为固定的8M-XX:MetaspaceSize=8m-XX:MaxMetaspaceSize=8m*/
public class JavaMethodAreaOOM {public static void main(String[] args) {while (true) {Enhancer enhancer = new Enhancer();enhancer.setSuperclass(OOMObject.class);enhancer.setUseCache(false);enhancer.setCallback(new MethodInterceptor() {public Object intercept(Object obj, Method method,Object[] args, MethodProxy proxy)throws Throwable {return proxy.invokeSuper(obj, args);}});//无限创建动态代理,生成Class对象enhancer.create();}}static class OOMObject {}
}

在JDK8的环境下将报出异常:Exception in thread "main" java.lang.OutOfMemoryError: Metaspace
这是因为在调用CGLib的创建代理时会生成动态代理类,即Class对象到Metaspace,所以While一下就出异常了。提醒一下:虽然我们日常叫"堆Dump",但是dump技术不仅仅是对于"堆"区域才有效,而是针对OOM的,也就是说不管什么区域,凡是能够报出OOM错误的,都可以使用dump技术生成dump文件来分析。
在经常动态生成大量Class的应用中,需要特别注意类的回收状况,这类场景除了例子中的CGLib技术,常见的还有,大量JSP,反射,OSGI等。需要特别注意,当出现此类异常,应该知道是哪里出了问题,然后看是调整参数,还是在代码层面优化。
附加-直接内存异常
直接内存异常非常少见,而且机制很特殊,因为直接内存不是直接向操作系统分配内存,而且通过计算得到的内存不够而手动抛出异常,所以当你发现你的dump文件很小,而且没有明显异常,只是告诉你OOM,你就可以考虑下你代码里面是不是直接或者间接使用了NIO而导致直接内存溢出。
好了,"JVM系列之实战内存溢出异常"到这里就给大家介绍完了,Have a good day .欢迎留言指错。

往期入口:

欧阳丰:【JVM成长之路】Java内存结构详解​zhuanlan.zhihu.com

如果大家喜欢这个系列可以点一下关注

java提升专栏里面有更多的文章和资料想学习更多东西的朋友关注一下哦!!!

java提升​zhuanlan.zhihu.com

java bufferedreader读大文件会内存溢出吗_【JVM成长系列】实战内存溢出异常相关推荐

  1. Java高效读取大文件(转)

    Java高效读取大文件 1.概述 本教程将演示如何用Java高效地读取大文件.这篇文章是Baeldung(http://www.baeldung.com/) 上"Java--回归基础&quo ...

  2. java内存中读文件_关于内存管理:读取Java中的大文件

    我需要一个非常了解Java和内存问题的人的建议. 我有一个大文件(大约1.5GB),我需要将此文件切成许多小文件(例如100个小文件). 我通常知道如何做到这一点(使用BufferedReader), ...

  3. Java中 读-写 文件 BufferedReader BufferedWriter

    本篇文章,为大家带来Java中进行文件读写的一种方式. 我的文件目录: /Users/gisboy/Desktop/a.txt Java中,用java.io.BufferedReader 进行文件内容 ...

  4. java读取excel大文件

    在读取excel大文件的时候就不能再使用poi包下面的Workbook类,会造成OOM等问题. 我们常见的excel分为xlsx格式和csv格式.分别实现一下. xlsx格式处理. 需要的pom依赖: ...

  5. Java高效读取大文件

    1.概述 本教程将演示如何用Java高效地读取大文件.这篇文章是Baeldung(http://www.baeldung.com/) 上"Java--回归基础"系列教程的一部分. ...

  6. java内存溢出前端_【面试笔录】内存溢出和内存泄漏

    一.Java是如何管理内存的 在Java中,我们需要通过new关键字为每一个对象申请内存空间(基本数据类型除外),所有的对象都是在堆(Heap)中分配空间的. 在Java中,内存的分配是管理员决定的, ...

  7. java怎样读txt文件_【后端开辟】java怎样读写txt文件?

    java怎样读取txt文件? 1.运用FileInputStream完成读取txt文件内容 2.运用FileOutputStream完成写入txt文件内容 package cn.xiaobing.ut ...

  8. java实现对大文件切割下载_Java实现大文件的切割与合并操作示例

    Java实现大文件的切割与合并操作示例 发布时间:2020-09-27 02:25:08 来源:脚本之家 阅读:99 作者:HiBoyljw 本文实例讲述了Java实现大文件的切割与合并操作.分享给大 ...

  9. java中计算一个文件的总字节数_【JVM故事】一个Java字节码文件的诞生记

    作者:李新杰·转自微:信公众号"编程新说" 万字长文,完全虚构.(12000字) (一) 组里来了个实习生,李大胖面完之后,觉得水平一般,但还是留了下来,为什么呢?各自猜去吧. 李 ...

  10. java jmap mat_利用jmap和MAT等工具查看JVM运行时堆内存

    jmap JDK自带了一些工具可以帮助我们查看JVM运行的堆内存情况,常用的是jmap命令 如果想学习Java工程化.高性能及分布式.深入浅出.微服务.Spring,MyBatis,Netty源码分析 ...

最新文章

  1. python之路-day19-面向对象之约束
  2. android getview方法,android 获取view的getLeft(), getRight(), getTop(),... - 简书
  3. Java 集合类图(转)
  4. 反思O2O演化的三个时代,大数据与智能化才是未来所在
  5. python 关于excelcsv与cookie的部分笔记
  6. pb blob存储到image_【Filecoin相关】速懂 Filecoin 自认证存储设计
  7. 实现一个顺序表的建立、查找、插入和删除操作【数据结构实验报告】
  8. java gzip 文件夹_Java GZip 基于磁盘实现压缩和解压的方法
  9. 远程登录软件secureCRT
  10. 联通光猫pt952g管理员密码获取 v1.0
  11. 数学公式神器【MathPix Snip】,截屏公式转为LaTeX代码
  12. mysql 数据库大小写敏感(数据库的名字、表名字、字段名字、字段值)
  13. WIN10如何设置默认便签应用
  14. 「产品读书」增长黑客:创业公司的用户与收入增长秘籍
  15. HDLBITS笔记15:组合逻辑之7420芯片
  16. Springboot整合Shiro之授权
  17. 今日收获:CSS基础
  18. 吴恩达深度学习编程作业 part 2-2
  19. js中利用prompt和parseFloat来实现用户体温华氏和摄氏的提取(18)
  20. 大数据时代,为什么很多JAVA程序员会转型做JAVA大数据

热门文章

  1. JDBC上传文件存入BLOB字段
  2. HTML(五)列表,区块,布局,表单和输入
  3. 获取URL参数JS函数
  4. HDU4635 Strongly connected
  5. redis 备份与恢复
  6. Leetcode: 3Sum
  7. 用Perl编写Apache模块续 - SVNAuth
  8. 在单例Bean中依赖原型Bean问题解决
  9. mysql 1045 拒绝远程链接
  10. POI操作Excel表格相关API说明