内存泄漏 与 内存溢出

news2024/9/20 21:35:37

1.内存溢出(Memory Overflow)

  • 生活样例:
            
    内存容量就像一个桶,内存就是水,水 溢出 就是水满了。
  • 定义:
            
    内存溢出是指程序试图使用超过其可用内存限制的内存。这种情况通常会导致程序崩溃或异常。内存溢出一般是由于分配了过多内存或者在使用数据结构时超出了其限制。
  • 例子:堆内存溢出
            堆内存用于动态分配对象。当程序尝试分配超过堆内存限制的内存时,就会发生堆内存溢出。
public class HeapMemoryoverflowExample {
    public static void main(string[] args) {
        List<int[]> list = new ArrayList<>();
        while (true) {
            list.add(new int[10000001);//不断分配大块内存
        }
    }
}

常见内存溢出情况及解决方案:

  • 堆内存溢出(Java Heap Space)

    • 原因:长时间运行的应用可能会持续创建对象,如果这些对象没有被及时回收,就可能导致堆内存耗尽。
    • 解决:增加JVM堆内存大小(通过-Xms-Xmx参数设置);优化代码以减少内存使用,比如使用对象池来减少对象创建;分析内存泄漏并修复。
  • 栈溢出(StackOverflowError)

    • 原因:通常是由于递归调用太深或循环创建了大量局部变量。
    • 解决:优化递归逻辑,确保有正确的终止条件;减少方法调用深度;优化循环逻辑,避免创建大量局部变量。
  • 元空间溢出(Metaspace)

    • 原因:Java 8 以后的版本使用元空间代替了永久代,用于存储类的元数据。如果类的元数据消耗过多内存,可能会触发元空间溢出。
    • 解决:增加元空间大小(通过-XX:MetaspaceSize-XX:MaxMetaspaceSize参数设置);优化代码以减少类加载。
  • 大对象处理不当

    • 原因:处理大型对象或集合时,可能会占用大量内存。
    • 解决:优化大对象的处理逻辑,比如分批处理、使用流式处理等。
  • 线程资源管理不当

    • 原因:线程创建过多,每个线程都有自己的栈空间,可能导致内存溢出。
    • 解决:合理管理线程资源,避免创建过多线程;使用线程池来复用线程。

2.内存泄露(Memory Leak)

  • 生活样例:
    桶破了,水漏出去了。桶中的水就相当于内存,慢慢的流失了
  • 定义:
            内存泄露是指程序在运行过程中动态分配内存后,没有正确地释放不再使用的内存,导致这些内存无法被再次分配和使用。长时间运行的程序如果存在内存泄露,会导致内存逐渐耗尽,最终可能导致系统性能下降或者程序崩溃。
  • 例子:
            在 Java 中,虽然有垃圾回收机制,但也可能出现内存泄露。例如,当某个对象不再需要但仍然被引用时,垃圾回收器无法回收该对象的内存。
     
    public class MemoryLeakExamplef
        public static void main(string[l args) {
            List<Object> list = new ArrayList<>();
            while (true) {
                list.add(new 0bject());// 对象不断增加,但没有被释放
            }
        }
    }

2.1 静态属性导致内存泄露

        会导致内存泄露的一种情况就是大量使用static静态变量。在Java中,静态属性的生命周期通常伴随着应用整个生命周期(除非ClassLoader符合垃圾回收的条件)。

public class StaticTest {
    public static List<Double> list = new ArrayList<>();

    public void populateList() {
        for (int i = 0; i < 10000000; i++) {
            list.add(Math.random());
        }
        Log.info("Debug Point 2");
    }

    public static void main(String[] args) {
        Log.info("Debug Point 1");
        new StaticTest().populateList();
        Log.info("Debug Point 3");
    }
}

如果监控内存堆内存的变化,会发现在打印Point1和Point2之间,堆内存会有一个明显的增长趋势图。

但当执行完populateList方法之后,对堆内存并没有被垃圾回收器进行回收。

针对上述程序,如果将定义list的变量前的static关键字去掉,再次执行程序,会发现内存发生了具体的变化。VisualVM监控信息如下图:

对比两个图可以看出,程序执行的前半部分内存使用情况都一样,但当执行完populateList方法之后,后者不再有引用指向对应的数据,垃圾回收器便进行了回收操作

因此,我们要十分留意static的变量,如果集合或大量的对象定义为static的,它们会停留在整个应用程序的生命周期当中。而它们所占用的内存空间,本可以用于其他地方。

那么如何优化呢?第一,进来减少静态变量;第二,如果使用单例,尽量采用懒加载。

2.2 未关闭的资源

无论什么时候当我们创建一个连接或打开一个流,JVM都会分配内存给这些资源。比如,数据库链接、输入流和session对象。

忘记关闭这些资源,会阻塞内存,从而导致GC无法进行清理。特别是当程序发生异常时,没有在finally中进行资源关闭的情况。

这些未正常关闭的连接,如果不进行处理,轻则影响程序性能,重则导致OutOfMemoryError异常发生。

如果进行处理呢?

