Chrome ile JavaScript bellek sızıntıları bulma


163

Bir Omurga görünümü oluşturan, bir olaya bir işleyici ekleyen ve kullanıcı tanımlı bir sınıfı başlatan çok basit bir test örneği oluşturdum. Bu örnekteki "Kaldır" düğmesini tıklatarak her şeyin temizleneceğini ve bellek sızıntısı olmaması gerektiğine inanıyorum.

Kod için bir jsfiddle burada: http://jsfiddle.net/4QhR2/

// scope everything to a function
function main() {

    function MyWrapper() {
        this.element = null;
    }
    MyWrapper.prototype.set = function(elem) {
        this.element = elem;
    }
    MyWrapper.prototype.get = function() {
        return this.element;
    }

    var MyView = Backbone.View.extend({
        tagName : "div",
        id : "view",
        events : {
            "click #button" : "onButton",
        },    
        initialize : function(options) {        
            // done for demo purposes only, should be using templates
            this.html_text = "<input type='text' id='textbox' /><button id='button'>Remove</button>";        
            this.listenTo(this,"all",function(){console.log("Event: "+arguments[0]);});
        },
        render : function() {        
            this.$el.html(this.html_text);

            this.wrapper = new MyWrapper();
            this.wrapper.set(this.$("#textbox"));
            this.wrapper.get().val("placeholder");

            return this;
        },
        onButton : function() {
            // assume this gets .remove() called on subviews (if they existed)
            this.trigger("cleanup");
            this.remove();
        }
    });

    var view = new MyView();
    $("#content").append(view.render().el);
}

main();

Ancak, gerçekte durumun bu olduğunu doğrulamak için Google Chrome'un profil oluşturucusunu nasıl kullanacağım belirsiz. Öbek profiler anlık görüntüsünde ortaya çıkan bir gazilyon şey var ve iyi / kötü olanı nasıl çözeceğimi bilmiyorum. Ben şimdiye kadar gördüğüm öğreticiler ya sadece "anlık görüntü profiler kullanmak" ya da bana tüm profiler nasıl çalıştığı hakkında ayrıntılı bir manifesto vermek söyle. Profil oluşturucuyu bir araç olarak kullanmak mümkün mü, yoksa her şeyin nasıl tasarlandığını gerçekten anlamak zorunda mıyım?

EDIT: Bunun gibi öğreticiler:

Gmail bellek sızıntısı düzeltmesi

DevTools'u kullanma

Gördüğüm kadarıyla, dışarıdaki daha güçlü materyallerin bazılarını temsil ediyorlar. Bununla birlikte, 3 Enstantane Tekniği kavramını tanıtmanın ötesinde , pratik bilgi açısından (benim gibi yeni başlayanlar için) çok az sunduklarını düşünüyorum. 'DevTools'u Kullanma' öğreticisi gerçek bir örnekle çalışmaz, bu yüzden meselelerin belirsiz ve genel kavramsal açıklaması aşırı derecede yararlı değildir. 'Gmail' örneğine gelince:

Yani bir sızıntı buldun. Şimdi ne olacak?

  • Profiller panelinin alt yarısında sızan nesnelerin tutma yolunu inceleyin

  • Tahsis yeri kolayca çıkarılamazsa (yani olay dinleyicileri):

  • Tahsis nesnesinin yapıcısını JS konsolu üzerinden ayırma için yığın izlemesini kaydetmek için kullanın

  • Kapanış mı Kullanıyorsunuz? İnşaat sırasında createStack özelliğini ayarlamak için mevcut uygun bayrağı (yani goog.events.Listener.ENABLE_MONITORING) etkinleştirin

Bunu okuduktan sonra kendimi daha karışık buluyorum, daha az değil. Ve yine, bu sadece beni anlatıyor etmek yapmak değil, şeyleri nasıl bunları yapmak için. Benim bakış açımdan, dışarıdaki tüm bilgiler ya çok belirsiz ya da sadece süreci zaten anlayan birine mantıklı geliyordu.

Bu daha spesifik konulardan bazıları, @Jonathan Naguin'in aşağıdaki cevabında gündeme gelmiştir .


2
Tarayıcılarda bellek kullanımını test etme hakkında hiçbir şey bilmiyorum, ancak görmediyseniz Addy Osmani'nin Chrome web denetçisi hakkındaki makalesi yardımcı olabilir.
Paul D.Waite

1
Öneri için teşekkürler, Paul. Ancak, kaldır'ı tıklatmadan önce bir anlık görüntü, ardından tıklatıldıktan sonra başka bir anlık görüntü aldığımda ve sonra 'makalesinde önerildiği gibi' anlık görüntü 1 ve 2 arasında ayrılan nesneleri 'seçtiğimde hala 2000'den fazla nesne var. Örneğin, benim için hiçbir anlam ifade etmeyen 4 'HTMLButtonElement' girişi var. Gerçekten, neler olduğu hakkında hiçbir fikrim yok.
EleventyOne

