警惕内存泄漏与溢出!你的代码是否隐藏着致命危机?

📕我是廖志伟,一名Java开发工程师、Java领域优质创作者、优快云博客专家、51CTO专家博主、阿里云专家博主、清华大学出版社签约作者、产品软文创造者、技术文章评审老师、问卷调查设计师、个人社区创始人、开源项目贡献者。🌎跑过十五公里、徒步爬过衡山、🔥有过三个月减肥20斤的经历、是个喜欢躺平的狠人。

📘拥有多年一线研发和团队管理经验,研究过主流框架的底层源码(Spring、SpringBoot、Spring MVC、SpringCould、Mybatis、Dubbo、Zookeeper),消息中间件底层架构原理(RabbitMQ、RockerMQ、Kafka)、Redis缓存、MySQL关系型数据库、 ElasticSearch全文搜索、MongoDB非关系型数据库、Apache ShardingSphere分库分表读写分离、设计模式、领域驱动DDD、Kubernetes容器编排等。🎥有从0到1的高并发项目经验,利用弹性伸缩、负载均衡、报警任务、自启动脚本,最高压测过200台机器,有着丰富的项目调优经验。

📙经过多年在优快云创作上千篇文章的经验积累,我已经拥有了不错的写作技巧。同时,我还与清华大学出版社签下了四本书籍的合约,并将陆续在明年出版。这些书籍包括了基础篇、进阶篇、架构篇的📌《Java项目实战—深入理解大型互联网企业通用技术》📌,以及📚《解密程序员的思维密码–沟通、演讲、思考的实践》📚。具体出版计划会根据实际情况进行调整,希望各位读者朋友能够多多支持!

以梦为马,不负韶华

希望各位读者大大多多支持用心写文章的博主,现在时代变了,信息爆炸,酒香也怕巷子深,博主真的需要大家的帮助才能在这片海洋中继续发光发热,所以,赶紧动动你的小手,点波关注❤️,点波赞👍,点波收藏⭐,甚至点波评论✍️,都是对博主最好的支持和鼓励!

💡在这个美好的时刻,本人不再啰嗦废话,现在毫不拖延地进入文章所要讨论的主题。接下来,我将为大家呈现正文内容。

优快云
警惕内存泄漏与溢出!你的代码是否隐藏着致命危机?

🍊 内存泄漏的原因

🎉 1. 对象未及时关闭

一些对象会打开一些外部资源,例如数据库连接、文件句柄等,如果在使用完这些资源后,没有关闭这些对象,那么这些对象就会一直存在于内存中,导致内存泄漏。解决这个问题的方法是在使用完这些对象后,要记得及时关闭它们。

例如:

// 定义一个公共方法readData,无返回值,无参数
public void readData() {
  // 声明一个BufferedReader类型变量reader,并初始化为null
  BufferedReader reader = null;
  try {
    // 用FileReader对象读取data.txt文件,并用BufferedReader对象包装它
    reader = new BufferedReader(new FileReader("data.txt"));
    // 声明一个String类型变量line
    String line;
    // 当读取到的行不是null时,进入循环
    while ((line = reader.readLine()) != null) {
      // 在此处对读取到的每一行进行操作
    }
  }
  // 捕捉异常IOException,并打印异常信息
  catch (IOException e) {
    e.printStackTrace();
  }
  // 此处应该关闭资源,但是被忘记了
}

在上面的代码中,如果在读取完数据后,没有关闭 reader 对象,那么它就会一直存在于内存中,导致内存泄漏。需要改为:

/**
 * 读取数据的方法
 */
public void readData() {
  // 创建一个字符缓冲读取器并初始化为 null
  BufferedReader reader = null;
  try {
    // 以文件名为参数创建一个 FileReader 对象,并将其传递给 BufferedReader 构造函数,创建一个字符缓冲读取器
    reader = new BufferedReader(new FileReader("data.txt"));
    String line;
    // 读取每一行数据,直到读取到文件末尾
    while ((line = reader.readLine()) != null) {
      // 对每一行数据进行处理
    }
  }
  catch (IOException e) {
    // 如果出现异常,则打印堆栈跟踪信息
    e.printStackTrace();
  }
  finally {
    // 判断字符缓冲读取器是否为空,若不为空则执行关闭操作
    if (reader != null) {
      try {
        reader.close();
      }
      catch (IOException e) {
        // 如果关闭操作出错,则打印堆栈跟踪信息
        e.printStackTrace();
      }
    }
  }
}

🎉 2. 静态集合类导致的内存泄漏

在 Java 中,静态变量的生命周期和应用程序是一样长的,如果一个静态变量引用了一个对象,那么这个对象就会一直驻留在内存中,即使没有任何引用该静态变量的对象存在。另外,如果一个静态集合类被添加了很多对象,但是这些对象却没有被清理,那么也会导致内存泄漏。

例如:

// 定义 MySingleton 单例类
public class MySingleton {
  // 私有静态对象列表 objects,用于存放添加的对象
  private static final List<Object> objects = new ArrayList<>();