  • 第一,始终记得在finally中进行资源的关闭;
  • 第二,关闭连接的自身代码不能发生异常;
  • 第三,Java7以上版本可使用try-with-resources代码方式进行资源关闭。

2.3 不当的equals方法和hashCode方法实现

当我们定义个新的类时,往往需要重写equals方法和hashCode方法。在HashSet和HashMap中的很多操作都用到了这两个方法。如果重写不得当,会造成内存泄露的问题。

下面来看一个具体的实例:

public class Person {
    public String name;
    
    public Person(String name) {
        this.name = name;
    }
}

现在将重复的Person对象插入到Map当中。我们知道Map的key是不能重复的。

@Test
public void givenMap_whenEqualsAndHashCodeNotOverridden_thenMemoryLeak() {
    Map<Person, Integer> map = new HashMap<>();
    for(int i=0; i<100; i++) {
        map.put(new Person("jon"), 1);
    }
    Assert.assertFalse(map.size() == 1);
}

上述代码中将Person对象作为key,存入Map当中。理论上当重复的key存入Map时,会进行对象的覆盖,不会导致内存的增长。

但由于上述代码的Person类并没有重写equals方法,因此在执行put操作时,Map会认为每次创建的对象都是新的对象,从而导致内存不断的增长。

VisualVM中显示信息如下图:

当重写equals方法和hashCode方法之后,Map当中便只会存储一个对象了。方法的实现如下:

public class Person {
    public String name;
    
    public Person(String name) {
        this.name = name;
    }
    
    @Override
    public boolean equals(Object o) {
        if (o == this) return true;
        if (!(o instanceof Person)) {
            return false;
        }
        Person person = (Person) o;
        return person.name.equals(name);
    }
    
    @Override
    public int hashCode() {
        int result = 17;
        result = 31 * result + name.hashCode();
        return result;
    }
}

经过上述修改之后,Assert中判断Map的size便会返回true。

@Test
public void givenMap_whenEqualsAndHashCodeNotOverridden_thenMemoryLeak() {
    Map<Person, Integer> map = new HashMap<>();
    for(int i=0; i<2; i++) {
        map.put(new Person("jon"), 1);
    }
    Assert.assertTrue(map.size() == 1);
}

重写equals方法和hashCode方法之后,堆内存的变化如下图:

在这里插入图片描述

另外的例子就是当使用ORM框架,如Hibernate时,会使用equals方法和hashCode方法进行对象的的分析和缓存操作。

如果不重写这些方法,则发生内存泄漏的可能性非常高,因为Hibernate将无法比较对象(每次都是新对象),然后不停的更新缓存。

如何进行处理?

  • 第一,如果创建一个实体类,总是重写equals方法和hashCode方法;
  • 第二,不仅要覆盖默认的方法实现,而且还要考虑最优的实现方式;

2.4 外部类引用内部类

这种情况发生在非静态内部类(匿名类)中,在类初始化时,内部类总是需要外部类的一个实例。

每个非静态内部类默认都持有外部类的隐式引用。如果在应用程序中使用该内部类的对象,即使外部类使用完毕,也不会对其进行垃圾回收。

public class OuterClass {
    private String importantData;

    public OuterClass(String importantData) {
        this.importantData = importantData;
    }

    public void doSomething() {
        // 创建并启动线程,使用静态匿名内部类
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("线程运行中..." + importantData);
            }
        });
        thread.start();
    }

    public static void main(String[] args) {
        OuterClass outerClass = new OuterClass("重要数据");
        outerClass.doSomething();
        
        //尝试释放outerClass对象
        outerClass = null;
        
        //...其他业务代码
    }
}

         这段代码中main方法执行 outerClass = null; 如果匿名内部类开启的线程没有执行结束,outerClass由于还被引用,不会被垃圾回收!

         在这个例子中,内存泄漏的原因在于非静态匿名内部类(实现了Runnable接口的类)隐式地持有对其外部类实例OuterClass的引用。这个引用是通过importantData字段访问外部类的成员变量时建立的。即使在main方法中将outer变量设置为null,外部类实例OuterClass也不能被垃圾回收,因为匿名内部类中的线程仍然持有对它的引用。也就是说如果这个线程没有结束,引用就一直存在。

