private+java
【编者按】本文介绍了一个使用了 Java 的双括号初始化语法导致内存泄漏的案例。作者分析了泄漏的原因,提出了几种解决的方法,并给出了代码示例。
链接:https://blog.p-y.wtf/avoid-java-double-brace-initialization
未经允许,禁止转载!
作者 | Pierre-Yves Ricau 责编 | 明明如月责编 | 夏萌出品 | CSDN(ID:CSDNnews)避免像这样,在 Java 中使用双括号初始化:
new HashMap<String, String>() {{ put("key", value);}};内存泄漏追踪我最近正在 LeakCanary 看到了以下内存泄漏追踪信息:
┬───│ GC Root: Global variable in native code│├─ 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$1 instance│ Leaking: UNKNOWN│ Anonymous subclass of java.util.HashMap│ ↓ MainActivity$1.this$0│ ~~~~~~╰→ com.example.MainActivity instance Leaking: YES (Activity#mDestroyed is true)当打开一个内存泄漏追踪日志时,我首先会看底部的对象,了解它的生命周期,这将帮助我理解内存泄漏追踪中的其他对象是否应该有相同的生命周期。在底部,我们看到:
╰→ com.example.MainActivity instance Leaking: YES (Activity#mDestroyed is true)Activity 已经被销毁,应该已被垃圾回收器给回收掉了,但它仍驻留在内存中。
此时,我开始在内存泄漏追踪日志中寻找已知类型,并尝试弄清楚它们是否属于同一个被销毁的范围(=> 正在泄漏)或更高的范围(=> 没有泄漏)。
在顶部,我们看到:
├─ com.bugsnag.android.Client instance│ Leaking: UNKNOWN我们的 BugSnag 客户端是一个用于分析崩溃报告单例,由于每个应用我们创建一个实例,所以它没有泄漏。
├─ com.bugsnag.android.Client instance│ 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$1 instance│ 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.BreadcrumbInternal instance│ Leaking: NO我们只需要关注从最后一个 Leaking: NO 到第一个Leaking: YES 的部分:
…├─ com.bugsnag.android.BreadcrumbInternal instance│ Leaking: NO│ ↓ BreadcrumbInternal.metadata│ ~~~~~~~~├─ com.example.MainActivity$1 instance│ Leaking: UNKNOWN│ Anonymous subclass of java.util.HashMap│ ↓ MainActivity$1.this$0│ ~~~~~~╰→ com.example.MainActivity instance Leaking: YES (Activity#mDestroyed is true)BreadcrumbInternal.metadata :内存泄漏追踪通过面包屑实现的元数据字段。
MainActivity$1 实例是 java.util.HashMap 的匿名子类:MainActivity$1 是在MainActivity 中定义的 HashMap 的匿名子类。它是从 MainActivity.java 中定义的第一个匿名类(因为是 $1 )。
this$0:每个匿名类都有一个隐式字段引用到定义它的外部类,这个字段被命名为 this$0 。
也就是说:记录到 BugSnag 的面包屑之一有一个元数据映射,这是一个 HashMap 的匿名子类 ,它保留对外部类的引用,这个外部类就是被销毁的 Activity 。
让我们看看我们在 MainActivity 中记录面包屑的地方:
void logSavingTicket(String ticketId) { Map<String, Object> metadata = new HashMap<String, Object>() {{ put("ticketId", ticketId); }}; bugsnagClient.leaveBreadcrumb("Saving Ticket", metadata, LOG);}这段代码利用了一个被称为“双括号初始化” 的有趣的 Java 代码块 。它允许你创建一个 HashMap,并通过添加代码到 HashMap 的匿名子类的构造函数中同时初始化它。
new HashMap<String, Object>() {{ put("ticketId", ticketId);}};Java 的匿名类总是隐式地引用其外部类。因此,这段代码:
void logSavingTicket(String ticketId) { Map<String, Object> metadata = new HashMap<String, Object>() {{ put("ticketId", ticketId); }}; bugsnagClient.leaveBreadcrumb("Saving Ticket", metadata, LOG);}实际上被编译为:
class MainActivity$1 extends HashMap<String, Object> { private final MainActivity this$1; MainActivity$1(MainActivity this$1, String ticketId) { this.this$1 = this$1; put("ticketId", ticketId); }}void logSavingTicket(String ticketId) { Map<String, Object> metadata = new MainActivity$1(this, ticketId); bugsnagClient.leaveBreadcrumb("Saving Ticket", metadata, LOG);}结果,这个 breadcrumb 就一直持有对已销毁的 activity 实例的引用。
总结尽管使用 Java 的双括号初始化看起来很"炫酷",但它会无故地额外创建类,可能会导致内存泄漏。因此避免在 Java 中使用双括号初始化。
你可以用下面这种更安全的方式来解决这个问题:
Map<String, Object> metadata = new HashMap<>();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 时遇到过内存泄漏的问题?
","gnid":"935ec0a37404a822a","img_data":[{"flag":2,"img":[{"desc":"","height":"80","s_url":"https://p0.ssl.img.360kuai.com/t0160bfdc18dd41df77_1.gif","title":"","url":"https://p0.ssl.img.360kuai.com/t0160bfdc18dd41df77.gif","width":"640"},{"desc":"","height":"567","title":"","url":"https://p0.ssl.img.360kuai.com/t013a1cce5c704dd9d1.jpg","width":"1080"}]}],"original":0,"pat":"art_src_1,sexf,sex4,sexc,disu_label,fts0,sts0","powerby":"hbase","pub_time":1689127089000,"pure":"","rawurl":"http://zm.news.so.com/30f9e88b7a1f66afc843236f7a2db8a7","redirect":0,"rptid":"32be6709d10398a9","rss_ext":[],"s":"t","src":"CSDN","tag":[{"clk":"ktechnology_1:java","k":"java","u":""},{"clk":"ktechnology_1:android","k":"android","u":""}],"title":"如何避免在 Java 中使用双括号初始化
费祁泻4414java中private和public,final代表什么意思? -
艾贷盼13554161006 ______ 您好,提问者: private:代表私有的,修饰变量只能在本类访问,private方法子类不能继承. public:是最大权限,都可以访问,是公共的. final:最终的,如果变量定义为final必须赋初始值,而且值不能改变,final方法,子类不能实现.
费祁泻4414java中的private -
艾贷盼13554161006 ______ 这是因为你的get那些方法是public的, JAVA bean里面的特性, field定义为private, 提供public方法,就可以访问了
费祁泻4414java中内部接口声明为private有什么用? -
艾贷盼13554161006 ______ 接口就是为了给别的类实现的啊,接口怎么可能声明为private的?虽然代码上你可以这么写,但是没有意义啊.你说的是不是接口内部的成员变量方法等用的private声明的. private声明就是仅在本类本接口使用,别人用不了.你得提供set,get方法才能用的了
费祁泻4414java的类文件中存在private类吗 -
艾贷盼13554161006 ______ private是一个关键字,是一种权限修饰符可以修饰类,属性和方法. 不能以private来命名类,不存在private类
费祁泻4414java中 private 的class有什么用处》 -
艾贷盼13554161006 ______ private 修饰符 是私有的意思,如果用在变量前面 这个变量就是私有变量, 如果用在方面前面 这个方法就是自由方法. 私有变量只允许定义该变量的类或者方法访问,而不允许其他方法或者类直接访问. 方法也一样,定义私有方法,只允许定义该方法的类访问,而其他类不能够直接访问. 定义私有方式 可以增加安全性!防止其他类来访问关键或者很重要的变量.
费祁泻4414java中private是防止被访问,但又提供接口支持访问修改,这不是多此一举吗 -
艾贷盼13554161006 ______ 其实是不矛盾的,我们需要的是可控访问. 假如你有个类中含有属性是身份证号,你不希望别人从这个类外部随意修改你的身份证号,所以你要封装这个属性成private. 但是对于那些没有身份证号的人,或是要更改身份证号的人.你又有这样的需求让他们申请新身份证号.于是你可以提供给他们一个接口,比如申请新的身份证号这个方法.通过这个方法你可以对于访问做很好的检查与限制. 一般要想让外界读写private属性,类会提供相应的getter和setter方法,不同于public属性,你可以在getter和setter方法中加入更多逻辑,比如setter方法中的null value的检查.远比public更具安全性,可控性.
费祁泻4414java代码中private什么作用? -
艾贷盼13554161006 ______ 说明 name 和age 是私有属性, 只能在类内部访问, 不能从外部直接访问,
费祁泻4414java中的"private"是什么意思
艾贷盼13554161006 ______ import javax.swing.JFrame是引入swing组件中的窗体JFrame
费祁泻4414Java中,构造函数设置权限为private的是单例函数吗? -
艾贷盼13554161006 ______ 不是,构造函数为private的话表示该构造函数只有自己能用,也就是说继承它的之类不能用父类的构造函数,他们必须定义自己的构造函数.不定义的话就会报错.
费祁泻4414java中private关键字 -
艾贷盼13554161006 ______ p.name="zhangsan";是错误的 因为是私有的 你无法直接这么赋值 你自己也说 用setget对外提供访问 所以是 p.setName("zhangsan");