内存泄漏的常见原因

首先说一些什么是内存泄漏?
Java中的内存泄漏主要指的是Java堆内存中的对象在不再被需要时,由于某种原因,垃圾回收器(GC)无法回收这些对象所占用的内存,导致这些内存被持续占用,最终可能导致OutOfMemoryError错误。

内存泄漏的原因可能是哪些呢?
1、静态集合类
静态集合的生命周期和JVM一致,所以静态集合类被引用的对象不能被释放。

public class OutOfMemoryErrorTest {
    static List list = new ArrayList();

    public void test() {
        Object object = new Object();
        list.add(object);
    }
}

2、单例模式
单例模式在初始化后会以静态变量的方式在JVM的整个生命周期中存在。如果单例对象有外部持有的引用,那么这个对象将不能被GC回收,导致内存泄漏。

3、IO使用后没有释放资源
这里主要指创建的创建的连接不在使用的时候,需要调用close方法关闭连接,只有被连接关闭后,GC才会回收对应的对象(Connection、Statement、Resultset、Session),忘记关闭这些资源都会导致持续占有CPU,无法被GC回收。

public class PrintStreamChatTest {

    public static void main(String[] args) {

        // 由手册可知:构造方法需要的是Reader类型的引用,但Reader类是个抽象类,实参只能传递子类的对象  字符流
        // 由手册可知: System.in代表键盘输入, 而且是InputStream类型的 字节流
        BufferedReader br = null;
        PrintStream ps = null;
        try {
            br = new BufferedReader(new InputStreamReader(System.in));
            ps = new PrintStream(new FileOutputStream("d:/a.txt", true));
            //省略

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 4.关闭流对象并释放有关的资源  如果不关闭,会导致OOM
            if (null != ps) {
                ps.close();
            }
            if (null != br) {
                try {
                    br.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

4、不合理的变量作用域
一个变量定义作用域大于其使用范围,很可能会存在内存泄露;或不在使用对象后没有及时将对象置为null,很可能导致内存泄漏的发生。关于变量作用域,它主要影响的是变量的可见性和生命周期,而不是直接导致内存泄漏。但是,如果作用域过大,可能会使得代码难以理解和维护,从而间接增加内存泄漏的风险(比如,因为代码复杂而忘记释放资源)。将不再使用的对象显式置为null在Java中通常不是必要的,因为一旦没有任何引用指向该对象,它就会被视为垃圾回收的候选对象。然而,在某些情况下,显式地将对象置为null可以作为一种代码清晰性的手段,或者在某些特定的上下文中(如缓存管理、资源释放等)可能有助于更早地触发垃圾回收。


public class OutOfMemoryErrorTest {

    // 静态变量,作用域是整个类,但可能只在某个方法中使用  
    private static List<Object> staticList = new ArrayList<>();

    public static void main(String[] args) {
        for (int i = 0; i < 1000000; i++) {
            // 在循环中向静态列表添加对象  
            staticList.add(new Object());

            // 假设这里有一些逻辑,但实际上我们忘记了在某个点清理这个列表  
        }

        // 此时,staticList包含了大量不再需要的对象,但由于它是静态的,  
        // 这些对象将一直存在,直到程序结束或显式清理  

        // 正确的做法应该是在适当的时候清理这个列表,比如:  
        // staticList.clear();  
        // 但在这个例子中,我们故意不这么做来模拟潜在的内存泄漏  
    }  

}

5、Hash值发生改变
对象Hash值发生改变,使用HashMap、HashSet等容器中时候,由于对象修改之后的Hash值和存储容器时的Hash值不同,所以无法找到存入的对象,自然也就无法单独删除,这也会导致内存泄漏。HashMap 和 HashSet 等基于哈希表的集合依赖于对象的哈希码(通过调用对象的 hashCode() 方法获得)来快速定位元素。如果对象的哈希码在对象被添加到集合之后发生了改变,那么这个对象在集合中的位置可能会变得不可预测,从而导致无法正确检索或删除该对象。然而,这通常不会直接导致内存泄漏,因为对象仍然存在于集合中,只是无法按预期方式访问。如果集合本身被持续引用,而其中的对象由于哈希码变化而变得无法访问,那么这些对象将占用不必要的内存,这可以被视为一种“逻辑上的内存浪费”,而不是传统意义上的内存泄漏。

public class HashCodeChangeExample {  
    public static void main(String[] args) {  
        Map<MutableObject, String> map = new HashMap<>();  
        MutableObject obj = new MutableObject(1);  
        map.put(obj, "Initial Value");  
  
        System.out.println("Before changing value: " + map.get(obj)); // 输出 Initial Value  
  
        // 修改对象的值,这会改变其hashCode  
        obj.setValue(2);  
  
        // 由于hashCode已经改变,现在无法从map中检索到该对象  
        System.out.println("After changing value: " + map.get(obj)); // 输出 null  
  
        // 注意:这里并没有内存泄漏,因为map仍然持有对obj的引用  
        // 但是,obj在map中变得无法访问,除非我们能够通过其他方式(如遍历map)找到它  
  
        // 如果map不再被引用,那么它和其中的所有对象最终都将被垃圾回收  
    }  

6、ThreadLocal使用不当
ThreadLocal的弱引用导致内存泄漏,使用完ThreadLocal一定要用remove方法来进行清除。

/**
 * 连接工具类:从数据源中获取一个连接,并且将获取到的连接与线程进行绑定
 * ThreadLocal:线程内部的存储类,可以在指定的线程内存储数据key:threadLocal(当前线程) value:任意类型的值 Connection
 */
@Component
public class ConnectionUtils {

    @Autowired
    private DataSource dataSource;

    private ThreadLocal<Connection> threadLocal = new ThreadLocal<>();

    /**
     * 获取当前线程上绑定连接:如果获取到的连接为空,那么就需要从数据源中获取连接,并且放到ThreadLocal中(绑定当前线程)
     * @return
     */
    public Connection getThreadConnection(){
        //1、先从ThreadLocal上获取连接
        Connection connection = threadLocal.get();
        //2、判断当前线程中是否有Connection
        if (null==connection){
            //3、从数据库中获取一个连接,并且存入到ThreadLocal中
            try {
                connection = dataSource.getConnection();
                threadLocal.set(connection);
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }
        return connection;
    }

    /**
     * 解除当前线程的绑定
     */
    public void removeThreadConnection(){
        threadLocal.remove();
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值