• 内存泄漏: 分配出去的内存没有被回收回来,失去对内存区域的控制,造成资源的浪费,比如:new出来了对象并没有引用,垃圾回收器不会回收他,造成内存泄漏
  • 内存溢出: 程序所需要的内存超出了系统所能分配的内存。
  • 2 分析内存溢出可能出现的地方

    从 Java代码的运行过程来看,有三个区域会发生 OOM,它们分别是:Metaspace、Java 虚拟机栈、堆内存。

    Java栈

    虚拟机栈,每执行一个方法都会有一个栈帧入栈,栈帧中包含参数、局部变量、返回值地址等信息。如果代码层次太深,不断有方法入栈却没有出栈,Java虚拟机栈就会OOM。

  • 虚拟机中的栈内存也是有限的,我们调用方法的时候会创建一个栈帧,紧接着方法入栈。如果一个线程一直调用方法入栈,栈内存终归是要满的,此时线程的栈中就会发生 OOM。
  • 发生这种情况一般就是代码除了问题,比如写了个递归调用,和 Metaspace 的内存溢出一样,也很少发生。
  • 如果在单线程的情况下,无论是栈帧太大还是虚拟机栈容量太小,当内存无法再分配的时候,虚拟机抛出的是StackOverflowError异常。
  • 如果在多线程下,不断地建立线程可能会产生OutOfMemoryError异常。
  • Metaspace

    保存类的基本信息,如果加载太多类就会 OOM

    永久代的垃圾收集主要回收两部分内容:废弃常量和无用的类。

    回收废弃常量与回收 Java 堆中的对象非常类似。以常量池中字面量的回收为例,假如一个字符串"abc"已经进入了常量池中,但是当前系统没有任何一个 String 对象是叫做"abc"的,也没有其他地方引用了这个字面量,如果这时发生内存回收,而且必要的话,这个"abc"常量就会被系统清理出常量池。常量池中的其他类(接口)、方法、字段的符号引用也与此类似。

    注意:在大量使用反射、动态代理、CGLib 等 ByteCode 框架、动态生成 JSP 以及 OSGi这类频繁自定义 ClassLoader 的场景都需要虚拟机具备类卸载的功能,以保证永久代不会溢出。

    堆中创建的对象过多就会触发GC,GC 的速度赶不上新建对象的速度也会发生 OOM。

  • 高并发场景下,请求量太大,创建了大量新的对象,且这些都是有用的、存活的。堆中无法放入更多对象就会导致堆内存溢出
  • 内存泄漏问题,长生命周期的对象引用了大量短生命周期的对象,没有及时取消对它们的引用,导致 GC 无法回收这些理应被回收的对象,就导致了堆内存溢出
  • Java堆中只会产生OutOfMemoryError异常。
  • 注意:类需要同时满足下面 3 个条件才能算是“无用的类”

  • 该类所有的实例都已经被回收,也就是 Java 堆中不存在该类的任何实例。
  • 加载该类的 ClassLoader 已经被回收。
  • 该类对应的 java.lang.Class 对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。
  • 虚拟机可以对满足上述3 个条件的无用类进行回收,这里说的仅仅是“可以”,而并不是和对象一样,不使用了就必然会回收。

    注意:方法区溢出方法区中只会产生OutOfMemoryError异常。

    2 分析内存泄漏的原因

    长生命周期对象持有短生命周期对象的引用可能会引起内存泄漏

    1、静态集合类:容器使用时引起的内存泄漏

    HashMap、Vector等很容易出现内存泄漏,

    集合被定义成静态的时候,由于它们的生命周期跟应用程序一样长

    他们引用的所有对象Object不能被释放,如果将Object对象置为null,也还是会被Vector引用,可以将Vector对象置为null,切出对静态集合类的引用。

    Vector vector = new Vector();
    for (int i = 1; i<100; i++) {
    	 Object object = new Object();
    	 vector.add(object);
    	 object = null;
    //这样会造成内存泄漏
    //...对vector的操作
    //...与vector无关的其他操作这样会造成短暂的内存泄漏,method方法结束后被回收,
    //...对vector的操作
    vector = null;
    //...与vector无关的其他操作
    

    2、各种连接时:未正确使用close()方法导致的内存泄漏

    各种IO或者数据库连接时,最后都需要close()释放对象,这样也是长对象引用短对象,造成的内存泄漏。

    SessionFactory factory = new SessionFactory();
    try {Session session = factory.connect();
    } finally{
    	 session.close();
    

    这里必须用close关闭连接,因为SessionFactory是长对象,session是短对象。

    3、外部模块的引用

    调用外部模块的时候,也应该注意防止内存泄漏。如模块A调用了外部模块B的一个方法,如:public void register(Object o)。这个方法有可能就使得A模块持有传入对象的引用,这时候需要查看B模块是否提供了去除引用的方法,如unregister()

    4、单例模式

    使用单例模式的时候也有可能导致内存泄漏。因为单例对象初始化后将在JVM的整个生命周期内存在,如果它持有一个外部对象(生命周期比较短)的引用,那么这个外部对象就不能被回收,而导致内存泄漏。如果这个外部对象还持有其它对象的引用,那么内存泄漏会更严重

    public class AppManager {
    	 private static AppManager instance;
    	 private Context context;
    	 private AppManager(Context context) {
    	 	this.context = context;
    	 public static AppManager getInstance(Context context) {
    		 if (instance != null) {
    			 instance = new AppManager(context);
    		 return instance;
     

    这是一个普通的单例模式,当创建这个单例的时候,由于需要传入一个Context,所以这个Context的生命周期的长短至关重要:

  • 如果此时传入的是 Application 的 Context,因为 Application 的生命周期就是整个应用的生命周期,所以这将没有任何问题。

  • 如果此时传入的是 Activity 的 Context,当这个 Context 所对应的 Activity 退出时,由于该 Context 的引用被单例对象所持有,其生命周期等于整个应用程序的生命周期,所以当前 Activity 退出时它的内存并不会被回收,这就造成泄漏了。

  • 正确的方式应该改为下面这种方式:

    public class AppManager {
    	 private static AppManager instance;
    	 private Context context; 
    	 private AppManager(Context context) { 
    			 // 使用Application 的context 
    			 this.context = context.getApplicationContext();
    	 public static AppManager getInstance(Context context) { 
    		 if (instance != null) { 
    		 		instance = new AppManager(context); 
    		 return instance; 
    

    或者这样写,连 Context 都不用传进来了: 在你的 Application 中添加一个静态方法,getContext() 返回 Application 的 context,

    context = getApplicationContext(); //获取全局的context - - >return返回全局context对象 public static Context getContext(){ return context; public classAppManager{ private static AppManager instance; private Context context; private AppManager() { // 使用Application 的context this.context = MyApplication.getContext(); public static AppManager getInstance() { if (instance != null) { instance = new AppManager(); return instance;

    如何解决以及监控JVM内存、CPU的情况,请关注下篇文章。。。

  • 内存泄漏: 分配出去的内存没有被回收回来,失去对内存区域的控制,造成资源的浪费,比如:new出来了对象并没有引用,垃圾回收器不会回收他,造成内存泄漏
  • 内存溢出: 程序所需要的内存超出了系统所能分配的内存。
  • 2 分析内存溢出可能出现的地方

    从 Java代码的运行过程来看,有三个区域会发生 OOM,它们分别是:Metaspace、Java 虚拟机栈、堆内存。

    Java栈

    虚拟机栈,每执行一个方法都会有一个栈帧入栈,栈帧中包含参数、局部变量、返回值地址等信息。如果代码层次太深,不断有方法入栈却没有出栈,Java虚拟机栈就会OOM。

  • 虚拟机中的栈内存也是有限的,我们调用方法的时候会创建一个栈帧,紧接着方法入栈。如果一个线程一直调用方法入栈,栈内存终归是要满的,此时线程的栈中就会发生 OOM。
  • 发生这种情况一般就是代码除了问题,比如写了个递归调用,和 Metaspace 的内存溢出一样,也很少发生。
  • 如果在单线程的情况下,无论是栈帧太大还是虚拟机栈容量太小,当内存无法再分配的时候,虚拟机抛出的是StackOverflowError异常。
  • 如果在多线程下,不断地建立线程可能会产生OutOfMemoryError异常。
  • Metaspace

    保存类的基本信息,如果加载太多类就会 OOM

    永久代的垃圾收集主要回收两部分内容:废弃常量和无用的类。

    回收废弃常量与回收 Java 堆中的对象非常类似。以常量池中字面量的回收为例,假如一个字符串"abc"已经进入了常量池中,但是当前系统没有任何一个 String 对象是叫做"abc"的,也没有其他地方引用了这个字面量,如果这时发生内存回收,而且必要的话,这个"abc"常量就会被系统清理出常量池。常量池中的其他类(接口)、方法、字段的符号引用也与此类似。

    注意:在大量使用反射、动态代理、CGLib 等 ByteCode 框架、动态生成 JSP 以及 OSGi这类频繁自定义 ClassLoader 的场景都需要虚拟机具备类卸载的功能,以保证永久代不会溢出。

    堆中创建的对象过多就会触发GC,GC 的速度赶不上新建对象的速度也会发生 OOM。

  • 高并发场景下,请求量太大,创建了大量新的对象,且这些都是有用的、存活的。堆中无法放入更多对象就会导致堆内存溢出
  • 内存泄漏问题,长生命周期的对象引用了大量短生命周期的对象,没有及时取消对它们的引用,导致 GC 无法回收这些理应被回收的对象,就导致了堆内存溢出
  • Java堆中只会产生OutOfMemoryError异常。
  • 注意:类需要同时满足下面 3 个条件才能算是“无用的类”

  • 该类所有的实例都已经被回收,也就是 Java 堆中不存在该类的任何实例。
  • 加载该类的 ClassLoader 已经被回收。
  • 该类对应的 java.lang.Class 对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。
  • 虚拟机可以对满足上述3 个条件的无用类进行回收,这里说的仅仅是“可以”,而并不是和对象一样,不使用了就必然会回收。

    注意:方法区溢出方法区中只会产生OutOfMemoryError异常。

    2 分析内存泄漏的原因

    长生命周期对象持有短生命周期对象的引用可能会引起内存泄漏

    1、静态集合类:容器使用时引起的内存泄漏

    HashMap、Vector等很容易出现内存泄漏,

    集合被定义成静态的时候,由于它们的生命周期跟应用程序一样长

    他们引用的所有对象Object不能被释放,如果将Object对象置为null,也还是会被Vector引用,可以将Vector对象置为null,切出对静态集合类的引用。

    Vector vector = new Vector();
    for (int i = 1; i<100; i++) {
    	 Object object = new Object();
    	 vector.add(object);
    	 object = null;
    //这样会造成内存泄漏
    //...对vector的操作
    //...与vector无关的其他操作这样会造成短暂的内存泄漏,method方法结束后被回收,
    //...对vector的操作
    vector = null;
    //...与vector无关的其他操作
    

    2、各种连接时:未正确使用close()方法导致的内存泄漏

    各种IO或者数据库连接时,最后都需要close()释放对象,这样也是长对象引用短对象,造成的内存泄漏。

    SessionFactory factory = new SessionFactory();
    try {Session session = factory.connect();
    } finally{
    	 session.close();
    

    这里必须用close关闭连接,因为SessionFactory是长对象,session是短对象。

    3、外部模块的引用

    调用外部模块的时候,也应该注意防止内存泄漏。如模块A调用了外部模块B的一个方法,如:public void register(Object o)。这个方法有可能就使得A模块持有传入对象的引用,这时候需要查看B模块是否提供了去除引用的方法,如unregister()

    4、单例模式

    使用单例模式的时候也有可能导致内存泄漏。因为单例对象初始化后将在JVM的整个生命周期内存在,如果它持有一个外部对象(生命周期比较短)的引用,那么这个外部对象就不能被回收,而导致内存泄漏。如果这个外部对象还持有其它对象的引用,那么内存泄漏会更严重

    public class AppManager {
    	 private static AppManager instance;
    	 private Context context;
    	 private AppManager(Context context) {
    	 	this.context = context;
    	 public static AppManager getInstance(Context context) {
    		 if (instance != null) {
    			 instance = new AppManager(context);
    		 return instance;
     

    这是一个普通的单例模式,当创建这个单例的时候,由于需要传入一个Context,所以这个Context的生命周期的长短至关重要:

  • 如果此时传入的是 Application 的 Context,因为 Application 的生命周期就是整个应用的生命周期,所以这将没有任何问题。

  • 如果此时传入的是 Activity 的 Context,当这个 Context 所对应的 Activity 退出时,由于该 Context 的引用被单例对象所持有,其生命周期等于整个应用程序的生命周期,所以当前 Activity 退出时它的内存并不会被回收,这就造成泄漏了。

  • 正确的方式应该改为下面这种方式:

    public class AppManager {
    	 private static AppManager instance;
    	 private Context context; 
    	 private AppManager(Context context) { 
    			 // 使用Application 的context 
    			 this.context = context.getApplicationContext();
    	 public static AppManager getInstance(Context context) { 
    		 if (instance != null) { 
    		 		instance = new AppManager(context); 
    		 return instance; 
    

    或者这样写,连 Context 都不用传进来了: 在你的 Application 中添加一个静态方法,getContext() 返回 Application 的 context,

    context = getApplicationContext(); //获取全局的context - - >return返回全局context对象 public static Context getContext(){ return context; public classAppManager{ private static AppManager instance; private Context context; private AppManager() { // 使用Application 的context this.context = MyApplication.getContext(); public static AppManager getInstance() { if (instance != null) { instance = new AppManager(); return instance;

    如何解决以及监控JVM内存、CPU的情况,请关注下篇文章。。。

  • 本文作者:Java技术债务
  • 原文链接: https://cuizb.top/myblog/article/1642654909
  • 版权声明: 本博客所有文章除特别声明外,均采用 CC BY 3.0 CN协议进行许可。转载请署名作者且注明文章出处。
  • MySQL数据库被攻击,被删库勒索,逼迫我使出洪荒之力进行恢复数据2022-05-06 Java技术债务:@capture 一起探讨学习,服务器被黑很正常,及时做好备份以及做好防护
  • MySQL数据库被攻击,被删库勒索,逼迫我使出洪荒之力进行恢复数据2022-04-13 capture:我的刚上线两天,网站里就两篇文章也被攻击了,纳闷
  • Java常用集合List、Map、Set介绍以及一些面试问题2022-01-18 Java技术债务:HashSet和TreeSet 相同点:数据不能重复 1、底层存储结构不同; HashSet底层使用HashMap哈希表存储 TreeSet底层使用TreeMap树结构存储 2、唯一性方式不同 HashSet底层使用hashcode()和equal()方法判断 TreeSet底层使用Comparable接口的compareTo判断的 3、HashSet无序,TreeSet有序
  • undefined2021-12-14 Java技术债务:如果不指定线程池,CompletableFuture会默认使用ForkJoin线程池,如果同一时间出现大量请求的话,会出现线程等待问题,建议使用自定义线程池。。。
  • undefined2021-12-02 you:很好,对于小白相当不错了,谢谢
  •