一、内存溢出、内存泄露
1、内存溢出
程序在申请内存时,没有足够的内存空间
- 栈溢出
虚拟机栈(线程独享的栈空间)StackOverflowError
栈溢出:不断创建线程,使得栈空间被打满,OutOfMemoryError - 堆溢出
不断的创建对象使得堆大小小于要创建的对象大小OutOfMemoryError - 直接内存溢出
分配的本地内存大小大于 JVM 的限制 - 方法区溢出
在经常动态生产大量 Class 的应用中
2、内存泄漏
程序在申请内存后,无法释放已申请的内存,导致可用内存变少
-
长生命周期对象持有短生命周期对象的引用
例如,ArrayList 设置为静态变量,则容器中的对象在程序结束之前将不能被释放,从而造成内存泄漏 -
连接未关闭
如数据库连接、网络连接、I/O等只有在使用完关闭后,GC才会回收他 -
变量作用域不合理
一个变量的作用范围大于其使用范围或未能及时的对象置为null -
内部类持有外部类的引用
Java 的非静态内部类的这种创建方式,会隐式地持有外部类的引用,而且默认情况下这个引用是强引用,因此,如果内部类的生命周期长于外部类的生命 周期,程序很容易就产生内存泄漏。
如果内部类的生命周期长于外部类的生命周期,程序很容易就产生内存泄漏(你认为垃圾回收器会回收掉外部类的实例,但由于内部类持有外部类的引 用,导致垃圾回收器不能正常工作)
解决方法:你可以在内部类的内部显示持有一个外部类的软引用(或弱引用),并通过构造方法的方式传递进来,在内部类的使用过程中,先判断一下外部 类是否被回收; -
Hash 值改变
在集合中,如果修改了对象中的那些参与计算哈希值的字段,会导致无法从集合中单独删除当前对象,造成内存泄露。
3、二者辨析
- 内存溢出:实实在在的内存空间不足导致;
- 内存泄漏:该释放的对象没有释放,多见于自己使用容器保存元素的情况下。 如何避免:
- 内存溢出:检查代码以及设置足够的空间
- 内存泄漏:一定是代码有问题 往往很多情况下,内存溢出往往是内存泄漏造成的。
二、内存溢出定位排查工具MAT
JVM中有两个参数
-XX:+HeapDumpOnOutOfMemoryError :当内存溢出时导出内存快照
-XX:HeapDumpPath=/usr/local/app/oom :指定导出内存快照的路径
MAT是一种可视化分析dump日志的工具。
1、深堆和浅堆
- 浅堆:是指一个对象所消耗的内存。例如,在 32 位系统中,一个对象引用会占据 4 个字节,一个 int 类型会占据 4 个字节,long 型变量 会占据 8 个字节,每个对象头需要占用 8 个字节。
- 深堆:这个对象被 GC 回收后,可以真实释放的内存大小,也就是只能通过对象被直接或间接访问到的所有对象的集合。
A引用了C、D、E。B引用了E,那么A的浅堆是A本身,深堆是ACD三个对象的内存大小总和,因为A被GC回收的时候可以回收ACD三个对象。
使用MAT工具分析的时候可以看到
可以看到当前线程中有对象linkedList浅堆是32byte但是深堆是28M多,这是因为我们程序中linkedList中存放了很多的对象,并且都是强引用,所以可以根据深度和浅堆来定位下是否有对象强引用了过多的对象导致的内存溢出。
2、incoming 和 outgoing
分别是当前对象被那些对象引用和引用了那些对象,可以帮助我们分析对象引用关系,进而分析是否发生了内存泄漏。
三、JDK提供的JVM操作命令
1、JPS
列出当前机器上运行的虚拟机进程,jps从操作系统的临时目录去找
- -q :仅仅显示进程
- -m:输出主函数传入的参数. 下的 hello 就是在执行程序时从命令行输入的参数
- -l: 输出应用程序主类完整 package 名称或 jar 完整名称.
- -v: 列出 jvm 参数, -Xms20m -Xmx50m 是启动程序指定的 jvm 参数
2、jstat
是用于监视虚拟机各种运行状态信息的命令行工具。它可以显示本地或者远程虚拟机进程中的类装载、内存、垃圾收集、JIT 编译等运行数据,在没有 GUI 图形界面,只提供了纯文本控制台环境的服务器上,它将是运行期定位虚拟机性能问题的首选工具。
假设需要每 250 毫秒查询一次进程 13616 垃圾收集状况,一共查询 10 次,那命令应当是:jstat-gc 13616 250 10
常用参数:
-class (类加载器)
-compiler (JIT)
-gc (GC 堆状态)
-gccapacity (各区大小)
-gccause (最近一次 GC 统计和原因) -gcnew (新区统计)
-gcnewcapacity (新区大小)
-gcold (老区统计)
-gcoldcapacity (老区大小) -gcpermcapacity (永久区大小)
-gcutil (GC 统计汇总) -printcompilation (HotSpot 编译统计)
3、Jinfo
查看和修改虚拟机的参数
- jinfo –sysprops 可以查看由 System.getProperties()取得的参数
- jinfo –flag 未被显式指定的参数的系统默认值
- jinfo –flags(注意 s)显示虚拟机的参数
- jinfo –flag +[ 参数 ] 可以增加参数,但是仅限于由 java -XX:+PrintFlagsFinal –version 查询出来且为 manageable 的参数
- jinfo –flag -[参数] 可以去除参数
4、jmap
用于生成堆转储快照(一般称为 heapdump 或 dump 文件)。jmap 的作用并不仅仅是为了获取 dump 文件,它还可以查询 finalize 执行队列、Java 堆和永 久代的详细信息,如空间使用率、当前用的是哪种收集器等。和 jinfo 命令一样,jmap 有不少功能在 Windows 平台下都是受限的,除了生成 dump 文件 -dump 选项和用于查看每个类的实例、空间占用统计的-histo 选项在所有操作系统都提供之外,其余选项都只能在 Linux/Solaris 下使用。
使用方法: jmap -dump:live,format=b,file=heap.bin <pid>
Sun JDK 提供 jhat(JVM Heap Analysis Tool)命令与 jmap 搭配使用,来分析 jmap 生成的堆转储快照。
5、jhat
jhat dump 文件名
后屏幕显示“Server is ready.”的提示后,用户在浏览器中键入 http://localhost:7000/就可以访问详情
使用 jhat 可以在服务器上生成堆转储文件分析(一般不推荐,毕竟占用服务器的资源,比如一个文件就有 1 个 G)
6、jstack
(Stack Trace for Java)命令用于生成虚拟机当前时刻的线程快照。线程快照就是当前虚拟机内每一条线程正在执行的方法堆栈的集合,生成线程快照的主要目的是定位线程出现长时间停顿的原因,如线程间死锁、死循环、请求外部资源导致的长时间等待等都是导致线程长时间停顿的常见原因
。 在代码中可以用 java.lang.Thread 类的 getAllStackTraces()方法用于获取虚拟机中所有线程的 StackTraceElement 对象。使用这个方法可以通过简单的几行 代码就完成 jstack 的大部分功能,在实际项目中不妨调用这个方法做个管理员页面,可以随时使用浏览器来查看线程堆栈。
四、可视化工具
JMX(Java Management Extensions,即 Java 管理扩展)是一个为应用程序、设备、系统等植入管理功能的框架。JMX 可以跨越一系列异构操作系统平台、 系统体系结构和网络传输协议,灵活的开发无缝集成的系统、网络和服务管理应用。
管理远程进程需要在远程程序的启动参数中增加:
-Djava.rmi.server.hostname=…
-Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=8888 -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false
1、Jconsole
- 查看内存数据
- 执行GC
- 线程相关信息
2、visualvm
- 查看JVM参数、概述信息
- 监视JVM信息
- 导出内存快照
- 线程相关信息
- 分析占用cpu使用情况