Java匿名内部类导致内存泄露的原因与解决方案详解

目录
  • 简介
  • 为什么要持有外部类
  • 实例:持有外部类
    • 代码
    • 编译查看class
    • 查看字节码
  • 什么时候会内存泄露
  • 不会内存泄漏的方案
    • 方案1:不返回内部类对象引用
    • 方案2:匿名内部类改为静态的

简介

说明

本文用示例介绍匿名内部类会导致内存泄漏的原因及其解决方案。

相关网址

普通内部类的内存泄露:Java内部类持有外部类导致内存泄露--原因/解决方案

为什么要持有外部类

Java 语言中,非静态内部类的主要作用有两个:

当匿名内部类只在外部类(主类)中使用时,匿名内部类可以让外部不知道它的存在,从而减少了代码的维护工作。

当匿名内部类持有外部类时,它就可以直接使用外部类中的变量了,这样可以很方便的完成调用,如下代码所示:

package org.example.a;

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

public class Demo {
    private static String name = "Tony";
    public static void main(String[] args) {
        List<String> list = new ArrayList<String>() {{
            add("a");
            add("b");
            add(name);
        }};
        System.out.println(list);
    }
}

实例:持有外部类

代码

package org.example.a;

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

class Test{
    public List<String> createList() {
        List<String> list = new ArrayList<String>() {{
            add("a");
            add("b");
        }};
        return list;
    }
}

public class Demo {
    public static void main(String[] args) {
        System.out.println(new Test().createList());
    }
}

编译查看class

命令:javac Demo.java

结果:

Idea查看Test$1.class(可以发现:持有了一个外部类Test对象)

package org.example.a;

import java.util.ArrayList;

class Test$1 extends ArrayList<String> {
    Test$1(Test var1) {
        this.this$0 = var1;
        this.add("a");
        this.add("b");
    }
}

Idea查看Test.class

package org.example.a;

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

class Test {
    Test() {
    }

    public List<String> createList() {
        ArrayList var1 = new ArrayList<String>() {
            {
                this.add("a");
                this.add("b");
            }
        };
        return var1;
    }
}

Idea查看Demo.class

package org.example.a;

public class Demo {
    public Demo() {
    }

    public static void main(String[] var0) {
        System.out.println((new Test()).createList());
    }
}

查看字节码

命令

javap -c Test$1.class

结果

Compiled from "Demo.java"
class org.example.a.Test$1 extends java.util.ArrayList<java.lang.String> {
  final org.example.a.Test this$0;
 
  org.example.a.Test$1(org.example.a.Test);
    Code:
       0: aload_0
       1: aload_1
       2: putfield      #1                  // Field this$0:Lorg/example/a/Test;
       5: aload_0
       6: invokespecial #2                  // Method java/util/ArrayList."<init>":()V
       9: aload_0
      10: ldc           #3                  // String a
      12: invokevirtual #4                  // Method add:(Ljava/lang/Object;)Z
      15: pop
      16: aload_0
      17: ldc           #5                  // String b
      19: invokevirtual #4                  // Method add:(Ljava/lang/Object;)Z
      22: pop
      23: return
}

分析

关键代码的在 putfield 这一行,此行表示有一个对 Test 的引用被存入到 this$0 中,也就是说这个匿名内部类持有了外部类的引用。

代码验证

package org.example.a;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;

class Test{
    public List<String> createList() {
        List<String> list = new ArrayList<String>() {{
            add("a");
            add("b");
        }};
        return list;
    }
}

public class Demo {
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
        List<String> list = new Test().createList();
        // 获取一个类的所有字段
        Field field = list.getClass().getDeclaredField("this$0");
        // 设置允许方法私有的 private 修饰的变量
        field.setAccessible(true);
        System.out.println(field.get(list).getClass());
    }
}

打个断点(注意:我这里是用Object模式(右键Variables里的this=> View as=> Object))

可见:它是持有外部类Test的对象的。

执行结果:

class org.example.a.Test

什么时候会内存泄露

非静态方法返回匿名内部类的引用可能导致内存泄露,例:

