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

目录
  • java内存管理关系及内存泄露原理
    • java对象和内存的关系
    • 创建对象
    • null的作用
    • 内存泄露
  • 检测内存泄露的原理

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虽然无法标示出发生问题的代码,但是它能检测出隐式的内存泄漏的存在,这是其他两类工具无能为力的地方。

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

(0)

相关推荐

  • 详解Java内存溢出的几种情况

    JVM(Java虚拟机)是一个抽象的计算模型.就如同一台真实的机器,它有自己的指令集和执行引擎,可以在运行时操控内存区域.目的是为构建在其上运行的应用程序提供一个运行环境.JVM可以解读指令代码并与底层进行交互:包括操作系统平台和执行指令并管理资源的硬件体系结构. 1. 前言 JVM提供的内存管理机制和自动垃圾回收极大的解放了用户对于内存的管理,大部分情况下不会出现内存泄漏和内存溢出问题.但是基本不会出现并不等于不会出现,所以掌握Java内存模型原理和学会分析出现的内存溢出或内存泄漏,对于使用J

  • Java中关于内存泄漏出现的原因汇总及如何避免内存泄漏(超详细版)

    Android 内存泄漏总结 内存管理的目的就是让我们在开发中怎么有效的避免我们的应用出现内存泄漏的问题.内存泄漏大家都不陌生了,简单粗俗的讲,就是该被释放的对象没有释放,一直被某个或某些实例所持有却不再被使用导致 GC 不能回收.最近自己阅读了大量相关的文档资料,打算做个 总结 沉淀下来跟大家一起分享和学习,也给自己一个警示,以后 coding 时怎么避免这些情况,提高应用的体验和质量. 我会从 java 内存泄漏的基础知识开始,并通过具体例子来说明 Android 引起内存泄漏的各种原因,以

  • java内存泄漏与内存溢出关系解析

    这篇文章主要介绍了java内存泄漏与内存溢出关系解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 内存溢出 out of memory,是指程序在申请内存时,没有足够的内存空间供其使用,出现out of memory: 内存泄露 memory leak,是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光. memory leak会最终会导致out of memory!

  • 浅谈Java编程中的内存泄露情况

    必须先要了解的 1.c/c++是程序员自己管理内存,Java内存是由GC自动回收的. 我虽然不是很熟悉C++,不过这个应该没有犯常识性错误吧. 2.什么是内存泄露? 内存泄露是指系统中存在无法回收的内存,有时候会造成内存不足或系统崩溃. 在C/C++中分配了内存不释放的情况就是内存泄露. 3.Java存在内存泄露 我们必须先承认这个,才可以接着讨论.虽然Java存在内存泄露,但是基本上不用很关心它,特别是那些对代码本身就不讲究的就更不要去关心这个了. Java中的内存泄露当然是指:存在无用但是垃

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

    目录 java内存管理关系及内存泄露原理 java对象和内存的关系 创建对象 null的作用 内存泄露 检测内存泄露的原理 java内存管理关系及内存泄露原理 这可能是最近写的博客中最接近底层的了.闲言少叙,进入正题. java对象和内存的关系 首先,我们要知道下面几条真理(自己总结的) 一个完整的建立对象流程是 1声明对象,2开辟内存空间,3将对象和内存空间建立联系. 一个对象只能对应一个内存空间,一个内存空间可以对应很多对象 回收一个内存空间 .如果,这个内存空间没有任何一个对象和他有联系.

  • C++内存管理之简易内存池的实现

    目录 什么是内存池? 它的实现过程为: 初步实现 使用嵌入指针改进 更简化:static allocator macor for static allocator 什么是内存池? 频繁的调用 malloc 会影响运行效率以及产生额外的 cookie, 而内存池的思想是预先申请一大块内存,当有内存申请需求时,从内存池中取出一块内存分配给目标对象. 它的实现过程为: 预先申请 chunk 大小的内存池, 将内存池划按照对象大小划分成多个内存块.以链表的形式,即通过指针将内存块相连,头指针指向第一个空

  • 深入解析PHP内存管理之谁动了我的内存

    首先让我们看一个问题: 如下代码的输出, 复制代码 代码如下: var_dump(memory_get_usage());$a = "laruence";var_dump(memory_get_usage());unset($a);var_dump(memory_get_usage());输出(在我的个人电脑上, 可能会因为系统,PHP版本,载入的扩展不同而不同):int(90440)int(90640)int(90472) 注意到 90472-90440=32, 于是就有了各种的结论

  • Java中管理资源的引用队列相关原理解析

    当对象改变其可达性状态时,对该对象的引用就可能会被置于引用队列(reference queue)中.这些队列被垃圾回收器用来与我们的代码沟通有关对象可达性变化的情况.这些队列是探测可达性变化的最佳方式,尽管我们也可以通过检查get方法的返回值是不是null来探测对象的可达性变化. 引用对象在构造时可以与特定队列建立关联.Reference的每一个子类都提供了如下形式的构造器: .public Strength Reference (T referent, ReferenceQueueq):该方法

  • 深入理解JVM自动内存管理

    目录 一.前言 1.1 计算机==>操作系统==>JVM 1.1.1 虚拟与实体(对上图的结构层次分析) 1.1.2 Java程序执行(对上图的箭头流程分析) 二.JVM内存空间与参数设置 2.1 运行时数据区 2.2 关于StackOverflowError和OutOfMemoryError 2.2.1 StackOverflowError 2.2.2 OutOfMemoryError 2.3 JVM堆内存和非堆内存 2.3.1 堆内存和非堆内存 2.3.2 JVM堆内部构型(新生代和老年代

  • C#内存管理CLR深入讲解(上篇)

    半年之前,PM让我在部门内部进行一次关于“内存泄露”的专题分享,我为此准备了一份PPT.今天无意中将其翻出来,觉得里面提到的关于CLR下关于内存管理部分的内存还有点意思.为此,今天按照PPT的内容写了一篇文章.本篇文章不会在讨论那些我们熟悉的话题,比如“值类型引用类型具有怎样的区别?”.“垃圾回收分为几个步骤?”.“Finalizer和Dispose有何不同”.等等,而是讨论一些不同的内容.整篇文章分上下两篇,上篇主要谈论的是“程序集(Assembly)和应用程序域(AppDomain)”.也许

  • 关于JVM翻越内存管理的墙

    目录 JVM运行时数据区域 程序计数器 Java虚拟机栈 栈桢 本地方法栈 Java堆 分配缓冲区TLAB(Thread Local Allocation Buffer) Java堆的大小设定 方法区 运行时常量池 小结 JVM垃圾回收机制 判断对象存活 引用计数算法 可达性分析算法 几种引用方式 垃圾回收算法 标记清除算法 标记复制算法 标记整理法 分代收集算法 内存回收策略 1.新生代的分配和回收 2.大对象直接进入老年代 3.长期存活的对象将进入老年代 参考 对于Java程序员来说,在虚拟

  • JavaScript内存管理与闭包实例详解

    目录 1. 内存管理的理解 1.1 认识内存管理 1.2 JavaScript的内存管理 2. 垃圾回收(GC) 2.1 认识垃圾回收 2.2 GC算法 – 引用计数 2.3 GC算法 – 标记清除 2.4 其他算法优化补充 3. 闭包的概念理解 3.1 JavaScript的函数式编程 3.2 定义 4. 闭包的内存流程 5. 闭包的内存泄漏 5.1 认识内存泄露 5.2 内存泄露的测试 5.3 浏览器的优化 总结 1. 内存管理的理解 1.1 认识内存管理 不管什么样的编程语言,在代码的执行

  • 解析PHP中的内存管理,PHP动态分配和释放内存

    摘要 内存管理对于长期运行的程序,例如服务器守护程序,是相当重要的影响:因此,理解PHP是如何分配与释放内存的对于创建这类程序极为重要.本文将重点探讨PHP的内存管理问题. 一. 内存在PHP中,填充一个字符串变量相当简单,这只需要一个语句"<?php $str = 'hello world '; ?>"即可,并且该字符串能够被自由地修改.拷贝和移动.而在C语言中,尽管你能够编写例如"char *str = "hello world ";&qu

  • 深入探讨PHP中的内存管理问题

    一. 内存 在PHP中,填充一个字符串变量相当简单,这只需要一个语句"<?php $str = 'hello world '; ?>"即可,并且该字符串能够被自由地修改.拷贝和移动.而在C语言中,尽管你能够编写例如"char *str = "hello world ";"这样的一个简单的静态字符串:但是,却不能修改该字符串,因为它生存于程序空间内.为了创建一个可操纵的字符串,你必须分配一个内存块,并且通过一个函数(例如strdup()

随机推荐