您当前的位置:首页 > 电脑百科 > 程序开发 > 语言 > JAVA

如何避免在 Java 中使用双括号初始化

时间:2023-07-12 17:03:29  来源:CSDN  作者:

【编者按】本文介绍了一个使用了 JAVA 的双括号初始化语法导致内存泄漏的案例。作者分析了泄漏的原因,提出了几种解决的方法,并给出了代码示例。

链接:https://blog.p-y.wtf/avoid-java-double-brace-initialization

作者 | Pierre-Yves Ricau责编 | 明明如月

责编 | 夏萌

出品 | CSDN(ID:CSDNnews)

结论先行

避免像这样,在 Java 中使用双括号初始化:

newHashMap< String, String> {{ put( "key", value); }};

内存泄漏追踪

我最近正在 LeakCanary看到了以下内存泄漏追踪信息:

┬─── │ GC Root: Global variable innativecode │ ├─ com.bugsnag.Android.AnrPlugin instance │ Leaking: UNKNOWN │ ↓ AnrPlugin.client │ ~~~~~~ ├─ com.bugsnag.android.Client instance │ Leaking: UNKNOWN │ ↓ Client.breadcrumbState │ ~~~~~~~~~~~~~~~ ├─ com.bugsnag.android.BreadcrumbState instance │ Leaking: UNKNOWN │ ↓ BreadcrumbState.store │ ~~~~~ ├─ com.bugsnag.android.Breadcrumb[] array │ Leaking: UNKNOWN │ ↓ Breadcrumb[ 494] │ ~~~~~ ├─ com.bugsnag.android.Breadcrumb instance │ Leaking: UNKNOWN │ ↓ Breadcrumb.impl │ ~~~~ ├─ com.bugsnag.android.BreadcrumbInternal instance │ Leaking: UNKNOWN │ ↓ BreadcrumbInternal.metadata │ ~~~~~~~~ ├─ com.example.MAInActivity$ 1instance │ Leaking: UNKNOWN │ Anonymous subclass of java.util.HashMap │ ↓ MainActivity$ 1. this$ 0 │ ~~~~~~ ╰→ com.example.MainActivity instance Leaking: YES (Activity#mDestroyed istrue)

当打开一个内存泄漏追踪日志时,我首先会看底部的对象,了解它的生命周期,这将帮助我理解内存泄漏追踪中的其他对象是否应该有相同的生命周期。

在底部,我们看到:

​​​​​​​╰→ com.example.MainActivityinstance Leaking: YES( Activity#mDestroyedistrue)

Activity已经被销毁,应该已被垃圾回收器给回收掉了,但它仍驻留在内存中。

此时,我开始在内存泄漏追踪日志中寻找已知类型,并尝试弄清楚它们是否属于同一个被销毁的范围(=> 正在泄漏)或更高的范围(=> 没有泄漏)。

在顶部,我们看到:

​​​​​​​├─ com.bugsnag.android.Clientinstance │ Leaking: UNKNOWN

我们的 BugSnag客户端是一个用于分析崩溃报告单例,由于每个应用我们创建一个实例,所以它没有泄漏。

​​​​​​​├─ com.bugsnag.android.Clientinstance │ Leaking: NO

所以我们现在需要转变焦点,特别关注从最后一个 Leaking: NO到第一个 Leaking: YES的部分:

… ├─ com.bugsnag.android.Client instance │ Leaking: NO │ ↓ Client.breadcrumbState │ ~~~~~~~~~~~~~~~ ├─ com.bugsnag.android.BreadcrumbState instance │ Leaking: UNKNOWN │ ↓ BreadcrumbState.store │ ~~~~~ ├─ com.bugsnag.android.Breadcrumb[] array │ Leaking: UNKNOWN │ ↓ Breadcrumb[494] │ ~~~~~ ├─ com.bugsnag.android.Breadcrumb instance │ Leaking: UNKNOWN │ ↓ Breadcrumb.impl │ ~~~~ ├─ com.bugsnag.android.BreadcrumbInternal instance │ Leaking: UNKNOWN │ ↓ BreadcrumbInternal.metadata │ ~~~~~~~~ ├─ com.example.MainActivity $1instance │ Leaking: UNKNOWN │ Anonymous subclass of java.util.HashMap │ ↓ MainActivity $1.this $0 │ ~~~~~~ ╰→ com.example.MainActivity instance Leaking: YES (Activity #mDestroyed is true)

BugSnag 客户端保持了一个面包屑的环形缓冲区。这些应该保留在内存中,它们也没有泄漏。

所以让我们跳过上述内容,从下面这里继续分析:

​​​​​​​├─ com.bugsnag.android.BreadcrumbInternalinstance │ Leaking: NO

我们只需要关注从最后一个 Leaking: NO到第一个Leaking: YES的部分:

… ├─ com.bugsnag.android.BreadcrumbInternal instance │ Leaking: NO │ ↓ BreadcrumbInternal.metadata │ ~~~~~~~~ ├─ com.example.MainActivity $1instance │ Leaking: UNKNOWN │ Anonymous subclass of java.util.HashMap │ ↓ MainActivity $1.this $0 │ ~~~~~~ ╰→ com.example.MainActivity instance Leaking: YES (Activity #mDestroyed is true)
  • BreadcrumbInternal.metadata :内存泄漏追踪通过面包屑实现的元数据字段。

也就是说:记录到 BugSnag 的面包屑之一有一个元数据映射,这是一个 HashMap的匿名子类 ,它保留对外部类的引用,这个外部类就是被销毁的 Activity 。

让我们看看我们在 MainActivity中记录面包屑的地方:

voidlogSavingTicket( StringticketId) { Map< String, Object> metadata = newHashMap< String, Object> {{ put( "ticketId", ticketId); }}; bugsnagClient.leaveBreadcrumb( "Saving Ticket", metadata, LOG); }

这段代码利用了一个被称为“双括号初始化” 的有趣的 Java 代码块 。它允许你创建一个 HashMap,并通过添加代码到HashMap的匿名子类的构造函数中同时初始化它。

newHashMap< String, Object> {{ put( "ticketId", ticketId); }};

Java 的匿名类总是隐式地引用其外部类。

因此,这段代码:

voidlogSavingTicket( StringticketId) { Map< String, Object> metadata = newHashMap< String, Object> {{ put( "ticketId", ticketId); }}; bugsnagClient.leaveBreadcrumb( "Saving Ticket", metadata, LOG); }

实际上被编译为:

​​​​​​​classMainActivity$1 extendsHashMap< String, Object> { private final MainActivity this$ 1;

MainActivity$ 1(MainActivity this$ 1, StringticketId) { this.this$ 1= this$ 1; put( "ticketId", ticketId); }}

voidlogSavingTicket( StringticketId) { Map< String, Object> metadata = newMainActivity$ 1( this, ticketId); bugsnagClient.leaveBreadcrumb( "Saving Ticket", metadata, LOG); }

结果,这个 breadcrumb 就一直持有对已销毁的 activity 实例的引用。

总结

尽管使用 Java 的双括号初始化看起来很"炫酷",但它会无故地额外创建类,可能会导致内存泄漏。因此避免在 Java 中使用双括号初始化。

你可以用下面这种更安全的方式来解决这个问题:

​​​​​​​Map< String, Object> metadata = newHashMap<>; metadata.put( "ticketId", ticketId); bugsnagClient.leaveBreadcrumb( "Saving Ticket", metadata, LOG);

或者利用 Collections.singletonMap进一步简化代码:

​​​​​​​Map< String, Object> metadata = singletonMap( "ticketId", ticketId); bugsnagClient.leaveBreadcrumb( "Saving Ticket", metadata, LOG);

或者,直接将文件转换为 Kotlin。

你是否在使用 Java 时遇到过内存泄漏的问题?



Tags:Java   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,不构成投资建议。投资者据此操作,风险自担。如有任何标注错误或版权侵犯请与我们联系(Email:2595517585@qq.com),我们将及时更正、删除。
▌相关推荐
【编者按】本文介绍了一个使用了 Java 的双括号初始化语法导致内存泄漏的案例。作者分析了泄漏的原因,提出了几种解决的方法,并给出了代码示例。链接:https://blog.p-y.wtf/avo...【详细内容】
2023-07-12  Tags: Java  点击:(0)  评论:(0)  加入收藏
1 背景前段时间组内针对 “拷贝实例属性是应该用 BeanUtils.copyProperties()还是 MapStruct” 这个问题进行了一次激烈的 battle。支持 MapStruct 的同学给出了他嫌弃 BeanUt...【详细内容】
2023-06-29  Tags: Java  点击:(24)  评论:(0)  加入收藏
N+1问题:N+1问题是指在使用关系型数据库时,在获取一组对象及其关联对象时,产生额外的数据库查询的问题。其中N表示要获取的主对象的数量,而在获取每个主对象的关联对象时,会产生...【详细内容】
2023-06-14  Tags: Java  点击:(35)  评论:(0)  加入收藏
Java IO(输入/输出)流是Java中最重要的API之一。它允许我们在程序中读写数据,并提供了一种灵活的方式来管理不同类型的数据源和目的地。在本文中,我们将深入探讨Java IO流的基础...【详细内容】
2023-04-14  Tags: Java  点击:(61)  评论:(0)  加入收藏
了解如何使用 Java 创建微服务架构。发现用于微服务的 Java 开发服务的好处、工具和最佳实践。嘿!那么,您听说过微服务架构吗?它是构建灵活、可扩展且易于维护的软件系统的现代...【详细内容】
2023-04-13  Tags: Java  点击:(59)  评论:(0)  加入收藏
1. 前言主要记录一些关于坐标和线段的计算方法。因为经常会碰见,需要在平面上,计算坐标点。例如两个坐标点之间的距离,两个线段是否平行,两个不相交的线段的交点。由于程序中的...【详细内容】
2023-04-04  Tags: Java  点击:(87)  评论:(0)  加入收藏
前言Java 8 中提供了许多函数式接口,包括Function、Consumer、Supplier、Predicate 等等。这 4 个接口就是本篇将要分享的内容,它们都位于 java.util.function 包下。 为什么...【详细内容】
2023-04-04  Tags: Java  点击:(77)  评论:(0)  加入收藏
Java 注解(Annotation)是一种元数据,它可以被添加到 Java 代码中,并可以提供额外的信息和指令。Java 注解可以用来描述类、方法、变量、参数等程序元素,它可以提供编译时检查、代...【详细内容】
2023-03-30  Tags: Java  点击:(80)  评论:(0)  加入收藏
一飞开源,介绍创意、新奇、有趣、实用的免费开源应用、系统、软件、硬件及技术,一个探索、发现、分享、使用与互动交流的开源技术社区平台。致力于打造活力开源社区,共建开源新...【详细内容】
2023-03-17  Tags: Java  点击:(125)  评论:(0)  加入收藏
1 尽可能使用基本类型而不是包装类型Long idNumber;long idNumber; // long 比 Long 占用更少的内存2 为变量选择合适的类型如果两种或多种类型满足功能需求,请使用占用内存...【详细内容】
2023-03-16  Tags: Java  点击:(73)  评论:(0)  加入收藏
▌简易百科推荐
【编者按】本文介绍了一个使用了 Java 的双括号初始化语法导致内存泄漏的案例。作者分析了泄漏的原因,提出了几种解决的方法,并给出了代码示例。链接:https://blog.p-y.wtf/avo...【详细内容】
2023-07-12    CSDN  Tags:Java   点击:(0)  评论:(0)  加入收藏
作者 | 京东云开发者-京东物流 王辰玮1 引言在我们的日常编程任务中,对于集合的制造和处理是必不可少的。当我们需要对于集合进行分组或查找的操作时,需要用迭代器对于集合进...【详细内容】
2023-07-12  京东云开发者    Tags:   点击:(2)  评论:(0)  加入收藏
我觉得 Java 在未来会被替代的可能性很小,但也不能掉以轻心,在后端开发领域,Go 已经在逐步蚕食 Java 得份额,今年非常火得 ai 模型领域相关,大部分代码也是基于 Python 编写。Jav...【详细内容】
2023-07-07  waynblog    Tags:Java   点击:(15)  评论:(0)  加入收藏
在Java中,继承是面向对象编程中的一个重要概念,它允许一个类(称为子类或派生类)继承另一个类(称为父类或基类)的属性和方法。通过继承,子类可以重用父类的代码,并可以在此基础上添...【详细内容】
2023-07-07    Java技术指北  Tags:Java   点击:(13)  评论:(0)  加入收藏
Java作为一门常用的编程语言,其基础知识的学习是非常重要的。而Java基础文档则是学习这门语言的必备资料之一。那么,Java基础文档内容一般包括什么呢?一、概述Java基础文档的概...【详细内容】
2023-07-03  动力节点Java程序员    Tags:Java   点击:(6)  评论:(0)  加入收藏
1 背景前段时间组内针对 “拷贝实例属性是应该用 BeanUtils.copyProperties()还是 MapStruct” 这个问题进行了一次激烈的 battle。支持 MapStruct 的同学给出了他嫌弃 BeanUt...【详细内容】
2023-06-29  京东云开发者    Tags:Java   点击:(24)  评论:(0)  加入收藏
sendfile实现的零拷贝,I/O发生了2次用户空间与内核空间的上下文切换,以及3次数据拷贝。其中3次数据拷贝中,包括了2次DMA拷贝和1次CPU拷贝。​1.并行与并发有什么区别?并行和并发...【详细内容】
2023-06-28  Java码农之路  今日头条  Tags:Java线程   点击:(23)  评论:(0)  加入收藏
Java过滤器是处于客户端与服务器资源文件之间的一道过滤网,在访问资源文件之前,通过一系列的过滤器可以对请求进行修改、判断等,把不符合规则的请求在中途拦截或修改;也可以对响...【详细内容】
2023-06-25    尚硅谷教育  Tags:Java   点击:(10)  评论:(0)  加入收藏
译者 | 布加迪Java生态系统里面有形形色色、五花八门的开源项目,这些项目是为了满足各种可以想象到的需求而开发的。我们很容易错过其中一些出色的项目。下面介绍七个开源Jav...【详细内容】
2023-06-15    51CTO  Tags:Java   点击:(36)  评论:(0)  加入收藏
N+1问题:N+1问题是指在使用关系型数据库时,在获取一组对象及其关联对象时,产生额外的数据库查询的问题。其中N表示要获取的主对象的数量,而在获取每个主对象的关联对象时,会产生...【详细内容】
2023-06-14  MobotStone    Tags:Java   点击:(35)  评论:(0)  加入收藏
站内最新
站内热门
站内头条