  // 构造函数私有化,防止其他类直接创建 MySingleton 实例
  private MySingleton() {}

  // 静态方法 addObject,用于往 objects 中添加对象
  public static void addObject(Object obj) {
    objects.add(obj); // 添加对象到 objects 列表中
  }
}

在上面的代码中,如果 objects 列表中添加了很多对象,但是却没有及时清理,那么这些对象就会一直驻留在内存中。

🎉 3. 匿名内部类导致的内存泄漏

在 Java 中,匿名内部类是一种定义在另一个类中的类,它没有名称,只有一个实例。如果在匿名内部类中引用了外部类实例,而这个外部类实例又持有了匿名内部类的对象引用,那么就会导致内存泄漏。

例如:

// 定义 MyListener 类
public class MyListener {
  // 注册监听器的方法
  public void registerListener() {
    // 创建一个 MyButton 的实例
    MyButton button = new MyButton();
    // 给 button 添加一个 ActionListener 匿名类的实例作为监听器
    button.addActionListener(new ActionListener() {
      // 实现 actionPerformed 方法
      public void actionPerformed(ActionEvent e) {
        // 事件处理
      }
    });
  }
}

在上面的代码中,如果 MyListener 类被销毁时,MyButton 对象被释放,但是匿名内部类对象却被引用了,那么就会导致内存泄漏。应该改为:

// 定义一个公共类 MyListener
public class MyListener {
  // 定义一个公共方法 registerListener
  public void registerListener() {
    // 新建一个 MyButton 对象
    MyButton button = new MyButton();
    // 添加一个 ActionListener 匿名内部类
    button.addActionListener(new ActionListener() {
      // 实现 actionPerformed 方法
      public void actionPerformed(ActionEvent e) {
        // 处理事件
      }
    });

    // 其他操作
  }
}

这样,匿名内部类对象的引用也会在 MyButton 对象被释放时一起被释放。

🍊 内存溢出的原因

🎉 1. 对象太多占用过多内存

当一个应用程序需要大量对象时,如果这些对象没有被及时回收,就会导致内存溢出。这通常是因为没有正确地管理对象的生命周期,或者没有释放不再需要的对象。

例如:

// 定义了一个名为MyList的类
public class MyList {
  // 定义了一个名为list的私有List成员,并初始化为一个空的ArrayList
  private List<Object> list = new ArrayList<>();

  // 定义了一个公有的add方法,该方法将传入的参数obj添加到list中
  public void add(Object obj) {
    list.add(obj);
  }
}

在上面的代码中,如果 MyList 类不断地向 list 中添加对象,但是没有及时清理不再需要的对象,就会导致内存溢出。

🎉 2. 内存泄漏导致的内存溢出

内存泄漏同样也会导致内存溢出,就是因为一些对象被无效引用而一直存在于内存中,导致占用过多内存。

例如:

// 定义一个单例模式类
public class MySingleton {
  // 声明一个静态单例对象,初始值为 null
  private static MySingleton instance;
  // 声明一个列表对象,存储对象
  private List<Object> objects = new ArrayList<>();
  // 私有化构造方法,防止外部直接创建对象
  private MySingleton() {}
  // 声明一个公有静态同步方法,返回单例实例
  public static synchronized MySingleton getInstance() {
    // 如果实例对象为 null,创建一个新的实例对象
    if (instance == null) {
      instance = new MySingleton();
    }
    // 返回单例实例对象
    return instance;
  }
  // 声明一个公有方法,用于添加对象到列表中
  public void addObject(Object obj) {
    objects.add(obj);
  }
}

在上面的代码中,MySingleton 类是一个单例类,每次调用 addObject 方法都会向 objects 中添加对象,但是这些对象却没有被及时清理,就会导致内存泄漏,并最终导致内存溢出。

🍊 如何避免内存泄漏和内存溢出

🎉 1. 及时关闭对象

在使用完对象之后,要记得及时关闭它们,例如关闭数据库连接、文件句柄等。

🎉 2. 避免使用静态集合类

在使用集合类时,避免使用静态集合类,或者及时清理其中不再需要的对象。

🎉 3. 避免使用匿名内部类

尽可能地避免使用匿名内部类,或者在使用匿名内部类时,避免引用外部类实例。

🎉 4. 善用垃圾回收器

及时地清理不再需要的对象,避免过多地占用内存。

🎉 5. 使用内存管理工具

使用内存管理工具,例如 visualVM、jmap 等,及时定位内存泄漏和内存溢出的问题。

🍊 例子

下面是一些常见的内存泄漏和内存溢出的例子:

🎉 1. 对象未及时关闭

