java内存管理关系及内存泄露的原理分析

 更新时间:2021年10月11日 15:13:09   作者:知我饭否  
这篇文章主要介绍了java内存管理关系及内存泄露的原理,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教

java内存管理关系及内存泄露原理

这可能是最近写的博客中最接近底层的了。闲言少叙,进入正题。

java对象和内存的关系

首先,我们要知道下面几条真理(自己总结的)

  • 一个完整的建立对象流程是 1声明对象,2开辟内存空间,3将对象和内存空间建立联系。
  • 一个对象只能对应一个内存空间,一个内存空间可以对应很多对象
  • 回收一个内存空间 。如果,这个内存空间没有任何一个对象和他有联系。就可以被回收,或者好几个对象环形引用,也会被回收
  • 对一个对象进行操作的时候,是先通过 对象 找到 内存空间,然后 对内存空间进行操作(读,写 改 删)

这是本人总结出来的四条经验。

特别重要的是,一定要有这种认知。不管任何语言,最终都是要物理内存上面反映的,对象 和 内存空间 是两个不同的个体。 如果 没有的话,那么你会发现 下面将的都是什么啊!

创建对象

       Stu one; //只声明 one对象 但是没有分配内存空间
        //用new 开辟新的内存空间 oneMemory ,调用构造函数赋值,并将内存空间 oneMemory 与 one对象建立联系。
        one = new Stu("one");
        
        //声明 two对象 并开辟内存 twoMemory 调用构造函数赋值,并将内存空间 twoMemory与 two对象建立联系
        Stu two = new Stu("two");
        
        //声明 three对象, 并找到one 对象联系的内存空间 oneMemory。并将 oneMemory与 three 对象建立联系
        Stu three = one;
        
        //此时 内存空间 oneMemory 与两个对象有联系。一个是 one对象,一个是three对象
        System.out.println("three 和 one 是否相等" + (three == one) + " one的哈希值" + one.hashCode() + " three的哈希值" + three.hashCode());

运行结果

在这里插入图片描述

我们可以发现,three对象 和one对象 指向的是同一个内存空间oneMemory。这个不就是符合上面所说的第二个真理 如果,我们对one对象进行操作,那么产生的影响,也会反映到three对象上。

因为,**他们指向的是同一个内存空间,对one对象操作,就是做内存空间oneMemory进行操作。而three对象指向的也是oneMemory。这个符合上面第四条真理。**例子如下

        System.out.println("three 对象的值" + three.getName() + three.hashCode());
        //修改one的值,第一步 找到one对象联系的内存空间 oneMemory , 将内存空间oneMemory 中的name值改变
        one.setName("change");
        //读取three对象值时候,先找到three对象联系的内存空间oneMemory,读取其中的name值
        System.out.println("three 对象的值" + three.getName() + three.hashCode());

null的作用

长久以来,我只知道,将一个值复制成null,那么他就是空的了。但是 完全不知道,为啥。

还是接着上面的例子,看一段代码;

         //读取three对象值时候,先找到three对象联系的内存空间oneMemory,读取其中的name值
        System.out.println("three 对象的值 before" + three.getName() + three.hashCode());
        /*
         此时 如果 我们把one 对象 设置为null的。 对内存空间 oneMemory 是没有影响的
         =null的作用是 将one对象自己本身 对内存空间的联系去除,并不会影响到内存空间和其他对象的联系
         */
        one = null;
        System.out.println("three 对象的值  after" + three.getName() + three.hashCode());

运行结果

在这里插入图片描述

我们会发现 将one对象赋值为空后,three对象还是和先前一样。以前一直认为null,是将对象和他的内存空间清楚。但现在不是!。代码注释里面写的很清楚了。null 并不是清除内存空间,他只是把对象自己本身和内存空间的联系切断了

内存泄露

如果,你明白了上面。那么内存泄露对你来说也很简单了。

再来学习一个新概念

在这里插入图片描述

上面这么多呢。在本篇文章里面,你只需要记住 被static关键词修饰的变量,类,方法的生命周期是伴随整个程序的。就是程序活多久,他们就活多久

接下来看代码

首先,我们定义一个静态集合 staticList ,他是程序一运行,就会被创建好的。程序结束运行了,它才会被回收。

