JVM 内部定义的 OOM 有 6 种:
Handle msg = java_lang_String::create_from_str("Java heap space", CHECK_false); java_lang_Throwable::set_message(Universe::_out_of_memory_error_java_heap, msg()); msg = java_lang_String::create_from_str("Metaspace", CHECK_false); java_lang_Throwable::set_message(Universe::_out_of_memory_error_metaspace, msg()); msg = java_lang_String::create_from_str("Compressed class space", CHECK_false); java_lang_Throwable::set_message(Universe::_out_of_memory_error_class_metaspace, msg()); msg = java_lang_String::create_from_str("Requested array size exceeds VM limit", CHECK_false); java_lang_Throwable::set_message(Universe::_out_of_memory_error_array_size, msg()); msg = java_lang_String::create_from_str("GC overhead limit exceeded", CHECK_false); java_lang_Throwable::set_message(Universe::_out_of_memory_error_gc_overhead_limit, msg()); msg = java_lang_String::create_from_str("Java heap space: failed reallocation of scalar replaced objects", CHECK_false); java_lang_Throwable::set_message(Universe::_out_of_memory_error_realloc_objects, msg());当应用系统出现 OOM 时,我们可以根据异常信息快速定位到 OOM 的内存区域。下一步是保留现场,把当前内存 Dump 成文件。
jmap -dump:live,format=b,file=heap.bin <pid>需要注意如果使用了 :live 则会先触发一次 Full GC 再 Dump,这个参数表示只 Dump 活着的对象。
如果配置了参数
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=http://www.likecs.com/home/admin/gclogs/heapdump当发生 OOM 时会自动 Dump 内存到指定目录。
拿到了 Dump 文件可以选择 MAT 工具或者 jhat 进行分析。以 MAT 为例,从 Histogram 和 Dominator_tree 视图中可分别以「类」和「对象实例」视角进行分析。前者可以快速定位存在大量实例的类;后者可以快速定位大量相同类的实例的引用关系。
Histogram 视角从这个视角中可以很方便的看到 Programmer 的实例有 51.7 万个,占用了 16 M的堆内存。
Shallow Heap & Retained HeapShallow Heap 表示对象自身所占用的空间,不包括直接或间接引用对象所占用的空间
Retained Heap 表示对象自身及其直接或间接引用对象所占用的空间
Dominator_tree 视角在这个视角下,通过堆内存占用量进行排序可以快速定位到持有大量对象实例的线程,观察发现这些就是我们创建的应用线程,展开详情可以看到持有的所有对象。
获取 openjdk 源码openjdk 的源码使用 Mercurial 这个反人类的源码管理工具进行管理,如果需要直接从官方下载源码,不仅需要采用特殊方式上网,还需要有强大的运气。但是 GitHub 上有一个好心人做了 同步仓库 ,你可以直接从这里下载到源码。
这个仓库非常大,以天朝访问 GitHub 的速度,我们有生之年不一定能够下载完成,所以建议你最好在境外服务器上下载,大约 3 分钟就能下载完成,通过切换不同的分支拿到不同版本的 openjdk 源码。