🦊 Java
1、Java基础
1.1 StringBuffer和StringBuilder的区别
1.2 ConcurrentHashMap和TreeMap实现原理
1.3 ArrayList和LinkedList实现原理
1.4 HashSet和TreeSet实现原理
1.5 深拷贝与浅拷贝
1.6 抽象类与接口
2、Java多线程
2.1 并发编程的三大特性
2.2 指令重排
2.3 Volatile原理
2.4 CAS原理
2.5 Java的4种引用级别
2.6 Java中的锁
2.7 Synchronized实现原理
2.8 线程池实现原理
2.9 AQS
2.10 创建线程的方式
2.11 ThreadLocal原理
3、JVM
3.1 判断对象是否存活的方法
3.2 JVM内存结构
3.3 常见的垃圾收集算法有哪些
3.4 指针碰撞和空闲列表
3.5 常见的垃圾收集器有哪些
3.6 内存溢出与内存泄漏的区别
3.7 常用的JVM启动参数有哪些
3.8 反射机制
4、NIO
4.1 概述
5、Spring
5.1 Spring IOC
5.2 Spring AOP
6、SpringBoot
6.1 SpringBoot、SpringCloud的联系与区别
-
+
游客
注册
登录
内存溢出与内存泄漏的区别
## 1 基本概念 ### 1.1 内存溢出 **内存溢出**(Out Of Memory)是指**程序在申请内存时,没有足够的内存空间供其使用**。 ### 1.2 内存泄漏 **内存泄漏**(Memory Leak)是指**不再使用的对象持续占有内存,使得这部分内存得不到及时释放,从而造成内存空间浪费**。 ## 2 二者关系 1. 如果存在**严重的内存泄漏问题**,随着时间的推移,则**必然会引起内存溢出**。 2. 内存泄漏一般是**资源管理问题**或**程序 Bug**,内存溢出则是**内存空间不足**和**内存泄漏**的最终结果。 ## 3 内存溢出的常见情况 ### 3.1 持久代溢出 1. 因为运行常量池在方法区,而方法区在持久代中,因此出现**持久代溢出**的原因可能是**运行时常量池溢出**。 2. 也可能是**程序中使用了大量的 jar 或 class**,使得**方法区中保存的 class 对象没有被及时回收**或**class 信息使用的内存超过了配置的大小**。 ### 3.2 堆溢出 1. 发生这种溢出的原因一般是**创建的对象太多**,在进行垃圾回收之前**对象数量达到了最大堆的容量限制**。 2. 解决这个区域异常的方法是**通过内存映像分析工具对 `Dump` 出来的转储快照进行分析,看到底是内存溢出还是内存泄漏**: 1. 如果是**内存泄漏**,可以通过工具**查看泄漏对象到 GC Roots 的应用链,定位泄漏代码的位置,修改程序或算法**。 2. 如果**不是内存泄漏**,说明**内存中的对象确实都还必须存活,那就应该检查虚拟机的堆参数 `-Xmx`(最大堆大小)和 `-Xms`(初始堆大小),与机器物理内存对比看是否可以调大。** ### 3.3 虚拟机栈和本地方法栈溢出 1. **[如果线程]()请求的栈深度大于虚拟机所允许的最大深度,将抛出 `StackOverFlowError`。** 2. **如果虚拟机在扩展栈时无法申请到足够的内存空间,则抛出 `OutOfMemoryError`。** ## 4 内存泄漏 ### 4.1 根本原因 **内存泄漏的根本原因是长生命周期的对象持有短生命周期对象的引用,尽管短生命周期的对象已经不再需要,但由于长生命周期对象持有它的引用而导致不能被回收。** ### 4.2 分类 以发生的方式来分类,内存泄漏可以分为 4 类: 1. **常发性内存泄漏:** 发生内存泄漏的代码**会被多次执行到**,每次被执行的时候都会导致一块内存泄漏。 2. **偶发性内存泄漏:** 发生内存泄漏的代码只有在**某些特定环境或操作过程**下才会发生。常发性和偶发性是相对的,对于特定的环境,偶发性的也许就变成了常发性的,所以测试环境和测试方法对检测内存泄漏至关重要。 3. **一次性内存泄漏:** 发生内存泄漏的代码**只会被执行一次**,或者由于**算法上的缺陷**,导致**总会有一块且仅有一块内存发生泄漏**,比如,**在类的构造函数中分配内存,在析构函数中却没有释放该内存**,所以内存泄漏只会发生一次。 ### 4.3 常见情况 #### 4.3.1 静态集合类引起的内存泄漏 像 `HashMap`、`Vector` 等的使用最容易出现内存泄漏,这些静态变量的生命周期和应用程序一致,他们所引用的所有对象也不能被释放,从而造成内存泄漏,因为他们也将一直被 `Vecotr` 等引用着。 ```java Vector<Object> v=new Vector<Object>(100); for (int i = 1; i<100; i++) { Object o = new Object(); v.add(o); o = null; } ``` 在这个例子中,循环申请 `Object` 对象,并将所申请的对象放入一个 `Vector` 中,如果仅仅释放引用本身(`o=null`),那么 `Vector` 仍然引用该对象,所以这个对象对 `GC` 来说是不可回收的。因此,如果对象加入到 `Vector` 后,还必须从 `Vector` 删除,最简单的方法就是将 `Vector` 对象设置为 `null`。 #### 4.3.2 修改 HashSet 中对象的参数值,且参数是计算哈希值的字段 当一个对象被存储到 `HashSet` 集合中以后,修改了这个对象中那些参与计算哈希值的字段后,这个对象的哈希值与最初存储在集合中的就不同了,这种情况下,用 `contains` 方法在集合中检索对象是找不到的,这将会导致无法从 `HashSet` 中删除当前对象,造成内存泄漏。 ```java public static void main(String[] args){ Set<Person> set = new HashSet<Person>(); Person p1 = new Person("张三","1",25); Person p2 = new Person("李四","2",26); Person p3 = new Person("王五","3",27); set.add(p1); set.add(p2); set.add(p3); System.out.println("总共有:"+set.size()+" 个元素!"); //结果:总共有:3 个元素! p3.setAge(2); //修改 p3 的年龄,此时 p3 元素对应的 hashcode 值发生改变 set.remove(p3); //此时 remove 不掉,造成内存泄漏 set.add(p3); //重新添加,可以添加成功 System.out.println("总共有:"+set.size()+" 个元素!"); //结果:总共有:4 个元素! for (Person person : set){ System.out.println(person); } } ``` #### 4.3.3 监听器 在 `Java` 编程中,通常一个应用中会用到很多监听器,我们会调用诸如 `addXXXListener()` 等方法来增加监听器,但往往释放对象的时候却没有记住去删除这些监听器,从而增加了内存泄漏的机会。 #### 4.3.4 各种连接 1. 比如**数据库连接**、**网络连接**和**IO 连接**,除非其**显示的调用了 `close` 方法将其连接关闭**,否则是不会自动被 GC 回收的。 2. 对于 `ResultSet` 和 `Statement` 对象来说,可以不进行显示回收,但 `Connection` 一定要显示回收,因为 `Connection` 在任何时候都无法自动回收,而 `Connection` 一旦回收,`ResultSet` 和 `Statement` 对象就会立即为 `NULL`。 3. 如果要使用**连接池**,除了要**显示关闭连接**,还必须显示关闭 `ResultSet` 和 `Statement` 对象(关闭其中一个,另外一个也会关闭),否则就会造成大量的 `Statement` 对象无法释放,从而引起内存泄漏,这种情况一般都会在 `try` 里面去连接,在 `finally` 里面释放连接。 #### 4.3.5 单例模式 1. 不正确使用单例模式是引起内存泄漏的一个常见问题,单例对象在被初始化后将在 `JVM` 的整个生命周期存在(以静态变量的方式),如果**单例对象持有外部对象的引用**,那么**这个外部对象将不能被 `JVM` 正常回收,导致内存泄漏**。 ```java class A{ public A(){ B.getInstance().setA(this); } .... } //B 类采用单例模式 class B{ private A a; private static B instance=new B(); public B(){} public static B getInstance(){ return instance; } public void setA(A a){ this.a=a; } //getter... } ``` `B` 采用 `singleton` 模式,他持有一个 `A` 对象的引用,因此这个 `A` 类的对象将不能被回收。 ### 4.4 建议 1. **尽早释放无用对象的引用。** 2. **避免在循环中创建对象。** 3. 使用字符串处理时避免使用 `String`,应使用 `StringBuffer`。 4. 尽量**少使用静态变量**,因为**静态变量存放在永久代,基本不参与垃圾回收**。 ## 5 参考文献 1. [内存溢出与内存泄漏](https://www.cnblogs.com/xiaoxi/p/7354857.html)。 2. [内存泄漏和内存溢出的关系和一般解决问题思路](https://www.jianshu.com/p/7dfbf3bf57b3)。
ricear
2022年7月11日 22:04
©
BY-NC-ND(4.0)
转发文档
收藏文档
上一篇
下一篇
手机扫码
复制链接
手机扫一扫转发分享
复制链接
Markdown文件
分享
链接
类型
密码
更新密码