3
doh, bu kulağa çok faydalı gelmiyor. JavaScript gibi çöp toplanmış bir dilde, testiniz kadar ayrıntılı bir düzeyde bellekle ne yaptığınızı doğrulamak istemiyor olabilirsiniz. Bellek sızıntılarını kontrol etmenin daha iyi bir yolu, bir mainkez yerine 10.000 kez aramak ve sonunda kullanımda çok daha fazla bellek olup olmadığını görmek olabilir.
Paul D.Waite

3
@ PaulD.Waite Evet, belki. Ama bana öyle geliyor ki, sadece şunu söylemek (ya da söylememek) yerine, sorunun tam olarak ne olduğunu belirlemek için ayrıntılı bir analize ihtiyacım var: "Tamam, burada bir yerde bir hafıza sorunu var". Ve profillerini bu kadar ayrıntılı bir seviyede kullanabilmem gerektiği izlenimini
edindim

Yanıtlar:


205

Bellek sızıntılarını bulmak için iyi bir iş akışı , ilk olarak Loreena Lee ve Gmail ekibi tarafından bazı bellek sorunlarını çözmek için kullanılan üç anlık görüntü tekniğidir. Adımlar genel olarak:

  • Bir yığın anlık görüntü alın.
  • Şeyler yapmak.
  • Başka bir yığın anlık görüntü alın.
  • Aynı şeyleri tekrarlayın.
  • Başka bir yığın anlık görüntü alın.
  • Anlık Görüntü 3'ün "Özet" görünümünde Anlık Görüntüler 1 ve 2 arasında ayrılan nesnelere filtre uygulayın.

Örneğin, bu işlemi göstermek için kodu uyarladım ( burada bulabilirsiniz ) Başlat düğmesinin click olayına kadar Omurga Görünümü'nün oluşturulmasını geciktirir. Şimdi:

  • HTML'yi çalıştırın (bu adresi kullanarak yerel olarak kaydedilir ) ve bir anlık görüntü çekin.
  • Görünümü oluşturmak için Başlat'ı tıklayın.
  • Başka bir fotoğraf çekin.
  • Kaldır'ı tıklayın.
  • Başka bir fotoğraf çekin.
  • Anlık Görüntü 3'ün "Özet" görünümünde Anlık Görüntüler 1 ve 2 arasında ayrılan nesnelere filtre uygulayın.

Artık bellek sızıntılarını bulmaya hazırsınız!

Birkaç farklı renkteki düğümleri fark edeceksiniz. Kırmızı düğümlerin Javascript'ten kendilerine doğrudan referansları yoktur, ancak ayrı bir DOM ağacının parçası oldukları için canlıdırlar. Ağaçta Javascript'ten başvurulan bir düğüm olabilir (belki bir kapatma veya değişken olarak), ancak tüm DOM ağacının çöp toplanmasını tesadüfen engelliyor.

resim açıklamasını buraya girin

Ancak sarı düğümlerin Javascript'ten doğrudan referansları vardır. Javascript'inizdeki referansları bulmak için aynı müstakil DOM ağacındaki sarı düğümleri arayın. DOM penceresinden öğeye giden bir özellik zinciri olmalıdır.

Özellikle kırmızı olarak işaretlenmiş bir HTML Div öğesi görebilirsiniz. Öğeyi genişletirseniz, bunun bir "önbellek" işlevi tarafından başvurulduğunu göreceksiniz.

resim açıklamasını buraya girin

Satırı seçin ve konsolunuzda $ 0 yazın, gerçek işlevi ve konumu göreceksiniz:

>$0
function cache( key, value ) {
        // Use (key + " ") to avoid collision with native prototype properties (see Issue #157)
        if ( keys.push( key += " " ) > Expr.cacheLength ) {
            // Only keep the most recent entries
            delete cache[ keys.shift() ];
        }
        return (cache[ key ] = value);
    }                                                     jquery-2.0.2.js:1166

Öğenize referansta bulunulan yer burasıdır. Ne yazık ki yapabileceğiniz çok şey yok, jQuery'den bir iç mekanizmadır. Ancak, sadece test amacıyla, işlevi gidin ve yöntemi şu şekilde değiştirin:

function cache( key, value ) {
    return value;
}

Şimdi işlemi tekrarlarsanız kırmızı bir düğüm görmeyeceksiniz :)

Belgeler:


8
Çabanı takdir ediyorum. Gerçekten, üç enstantane tekniği düzenli olarak öğreticilerden bahsedilmektedir. Ne yazık ki, ayrıntılar genellikle dışarıda bırakılır. Örneğin, tanıtımı takdir $0benim için yeni konsolda fonksiyonu, - Elbette, ben yaptığına dair hiçbir fikrim yok ya Kullanmaya nasıl bildiğini ( $1yararsız iken görünüyor $2aynı şeyi yapmak görünüyor). İkinci olarak, #button in function cache()diğer düzinelerce satırı değil , satırı vurgulamayı nasıl bildiniz ? Son olarak, orada kırmızı düğümleri vardır NodeListve HTMLInputElementçok, ama onları çözemiyorum.
EleventyOne

7
cacheDiğerlerinin içermediği sırada satırın bilgi içerdiğini nasıl bildiniz ? Birinden daha düşük bir mesafeye sahip çok sayıda dal vardır cache. Ve HTMLInputElementbunun bir çocuk olduğunu nasıl bildiğinden emin değilim HTMLDivElement. İçinde referans görüyorum ("HTMLDivElement içinde yerel"), ama aynı zamanda kendisi ve HTMLButtonElementbana anlamsız iki s, başvuruyor . Bu örneğin cevabını tanımladığınız için teşekkür ederim, ancak bunu diğer konulara nasıl genelleştireceğiniz konusunda hiçbir fikrim yok.
EleventyOne

2
Bu garip, ben senin örneğini kullanıyordum ve senden daha farklı bir sonuç aldım (ekran görüntünüzden). Yine de, tüm yardımlarınız için çok teşekkür ederim. Şimdilik yeterli olduğumu düşünüyorum ve özel yardıma ihtiyacım olan gerçek bir hayat örneğim olduğunda, burada yeni bir soru oluşturacağım. Tekrar teşekkürler.
EleventyOne

2
0 $ 'a ilişkin açıklama burada bulunabilir: developer.chrome.com/devtools/docs/commandline-api#0-4
Sukrit Gupta

4
Ne anlama Filter objects allocated between Snapshots 1 and 2 in Snapshot 3's "Summary" view.geliyor?
K - SO'da toksisite artıyor.

8

Aşağıda bir jsfiddle bellek profili oluşturmayla ilgili bir ipucu verilmiştir: jsfiddle sonucunuzu izole etmek için aşağıdaki URL'yi kullanın, tüm jsfiddle çerçevesini kaldırır ve yalnızca sonucunuzu yükler.

http://jsfiddle.net/4QhR2/show/

Aşağıdaki belgeleri okuyana kadar, bellek sızıntılarını izlemek için Zaman Çizelgesi ve Profiler'in nasıl kullanılacağını asla anlayamadım. 'Nesne ayırma izleyicisi' başlıklı bölümü okuduktan sonra, 'Yığın Tahsislerini Kaydet' aracını kullanabildim ve bazı Müstakil DOM düğümlerini takip edebildim.

Sorun jQuery olay bağlama, omurga olay temsilci kullanarak geçerek düzeltildi. Anladığım kadarıyla, Backbone'un yeni sürümlerinin aradığınızda olayları sizin için otomatik olarak çözeceğini anladım View.remove(). Bazı demoları kendiniz yapın, tanımlamanız için bellek sızıntıları ile ayarlanırlar. Bu belgeleri inceledikten sonra hala alamıyorsanız, burada soru sormaya çekinmeyin.

https://developers.google.com/chrome-developer-tools/docs/javascript-memory-profiling


6

Temel olarak, yığın anlık görüntünüzün içindeki nesne sayısına bakmanız gerekir. İki anlık görüntü arasında nesne sayısı artarsa ​​ve nesneleri atarsanız, bellek sızıntısı olur. Tavsiyem, kodunuzda ayrılmayan olay işleyicileri aramaktır.


3
Örneğin, jsfiddle'ın yığın anlık görüntüsüne bakarsam, 'Kaldır'ı tıklamadan önce 100.000'den fazla nesne vardır. Benim jsfiddle kodunun gerçekte oluşturduğu nesneleri nerede ararım? Bunun Window/http://jsfiddle.net/4QhR2/showyararlı olabileceğini düşündüm , ama sadece sonsuz fonksiyonlar. Orada neler olduğu hakkında hiçbir fikrim yok.
EleventyOne

@EleventyOne: jsFiddle kullanmam. Neden test etmek için kendi bilgisayarınızda bir dosya oluşturmuyorsunuz?
Mavi Gökyüzü

1
@BlueSkies Buradaki insanlar aynı kod tabanından çalışabilsin diye bir jsfiddle yaptım. Bununla birlikte, test için kendi bilgisayarımda bir dosya oluşturduğumda, yığın anlık görüntüsünde hala 50.000'den fazla nesne var.
EleventyOne

@EleventyOne Bir yığın anlık görüntüsü size bellek sızıntısı olup olmadığı hakkında bir fikir vermez. En az iki taneye ihtiyacın var.
Konstantin Dinev

