一、内存泄漏(Memory Leak)
是指程序中已动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。
二、一般内存泄露的方式
- 常发性内存泄漏:发生内存泄漏的代码会被多次执行到,每次被执行时都会导致一块内存泄漏;
- 偶发性内存泄漏:发生内存泄漏的代码只有在某些特定环境或操作过程下才会发生。常发性和偶发性是相对的。对于特定的环境,偶发性的也许就变成了常发性的。所以测试环境和测试方法对检测内存泄漏至关重要;
- 一次性内存泄漏:发生内存泄漏的代码只会被执行一次,或者由于算法上的缺陷,导致总会有一块且仅有一块内存发生泄漏;
- 隐式内存泄漏:程序在运行过程中不停的分配内存,但是直到结束的时候才释放内存。严格的说这里并没有发生内存泄漏,因为最终程序释放了所有申请的内存。但是对于一个服务器程序,需要运行几天,几周甚至几个月,不及时释放内存也可能导致最终耗尽系统的所有内存。所以,我们称这类内存泄漏为隐式内存泄漏。从用户使用程序的角度来看,内存泄漏本身不会产生什么危害,作为一般的用户,根本感觉不到内存泄漏的存在。真正有危害的是内存泄漏的堆积,这会最终耗尽系统所有的内存。从这个角度来说,一次性内存泄漏并没有什么危害,因为它不会堆积,而隐式内存泄漏危害性则非常大,因为较之于常发性和偶发性内存泄漏它更难被检测到;
三、JAVA中的内存泄露
上面所描述的是通常的内存泄露方式,当然也适用于java,但是对于java而言,问题似乎变得简单了,JAVA的一个重要特性就是通过垃圾收集器(GC)自动管理内存的回收,而不需要程序员自己来释放内存。理论上Java中所有不会再被利用的对象所占用的内存,都可以被GC回收,但是Java也存在内存泄露,但它的表现与C或者C++不同而已。这通常都是设计不合理造成的,也因此通过设计是可以避免的。根本问题在于,是否我们需要掌控的对象,在应该销毁的时候没有销毁,或者没有预料到对象的增量超出我们的预想。
- 对象增长超出预想;
- 设计应该销毁的对象,而常驻内存;
对于问题一,举一个常见的设计规约:线程池的创建应该显示指定阻塞队列得到小,避免默认值失去控制,极坏的情况下创建了大量的线程,导致OOM。
问题二,经常出现在设计缓存,存储的map,list中,无限增长,失去控制。
四、常见的容易导致内存泄露的点
- 线程池创建未显示指定阻塞队列大小;
- ThreadLocal 的管理中忘记回收对象;
objectThreadLocal.set(userInfo);
try {
// ...
} finally {
objectThreadLocal.remove();
}
- 所有涉及资源链接的地方,都不要忘记关闭资源;
- 类的成员变量为集合,或者单例的模式中有集合,引用了大量的其他对象;
- java方法,是传值还是传引用,造成的小时间段内,内存没按照预想回收掉;
五、内存溢出(Out Of Memory)
内存溢出就是内存越界。内存越界有一种很常见的情况是调用栈溢出(即stackoverflow),虽然这种情况可以看成是栈内存不足的一种体现。
六、内存溢出跟内存泄露区别
- 内存溢出:申请内存时,JVM没有足够的内存空间;
- 内存泄露:申请了内存,但是没有释放,导致内存空间浪费;
七、JVM内存布局
八、类的生命周期
九、JVM参数
9.1 JVM的参数类型
- 标配参数:-version,-help
- X参数(了解):-Xint,-Xmixed
- XX参数:
- boolean类型:-XX:+PrintGCDetails
- KV设值类型:-XX:MetaspaceSize=128m
9.2 查看内存参数
-
-XX:+PrintFlagsInitial 主要查看初始默认(不依赖java进程)
案例:java -XX:+PrintFlagsInitial -
-XX:+PrintFlagsFinal 主要查看修改更新(不依赖java进程)
案例:java -XX:+PrintFlagsFinal -
-XX:+PrintCommandLineFlags 打印命令行参数
-
jinfo 查看进程相关数据
案例: jinfo -flag MetaspaceSize pid
9.3 问题:
- -Xms:初始大小内存,默认物理内存1/64,等价于-XX:InitialHeapSize;
- -Xmx:最大分配内存,默认为物理内存1/4,等价于-XX:MaxHeapSize;
- -Xss:设置单个线程栈的大小,一般默认为512k~1024k,等价于-XX:ThreadStackSize;
参考文章:
【JVM参数调优】
官方文档
十、常用配置
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=heapdump.txt
-XX:+PrintGCDetails
-Xloggc:gc.log
十一、模拟内存溢出
见代码;
十二、什么条件触发GC-CMS为例
- eden区满了,会不会触发?
- 老年代满了会不会触发?
- 直接缓冲区满了会不会触发?
- 元空间满了,会不会触发?
十三、工具
13.1 jvm gc日志分析工具
https://gceasy.io/ft-index.jsp
13.2 内存快照分析工具
mat,jprofile,VisualVM
13.3 java自带
-
-jps 进程查看
-
-jstat:用于监视虚拟机各种运行状态信息的命令行工具。可以显示本地或者远程虚拟机进程中的类记载、内存、垃圾收集、JIT编译等运行数据:
jstat -gc pid #垃圾回收统计
jstat -gccapacity pid #堆内存统计
jstat -gcnew pid #新生代垃圾回收统计
jstat -gcnewcapacity pid #新生代内存统计
jstat -gcold pid #老年代垃圾回收统计
jstat -gcoldcapacity pid #老年代内存统计
jstat -gcutil pid #总结垃圾回收统计
jstat -printcompilation pid #JVM编译方法统计
jstat -class pid #类加载统计
-
-jinfo 参数配置查看
-
-jmap 内存监控:
jmap -clstats pid #打印进程的类加载器和类加载器加载的持久代对象信息
jmap -heap pid #查看进程堆内存使用情况,包括使用的GC算法、堆配置参数和各代中堆内存使用情况。
jmap -histo[:live] pid #查看堆内存中的对象数目、大小统计直方图,如果带上live则只统计活对象
jmap -dump:format=b,file=dumpFileName pid #jmap把进程内存使用情况dump到文件中
- -jstatck线程监控
十四、思考
14.1 eden space+ from space > to space可能么? 会发生什么GC?
个人答案:可能,
14.2 Integer为什么用equals判断相等?
对应用类型,比如Integer、Long和String,进行判等,需要使用equals进行内容判等。因为引用类型的直接值是指针,使用==的话,比较的是指针,也就是两个对象在内存中的地址,即比较它们是不是同一个对象,而不是比较对象的内容。
Integer会自动拆箱:
默认情况下会缓存[-128,127]的数值,在这个值只能用==,会返回true,否则返回false。
14.3 jvm这些区,哪些是线程共享的,哪些是能抛出oom的?
JDK7 以前的运行时数据区分布图如下:
JDK8 以后的运行时数据区分布图如下:
内存溢出通俗的讲就是内存不够用了,并且 GC
通过垃圾回收也无法提供更多的内存。实际上除了程序计数器,其他区域都有可能发生 OOM, 简单总结如下:
- 堆内存不足是最常见的 OOM 原因之一,抛出错误信息 java.lang.OutOfMemoryError:Java heapspace,原因也不尽相同,可能是内存泄漏,也有可能是堆的大小设置不合理。
- 对于虚拟机栈和本地方法栈,导致 OOM 一般为对方法自身不断的递归调用,且没有结束点,导致不断的压栈操作。类似这种情况,JVM实际会抛出 StackOverFlowError , 但是如果 JVM 试图去拓展栈空间的时候,就会抛出 OOM。
- 对于老版的 JDK, 因为永久代大小是有限的,并且 JVM 对老年代的内存回收非常不积极,所以当我们添加新的对象,老年代发生 OOM的情况也非常常见。
- 随着元数据区的引入,方法区内存已经不再那么窘迫,所以相应的 OOM 有所改观,出现OOM,异常信息则变成了:“java.lang.OutOfMemoryError: Metaspace”。
参考:
JVM区域划分以及那些区域可能产生OOM(out of memory)
14.4 垃圾回收会回收哪些区域?
——方法区、堆。
参考:
【JVM】万字详解垃圾回收机制(面试常问)
14.5 oom之后java进程还在运行吗?
这段代码抛出异常了,其他线程还会运行吗?
——会运行;
——这里的栈溢出,只是一个栈帧装不下;
14.6 mysql发生死锁后,会影响其他连接吗?
——不会影响其他连接。
14.7 字符串常量池存在哪?
——堆
在JDK6.0及之前版本,字符串常量池是放在Perm Gen区(也就是方法区)中;
在JDK7.0版本,字符串常量池被移到了堆中了。至于为什么移到堆内,大概是由于方法区的内存空间太小了。
14.8 堆内存溢出会影响其他线程吗?
——不一定;
——堆内存溢出影响创建新对象的存储,但是如果有线程不用创建新对象,则不影响;