如何避免在Java?中使用雙括號(hào)初始化
結(jié)論先行
避免像這樣,在 Java 中使用雙括號(hào)初始化:
new HashMap<String, String>() {{
put("key", value);
}};內(nèi)存泄漏追蹤
我最近正在 LeakCanary 看到了以下內(nèi)存泄漏追蹤信息:
┬───
│ 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)當(dāng)打開一個(gè)內(nèi)存泄漏追蹤日志時(shí),我首先會(huì)看底部的對(duì)象,了解它的生命周期,這將幫助我理解內(nèi)存泄漏追蹤中的其他對(duì)象是否應(yīng)該有相同的生命周期。
在底部,我們看到:
╰→ com.example.MainActivity instance
Leaking: YES (Activity#mDestroyed is true)Activity 已經(jīng)被銷毀,應(yīng)該已被垃圾回收器給回收掉了,但它仍駐留在內(nèi)存中。
此時(shí),我開始在內(nèi)存泄漏追蹤日志中尋找已知類型,并嘗試弄清楚它們是否屬于同一個(gè)被銷毀的范圍(=> 正在泄漏)或更高的范圍(=> 沒有泄漏)。
在頂部,我們看到:
├─ com.bugsnag.android.Client instance │ Leaking: UNKNOWN
我們的 BugSnag 客戶端是一個(gè)用于分析崩潰報(bào)告單例,由于每個(gè)應(yīng)用我們創(chuàng)建一個(gè)實(shí)例,所以它沒有泄漏。
├─ com.bugsnag.android.Client instance │ Leaking: NO
所以我們現(xiàn)在需要轉(zhuǎn)變焦點(diǎn),特別關(guān)注從最后一個(gè) Leaking: NO 到第一個(gè) 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 客戶端保持了一個(gè)面包屑的環(huán)形緩沖區(qū)。這些應(yīng)該保留在內(nèi)存中,它們也沒有泄漏。
所以讓我們跳過上述內(nèi)容,從下面這里繼續(xù)分析:
├─ com.bugsnag.android.BreadcrumbInternal instance │ Leaking: NO
我們只需要關(guān)注從最后一個(gè) Leaking: NO 到第一個(gè)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:內(nèi)存泄漏追蹤通過面包屑實(shí)現(xiàn)的元數(shù)據(jù)字段。MainActivity$1實(shí)例是java.util.HashMap的匿名子類:MainActivity$1是在MainActivity中定義的HashMap的匿名子類。它是從MainActivity.java中定義的第一個(gè)匿名類(因?yàn)槭?$1)。this$0:每個(gè)匿名類都有一個(gè)隱式字段引用到定義它的外部類,這個(gè)字段被命名為this$0。
也就是說:記錄到 BugSnag 的面包屑之一有一個(gè)元數(shù)據(jù)映射,這是一個(gè) HashMap 的匿名子類 ,它保留對(duì)外部類的引用,這個(gè)外部類就是被銷毀的 Activity 。
讓我們看看我們?cè)?MainActivity 中記錄面包屑的地方:
void logSavingTicket(String ticketId) {
Map<String, Object> metadata = new HashMap<String, Object>() {{
put("ticketId", ticketId);
}};
bugsnagClient.leaveBreadcrumb("Saving Ticket", metadata, LOG);
}這段代碼利用了一個(gè)被稱為“雙括號(hào)初始化” 的有趣的 Java 代碼塊 。它允許你創(chuàng)建一個(gè) HashMap,并通過添加代碼到 HashMap 的匿名子類的構(gòu)造函數(shù)中同時(shí)初始化它。
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);
}實(shí)際上被編譯為:
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);
}結(jié)果,這個(gè) breadcrumb 就一直持有對(duì)已銷毀的 activity 實(shí)例的引用。
總結(jié)
盡管使用 Java 的雙括號(hào)初始化看起來很"炫酷",但它會(huì)無故地額外創(chuàng)建類,可能會(huì)導(dǎo)致內(nèi)存泄漏。因此避免在 Java 中使用雙括號(hào)初始化。
你可以用下面這種更安全的方式來解決這個(gè)問題:
Map<String, Object> metadata = new HashMap<>();
metadata.put("ticketId", ticketId);
bugsnagClient.leaveBreadcrumb("Saving Ticket", metadata, LOG);或者利用 Collections.singletonMap() 進(jìn)一步簡化代碼:
Map<String, Object> metadata = singletonMap("ticketId", ticketId);
bugsnagClient.leaveBreadcrumb("Saving Ticket", metadata, LOG);或者,直接將文件轉(zhuǎn)換為 Kotlin。
到此這篇關(guān)于如何避免在Java 中使用雙括號(hào)初始化的文章就介紹到這了,更多相關(guān)java雙括號(hào)初始化內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
java使用elasticsearch分組進(jìn)行聚合查詢過程解析
這篇文章主要介紹了java使用elasticsearch分組進(jìn)行聚合查詢過程解析,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-02-02
Java 入門圖形用戶界面設(shè)計(jì)之事件處理上
圖形界面(簡稱GUI)是指采用圖形方式顯示的計(jì)算機(jī)操作用戶界面。與早期計(jì)算機(jī)使用的命令行界面相比,圖形界面對(duì)于用戶來說在視覺上更易于接受,本篇精講Java語言中關(guān)于圖形用戶界面的事件處理2022-02-02
Spring Boot中優(yōu)雅的獲取yml文件工具類
今天小編就為大家分享一篇關(guān)于Spring Boot中優(yōu)雅的獲取yml文件工具類,小編覺得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來看看吧2018-12-12
Java實(shí)現(xiàn)雪花算法(snowflake)
這篇文章主要介紹了Java實(shí)現(xiàn)雪花算法(snowflake),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-08-08
Java如何實(shí)現(xiàn)HTTP斷點(diǎn)續(xù)傳功能
其實(shí)斷點(diǎn)續(xù)傳的原理很簡單,就是在Http的請(qǐng)求上和一般的下載有所不同而已,本文將詳細(xì)介紹Java如何實(shí)現(xiàn)HTTP斷點(diǎn)續(xù)傳功能,需要的朋友可以參考下2012-11-11

