我们正处于一个信息大暴发的时代,每天都能产生数以百万计的新闻资讯!
虽然有大数据推荐,但面对海量数据,通过我们的调研发现,在一个小时的时间里,您通常无法真正有效地获取您感兴趣的资讯!
头条新闻资讯订阅,旨在帮助您收集感兴趣的资讯内容,并且在第一时间通知到您。可以有效节约您获取资讯的时间,避免错过一些关键信息。
【编者按】本文介绍了一个使用了 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 instanceLeaking: YES (Activity#mDestroyed istrue)
当打开一个内存泄漏追踪日志时,我首先会看底部的对象,了解它的生命周期,这将帮助我理解内存泄漏追踪中的其他对象是否应该有相同的生命周期。
在底部,我们看到:╰→ com.example.MainActivityinstanceLeaking: 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 instanceLeaking: 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 instanceLeaking: 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 时遇到过内存泄漏的问题?
▶短短 5 天,Python 开发的“Twitter 杀手”Threads 用户过亿,增幅猛超 ChatGPT,马斯克暴怒!
▶ 开源商业化,走出“射手假说”迷雾
▶ ChatGPT 点燃向量数据库赛道,刚刚,Zilliz Cloud 云服务重磅发布! 返回搜狐,查看更多
责任编辑:
以上内容为资讯信息快照,由td.fyun.cc爬虫进行采集并收录,本站未对信息做任何修改,信息内容不代表本站立场。
快照生成时间:2023-07-12 15:45:09
本站信息快照查询为非营利公共服务,如有侵权请联系我们进行删除。
信息原文地址: