内存溢出的原因:程序在申请内存时,没有足够的内存空间
方法死循环递归调用(StackOverflowError)、不断建立线程(OutOfMemoryError)
不断创建对象,分配对象大于最大堆的大小(OutOfMemoryError)
JVM 分配的本地直接内存大小大于 JVM 的限制(可以通过-XX:MaxDirectMemorySize 来设置(不设置的话默认与堆内存最大值一样,也会出现OOM 异常)
在经常动态生产大量 Class 的应用中,CGLIb 字节码增强,动态语言,大量 JSP(JSP 第一次运行需要编译成 Java 类),基于 OSGi 的应用(同一个类,被不同的加载器加载也会设为不同的类)
程序在申请内存后,无法释放已申请的内存空间。
例如将 ArrayList 设置为静态变量,则容器中的对象在程序结束之前将不能被释放,从而造成内存泄漏
如数据库连接、网络连接和 IO 连接等,只有连接被关闭后,垃圾回收器才会回收对应的对象。
例如,
1.一个变量的定义的作用范围大于其使用范围,
2.如果没有及时地把对象设置为 null
Java 的非静态内部类的这种创建方式,会隐式地持有外部类的引用,而且默认情况下这个引用是强引用,因此,如果内部类的生命周期长于外部类的生命周期,程序很容易就产生内存泄漏
如果内部类的生命周期长于外部类的生命周期,程序很容易就产生内存泄漏(你认为垃圾回收器会回收掉外部类的实例,但由于内部类持有外部类的引用,导致垃圾回收器不能正常工作)
解决方法:你可以在内部类的内部显示持有一个外部类的软引用(或弱引用),并通过构造方法的方式传递进来,在内部类的使用过程中,先判断一下外部类是否被回收;
在集合中,如果修改了对象中的那些参与计算哈希值的字段,会导致无法从集合中单独删除当前对象,造成内存泄露(有代码案例 Node 类)
内存溢出:实实在在的内存空间不足导致;
内存泄漏:该释放的对象没有释放,常见于使用容器保存元素的情况下。
如何避免:
内存溢出:检查代码以及设置足够的空间
内存泄漏:一定是代码有问题
往往很多情况下,内存溢出往往是内存泄漏造成的。
浅堆 :(Shallow Heap)是指一个对象所消耗的内存。例如,在 32 位系统中,一个对象引用会占据 4 个字节,一个 int 类型会占据 4 个字节,long 型变量会占据 8 个字节,每个对象头需要占用 8 个字节。
深堆 :这个对象被 GC 回收后,可以真实释放的内存大小,也就是只能通过对象被直接或间接访问到的所有对象的集合。通俗地说,就是指仅被对象所持有的对象的集合。深堆是指对象的保留集中所有的对象的浅堆大小之和。
举例:对象 A 引用了 C 和 D,对象 B 引用了 E。那么对象 A 的浅堆大小只是 A 本身,而如果 A 被回收,那么 C 和 D 都会被回收(可达性分析算法),所以 A的深堆大小为 A+C+D 之和,同时由于对象 E 还可以通过对象 B 访问到,因此不在对象 A 的深堆范围内。
列出当前机器上正在运行的虚拟机进程,JPS 从操作系统的临时目录上去找(所以有一些信息可能显示不全)。
是用于监视虚拟机各种运行状态信息的命令行工具。它可以显示本地或者远程虚拟机进程中的类装载、内存、垃圾收集、JIT 编译等运行数据,在没有 GUI图形界面,只提供了纯文本控制台环境的服务器上,它将是运行期定位虚拟机性能问题的首选工具。假设需要每 250 毫秒查询一次进程 13616 垃圾收集状况,一共查询 10 次,那命令应当是:
jstat -gc 13616 250 10
常用参数:
查看和修改虚拟机的参数
jinfo –sysprops 可以查看由 System.getProperties()取得的参数
jinfo –flag 未被显式指定的参数的系统默认值
jinfo –flags(注意 s)显示虚拟机的参数
jinfo –flag +[ 参 数 ] 可以 增加参数, 但是仅限 于
由 java -XX:+PrintFlagsFinal –version 查 询 出 来 且为 manageable 的参数
jinfo –flag -[参数] pid 可以修改参数
Thread.getAllStackTraces();
案例:JinfoTest 类
1.程序运行时只打印简单 GC2.通过 jinfo 修改 参数,打印 GC 详情
用于生成堆转储快照(一般称为 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 生成的堆转储快照。
jhat dump 文件名
后屏幕显示“Server is ready.”的提示后,用户在浏览器中键入 http://localhost7000/就可以访问详情
使用 jhat 可以在服务器上生成堆转储文件分析(一般不推荐,毕竟占用服务器的资源,比如一个文件就有 1 个 G 的话就需要大约吃一个 1G 的资源)
(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
插件中心地址
https://visualvm.github.io
但是注意版本问题,不同的 JDK 所带的 visualvm 是不一样的,下载插件时需要下对应的版本。
一般来说,这个工具是本机调试用,一般生产上来说,你一般是用不了的(除非启用远程连接)