​class Test{
    public List<String> createList() {
        List<String> list = new ArrayList<String>() {{
            add("a");
            add("b");
        }};
        return list;
    }
}

跟上边“普通内部类” 一样,若Test类里边有比较大的对象,而这些大对象根本没被用到,则会内存泄露。

不会内存泄漏的方案

方案1:不返回内部类对象引用

业务直接处理,不返回内部类对象引用

class Test{
    public void createList() {
        List<String> list = new ArrayList<String>() {{
            add("a");
            add("b");
        }};
        System.out.println(list);
    }
}

方案2:匿名内部类改为静态的

将匿名内部类改为静态的。此时,内部类不会持有外部类的对象的引用。

为什么这样就不会内存泄露了?

因为匿名内部类是静态的之后,它所引用的对象或属性也必须是静态的了,因此就可以直接从 JVM 的 Method Area(方法区)获取到引用而无需持久外部对象了。

代码

package org.example.a;

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

class Test{
    public static List<String> createList() {
        List<String> list = new ArrayList<String>() {{
            add("a");
            add("b");
        }};
        return list;
    }
}

public class Demo {
    public static void main(String[] args) {
        System.out.println(Test.createList());
    }
}

执行结果

[a, b]

编译

命令:javac Demo.java

结果

Idea查看Test$1.class

package org.example.a;

import java.util.ArrayList;

final class Test$1 extends ArrayList<String> {
    Test$1() {
        this.add("a");
        this.add("b");
    }
}