static List<Stu> staticList = new ArrayList<>();//开辟内存空间 listMemory
       System.out.println("three 对象的值  after" + three.getName() + three.hashCode());
        /*
         内存泄露 是长生命周期的对象 对一个内存空间有联系,造成内存空间没有办法被回收
         */
        /*
        将three对象添加到静态集合里面,步骤是这样的,
        第一步 找到three对象联系的内存空间 oneMemory
        第二步 找到 staticList集合对象联系的内存空间 listMemory
        第三步 告诉系统 staticList集合对象的部分成员 和内存空间 oneMemory 建立联系。
         */
        staticList.add(three);
        
        /*
        在这里 即使three对象已经和内存空间 oneMemory 没有联系了。
        oneMemory 也不会被回收,因为上面说了内存空间和对象的关系是1对多。
         而回收的条件是 一个内存空间没有一条和对象的联系才可以回收。
         此时 内存空间 和staticList集合对象的部分成员 有联系,所以 内存空间不会被回收。
         又由于staticList 集合对象联系的内存空间在 静态存储区,是伴随整个程序的。所以 在整个程序生命里面,
         内存空间 oneMemory  就得不到 回收。  就是内存泄露了。
         */
        three = null;
        System.out.println(staticList.get(0).hashCode());

运行结果

在这里插入图片描述

可以看见。在我们将three对象赋值null切断和内存空间 oneMemory的联系后。静态集合staticList对象的部分成员依然和内存空间 oneMemory有联系。根据上面第三条所说,因为内存空间 oneMemory 还是和对象有联系的(staticList)。所以不会回收oneMemory内存空间。又由于staticList是静态的,生命和程序一样长。 那么在整个程序周期里面,oneMemory内存空间 都不会被回收。就造成了内存泄露。

附上完整的代码

package com.zfh.test;
import java.util.ArrayList;
import java.util.List;
public class JavaMain {
    static List<Stu> staticList = new ArrayList<>();//开辟内存空间 listMemory
    public static void main(String[] args) {
        Stu one; //只声明 one对象 但是没有分配内存空间
        //用new 开辟新的内存空间 oneMemory ,调用构造函数赋值,并将内存空间 oneMemory 与 one对象建立联系。
        one = new Stu("one");
        //声明 two对象 并开辟内存 twoMemory 调用构造函数赋值,并将内存空间 twoMemory与 two对象建立联系
        Stu two = new Stu("two");
        //声明 three对象, 并找到one 对象联系的内存空间 oneMemory。并将 oneMemory与 three 对象建立联系
        Stu three = one;
        //此时 内存空间 oneMemory 与两个对象有联系。一个是 one对象,一个是three对象
        System.out.println("three 和 one 是否相等" + (three == one) + " one的哈希值" + one.hashCode() + " three的哈希值" + three.hashCode());
        System.out.println("three 对象的值" + three.getName() + three.hashCode());
        //修改one的值,第一步 找到one对象联系的内存空间 oneMemory , 将内存空间oneMemory 中的name值改变
        one.setName("change");
        //读取three对象值时候,先找到three对象联系的内存空间oneMemory,读取其中的name值
        System.out.println("three 对象的值 before" + three.getName() + three.hashCode());
        /*
         此时 如果 我们把one 对象 设置为null的。 对内存空间 oneMemory 是没有影响的
         =null的作用是 将one对象自己本身 对内存空间的联系去除,并不会影响到内存空间和其他对象的联系
         */
        one = null;
        System.out.println("three 对象的值  after" + three.getName() + three.hashCode());
        /*
         内存泄露 是长生命周期的对象 对一个内存空间有联系,造成内存空间没有办法被回收
         */
        /*
        将three对象添加到静态集合里面,步骤是这样的,
        第一步 找到three对象联系的内存空间 oneMemory
        第二步 找到 staticList集合对象联系的内存空间 listMemory
        第三步 告诉系统 staticList集合对象的部分成员 和内存空间 oneMemory 建立联系。
         */
        staticList.add(three);
        /*
        在这里 即使three对象已经和内存空间 oneMemory 没有联系了。
        oneMemory 也不会被回收,因为上面说了内存空间和对象的关系是1对多。
         而回收的条件是 一个内存空间没有一条和对象的联系才可以回收。
         此时 内存空间 和staticList集合对象的部分成员 有联系,所以 内存空间不会被回收。
         又由于staticList 集合对象联系的内存空间在 静态存储区,是伴随整个程序的。所以 在整个程序生命里面,
         内存空间 oneMemory  就得不到 回收。  就是内存泄露了。
         */
        three = null;
        System.out.println(staticList.get(0).hashCode());
    }
}

bean对象 即Stu

package com.zfh.test;
public class Stu {
  /*  static {
        System.out.println("静态代码块 我只调用一次");
    }*/
  
    private String name;
    
  /*  {
        System.out.println("构造代码块");
    }*/
  
    public Stu(String name) {
        this.name = name;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public void sout(){
        System.out.println(this.name+this.hashCode());
    }
    public void printer() {
        System.out.println(Stu.class.hashCode());
    }
    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        System.out.println("终结了");
    }
}

