目录
目录.png参数配置
堆内内存问题
六种OOM
- java.lang.StackOverFlowError 虚拟机栈
- java.lang.OutOfMemoryError:Java heap space 堆
- java.langOutOfMemoryError:Metaspace 元数据空间
- java.lang.OutOfMemoryError:Direct buffer memory 堆外
- java.lang.OutOfMemoryError:GC overhead limit exceeded GC回收时间长时会抛出OutOfMemoryError
- java.lang.OutOfMemoryError:unable to create new native thread linux系统默认允许单个进程可以创建的线程数是1024个
堆内内存,但无oom文件
- 在grafana上面看到堆内内存上升
- 拉取一台机器,并且生成dump文件,然后使用MAT分析。
-
MAT简介
MAT简介.png
MAT_dump文件分析.png -
在thread_override中可以看到push_data_thread有30个,占用了大部分内存
image.png -
从dominator_tree中可以看出是PriceChangePullPushService中push_data_thread中HttpClient的BufferedWritter写入过多String
image.png - 代码定位,这块代码只是用来测试模拟,堆内内存溢出
jvm配置: -Xms350m -Xmx350m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=./ -XX:+UseG1GC -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:MaxGCPauseMillis=200m
代码定位.png
参考文章
堆内内存有oom
- 线上邮件收到报OOM,因为配置OOM时生成dump文件
-
用MAT分析, 模拟100M内存,这里看占用了绝大多数
image.png - 定位代码
jvm配置: -Xms10m -Xmx10m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=./ -XX:+PrintGCDetails -Xloggc:./gc.log
public class TestOOM implements Runnable {
private List<String> list = new ArrayList<>();
@Override
public void run() {
int i = 0;
++i;
list.add(String.valueOf(i));
}
public void test(){
//System.out.println("I am testoom");
}
}
public class Test {
private final static ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(8, 8,
30, TimeUnit.SECONDS, new LinkedBlockingDeque<>(), r -> new Thread("test oom thread"));
public static void main(String[] args) {
while (true) {
threadPoolExecutor.execute(new TestOOM());
}
}
}
Humous大对象分配测试
- MAT中能看到超过1M的大对象
public class TestHumous {
public static void main(String[] args) {
TestHumous testHumous = new TestHumous();
testHumous.test();
try {
Thread.sleep(10000000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private void test(){
int size = 1024 * 1024;
byte[] myAlloc1 = new byte[ size];
byte[] myAlloc2 = new byte[ size];
byte[] myAlloc3 = new byte[ size];
byte[] myAlloc4 = new byte[ size];
System.out.println("MyTest.main");
try {
Thread.sleep(100000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
堆外内存
代码模拟堆外内存溢出
jvm option: -XX:NativeMemoryTracking=detail -XX:+UseG1GC
// 开始先给点时间设置基准线
try {
Thread.sleep(120000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("开始测试");
ByteBuffer buffer = ByteBuffer.allocateDirect(50 * 1024 * 1024);
try {
Thread.sleep(300000000);
} catch (InterruptedException e) {
e.printStackTrace();
}
排查思路
- 先确定下是否堆问题。如果是堆问题,再确定下堆内内存有问题,不是堆内内存大概率就是堆外
- NMT是Java7U40引入的HotSpot新特性,配合jcmd命令我们就可以看到具体内存组成了。jvm参数需开启-XX:NativeMemoryTracking=summary,会略微耗性能。
- 排查命令
// 先看下出问题pid,测试的话是可以直接通过jps -l看到的, 线上top看看
jps -l
// 先设置个基准线
jcmd 96328 VM.native_memory baseline
// 过段时间看下diff
jcmd 96328 VM.native_memory summary.diff
- native_memory日志
Native Memory Tracking:
Total: reserved=10116110KB +51219KB, committed=927226KB +51219KB
Java Heap (reserved=8388608KB, committed=524288KB)
(mmap: reserved=8388608KB, committed=524288KB)
Class (reserved=1068164KB, committed=16260KB)
(classes #460 +1)
(malloc=11396KB #236 +5)
(mmap: reserved=1056768KB, committed=4864KB)
Thread (reserved=25706KB, committed=25706KB)
(thread #26)
(stack: reserved=25600KB, committed=25600KB)
(malloc=76KB #137)
(arena=30KB #47)
Code (reserved=249636KB, committed=2572KB)
(malloc=36KB #323 +1)
(mmap: reserved=249600KB, committed=2536KB)
GC (reserved=319169KB, committed=293573KB)
(malloc=12689KB #137)
(mmap: reserved=306480KB, committed=280884KB)
Compiler (reserved=133KB, committed=133KB)
(malloc=2KB #25)
(arena=131KB #7)
// 可以看到这里加了很多,也就是代码里面的堆外内存分配
Internal (reserved=62863KB +51201KB, committed=62863KB +51201KB)
(malloc=62831KB +51201KB #3597 +6)
(mmap: reserved=32KB, committed=32KB)
Symbol (reserved=1441KB, committed=1441KB)
(malloc=953KB #564)
(arena=488KB #1)
Native Memory Tracking (reserved=215KB +18KB, committed=215KB +18KB)
(malloc=111KB +14KB #1565 +197)
(tracking overhead=104KB +3KB)
Arena Chunk (reserved=175KB, committed=175KB)
(malloc=175KB)
// 原生内存 + 堆保留内存 = jvm 进程使用内存
- Java Heap部分表示heap内存目前占用了7372MB;Class部分表示已经加载的classes个数为460,占用了16260KB包含metadata;Thread部分表示目前有26个
线程,占用了25706KB;Code部分表示JIT生成的或者缓存的instructions占用了2572KB;GC部分表示目前已经占用了293573KB的内存空间用于帮助GC;
Internal部分表示命令行解析、JVMTI(JVM Tool Interface由Java虚拟机提供的native编程接口)等占用了62863KB;Symbol部分表示诸如String table及constant pool等symbol占用了1441KB;Native Memory Tracking部分表示该功能自身占用了215KB - DirectByteBuffer分配内存的话,是需要full GC或者手动system.gc来进行回收的(所以最好不要使用-XX:+DisableExplicitGC)。 之前项目是CMS回收器时分析过,-XX:+DisableExplicitGC导致堆外内存很高。这里链接里面是先用上述分析,方法再jstack 抓,才解决: CMS堆外内存泄露案例1。
拓展
- 除堆内内存和方法区,这些地方也要注意
1.Direct Memory:可以通过-XX:MaxDirectMemorySize调整大小,内存不足时抛出OutOfMemoryError或OutOfMemoryError:Direct buffer memory。注意有时候堆外内存太小也会有问题。
2.线程堆栈:可通过-Xss调整大小内存不足时抛出StackoverflowErroe(纵向无法分配,即无法分配新的栈帧)或OutOfMemoryError:unable to create new native thread(横向无法分配,即无法建立新的线程)。
3.Socket缓存区:每个Socket连接都Receive和Send两个缓存区,分别占大约37KB和25KB的内存,连接多的话这块内存占用也比较可观。如果无法分配,则可能会抛出IOException:Too many open files异常。
4.JNI代码:如果代码中使用JNI调用本地库,那么本地库使用内存也不在堆中
5.虚拟机和GC:虚拟机和GC的代码执行也要消耗一定的内存。
参考文章
metaspace引发full gc
代码模拟metaspae溢出
jvm option: -XX:+UseG1GC -Xms1000M -Xmx1000M -XX:+UseG1GC -XX:+PrintGCDetails
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=./ -XX:+PrintGCDateStamps -XX:MaxGCPauseMillis=200m
// 注意这个,如果改成非0比如7000这种数值,就不会有以下说的大量加载卸载恩恩现象
-XX:SoftRefLRUPolicyMSPerMB=0
-XX:MaxMetaspaceSize=10m -XX:+TraceClassLoading -XX:+TraceClassUnloading
public class TestMetaSpaceOverFlow {
static ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(3000, 3000, 30, TimeUnit.SECONDS, new LinkedBlockingDeque<>());
public static void main(String[] args) throws Exception {
Thread.sleep(1000);
System.out.println("+++++++++");
// 模拟日常gc
new Thread(() -> {
while (true) {
System.gc();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.gc();
}
}).start();
// 如果反射用到了软引用则因为并发问题会生成及少量的GeneratedMethodAccessor***, 但是如果
// 不用软引用,则会有可能生成超大量的GeneratedMethodAccessor**对应很多个类加载器DelegatingClassLoader
// 有可能造成方法区内存溢出
for(int i = 0; i < 2000000; i++) {
threadPoolExecutor.execute(() -> {
try {
Class<?> clazz1 = Class.forName("TestOOM");
TestOOM testOOM1 = (TestOOM) clazz1.getDeclaredConstructor().newInstance();
Method method1 = clazz1.getDeclaredMethod("test");
method1.invoke(testOOM1);
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
});
}
Thread.sleep(1000000000);
}
}
高并发场景下重现
用的jdk1.8_201和G1回收器。Metaspace当时调的是256Mb, 邮件收到OOM: Error Metaspace。遇到这个问题后先把Metaspace调整成500Mb,但是还有上升趋势。加了-XX:+TraceClassLoading -XX:+TraceClassUnloading后发现catalina.out文件有很多反射类加载卸载,公司RPC大量用到反射。查看jvm参数发现-XX:SoftRefLRUPolicyMSPerMB=0(不知道谁设置的,得问问) ,这个是罪魁祸首,后面改成-XX:SoftRefLRUPolicyMSPerMB=1000。metaspace就正常了,也没这么多类加载卸载了。
[Loaded sun.reflect.GeneratedMethodAccessor16 from __JVM_DefineClass__]
[UnLoaded sun.reflect.GeneratedMethodAccessor16 from __JVM_DefineClass__]
拓展
反射
- Metaspace内存空间如何泄漏: Metaspace空间泄露,其实就是ClassLoader内存空间泄露。
Metaspace由: klass, 是我们熟知的class文件在jvm里的运行时数据结构; NoKlass Metaspace专门来存klass相关的其他的内容,比如method,constantPool等,这块内存是由多块内存组合起来的,所以可以认为是不连续的内存块组成的; Klass Metaspace和NoKlass Mestaspace都是所有classloader共享的,所以类加载器们要分配内存,但是每个类加载器都有一个SpaceManager,来管理属于这个类加载的内存小块; - 动态代理类生成和反射调用,这些都会导致ClassLoader空间出现泄露。在反射调用重灾区的项目中可能会存在很多的DelegatingClassLoader对象没有及时回收,对应加载的类也没 有及时卸载,当并发量大的时候可能导致Metaspace空间不足从而引发FullGC。
- 反射原理: 反射原理
// 反射用到了软引用则因为并发问题会生成及少量的GeneratedMethodAccessor***, 但是如果
// 不用软引用,则会有可能生成超大量的GeneratedMethodAccessor**对应很多个类加载器DelegatingClassLoader
// 有可能造成方法区内存溢出
Class<?> clazz1 = Class.forName("TestOOM");
TestOOM testOOM1 = (TestOOM) clazz1.getDeclaredConstructor().newInstance();
Method method1 = clazz1.getDeclaredMethod("test");
method1.invoke(testOOM1);
软引用
- 软引用回收时机会:
clock - timestamp > free_heap*ms_per_mb 则被回收
clock: 最后一次GC时间
timestamp: 使用软引用后最后一次GC时间
free: 空间内存/mb
ms_per_mb: -XX:SoftRefLRUPolicyMSPerMB的值
- 测试软引用, 如果测试软引用不过期则改变XX:SoftRefLRUPolicyMSPerMB值即可
jvm option: -Xmx20M -XX:+PrintGCDetails -verbose:gc -XX:SoftRefLRUPolicyMSPerMB=0 -XX:+UseG1GC -XX:MaxMetaspaceSize=10m
private static final int _4MB= 30*512*1024;
public static void main(String[] args) throws IOException, InterruptedException {
soft();
}
public static void soft() throws InterruptedException {
SoftReference<byte[]> reference = new SoftReference<>(new byte[_4MB]);
System.out.println("=======" + reference.get());
// 这次gc更新了timestamp的值
System.gc();
Thread.sleep(3000);
// 这次gc更新了clock的值
System.gc();
Thread.sleep(3000);
// clock - timestamp 的值 > freeSpace * SoftRefLRUPolicyMSPerMB=0, 所以被回收了
// 改变SoftRefLRUPolicyMSPerMB的值就有可能不变回收左边表达式小于右边就不回收
System.out.println("+++++++++" + reference.get());
}
参考文献
- 东方龙马 | 慎用java.lang.ref.SoftReference实现缓存
- JVM Metaspace内存溢出排查与总结
- Java内存之本地内存分析神器: NMT 和 pmap
- 反射和Cglib调用asm 有什么区别?
- 从Java字节码到ASM实践
- JAVA内存漫谈之——JVM结构篇
老年代不足引发fullgc
cpu问题
- top查看资源占用pid
-
top -H -p pid 定位到线程
image.png - jstack <进程号> | grep <tid 16进制(上图中的pid 比如14293的16进制)>
- 结合第三点第二点,查到对应堆栈,找到对应代码
ps arthas也可以用作排查工具
参考文章
磁盘问题
- 这篇文章有部分小节涉及到磁盘问题,目前还没接触过磁盘问题,只能看文章稍微积累点理论知识: JAVA 线上故障排查完整套路,从 CPU、磁盘、内存、网络、GC 一条龙!
网友评论