// 定义一个公共方法,用于读取数据
public void readData() {
  // 声明一个 BufferedReader 对象,用于从文件中读取数据
  BufferedReader reader = null;
  try {
    // 创建 BufferedReader 对象,并与文件 "data.txt" 关联起来
    reader = new BufferedReader(new FileReader("data.txt"));
    // 声明一个字符串变量,用于存储从文件中读取的每一行数据
    String line;
    // 循环读取文件中的每一行数据,直到读取结束
    while ((line = reader.readLine()) != null) {
      // 对每一行数据进行处理
      // 这里需要自行添加代码
    }
  }
  // 捕获 IO 异常,防止程序崩溃
  catch (IOException e) {
    e.printStackTrace();
  }
  // 由于忘记关闭资源,可能会导致资源泄漏,应当及时关闭
  // 这里需要自行添加关闭资源的代码
}

🎉 2. 静态集合类导致的内存泄漏

// 定义单例模式的类
public class MySingleton {
  // 静态变量,保存对象的列表
  private static final List<Object> objects = new ArrayList<>();

  // 私有构造函数,防止外部直接实例化对象
  private MySingleton() {}

  // 静态方法,向对象列表中添加对象
  public static void addObject(Object obj) {
    objects.add(obj); // 添加对象
  }
}

🎉 3. 匿名内部类导致的内存泄漏

// 定义一个名为MyListener的公共类
public class MyListener {
  // 定义公共无返回值的registerListener()方法
  public void registerListener() {
    // 创建名为button的新的MyButton对象
    MyButton button = new MyButton();
    // 给button添加一个ActionListener监听器
    button.addActionListener(new ActionListener() {
      // 实现ActionListener接口中的actionPerformed()方法,当事件发生时执行
      public void actionPerformed(ActionEvent e) {
        // 处理事件
      }
    });
  }
}

🎉 4. 对象太多占用过多内存

// 定义一个公共的类名为 MyList
public class MyList {
  // 定义一个私有的 List 属性,类型为 Object 的 ArrayList
  private List<Object> list = new ArrayList<>();

  // 定义一个公共的方法名为 add,参数为 Object 类型
  public void add(Object obj) {
    // 将 obj 对象添加到 list 中
    list.add(obj);
  }
}

🎉 5. 内存泄漏导致的内存溢出

// 定义一个单例类MySingleton
public class MySingleton {
  // 定义一个私有静态变量instance
  private static MySingleton instance;
  // 定义一个List对象,存储Object类型的对象
  private List<Object> objects = new ArrayList<>();
  // 定义一个私有构造函数
  private MySingleton() {}
  // 定义一个公有的静态同步方法,返回MySingleton实例对象
  public static synchronized MySingleton getInstance() {
    // 如果实例对象instance为null,创建一个新的MySingleton实例
    if (instance == null) {
      instance = new MySingleton();
    }
    // 返回MySingleton实例对象
    return instance;
  }
  // 定义一个公有方法,用于添加Object对象到List中
  public void addObject(Object obj) {
    objects.add(obj);
  }
}

🍊 总结

内存泄漏和内存溢出是 Java 开发中常见的问题,可以通过关闭对象、避免使用静态集合类、避免使用匿名内部类、善用垃圾回收器和使用内存管理工具来避免。需要注意对象的生命周期,及时清理不再需要的对象,避免占用过多内存。同时,也可以通过学习常见的例子和使用内存管理工具来提高对于内存泄漏和内存溢出的诊断和处理能力。

优快云

🔔如果您需要转载或者搬运这篇文章的话,非常欢迎您私信我哦~

希望各位读者大大多多支持用心写文章的博主,现在时代变了,信息爆炸,酒香也怕巷子深,博主真的需要大家的帮助才能在这片海洋中继续发光发热,所以,赶紧动动你的小手,点波关注❤️,点波赞👍,点波收藏⭐,甚至点波评论✍️,都是对博主最好的支持和鼓励!

📥博主的人生感悟和目标

探寻内心世界,博主分享人生感悟与未来目标

  • 🍋程序开发这条路不能停,停下来容易被淘汰掉,吃不了自律的苦,就要受平庸的罪,持续的能力才能带来持续的自信。我本身是一个很普通程序员,放在人堆里,除了与生俱来的盛世美颜,就剩180的大高个了,就是我这样的一个人,默默写博文也有好多年了。
  • 📺有句老话说的好,牛逼之前都是傻逼式的坚持,希望自己可以通过大量的作品、时间的积累、个人魅力、运气、时机,可以打造属于自己的技术影响力。
  • 💥内心起伏不定,我时而激动,时而沉思。我希望自己能成为一个综合性人才,具备技术、业务和管理方面的精湛技能。我想成为产品架构路线的总设计师,团队的指挥者,技术团队的中流砥柱,企业战略和资本规划的实战专家。
  • 🎉这个目标的实现需要不懈的努力和持续的成长,但我必须努力追求。因为我知道,只有成为这样的人才,我才能在职业生涯中不断前进并为企业的发展带来真正的价值。在这个不断变化的时代,我必须随时准备好迎接挑战,不断学习和探索新的领域,才能不断地向前推进。我坚信,只要我不断努力,我一定会达到自己的目标。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Java程序员廖志伟

赏我包辣条呗

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值