这里我们只需要拷贝一份局部变量,就可以解除这个引用,从而避免内存泄漏的问题。

public class OuterClass {
    private String importantData;

    public OuterClass(String importantData) {
        this.importantData = importantData;
    }

    public void doSomething() {
        // 创建并启动线程,使用静态匿名内部类
        String data = this.importantData; //定义个局部的final变量
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("线程运行中..." + data);
            }
        });
        thread.start();
    }

    public static void main(String[] args) {
        OuterClass outerClass = new OuterClass("重要数据");
        outerClass.doSomething();
    }
}

这样的话,匿名内部类中就不存在对外部类实例的引用。线程就不再直接引用OuterClass实例的成员变量,而是引用了一个局部变量的副本。因此,即使线程还在运行,一旦main方法中将outerClass变量设置为nullOuterClass的实例就可以被垃圾回收了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1965256.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Mixture of Experts with Attention论文解读

注意这篇论文没有代码&#xff0c;文章所谓的注意力是加性注意力&#xff0c;找scaled dot-product的伙计可以避坑了&#xff0c;但还是有值得学习的地方。 score是啥&#xff1f; 这个score标量怎么计算得到&#xff0c;请假设一下x和z的值&#xff0c;计算演示一下 expert是…

第十二章(重点 元数据管理)

语境关系图&#xff1a; 1. 元数据概念&#xff1a; 元数据从技术的角度叫元数据 从业务的角度叫数据资源管理目录 技术 元数据 业务 数据资源管理目录 但是并不是数据资产目录 如果没有可靠的原数据&#xff0c;组织就不知道它拥有什么数据&#xff0c;数据表示什么&#xff…

运行ruoyi

创建数据库 根据ry_20240629.sql创建ry-cloud数据库 根据ry_config_20231204.sql创建ry-config数据库 nacos 数据库配置 修改nacos/conf/application.properties 单机版运行 startup.cmd -m standalone redis 运行后端 运行gateway,auth,modules/system模块 可能遇到的问…

怎么给电脑选一款合适的固态硬盘?就看这个参数!

前言 前段时间有很多小伙伴找小白修电脑&#xff0c;在修电脑的过程中&#xff0c;小白也会稍微看一下硬件配置。 小白就发现一个事情&#xff1a;很多小伙伴其实都不太懂电脑硬件。 为啥这么说呢&#xff1f;简单来说就是主板上使用了“不合适”的固态硬盘作为主系统硬盘。…

VulnHub-Tomato靶机渗透教程 简单易懂 报错链接

Tomato靶机是一个用于渗透测试和漏洞研究的虚拟机。 环境准备 攻击机&#xff08;Kali Linux&#xff09;IP&#xff1a;192.168.252.134 目标机 IP&#xff1a;192.168.252.133 这里我两台虚拟机都是NAT模式 渗透步骤 1.端口扫描 这里我没用kali自带的 我用的物理机上…

【Python学习手册(第四版)】学习笔记12.1-语法规则拓展

个人总结难免疏漏&#xff0c;请多包涵。更多内容请查看原文。本文以及学习笔记系列仅用于个人学习、研究交流。 本文是对【学习笔记10】-语句编写的通用规则 介绍过的语法概念进行复习并扩展。非常简单&#xff0c;应该是我写过的最简单的文章&#xff0c;阅读时间&#xff1a…

学习Mybatis及其简单配置

目录 JDBC的弊端 为什么要有ORM模型&#xff1f; 什么是ORM模型&#xff1f; Mybatis和hibernate 区别: Mybatis解决了jdbc的问题 为什么选择myBatis&#xff08;优势&#xff09;&#xff1f; 什么是MyBatis 主配置文件&#xff08;config文件&#xff09; Mapper文件…

TwinCAT3 C++环境安装教程

文章目录 下载windos插件:下载地址&#xff0c;安装这个插件是为了能在 TwinACT 3 工程环境创建和编辑 C模块。 点击下载的文件&#xff0c;打开其中的KitSetup.exe 在打开的窗口中选择"Build Environment"后点击OK 弹出的窗口点击ok 选择“I agree”后点击…

从零逐步实现SVM(含公式推导)上

支持向量机&#xff08;SVM&#xff09;相关概念 支持向量&#xff1a;支持或支撑平面上把两类类别划分开的超平面的向量点线性可分支持向量机&#xff1a;通过硬间隔最大化&#xff0c;学习一个线性分类器线性支持向量机&#xff1a;通过软间隔最大&#xff0c;学习一个线性分…

