Java面试之Jvm内存泄漏

简介: 详细讲解Jvm内存泄漏

今天我们讲解一下Jvm内存泄漏

内存泄漏和内存溢出的关系

内存泄露:指程序中动态分配内存给一些临时对象,但是对象不会被GC所回收,它始终占用内存。即被分配的对象可达但已无用。
内存溢出:指程序运行过程中无法申请到足够的内存而导致的一种错误。内存溢出通常发生于OLD段或Perm段垃圾回收后,仍然无内存空间容纳新的Java对象的情况。

从定义上可以看出内存泄露是内存溢出的一种诱因,但是不是唯一因素。

可以使用Runtime.getRuntime().freeMemory()进行内存泄漏查询

Runtime.getRuntime().freeMemory()表示当前还有多少空闲内存

package com.one.util;

public class Hello {
    public static void main(String[] args) {
        System.out.println("free内存:" + Runtime.getRuntime().freeMemory() / 1024
            / 1024);
        String[] aaa = new String[2000000];
        for (int i = 0; i < 2000000; i++) {
            aaa[i] = new String("aaa");
        }
        System.out.println("free内存:" + Runtime.getRuntime().freeMemory() / 1024 / 1024);
    }
}

此时结果如下所示

20190719100158797

内存泄漏的例子

如果长生命周期的对象持有短生命周期的引用,就很可能会出现内存泄露

比如下面的代码,这里的object实例,其实我们期望它只作用于method1()方法中,且其他地方不会再用到它,但是,当method1()方法执行完成后,object对象所分配的内存不会马上被认为是可以被释放的对象,只有在Simple类创建的对象被释放后才会被释放,严格的说,这就是一种内存泄露。

public class Simple {
 
    Object object;
 
    public void method1(){
        object = new Object();
        //...其他代码
    }
}

怎么解决上面的问题呢,加上下面的蓝色代码注释就好了

public class Simple {
 
    Object object;
 
    public void method1(){
        object = new Object();
        //...其他代码
        // 蓝色代码注释开始
        object = null;
        // 蓝色代码注释结束
    }
}

集合里面的内存泄漏

集合里面的数据都设置成null,但是集合内存还是存在的

比如下面的代码
因为你已经在下面的蓝色代码注释里面进行company=null了,所以下面的list集合里面的数据都是无用的了,但是此时list集合里面的所有元素都不会进行垃圾回收

package com.four;

import java.util.ArrayList;
import java.util.List;

public class Hello {
    public static void main(String[] args) {
        List<Company> list = new ArrayList<Company>();
        int i=0;
        for(int j=0;j<10;j++){
            Company company = new Company();
            company.setName("ali");
            list.add(company);
            // 蓝色代码注释开始
            company = null;
            // 蓝色代码注释结束
        }

        System.gc();
        while(true){
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("已经测试了"+(++i)+"秒");
        }
    }

}


class Company {
    private String name;

    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        System.out.println("回收Comapny");
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }
}

20190719095000536
怎么解决上面的问题呢,就是把上面的list集合变量也变成null,比如加上下面的红色代码注释

package com.one.util;

import java.util.ArrayList;
import java.util.List;

public class Hello {
    public static void main(String[] args) {
        List<Company> list = new ArrayList<Company>();
        int i = 0;
        for (int j = 0; j < 10; j++) {
            Company company = new Company();
            company.setName("ali");
            list.add(company);
            // 蓝色代码注释开始
            company = null;
            // 蓝色代码注释结束
        }
        // 红色代码注释开始
        list = null;
       // 红色代码注释结束
        System.gc();
        while (true) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("已经测试了" + (++i) + "秒");
        }
    }

}

class Company {
    private String name;

    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        System.out.println("回收Comapny");
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }
}

此时结果如下所示,可以看出来集合里面的Company变量都回收了

2019071909522653

还有就是使用remove()方法进行移除元素的时候,也可能会造成内存泄漏

什么意思呢,
就比如ArrayList里面的pop(),如果是下面的写法就会造成内存泄漏,因为下面的elementData[--size]这个元素移除之后,并没有进行设置成null

public E pop(){
    if(size == 0)
        return null;
    else
        return (E) elementData[size];
}

所以上面的代码应该变成下面这样,此时注意下面的蓝色代码注释里面的size值比下面的红色代码注释里面的size小1

