成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

一個(gè)優(yōu)秀程序員不可避免的問(wèn)題:內(nèi)存泄漏

tanglijun / 1361人閱讀

摘要:前言內(nèi)存泄漏,一個(gè)說(shuō)大不大說(shuō)下不小的瑕疵。所以今天咱們來(lái)聊一聊中的內(nèi)存泄漏。主線程進(jìn)行耗時(shí)操作,每一個(gè)開(kāi)發(fā)者都明白這意味著什么所以內(nèi)存泄漏足夠嚴(yán)重,其危害還是很嚴(yán)重的。

前言

內(nèi)存泄漏,一個(gè)說(shuō)大不大說(shuō)下不小的瑕疵。作為開(kāi)發(fā)者,我們都很清楚內(nèi)存泄漏是我們代碼問(wèn)題導(dǎo)致的。但是話說(shuō)回來(lái),泄漏后果會(huì)很嚴(yán)重嘛?這不好說(shuō),如果我們不泄漏Bitmap這種大內(nèi)存的對(duì)象,那么修補(bǔ)內(nèi)存泄漏就像雞肋一樣,“食之無(wú)味,棄之可惜”。 就比如說(shuō)我們項(xiàng)目組,近2000w的DAU,只要不明顯影響用戶體驗(yàn),一切以上需求為主…

但是這作為一個(gè)996福報(bào)碼農(nóng),不能只挖坑,不填坑,畢竟技術(shù)債都是要還的。所以今天咱們來(lái)聊一聊Android中的內(nèi)存泄漏。這篇文章總結(jié)翻譯了外國(guó)友人的一篇文章:原文如下

techbeacon.com/app-dev-tes…

一、理論

先上一張圖:

解釋一下這張圖,每個(gè)Android(或Java)應(yīng)用程序都有一個(gè)起點(diǎn)(GC Root),從這個(gè)點(diǎn)中實(shí)例化對(duì)象、調(diào)用方法。。一些對(duì)象直接引用GC Root,另一些對(duì)象又引用了這些對(duì)象。因此,形成了引用鏈,就像上圖一樣。因此垃圾收集器從GC Root開(kāi)始并遍歷直接或間接鏈接到GC Root的對(duì)象。在此過(guò)程結(jié)束時(shí),脫離GC Root的對(duì)象/對(duì)象鏈將被回收。

接下來(lái)咱們?cè)傧肓硪粋€(gè)問(wèn)題:

什么是內(nèi)存泄漏?

有了上圖,理解內(nèi)存泄漏的概念就很簡(jiǎn)單,說(shuō)白了就是:長(zhǎng)生命周期對(duì)象A持有了短生命周期的對(duì)象B,那么只要A不脫離GC Root的鏈,那么B對(duì)象永遠(yuǎn)沒(méi)有可能被回收,因此B就泄漏了。

有什么危害?

危害的話,如開(kāi)篇所說(shuō)。如果泄漏的內(nèi)存很小,幾字節(jié),幾kb….對(duì)于現(xiàn)在的機(jī)器性能,就像星爵打滅霸…“傷害”基本無(wú)視。但是如果泄漏的足夠多,普通的GC無(wú)法回收這些泄漏的內(nèi)存,那么堆將持續(xù)增加,當(dāng)堆足夠大的時(shí)候,就會(huì)觸發(fā)“stop-the-world” GC,直接在主線程進(jìn)行耗時(shí)的GC。

主線程進(jìn)行耗時(shí)操作,每一個(gè)android開(kāi)發(fā)者都明白這意味著什么….

所以內(nèi)存泄漏足夠嚴(yán)重,其危害還是很嚴(yán)重的。

二、實(shí)踐

對(duì)于我們?nèi)粘i_(kāi)發(fā)來(lái)說(shuō),有比較多的場(chǎng)景稍不注意就會(huì)存在內(nèi)存泄漏的風(fēng)險(xiǎn)。讓我們一起留意一下:

2.1、內(nèi)部類Inner classes

內(nèi)部類存在內(nèi)存泄漏的風(fēng)險(xiǎn),是一個(gè)老生常談的話題。說(shuō)白了就是因?yàn)槲覀冊(cè)趎ew一個(gè)內(nèi)部類時(shí),編譯器會(huì)在編譯時(shí)讓這個(gè)內(nèi)部類的實(shí)例持有外部對(duì)象。

這也就是,為啥我們的內(nèi)部類可以引用到外部類變量、方法的原因。

上段代碼:

public class BadActivity extends Activity {

??? private TextView mMessageView;

??? @Override
??? protected void onCreate(Bundle savedInstanceState) {
??????? super.onCreate(savedInstanceState);
??????? setContentView(R.layout.layout_bad_activity);
??????? mMessageView = (TextView) findViewById(R.id.messageView);

??????? new LongRunningTask().execute();
??? }

??? private class LongRunningTask extends AsyncTask<Void, Void, String> {

??????? @Override
??????? protected String doInBackground(Void... params) {
??????????? return "Am finally done!";
??????? }

??????? @Override
??????? protected void onPostExecute(String result) {
??????????? mMessageView.setText(result);
??????? }
??? }
}

大家應(yīng)該都能看出這里的問(wèn)題吧。作為非靜態(tài)內(nèi)部類的LongRunningTask,會(huì)持有BadActivity。并且LongRunningTask是一個(gè)長(zhǎng)時(shí)間任務(wù),也就是說(shuō),在這個(gè)任務(wù)沒(méi)有完成時(shí),BadActivity是不會(huì)被回收的,因此我們的BadActivity就被泄漏了。那么怎么改呢?

解決原理

首先我不能讓LongRunningTask持有BadActivity。那么我們需要使用靜態(tài)內(nèi)部類(static class)。這樣的確不會(huì)持有BadActivity,但是問(wèn)題來(lái)了,我們LongRunningTask不持有BadActivity,也就意味著沒(méi)辦法引用到BadActivity中的變量,那么我們的更新UI的操作就做不了,也就是說(shuō)還是要顯示的傳一個(gè)BadActivity中我們需要的變量進(jìn)來(lái)…但是這樣有造成了同樣的泄漏問(wèn)題。

因此,我們需要對(duì)傳入的變量使用WeakReference進(jìn)行包一層。但發(fā)生GC的時(shí)候,告訴GC收集器“我”可以被回收。

上改造后的代碼:

public class GoodActivity extends Activity {

??? private AsyncTask mLongRunningTask;
??? private TextView mMessageView;

??? @Override
??? protected void onCreate(Bundle savedInstanceState) {
??????? super.onCreate(savedInstanceState);
??????? setContentView(R.layout.layout_good_activity);
??????? mMessageView = (TextView) findViewById(R.id.messageView);

??????? mLongRunningTask = new LongRunningTask(mMessageView).execute();
??? }

??? @Override
??? protected void onDestroy() {
??????? super.onDestroy();
??????? mLongRunningTask.cancel(true);
??? }

??? private static class LongRunningTask extends AsyncTask<Void, Void, String> {

??????? private final WeakReference messageViewReference;

??????? public LongRunningTask(TextView messageView) {
??????????? this.messageViewReference = new WeakReference<>(messageView);
??????? }

??????? @Override
??????? protected String doInBackground(Void... params) {
??????????? String message = null;
??????????? if (!isCancelled()) {
??????????????? message = "I am finally done!";
??????????? }
??????????? return message;
??????? }

??????? @Override
??????? protected void onPostExecute(String result) {
??????????? TextView view = messageViewReference.get();
??????????? if (view != null) {
??????????????? view.setText(result);
??????????? }
??????? }
??? }
}
2.2、匿名類 Anonymous classes

這一類和2.1很類似。本質(zhì)都是持有外部對(duì)象的引用。

上一段很常見(jiàn)的代碼:

public class MoviesActivity extends Activity {

??? private TextView mNoOfMoviesThisWeek;

??? @Override
??? protected void onCreate(Bundle savedInstanceState) {
??????? super.onCreate(savedInstanceState);
??????? setContentView(R.layout.layout_movies_activity);
??????? mNoOfMoviesThisWeek = (TextView) findViewById(R.id.no_of_movies_text_view);

??????? MoviesRepository repository = ((MoviesApp) getApplication()).getRepository();
??????? repository.getMoviesThisWeek()
??????????????? .enqueue(new Callback>() {
???????????????????
??????????????????? @Override
??????????????????? public void onResponse(Call> call,
?????????????????????????????????????????? Response> response) {
??????????????????????? int numberOfMovies = response.body().size();
??????????????????????? mNoOfMoviesThisWeek.setText("No of movies this week: " + String.valueOf(numberOfMovies));
??????????????????? }

??????????????????? @Override
??????????????????? public void onFailure(Call> call, Throwable t) {
??????????????????????? // Oops.
??????????????????? }
??????????????? });
??? }
}
2.3、注冊(cè)Listener
SingleInstance.setMemoryLeakListener(new OnMemoryLeakListener(){
	//…..
})