掌握时间的秘密:pytz 库的神奇之旅

文章目录 掌握时间的秘密&#xff1a;pytz 库的神奇之旅背景&#xff1a;为何选择 pytz&#xff1f;pytz 库是什么&#xff1f;如何安装 pytz&#xff1f;函数的使用方法场景应用常见问题与解决方案总结 掌握时间的秘密&#xff1a;pytz 库的神奇之旅 背景&#xff1a;为何选择…

【Vue3】默认插槽

【Vue3】默认插槽 背景简介开发环境开发步骤及源码 背景 随着年龄的增长&#xff0c;很多曾经烂熟于心的技术原理已被岁月摩擦得愈发模糊起来&#xff0c;技术出身的人总是很难放下一些执念&#xff0c;遂将这些知识整理成文&#xff0c;以纪念曾经努力学习奋斗的日子。本文内…

学习c语言第18天(字符串和内存函数)

1.函数介绍 1.1 strlen size_t(就是无符号整形) strlen(const char * str); 字符串已经\0作为结束标志&#xff0c;strlen函数返回的是在字符串中\0前面出现的字符个数(不包 含\0) 参数指向的字符串必须要以\0结束。 注意函数的返回值为size_t&#xff0c;…

Java并发—Java内存模型以及线程安全

目录 一、Java内存模型 JMM的核心概念 二、什么是线程安全&#xff1f; 1、原子性 2、有序性 3、可见性 三、如何确保线程安全&#xff1f; 1、sychronized关键字 2、Lock接口和其实现 3、volatile关键字 4、Atomic原子类 5、ThreadLocal 6、不可变对象 7、并发集…

电商数据采集封装API的详细步骤分享(API测试实例)

在当今的电商行业中&#xff0c;数据采集已成为企业获取市场洞察、优化运营策略、提升用户体验的重要手段。而封装电商数据采集的API接口&#xff0c;则是将这一复杂过程标准化、模块化的有效方式。本文将详细分享电商数据采集封装API的步骤&#xff0c;并通过一个实际的API测试…

努力努力努力的第十四天(2024.7.31)

昨天日期写错了写成2020.7.30,应该是2024.7.31&#xff08;手滑了哈哈哈&#xff09; 1.行列转换 效果演示&#xff1a; 这是未经行列转换操作的t_score表&#xff1a; 这是经过行列转换后的t_score表&#xff1a; 第一步&#xff1a;确定初步的做法 使用分组查询(group by…

20240731在WIN10下数框框的方法【CPU】

20240731在WIN10下数框框的方法【CPU】 2024/7/31 20:14 百度&#xff1a;WINDOWs 10 多核CPU 数框框 win10怎么数框框 1、首先打开win10系统&#xff0c;进入任务管理器。 2、其次点击CPU使用率窗口&#xff0c;鼠标悬停在右边的窗口按右键。 3、最后将图形更改为&#xff1a;…

【计算机网络】http协议的原理与应用,https是如何保证安全传输的

✨✨ 欢迎大家来到景天科技苑✨✨ &#x1f388;&#x1f388; 养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; &#x1f3c6; 作者简介&#xff1a;景天科技苑 &#x1f3c6;《头衔》&#xff1a;大厂架构师&#xff0c;华为云开发者社区专家博主&#xff0c;…

优化|高性能模块预测控制求解器

本文介绍的是另外一种针对模块控制MPC问题的一种高效求解方法。High-Performance Small-Scale Solvers for Linear Model Predictive Control&#xff0c;Gianluca Frison, Hans Henrik Brandenborg Srensen, Bernd Dammann, John Bagterp Jrgensen [1]。本文讲了HPIPM的前身&a…

Phalco安装过程以及踩的一些坑(mac环境)

一 背景 公司用Phalcon框架好长时间了,中途发现了一些Phalcon使用的上的问题,于是想在本地搭建一套Phalcon的环境,方便排查问题使用。 二 Mac系统下的安装 看了很多说法,最终发现还是官网给力,安装Phalcon使用下列命令即可(前提条件是PHP已安装好,工具pecl也安装好了):…

2024-07-31 Android studio gradle、sdk、ndk 等路径记录

一、gradle的下载路径&#xff1a;C:\Users\user\.gradle\wrapper\dists 二、NDK下载路径C:\Users\user\AppData\Local\Android\Sdk\ndk 三、SDK下载路径&#xff1a;C:\Users\user\AppData\Local\Android\Sdk\platforms