到此这篇关于Java匿名内部类导致内存泄露的原因与解决方案详解的文章就介绍到这了,更多相关Java内存泄露内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • java内部类之成员内部类、局部内部类和匿名内部类用法及说明

    目录 内部类概念 分类 成员内部类 使用成员内部类 间接方式访问内部类 直接方式访问内部类 外部类和内部类变量重名问题 局部内部类 修饰内部类的权限 匿名内部类 测试类 一 匿名内部内应用 总结 内部类概念 一个类中包含另外一个类. 分类 成员内部类. 局部内部类(包含匿名内部类). 成员内部类 定义格式: 修饰符 class 类名称 { 修饰符 class 类名称 { //... } //... } 注意: 内部类使用外部,可以随意访问,但是外部类使用内部类要借助内部类的对象. 使用成员内部类

  • 关于JavaEE匿名内部类和Lambda表达式的注意事项

    目录 前言 一.匿名内部类 注意事项: 二.Lambda表达式 1.背景 2.函数式接口 3.Lambda表达式的语法 4.Lambda表达式的基本使用 (1)无返回值无参数 (2)无返回值有参数 (3)有返回值无参数 (4)有返回值有参数 总结 前言 上一篇介绍了内部类的前三种类型,链接如下: 关于JavaEE内部类的部分注意事项 接下来将详细介绍匿名内部类以及Lambda表达式的用法和注意事项. 一.匿名内部类 定义在方法中(方法的形参或者实参),没有任何权限修饰符,甚至连类名称都没有的内部

  • Java内部类与匿名内部类

    目录 一.内部类 什么是内部类 成员内部类 访问特点 二.匿名内部类 前提 格式 使用方式 一.内部类 什么是内部类 将一个类A定义在另一个类B里面,里面的那个类A就称为内部类,B则称为外部类. 成员内部类 成员内部类 :定义在类中方法外的类. 定义格式: class 外部类 {class 内部类{}} 在描述事物时,若一个事物内部还包含其他事物,就可以使用内部类这种结构.比如,电脑类Computer 中包含中央处理器类Cpu ,这时, Cpu 就可以使用内部类来描述,定义在成员位置. 代码举例

  • JAVA匿名内部类(Anonymous Classes)的具体使用

    目录 1.前言 2.匿名内部类 2.1 定义匿名内部类 2.2 匿名内部类的语法 3.访问作用域内的局部变量.定义和访问匿名内部类成员 4.匿名内部类实例 写在最后: 1.前言 匿名内部类在我们JAVA程序员的日常工作中经常要用到,但是很多时候也只是照本宣科地用,虽然也在用,但往往忽略了以下几点:为什么能这么用?匿名内部类的语法是怎样的?有哪些限制?因此,最近,我在完成了手头的开发任务后,查阅了一下JAVA官方文档,将匿名内部类的使用进行了一下总结,案例也摘自官方文档.感兴趣的可以查阅官方文档(

  • Java基础之匿名内部类、包装类

    目录 1.匿名内部类 2.Object类简介 2.1 取得对象信息toString() 2.2 对象的比较equals() 2.3 Object接口引用数据类型 3.包装类 3.1 装箱与拆箱 3.2 字符串与基本数据类型的转换 3.3 包的定义 3.4 包的导入 4.访问控制权限 5.jar命令 1.匿名内部类 内部类:在一个类的内部定义了另外的类,称为内部类,匿名内部类指的是没有名字的内部类.为了清楚内部类的主要作用,下面首先观察一个代码. interface IMessage{ publi

  • 详解Java匿名内部类

    匿名内部类: 先举个例子吧,给大家看一下什么是匿名内部类,Endeavor刚刚接触的时候,觉得哇哦,好奇怪的样子,这也太别扭了吧,不知道大家是什么感觉. 为了进行对比,先举一个正常的类方法调用的例子(大家应该都看的懂吧): 输出结果为: 接下来便开始说正题吧,匿名内部类,通过名字,想必大家就知道什么是匿名内部类了吧, 1.定义:就是没有名字的内部类(内部类之前介绍过了哦). 2.使用内部类有什么好处呢,一句话就概括了:简化书写,至于是怎么简化的,哪里简化了等下再说. 3.先说一下什么时候使用匿名

  • Java匿名内部类和Lambda(->) 的多种写法总结

    引入: 最近使用到 Arrays.sort(); 看了他的重载方法(试着模仿一下) 就以这个玩出了许多的方式;如下:自定义排序 首先 写了个冒泡排序(备用) //给一个integres 的数组, 然后再给个 Comparator的接口 c /** * * @param integers 给一个 integres 的数组,作为要排序的数组 * @param c 接受一个 Comparator 接口 ,然后使用Comparator 重写的 compare 方法 */ public static vo

  • Java内部类持有外部类导致内存泄露的原因与解决方案详解

    目录 简介 为什么要持有外部类 实例:持有外部类 实例:不持有外部类 实例:内存泄露 不会内存泄露的方案 简介 说明 本文介绍Java内部类持有外部类导致内存泄露的原因以及其解决方案. 为什么内部类持有外部类会导致内存泄露? 非静态内部类会持有外部类,如果有地方引用了这个非静态内部类,会导致外部类也被引用,垃圾回收时无法回收这个外部类(即使外部类已经没有其他地方在使用了). 解决方案 1.不要让其他的地方持有这个非静态内部类的引用,直接在这个非静态内部类执行业务. 2.将非静态内部类改为静态内部

  • Java String index out of range:100错误解决方案详解

    问题出错情况:字符串截取长度,没有那么长的长度所以截取失败. 在这里进行debug之后可以看到,异常在substring中: 也就是判断字符串的时候报错:具体原因就是string字符串indexof的值本身只有5,然后在这里去取其第100 个字符作为截止,因此就会报这个错: 知识点:主要是堆String概念不清.下面针对字符串相关概念做一个简介. 针对上述问题解决办法: 到此这篇关于Java String index out of range:100错误解决方案详解的文章就介绍到这了,更多相关J

  • Java中ThreadLocal 导致内存 OOM 的原因分析

    目录 原因分析 正确的使用方式 原因分析 ThreadLocal 导致内存 OOM 的原因是什么? ThreadLocal 底层通过 ThreadLocalMap 存储数据 源码如下:  当我们使用ThreadLocal.set()时,set的value与key(即业务自己定义的ThreadLocal类)会存储在ThreadLocalMap的Entry[]数组里 源码如下: 其中Entry是实现了一个弱引用WeakReference,Entry的key(即业务方定义的 ThreadLocal类)

  • Java中的内存泄露问题和解决办法

    目录 为什么会产生内存泄漏? 内存泄漏对程序的影响? 如何检查和分析内存泄漏? 常见的内存泄漏及解决方法 1.单例造成的内存泄漏 2.非静态内部类创建静态实例造成的内存泄漏[已无] 3.Handler造成的内存泄漏 4.线程造成的内存泄漏 5.资源未关闭造成的内存泄漏 6.使用ListView时造成的内存泄漏 7.集合容器中的内存泄露 8.WebView造成的泄露 如何避免内存泄漏? 总结 (Memory Leak,内存泄漏) 为什么会产生内存泄漏? 当一个对象已经不需要再使用本该被回收时,另外

  • Java @Async注解导致spring启动失败解决方案详解

    前言 在这篇文章里,最后总结处,我说了会讲讲循环依赖中,其中一个类添加@Async有可能会导致注入失败而抛异常的情况,今天就分析一下. 一.异常表现,抛出内容 1.1循环依赖的两个class 1.CycleService1 @Service public class CycleService1 { @Autowired private CycleService2 cycleService2; @WangAnno @Async public void doThings() { System.out

  • C语言内存泄露很严重的解决方案

    目录 1.前言 2.内存泄漏问题原理 2.1堆内存在C代码中的存储方式 2.2堆内存的获取方法 2.3内存泄漏三要素 2.4内存释放误区 3.内存泄漏问题检视方法 1.前言 最近部门不同产品接连出现内存泄漏导致的网上问题,具体表现为单板在现网运行数月以后,因为内存耗尽而导致单板复位现象. 一方面,内存泄漏问题属于低级错误,此类问题遗漏到现网,影响很坏:另一方面,由于内存泄漏问题很可能导致单板运行固定时间以后就复位,只能通过批量升级才能解决,实际影响也很恶劣. 同时,接连出现此类问题,尤其是其中一

  • Java ThreadLocal原理解析以及应用场景分析案例详解

    目录 ThreadLocal的定义 ThreadLocal的应用场景 ThreadLocal的demo TheadLocal的源码解析 ThreadLocal的set方法 ThreadLocal的get方法 ThreadLocalMap的结构 ThreadLocalMap的set方法 ThreadLocalMap的getEntry方法 ThreadLocal的内存泄露 如何避免内存泄露呢 应用实例 实际应用二 总结 ThreadLocal的定义 JDK对ThreadLocal的定义如下: The

  • Java中的引用和动态代理的实现详解

    我们知道,动态代理(这里指JDK的动态代理)与静态代理的区别在于,其真实的代理类是动态生成的.但具体是怎么生成,生成的代理类包含了哪些内容,以什么形式存在,它为什么一定要以接口为基础? 如果去看动态代理的源代码(java.lang.reflect.Proxy),会发现其原理很简单(真正二进制类文件的生成是在本地方法中完成,源代码中没有),但其中用到了一个缓冲类java.lang.reflect.WeakCache<ClassLoader,Class<?>[],Class<?>

  • Java代码块与代码加载顺序原理详解

    这篇文章主要介绍了Java代码块与代码加载顺序原理详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 本文首先介绍几个基本的名次,然后介绍了三种代码块的特性和使用方法. 在面试大型公司时,如果遇到大型国企或者大的互联网私企,笔试中经常遇到代码块和代码加载顺序的笔试题.这里做一个总结,也方便各位小伙伴飙车不会飘. 名词解释 代码块 由 { } 包起来的代码,称为代码块 静态代码块 由 static { } 包起来的代码,称为静态代码块. 不同类型

  • java中Servlet监听器的工作原理及示例详解

    监听器就是一个实现特定接口的普通java程序,这个程序专门用于监听另一个java对象的方法调用或属性改变,当被监听对象发生上述事件后,监听器某个方法将立即被执行. 监听器原理 监听原理 1.存在事件源 2.提供监听器 3.为事件源注册监听器 4.操作事件源,产生事件对象,将事件对象传递给监听器,并且执行监听器相应监听方法 监听器典型案例:监听window窗口的事件监听器 例如:swing开发首先制造Frame**窗体**,窗体本身也是一个显示空间,对窗体提供监听器,监听窗体方法调用或者属性改变:

随机推荐