首页 >>  正文

java中array

来源:baiyundou.net   日期:2024-09-21

【编者按】本文介绍了一个使用了 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 instanceLeaking: UNKNOWN

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

├─ com.bugsnag.android.Client instanceLeaking: 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 instanceLeaking: 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":"cache","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 中使用双括号初始化

狐发斧3756在Java中怎么理解array的一些句子
山姜雁18956766926 ______ 定义1个int类型的变量temp 设置temp变量值是x数组的最后1个数值 因为数组后面的"[x]"下标 下标规定是从0开始,到实际的长度-1,和实际的位置对应 例如: 数组下标:0 1 2 3 4 5 实际位置:1 2 3 4 5 6 由此可以看出,x.length-1的这个下标位置是当前x数组的最后1个值 故取出的值是x数组的最后1个值

狐发斧3756请问java中array与toarray怎么使用
山姜雁18956766926 ______ List list = new List(); list.add("aaa"); int a[] = list.toArray(new int[0]);

狐发斧3756java中array如何初始化
山姜雁18956766926 ______ 构造函数是 私有的,不能直接调用的! 通过 调用 静态方法,newInstance来 生成 该类的一个实例, 这个函数的第一个参数 是 创建的数组的类型, 第二个参数是 数组的形式 :数字就是 一维数组; 定义多维要这样定义: //dims的长度就表示了...

狐发斧3756Java当中的Array和Arrays的区别到底是什么 -
山姜雁18956766926 ______ 是两个不同的类,Arrays貌似是个处理数组的工具类,至于Array真心没用过,不过Array在reflect和sql包里面有各有一个,前者应该是和反射有关,后者应该是和数据库有关

狐发斧3756JAVA中数组的形参和实参 -
山姜雁18956766926 ______ 形式参数和实际参数 函数的参数分为形参和实参两种.在本小节中,进一步介绍形参、实参的特点和两者的关系.形参出现在函数定义中,在整个函数体内都可以使用,离开该函数则不能使用.实参出现在主调函数中,进入被调函数后,实参变...

狐发斧3756数组的英文单词是Array,为什么Java中喜欢用Arrays来表示数组,这有什么原因吗? -
山姜雁18956766926 ______ 没人用Arrays来代表数组,在Java中Arrays代表的是“数组操作类”. 同样Collection代表集合,Collections代表“集合操作类”. 在Arrays中有许多操作数组的实用方法,如排序,填充,输出,拷贝,二分查找,相等比较等.

狐发斧3756java的JSONArray如何使用? -
山姜雁18956766926 ______ 从json数组中得到相应java数组,如果要获取java数组中的元素,只需要遍历该数组. 数组内容如下:/*** 从json数组中得到相应java数组* JSONArray下的toArray()方法的使用* @param str* @return*/ public static Object[] getJsonToArray(String...

狐发斧3756Array,List,Set和Map的异同 -
山姜雁18956766926 ______ 1. Array:一般表示是数组,但是在java中有Array这样一个类java.lang.reflect.Array.2. List:是一个接口.此接口的用户可以对列表中每个元素的插入位置进行精确地控制.用户可以根据元素的整数索引(在列表中的位置)访问元素,并搜索列...

狐发斧3756java中 list和array哪个效率高?map是如何取值的? -
山姜雁18956766926 ______ array效率高,map是Key-Value的,用法:1、保存值:map.set("userId", "zmk"),把value为zmk的字符串保存到map里面,对应的key为userId2、取值:map.get("userId")这时返回zmk

狐发斧3756关于Java中Array.sort()排序原理,越详细越好! -
山姜雁18956766926 ______ 是 Arrays.sort(a); 吧 给你看源码 ============= /* */ public static void sort(int[] paramArrayOfInt) /* */ { /* 96 */ sort1(paramArrayOfInt, 0, paramArrayOfInt.length); /* */ } // /* */ private static void sort1(int[] paramArrayOfInt, int paramInt1, int paramInt...

(编辑:自媒体)
关于我们 | 客户服务 | 服务条款 | 联系我们 | 免责声明 | 网站地图 @ 白云都 2024