检测内存泄露的原理

检测内存泄漏的关键是要能截获住对分配内存和释放内存的函数的调用。截获住这两个函数,我们就能跟踪每一块内存的生命周期,比如,每当成功的分配一块内存后,就把它的指针加入一个全局的list中;每当释放一块内存,再把它的指针从list中删除。这样,当程序结束的时候,list中剩余的指针就是指向那些没有被释放的内存。这里只是简单的描述了检测内存泄漏的基本原理,详细的算法可以参见Steve Maguire的<<Writing Solid Code>>。  

如果要检测堆内存的泄漏,那么需要截获住malloc/realloc/free和new/delete就可以了(其实new/delete最终也是用malloc/free的,所以只要截获前面一组即可)。对于其他的泄漏,可以采用类似的方法,截获住相应的分配和释放函数。比如,要检测BSTR的泄漏,就需要截获SysAllocString/SysFreeString;要检测HMENU的泄漏,就需要截获CreateMenu/ DestroyMenu。(有的资源的分配函数有多个,释放函数只有一个,比如,SysAllocStringLen也可以用来分配BSTR,这时就需要截获多个分配函数)。

在Windows平台下,检测内存泄漏的工具常用的一般有三种,MS C-Runtime Library内建的检测功能;外挂式的检测工具,诸如,Purify,BoundsChecker等;利用Windows NT自带的Performance Monitor。这三种工具各有优缺点,MS C-Runtime Library虽然功能上较之外挂式的工具要弱,但是它是免费的;Performance Monitor虽然无法标示出发生问题的代码,但是它能检测出隐式的内存泄漏的存在,这是其他两类工具无能为力的地方。

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • Java在Word中插入上标和下标的实现方法

    Java在Word中插入上标和下标的实现方法

    在某些情况下,你可能需要在Microsoft Word中插入上标和下标。例如,当你正在创建一个涉及科学公式的学术文件时,在这篇文章中,你将学习如何使用Spire.Doc for Java库在Word文档中插入上标和下标,需要的朋友可以参考下
    2022-10-10
  • Java各种比较对象的方式的对比总结

    Java各种比较对象的方式的对比总结

    比较对象是面向对象编程语言的一个基本特征.在本教程中,我们将介绍Java语言的一些特性,这些特性允许我们比较对象.此外,我们还将研究外部库中的这些特性,需要的朋友可以参考下
    2021-06-06
  • Java使用Preference类保存上一次记录的方法

    Java使用Preference类保存上一次记录的方法

    这篇文章主要介绍了Java使用Preference类保存上一次记录的方法,较为详细的分析了Preference类的使用技巧,需要的朋友可以参考下
    2015-05-05
  • JAVA中的Token 基于Token的身份验证实例

    JAVA中的Token 基于Token的身份验证实例

    这篇文章主要介绍了JAVA中的Token 基于Token的身份验证实例,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-08-08
  • 详解Maven项目Dependencies常见报错及解决方案

    详解Maven项目Dependencies常见报错及解决方案

    这篇文章主要介绍了详解Maven项目Dependencies常见报错及解决方案,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-11-11
  • 使用Java代码实现Redis和数据库数据同步

    使用Java代码实现Redis和数据库数据同步

    这篇文章主要介绍了使用Java代码实现Redis和数据库数据同步问题,文中通过代码示例给大家讲解的非常详细,对大家的学习或工作有一定的帮助,需要的朋友可以参考下
    2024-06-06
  • Mybatis拦截器实现一种百万级轻量分表方案

    Mybatis拦截器实现一种百万级轻量分表方案

    这篇文章主要介绍了Mybatis拦截器实现一种百万级轻量分表方案,需要的朋友可以参考下
    2024-02-02
  • Spring AOP的概念与实现过程详解

    Spring AOP的概念与实现过程详解

    AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,可通过运行期动态代理实现程序功能的统一维护的一种技术。AOP是 Spring框架中的一个重要内容
    2023-02-02
  • java实现合并单元格的同时并导出excel示例

    java实现合并单元格的同时并导出excel示例

    这篇文章主要给大家介绍了关于java实现合并单元格的同时并导出excel的相关资料,文中先进行了简单的介绍,之后给出了详细的示例代码,相信对大家具有一定的参考价值,需要的朋友们下面来一起看看吧。
    2017-03-03
  • java中hashCode、equals的使用方法教程

    java中hashCode、equals的使用方法教程

    hashCode()和equals()定义在Object类中,这个类是所有java类的基类,所以所有的java类都继承这两个方法。下面这篇文章主要给大家介绍了关于java中hashCode、equals的使用方法,需要的朋友可以参考下。
    2017-12-12

最新评论