這里寫了段很常見(jiàn)的偽碼,一個(gè)單例的對(duì)象,register了一個(gè)Listener,并且這個(gè)Listener被單例的一個(gè)成員變量引用。

OK,那么問(wèn)題很明顯了。單例作為靜態(tài)變量,肯定是一直存在的。而其內(nèi)部持有了Listener,而Listener作為一個(gè)匿名類,有持有了外部對(duì)象的引用。因此這條GC鏈上的所有對(duì)象都不會(huì)被釋放。

解決也很簡(jiǎn)單,適當(dāng)?shù)臅r(shí)機(jī),在單例中將Listener的引用置為null。這樣,Listener和單例之間的引用關(guān)系斷了,Listener鏈上的所有內(nèi)容就可以被正常釋放掉了。也就是咱們常做的在onDestory()進(jìn)行unRegisterListener的操作。

類似不注意的內(nèi)容,還包括Lambda。不過(guò)有一點(diǎn)值得注意的,在Kotlin的Lambda中,如果我們沒(méi)有使用外部對(duì)象的變量或者方法,那么Kotlin在編譯時(shí),這個(gè)Lambda是不會(huì)持有外部對(duì)象的引用的。也算是Kotlin的一些優(yōu)化吧

2.4、Contexts

上下文的濫用,也是泄漏的大客戶。不過(guò)大家針對(duì)這類問(wèn)題應(yīng)該比較熟悉。

比如:長(zhǎng)時(shí)間存活的對(duì)象,不建議持有Activity的context,而是使用ApplicationContext。如果ApplicationContext沒(méi)辦法完成業(yè)務(wù),那么就需要好好考慮一下:這個(gè)長(zhǎng)時(shí)間存活的對(duì)象,為什么必須要持有Activity的context。它設(shè)計(jì)的是否合理,是否它應(yīng)該是一個(gè)長(zhǎng)時(shí)間存活的對(duì)象(比如單例)。

尾聲

關(guān)于內(nèi)存泄漏,還是需要咱們平時(shí)多注意,對(duì)自己寫的每一行代碼都多思考。畢竟這東西“不是病,但疼起來(lái)真要命”。

我是一個(gè)應(yīng)屆生,最近和朋友們維護(hù)了一個(gè)公眾號(hào),內(nèi)容是我們?cè)趶膽?yīng)屆生過(guò)渡到開(kāi)發(fā)這一路所踩過(guò)的坑,以及我們一步步學(xué)習(xí)的記錄,如果感興趣的朋友可以關(guān)注一下,一同加油~

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/7353.html

相關(guān)文章

  • 優(yōu)秀代碼必知必會(huì)(一)?

    摘要:但由于模式本身有嚴(yán)重的缺陷,由于構(gòu)造方法在多次調(diào)用中被分割,導(dǎo)致可能處于不一致的狀態(tài),并且還需要額外增加工作以確保線程安全。方法必須遵從類指定的常規(guī)約定,將不同的哈希碼分配給不同的實(shí)例對(duì)象。 1.使用靜態(tài)工廠方法替代構(gòu)造方法 靜態(tài)工廠方法的優(yōu)點(diǎn): 不像構(gòu)造方法,它是有名字的。 它不需要每次調(diào)用時(shí)都創(chuàng)建一個(gè)新對(duì)象。 它可以返回 其返回類型的任何子類型的對(duì)象。 返回對(duì)象的類可以根...

    ChanceWong 評(píng)論0 收藏0
  • JavaScript中內(nèi)存泄漏以及如何處理

    摘要:本文將會(huì)討論中的內(nèi)存泄漏以及如何處理,方便大家在使用編碼時(shí),更好的應(yīng)對(duì)內(nèi)存泄漏帶來(lái)的問(wèn)題。當(dāng)內(nèi)存不再需要時(shí)進(jìn)行釋放大部分內(nèi)存泄漏問(wèn)題都是在這個(gè)階段產(chǎn)生的,這個(gè)階段最難的問(wèn)題就是確定何時(shí)不再需要已分配的內(nèi)存。中的相同對(duì)象稱為全局。 隨著現(xiàn)在的編程語(yǔ)言功能越來(lái)越成熟、復(fù)雜,內(nèi)存管理也容易被大家忽略。本文將會(huì)討論JavaScript中的內(nèi)存泄漏以及如何處理,方便大家在使用JavaScript...

    itvincent 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<