public E pop(){
    if(size == 0)
        return null;
    else{
        // 红色代码注释开始
        E e = (E) elementData[--size];
        // 红色代码注释结束
        // 蓝色代码注释开始
        elementData[size] = null;
        // 蓝色代码注释结束
        return e;
    }
}

连接没有关闭会泄漏

比如数据库连接(dataSourse.getConnection()),网络连接(socket)和io连接,这些链接在使用的时候,除非显式的调用了其close()方法(或类似方法)将其连接关闭,否则是不会自动被GC回收的。其实原因依然是长生命周期对象持有短生命周期对象的引用。所以我们经常在网上看到在连接调用结束的时候要进行调用close()进行关闭,这样可以回收不用的内存对象,增加可用内存。

能看到这里的同学,就帮忙点个赞吧,Thanks(・ω・)ノ

原文链接

目录
相关文章
|
9天前
|
Oracle Java 关系型数据库
java体系结构和jvm
java体系结构和jvm
|
21天前
|
Java 程序员
java线程池讲解面试
java线程池讲解面试
38 1
|
21天前
|
存储 缓存 Java
金石原创 |【JVM盲点补漏系列】「并发编程的难题和挑战」深入理解JMM及JVM内存模型知识体系机制(1)
金石原创 |【JVM盲点补漏系列】「并发编程的难题和挑战」深入理解JMM及JVM内存模型知识体系机制(1)
34 1
|
20天前
|
缓存 Java C#
【JVM故障问题排查心得】「Java技术体系方向」Java虚拟机内存优化之虚拟机参数调优原理介绍(一)
【JVM故障问题排查心得】「Java技术体系方向」Java虚拟机内存优化之虚拟机参数调优原理介绍
60 0
|
1天前
|
缓存 监控 Java
深入理解Java虚拟机(JVM)性能调优
【4月更文挑战第18天】本文探讨了Java虚拟机(JVM)的性能调优,包括使用`jstat`、`jmap`等工具监控CPU、内存和GC活动,选择适合的垃圾回收器(如Serial、Parallel、CMS、G1),调整堆大小和新生代/老年代比例,以及代码优化和JIT编译策略。通过这些方法,开发者能有效提升应用性能并应对复杂性挑战。性能调优是持续过程,需伴随应用演进和环境变化进行监控与优化。
|
7天前
|
存储 前端开发 安全
JVM内部世界(内存划分,类加载,垃圾回收)(上)
JVM内部世界(内存划分,类加载,垃圾回收)
34 0
|
10天前
|
Java 关系型数据库 MySQL
大厂面试题详解:Java抽象类与接口的概念及区别
字节跳动大厂面试题详解:Java抽象类与接口的概念及区别
33 0
|
20天前
|
存储 缓存 算法
Java入门高频考查基础知识4(字节跳动面试题18题2.5万字参考答案)
最重要的是保持自信和冷静。提前准备,并对自己的知识和经验有自信,这样您就能在面试中展现出最佳的表现。祝您面试顺利!Java 是一种广泛使用的面向对象编程语言,在软件开发领域有着重要的地位。Java 提供了丰富的库和强大的特性,适用于多种应用场景,包括企业应用、移动应用、嵌入式系统等。下是几个面试技巧:复习核心概念、熟悉常见问题、编码实践、项目经验准备、注意优缺点、积极参与互动、准备好问题问对方和知其所以然等,多准备最好轻松能举一反三。
46 0
Java入门高频考查基础知识4(字节跳动面试题18题2.5万字参考答案)
|
24天前
|
Java 程序员 API
java1.8常考面试题
在Java 1.8版本中,引入了很多重要的新特性,这些特性常常成为面试的焦点
42 8
|
2月前
|
存储 关系型数据库 MySQL
2024年Java秋招面试必看的 | MySQL调优面试题
随着系统用户量的不断增加,MySQL 索引的重要性不言而喻,对于后端工程师,只有在了解索引及其优化的规则,并应用于实际工作中后,才能不断的提升系统性能,开发出高性能、高并发和高可用的系统。 今天小编首先会跟大家分享一下MySQL 索引中的各种概念,然后介绍优化索引的若干条规则,最后利用这些规则,针对面试中常考的知识点,做详细的实例分析。
237 0
2024年Java秋招面试必看的 | MySQL调优面试题