2
Aslında. Binlerce nesne olduğunda neyin aranacağını bilmenin ne kadar zor olduğunu vurgulamaktaydım.
EleventyOne


3

Geliştirici araçlarındaki Zaman Çizelgesi sekmesine de bakabilirsiniz. Uygulamanızın kullanımını kaydedin ve DOM Düğümü ve Etkinlik dinleyicisi sayısına dikkat edin.

Bellek grafiği gerçekten bir bellek sızıntısını gösteriyorsa, profilleyiciyi kullanarak neyin sızıntı yaptığını anlayabilirsiniz.



2

Bir yığın anlık görüntü alma tavsiyesini ikinciyim, bellek sızıntılarını tespit etmek için mükemmeller, krom mükemmel bir anlık görüntü işi yapıyor.

Derecem için araştırma projemde, 'katmanlarda' oluşturulmuş çok fazla veri üretmek zorunda olan etkileşimli bir web uygulaması inşa ediyordum, bu katmanların çoğu kullanıcı arayüzünde 'silinecek' ama bir nedenle hafıza Anlık görüntü aracı kullanılarak, JQuery nesne üzerinde bir başvuru tutmak olduğunu belirleme başardı (kaynak ben tetiklemeye çalışırken oldu .load() kapsam dışına rağmen rağmen referansı tutan olay ). Bu bilginin elimden tek başına kurtarılması, başkalarının kütüphanelerini kullanırken son derece kullanışlı bir araçtır ve GC'nin işini yapmasını engelleyen kalıcı referanslara sahip olursunuz.

DÜZENLEME: Ayrıca, anlık görüntü harcamak için harcanan zamanı en aza indirmek için hangi eylemleri gerçekleştireceğinizi planlamak, soruna neyin sebep olabileceğini varsaymak ve her senaryoyu test etmek, önce ve sonra anlık görüntüler yapmak yararlıdır.


0

Chrome Geliştirici araçlarını kullanarak bellek sızıntılarını belirlemeye ilişkin birkaç önemli not:

1) Chrome'un kendisi, şifre ve sayı alanları gibi belirli öğeler için bellek sızıntılarına sahiptir. https://bugs.chromium.org/p/chromium/issues/detail?id=967438 . Ayıklanan öğeleri ararken yığın anlık görüntünüzü kirlettiği için hata ayıklama sırasında bunları kullanmaktan kaçının.

2) Tarayıcı konsoluna herhangi bir şey kaydetmekten kaçının . Chrome, konsola yazılan nesneleri çöp toplamaz ve dolayısıyla sonucunuzu etkiler. Komut dosyanızın / sayfanızın başına aşağıdaki kodu yerleştirerek çıktıyı engelleyebilirsiniz:

console.log = function() {};
console.warn = console.log;
console.error = console.log;

3) Öbek anlık görüntülerini kullanın ve ayrılmış DOM öğelerini tanımlamak için "ayır" ı arayın. Nesneleri gezdirerek, id ve externalHTML dahil olmak üzere her öğenin tanımlanmasına yardımcı olabilecek tüm özelliklere erişebilirsiniz . Ayrılmış DOM öğesi hakkında ayrıntılar içeren JS Heap Snapshot'ın ekran görüntüsü Ayrılmış öğeler hala tanınamayacak kadar genelse, testinizi çalıştırmadan önce tarayıcı konsolunu kullanarak benzersiz kimlikler atayın, örn:

var divs = document.querySelectorAll("div");
for (var i = 0 ; i < divs.length ; i++)
{
    divs[i].id = divs[i].id || "AutoId_" + i;
}
divs = null; // Free memory

Şimdi, ile ayrılmış bir öğeyi tanımladığınızda, id = "AutoId_49" diyelim, sayfanızı yeniden yükleyelim, yukarıdaki snippet'i tekrar yürütebilir ve DOM denetçisini veya document.querySelector (..) kullanarak id = "AutoId_49" olan öğeyi bulalım. . Doğal olarak bu yalnızca sayfa içeriğiniz öngörülebilirse işe yarar.

Bellek sızıntılarını belirlemek için testlerimi nasıl çalıştırırım

1) Sayfa yükle (konsol çıkışı bastırılmış halde!)

2) Sayfada bellek sızıntılarına neden olabilecek şeyler yapın

3) Bir yığın anlık görüntüsü almak ve "ayır" ı aramak için Geliştirici Araçlarını kullanın

4) Öğeleri id veya externalHTML özelliklerinden tanımlamak için fareyle üzerine gelin


Ayrıca, tarayıcıda hata ayıklamayı daha zor hale getirdiğinden, küçültmeyi / çirkinleştirmeyi devre dışı bırakmak her zaman iyi bir fikirdir.
Jimmy Thomsen
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.