1. 通用规则

1.1. 不要假设问题出在堆空间

1.2. 谨慎地创建对象并尽快丢弃它们

1.3. 使用更少的内存是提高垃圾回收器效率最好的办法

1.3.1. 减少内存使用是一个重要的目标

1.3.2. 和大多数性能优化主题一样,将精力集中于最大化利用现有内存会更有帮助

1.4. 对象重用方式

1.4.1. 线程局部变量

1.4.2. 特殊对象引用

1.4.3. 对象池

1.5. 重用对象意味着它们将长期存在并会影响垃圾回收器

1.5.1. 当它们被合理地重用时,整体性能将得到提升

2. 堆直方图

2.1. heap histogram

2.2. 快速查看应用程序中对象数量的方法,不需要生成完整堆转储

2.3. 用于识别由于某种特定类型的对象创建得太多而导致的内存问题

2.4. % jmap -histo process_id

2.5. % jmap -histo:live process_id

3. 堆转储

3.1. heap dump

3.2. 最强大的跟踪内存使用的技术

3.3. 常用工具

3.3.1. jvisualvm

3.3.1.1. 从正在运行的程序中获得堆转储文件

3.3.1.2. 可以打开之前生成的堆转储文件

3.3.1.3. 检查最大的保留对象并执行对堆的任意查询

3.3.2. 开源的Eclipse Memory Analyzer Tool

3.3.2.1. mat

3.3.2.2. 加载一个或者多个堆转储文件并对其进行分析

3.3.2.3. 可以产生报告

3.3.2.4. 可以用来浏览堆

3.3.2.5. 对堆执行类似SQL的查询

3.3.2.6. 提示可能出现问题的地方

3.4. % jcmd process_id GC.heap_dump /path/to/heap_dump.hprof

3.5. % jmap -dump:live,file=/path/to/heap_dump.hprof process_id

3.5.1. 包含live选项会在堆被转储之前强制执行Full GC

3.5.2. 如果因为某些原因你想包含其他的对象(死对象),可以在jcmd命令行的末尾加上-all

4. 内存分析

4.1. 对象的浅大小(shallow size)

4.1.1. 对象本身的大小

4.2. 对象的深大小(deep size)

4.2.1. 包含它所引用的对象的大小

4.3. 对象的深大小和保留大小的区别在于其引用的对象是否是共享的

4.4. 保留大量堆空间的对象通常被称为堆的支配者

4.5. 一般的经验法则是,寻找路径应该从集合对象(如HashMap)而不是从条目(如HashMap$Entry)开始,并且要寻找最大的集合

5. 内存溢出错误

5.1. out-of-memory

5.2. JVM没有可用的原生内存

5.2.1. 在Linux中,用户常常只被允许创建1024个进程

5.2.1.1. 可以通过运行ulimit-u来检查这个值

5.2.1.2. 试图创建第1025个线程,JVM就会抛出OutOfMemoryError异常

5.2.1.3. 操作系统对进程数量施加的限制导致的错误

5.2.2. 错误消息

5.2.2.1. Exception in thread "main" java.lang.OutOfMemoryError:unable to create new native thread

5.3. 元空间内存不足

5.3.1. 这个错误的发生通常是因为你设置了最大大小

5.3.1.1. 元空间默认情况下没有最大大小的限制

5.3.2. 根本原因

5.3.2.1. 分配的元空间已经放不下应用程序使用的类

5.3.2.2. 涉及类加载器的内存泄漏

5.3.2.2.1. 最常发生在动态加载类的服务器中
5.3.2.2.1.1. 增加元空间的大小会有帮助,但也只是推迟了错误发生的时间而已
5.3.2.2.1.2. 除了联系应用服务器厂商让他们修复内存泄漏问题之外,没有其他办法

5.3.3. 错误消息

5.3.3.1. Exception in thread "main" java.lang.OutOfMemoryError: Metaspace

5.4. Java堆本身内存不足

5.4.1. 对于给定大小的堆,应用程序已经无法创建任何额外的对象

5.4.2. 错误消息

5.4.2.1. Exception in thread "main" java.lang.OutOfMemoryError: Java heap space

5.5. -XX:+HeapDumpOnOutOfMemoryError

5.5.1. 打开这个标志会让JVM在抛出内存溢出错误时创建堆转储

5.5.2. 默认是false

5.6. -XX:HeapDumpPath=

5.6.1. 该标志指定堆转储的写入位置

5.6.1.1. 可以指定目录

5.6.1.1.1. 会使用默认文件名

5.6.1.2. 可以指定要生成的实际文件名

5.6.2. 默认位置是应用程序当前工作目录下的java_pid.hprof

5.7. -XX:+HeapDumpAfterFullGC

5.7.1. 运行Full GC之后生成堆转储

5.8. -XX:+HeapDumpBeforeFullGC

5.8.1. 运行Full GC之前生成堆转储

5.9. -XX:+ExitOnOutOfMemoryError

5.9.1. JVM在堆内存用尽时退出

5.9.2. 默认是false

5.10. JVM花费了太多时间执行GC

5.10.1. 错误消息

5.10.1.1. Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded

5.11. -XX:GCTimeLimit=N

5.11.1. 在Full GC中花费的时间超过了指定值

5.11.2. 默认值是98

5.11.2.1. 98%的时间花在GC上,该条件就满足

5.12. -XX:GCHeapFreeLimit=N

5.12.1. 一次Full GC回收的内存量小于指定值

5.12.2. 默认值是2

5.12.2.1. Full GC期间回收的内存小于堆的2%,该条件就满足

5.13. -XX:+UseGCOverheadLimit

5.13.1. 前面两个条件在5个连续的Full GC周期中都成立

5.13.2. 默认值true