JavaScript

在 Windows 市集應用程式中管理記憶體

David Tepper

 

Windows 8 為了感受流體和活著,讓使用者在多個應用程式來完成各項任務和活動之間迅速切換。 使用者期望快速流行和不同的經驗,他們從來沒有想要感覺他們有等待應用程式,當他們需要使用它。 在此模型中,應用程式由使用者 ; 很少終止 相反他們經常是國的執行和暫停之間切換。 應用程式被帶到使用前景色,然後在當使用者切換到另一個應用程式移動到後臺 — — 和使用者,同時期望他們的機器不能減慢或感覺遲鈍,即使在他們打開更多和更多的應用程式。

在 Microsoft Windows 應用程式體驗團隊調查中,我們已經看到一些 Windows 存儲應用程式開始在長時間使用過程中遇到資源問題。 在應用程式中的記憶體管理錯誤可以隨著時間,從而導致不必要的記憶體使用方式並產生不利影響整體機複合。 在我們努力在我們自己的產品壁球這些 bug,我們已經確定了一些重複問題的模式,以及共同的修復和技術來逃脫出來。 在本文中,我將討論如何考慮記憶體管理在您 Windows 存儲的應用程式,以及如何識別潛在的記憶體洩漏。 我也將提供共同的問題,小組注意到,一些編纂的解決方案。

記憶體洩漏是什麼?

在應用程式中的資源,可以將回收既使用會導致任何方案被認為是記憶體洩漏。 換句話說,如果在 app 持有一塊記憶體系統的其餘部分將永遠無法使用,直到終止該應用程式,則應用程式本身沒有使用它,就有問題。 這是一個更廣泛的定義,比典型的詮釋的記憶體洩漏,"動態分配的記憶體中不可訪問,在代碼中,"但這也是更有用的因為它包含可以對使用者和系統產生負面影響的其他的類似的資源利用問題。 例如,如果 app 存儲來自各地的代碼可訪問的資料,但只有一次並且不釋放以後使用資料,它是根據這一定義的洩漏。

它是重要的是要記住,有時資料存儲在記憶體中將永遠不會僅僅因為使用者的操作在該特定實例中使用。 只要可能可用在該應用程式的整個存留期內或被釋放時不再需要此資訊,它沒有考慮到洩漏,儘管從未使用。

影響是什麼?

當機器都在資源可用性的天空到比賽的日子一去不復返了。 電腦越來越小,更便攜,較少的可用資源,比他們的前輩。 這從根本上是與日益普遍涉及切換之間多方面的經驗迅速,快點的 UI 並立即可用的所有內容,預期的使用模式。 今天,長時間的應用程式是眾多和活著。 與此同時,機器有較少的記憶體,支援他們,和性能的使用者期望從來沒有更高。

但不會洩漏幾百萬位元組真的使那麼大的差別呢? 嗯,這個問題不是幾百萬位元組洩露一次,那就是作為繼續使用該應用程式的記憶體洩漏在隨著時間的推移往往復合的代碼中。 如果方案導致無法恢復的資源,無法恢復的資源的數量將增長,通常沒有邊界,隨著使用者不斷重複這種情況。 這迅速降低整個系統的可用性,因為較少的記憶體可供其他進程,它導致特性差系統性能對您的應用程式的使用者。 記憶體洩漏是最嚴重的當他們出現在:

  • 常見任務 (如解碼下一幀的視頻)
  • 不需要使用者交互來啟動的任務 (例如,自動-定期保存文檔)
  • 對於長時間 (例如背景工作) 運行的方案

在這些情況下 (和一般) 洩漏可以極大地提高應用程式佔用的記憶體量。 不僅可以此鉛為整個系統的資源利用危機,它還可以使您的應用程式更有可能終止而不是在不使用時暫停使用。 終止應用程式以較長時間才能重新啟動比暫停應用程式,減少使用者可以體驗您的方案的易用性。 Windows 如何使用進程存留期管理器以回收未使用的應用程式中的記憶體的全部詳細資訊,請參閱在建築 Windows 8 的博客 bit.ly/JAqexg

因此,記憶體洩漏是壞 — — 但你如何找到他們嗎? 在何處以及如何,我將會在接下來的幾節中尋找這些問題,然後看一看為什麼會發生並你可以做他們的。

不同種類的記憶體

並非所有的位是平等地分配。 Windows 跟蹤的不同相符或將應用程式的記憶體使用,方便進行性能分析任務的視圖。 更好地瞭解如何檢測記憶體洩漏,是有用來瞭解這些不同的記憶體分類。 (本節假定作業系統記憶體管理通過分頁的一些知識)。

私人工作集頁面集當前正在使用您的應用程式存儲其自己唯一的資料。 當你認為的"我的應用程式的記憶體使用方式"時,這可能是你在想什麼。

共用工作集的一組頁您的應用程式是利用但不是擁有您的進程。 如果您的應用程式使用共用的運行時或框架,常見的 Dll 或其他多進程的資源,這些資源將一些的記憶體量。 共用的工作集是這些共用資源的措施。

共工作設置 (TWS) 有時簡稱為"一套工作",這是私人的工作集和共用的工作集的總和。

TWS 表示應用程式的全面影響的系統上,所以我將描述的測量技術將使用此號碼。 然而時追蹤潛在的問題,您可能會發現它有用,調查私營或共用分開,工作集,因為這可以告訴你是否正在洩漏的應用程式或應用程式使用的資源。

發現記憶體洩漏

發現您的應用程式使用每個類別中多少記憶體的最簡單方法是使用內置的 Windows 工作管理員。

  1. 通過按下 Ctrl + Shift + Esc,啟動工作管理員,按一下底部附近的更多詳細資訊。
  2. 按一下選項功能表項目,請確保選中了"總在最前"。 這可以防止您從背景去和暫停時您要查找在工作管理員中的應用程式。
  3. 啟動您的應用程式。 一旦應用程式將顯示在工作管理員中,按右鍵它並按一下"轉到詳細資訊"。
  4. 頂部附近,按右鍵任何列,去"選擇列"。
  5. 你就會看到這裡的選項為共用和專用工作集 (及其他),但暫時只是請確保選中了"工作集 (記憶體)"和按一下確定 (見圖 1)。
  6. 您將看到的值是 TWS 為您的應用程式。

Checking the Total Working Set in Windows Task Manager
檢查工作集在 Windows 工作管理員中的總圖 1

快速發現潛在的記憶體洩漏,留下您的應用程式和工作管理員打開並寫下您的應用程式 TWS。 現在您要測試的應用程式中選擇方案。 方案由典型的使用者會經常執行通常涉及不超過四個步驟 (頁面之間導航,執行搜索等等) 的行動。 作為使用者,並注意到任何增加 TWS 執行該方案。 然後,而無需關閉該應用程式,再通過該方案,從開頭開始。 做這 10 次,並記錄 TWS 之後的每一步。 它是正常的 TWS 增加首幾次反覆運算,然後高原。

您的應用程式記憶體使用方式是否增加每次執行了該方案,無需不斷重置到原來的水準嗎? 如果這樣,有可能在該方案中存在記憶體洩漏,要看一看下面的建議。 如果不,太棒了 ! 但是,請確保檢查您的應用程式,特別是那些很常見,或使用大量資源,如圖像的其他方案。 然而 ; 避免執行這一進程的虛擬機器上或通過遠端桌面 這些環境可以導致誤報,尋找洩漏時,增加您的記憶體使用率號碼超出其實際價值。

使用 Pre-Windows 8 記憶體洩漏偵查工具

您可能想知道是否你可以使用現有記憶體洩漏偵查工具來確定與您的 Windows 存儲應用程式問題。 這些工具將更新以與 Windows 8 的工作,除非它是很有可能他們會感到"困惑"正常關機 (這已被暫停所取代) 應用程式的缺乏。 要解決此問題,可以使用 AppObject"的退出"功能直接關閉在井然有序地,而不是強行關閉它通過外部終止應用程式:

  • C++—CoreApplication::Exit()
  • C#—Application.Current.Exit()
  • JavaScript—window.close()

當使用這種技術,請確保不要船您與此代碼中位置的產品。 您的應用程式不會調用任何中止和意志上的觸發器需要重新啟動的代碼 (而不是續會) 每次打開時。 這種技術應僅用於調試目的和之前提交到 Windows 存儲應用程式中刪除。

常見的記憶體洩漏來源

在本節中,我將討論一些常見的陷阱,我們看到開發人員運行到跨所有類型的應用程式和語言,以及如何能夠在您的應用程式處理這些問題。

事件處理常式事件處理常式是最常見的我們已經看到在 Windows 存儲應用程式中的記憶體洩漏來源。 根本的問題是缺乏瞭解有關事件處理常式的工作原理。 事件處理常式並不只是代碼獲取執行 ; 他們分配的資料物件。 它們保存引用的其他內容,與他們保持對引用可能不明顯。 從概念上講,具現化和註冊事件處理常式包括三個部分:

  1. 事件的來源。
  2. 事件處理常式方法 (執行)
  3. 承載該方法的物件

作為一個例子,讓我們看看 LeakyApp,顯示在一個叫做圖 2

圖 2 LeakyApp

public sealed partial class ItemDetailPage : 
  LeakyApp.Common.LayoutAwarePage
{
  public ItemDetailPage()
  {
    this.InitializeComponent();
  }
  Protected override void OnNavigatedTo(NavigationEventArgs e)
  {
    Window.Current.SizeChanged += WindowSizeChanged;
  }
  private void WindowSizeChanged(object sender,
    Windows.UI.Core.WindowSizeChangedEventArgs e)
  {
    // Respond to size change
  }
  // Other code
}

LeakyApp 代碼顯示一個事件處理常式的三個部分:

  • Window.Current 是源自 (火災) 的物件的事件。
    • 實例是接收的物件 ItemDetailPage (匯) 事件。
  • WindowSizeChanged 是 ItemDetailPage 實例中的事件處理常式方法。

後註冊的事件通知,當前視窗物件已在一個 ItemDetailPage 物件中的事件處理常式的引用中所示圖 3。 此引用導致 ItemDetailPage 物件仍然活著的只要當前視窗物件仍然活著,或直到當前視窗物件降到專案的引用­DetailPage 實例 (現在,忽略這些物件的其他外部引用)。

A Reference to the Event Handler
圖 3 引用的事件處理常式

請注意對於 ItemDetailPage 實例都能正常運行,雖然它是活著 Windows 運行庫 (WinRT) 間接讓該實例使用的所有資源活下去。 實例應包含對大量分配如陣列或圖像的引用,這些撥款就能活下去,實例的存留期。 實際上,註冊事件處理常式擴展包含事件處理常式中,和所有其依賴項,以匹配的事件源的存留期的物件實例的存留期。 當然,到目前為止,這不是資源洩漏。 這也是只需訂閱事件的結果。

ItemDetailPage 是類似于一個應用程式中的所有頁面。 它用於當使用者導航至頁,但不再需要時他們會導航到另一頁。 當使用者導航回 ItemDetailPage 時,應用程式通常會創建頁面的一個新實例,並與當前視窗接收 SizeChanged 事件註冊的新實例。 不過,在此示例中,bug 是當使用者離開 ItemDetailPage,頁面無法登出其從當前視窗 SizeChanged 事件的事件處理常式。 當使用者導航從 ItemDetailPage 時,當前視窗仍有前一頁的引用和當前視窗繼續火到頁的 SizeChanged 事件。 當使用者導航回 ItemDetailPage 時,此新的實例也註冊當前視窗,如中所示圖 4

A Second Instance Registered with the Current Window
圖 4 第二個實例的當前視窗中註冊

五個導航後,五個 ItemDetailPage 物件註冊的當前視窗 (見圖 5) 和及其依賴的資源將保持活動狀態。

Five Objects Registered with the Current Window
圖 5 5 個物件當前視窗中註冊

這些無-長使用 ItemDetailPage 實例是資源,它們永遠不會使用或回收 ; 他們有效地洩露出去。 如果你把這篇文章的一件事,請確保它是預防最常見的記憶體洩漏的最佳方法是登出事件處理常式,當不再需要他們。

要在 LeakyApp 中修復的問題,我們需要先刪除從當前視窗的 SizeChanged 事件處理常式的引用。 這可以通過取消從事件處理常式,當網頁檢視,就像這樣:

protected override void OnNavigatedFrom(NavigationEventArgs e)
{
  Window.Current.SizeChanged -= WindowSizeChanged;
}

後向 ItemDetailPage 類中添加此重寫,ItemDetailPage 實例不再積累和洩漏固定的。

注意此類問題可能出現的任何物件 — — 長壽命的任何物件保持活著它引用的一切。 我喊這裡的事件處理常式,因為它們是迄今為止最常見的來源,這一問題的 — — 但會介紹,他們就不再需要清理的物件是避免大記憶體洩漏的最佳辦法。

循環參考的事件處理常式中,跨選區創建一個特定的事件處理常式,當您啟動通過指定時觸發該事件時,將調用一個函數,然後將該處理常式附加到將收到有關該事件的物件。 當實際上會觸發該事件時,處理函數有參數,表示的物件的最初收到的事件,稱為"事件源"。該按鈕的按一下事件處理常式,如下,"寄件者"參數是事件源:

private void Button_Click(object sender, RoutedEventArgs e)
{
}

根據定義,事件源已對該事件處理常式的引用,否則源無法觸發該事件。 如果捕獲到該事件處理常式內源的引用,該處理常式現在有回源的引用,您已經創建循環參考。 讓我們看看這行動中相當常見模式:

// gl is declared at a scope where it will be accessible to multiple methods
Geolocator gl = new Geolocator();
public void CreateLeak()
{           
  // Handle the PositionChanged event with an inline function
  gl.PositionChanged += (sender, args) =>
    {
      // Referencing gl here creates a circular reference
      gl.DesiredAccuracy = PositionAccuracy.Default;
    };
}

在此示例、 gl 和寄件者是相同的。 引用 gl lambda 函數中的創建循環參考,因為源引用的處理常式,反之亦然。 通常這種循環參考不是問題,因為 (Gc) 的 JavaScript 和 CLR 垃圾回收器足夠的智慧化,以處理這類案件。 但是,循環參考的一側不屬於 GC 環境或屬於不同的 GC 環境時,可以出現問題。

Geolocator 是一個 WinRT 物件。 WinRT 物件在 C/c + + 中實現,因此使用引用計數系統而不是 GC。 當 CLR GC 嘗試清理此循環參考時,它無法清除 gl 靠自己。 同樣,gl 的引用計數將永遠不會達到零,因此 C/c + + 側的事情不會收拾要麼。

當然,這是一個非常簡單的示例來說明這個問題。 如果它不是一個單一而是物件的使用者介面元素 (如面板 (或在 JavaScript 中,一個 div) 的大型分組嗎? 洩漏將包括所有這些物件和跟蹤源將非常困難。

有的地方中各種緩解使許多這些姿勢可以檢測並清除了氣相色譜法。 例如,涉及是用 JavaScript 代碼的週期中的一個 WinRT 事件源的循環參考 (或與 XAML 物件作為事件源的循環參考) 正確填海。 但是,(如 JavaScript 事件與 C# 事件處理常式),涵蓋的姿勢並不是所有形式和隨著的數量和複雜性對事件源的引用的增長,GC 特別緩解措施成為不少保證。

如果您需要創建事件源的引用,您可以始終顯式登出以後要推倒圓度和防止任何洩漏 (這可以回溯到您創建的物件的存留期的推理) 的事件處理常式或 null 引用出。 但如果事件處理常式永遠不會保存到源的引用,則不需要依賴于平臺提供緩解或顯式的代碼,以防止什麼可以是一個很大的資源利用問題。

使用緩存無界資料結構在許多應用程式,它使存儲有關使用者的最近的活動,以改善體驗一些資訊有意義。 例如,假設一個搜索應用程式,顯示過去五個查詢使用者輸入。 一種編碼模式,實現這一目標就是在一個清單或其他資料結構中存儲的每個查詢,並提出意見,屆時檢索的前五名。 這種方法的問題在於如果應用程式在長時間保持打開狀態,清單將會增長沒有邊界,最終佔用了大量的不必要的記憶體。

不幸的是,GC (或任何其他記憶體管理器) 有沒有辦法對非常大,但可以到達,永遠不會使用的資料結構的原因。 若要避免此問題,您將存儲在快取記憶體中的專案數繼續硬限制。 定期淘汰舊的資料並不依賴于您的應用程式正在終止釋放這些類型的資料結構。 如果特別時間敏感或容易重組所存儲的資訊,您可以考慮掛起時,完全清空快取記憶體。 如果不是,保存緩存到本地狀態並釋放記憶體資源 ; 它可以在簡歷上重新獲取。

避免舉行大型引用中止問題

無論語言,持有大量引用掛起時可能會導致 UX 問題。 您的應用程式將保持懸浮的只要該系統能夠無需額外的記憶體,只能通過終止應用程式檢索服務其他正在運行的進程的請求。 因為住懸浮的手段由使用者可以更輕鬆地訪問您的應用程式,是保持您的記憶體佔用量較小的暫時吊銷期間您最感興趣。

一個簡單的方法來實現此目的是時暫停,可在簡歷上重組只是免費的大型物件的引用。 例如,如果您的應用程式持有本地應用程式資料在記憶體中引用,釋放引用可能會大大降低您私人的工作集,並很容易在簡歷上重新獲取,因為此資料不哪兒去。 (關於應用程式資料的詳細資訊,請參閱 bit.ly/MDzzIr.)

若要完全釋放一個變數,將分配變數 (和所有對變數的引用) 為 null。 在 c + +,這將立即回收記憶體。 GC 將為 JavaScript 和 Microsoft.NET 框架的應用程式,運行時應用程式掛起,收回這些變數的記憶體。 這是一深度防禦辦法,確保正確的記憶體管理。

請注意,但是,如果您的應用程式在 JavaScript 中編寫的有一些.NET 元件,則不會在上運行.NET GC 暫停。

在 JavaScript Windows 存儲應用程式中的記憶體管理

這裡有一些在 JavaScript 中創建資源節約的 Windows 存儲應用程式的提示。 這些都是常見的問題在我們自己的應用程式,我們已經看到的推薦修復程式,考慮到與他們的設計將説明避開許多潛在的問題之前他們帶來的麻煩。

使用代碼品質工具經常被忽略的資源,免費軟體代碼品質工具可供所有 JavaScript 開發在 Web 上。 這些工具檢查您的代碼很多常見的問題,包括記憶體洩漏,而可以將早期捕捉問題的打賭的最好。 兩個有用的工具是 JSHint (jshint.com) 和 JSLint (jslint.com)。

使用嚴格模式 JavaScript 了一個"嚴"的模式,限制了您可以在代碼中使用變數的方式。 這些限制把自己視為額外規則受到侵犯時被扔掉的執行階段錯誤。 這種編碼限制可以説明您避免常見的記憶體洩漏,如隱式聲明變數在全域範圍內。 在嚴格模式下,它的使用和施加的限制的詳細資訊,在退房的 MSDN 庫文章,"嚴格模式 (JavaScript)" bit.ly/RrnjeU

避免封閉的循環參考 JavaScript 有一個相當複雜的存儲對變數的引用,每當 lambda (或內聯) 函數使用系統。 基本上,為了要正確執行調用時的內聯函數,JavaScript 存儲一組稱為一個封閉的引用中的可用變數的範圍。 這些變數將保持活動狀態直至內聯函數本身不再被引用的記憶體中。 讓我們看看一個示例:

myClass.prototype.myMethod = function (paramA, paramB) {
  var that = this;
  // Some code
  var someObject = new someClass(
    // This inline function's closure contains references to the "that" variable,
    // as well as the "paramA" and "paramB" variables
    function foo() {
      that.somethingElse();
    }
  );
  // Some code: someObject is persisted elsewhere
}

保持 someObject 後,"那,""帕爾瑪"所引用的記憶體和"paramB"不會被回收,直到 someObject 被破壞,或者釋放其對傳遞給它的在此處將有機可乘建構函式中的內聯函數引用。

可以出現問題的內聯函數關閉如果不釋放對內聯函數的引用,如關閉引用將永久駐留在記憶體中,導致洩漏。 出現這種情況的最常見方法是當關閉包含對本身的循環參考。 內聯函數引用變數的引用的內聯函數時通常會發生此種情況:

function addClickHandler(domObj, paramA, paramB, largeObject) {
  domObj.addEventListener("click",
  // This inline function's closure refers to "domObj", "paramA",
  // "paramB", and "largeObject"
    function () {
      paramA.doSomething();
      paramB.somethingElse();
    },
  false);
}

在此示例中,domObj 包含 (通過事件攔截器) 的內聯函數的引用和內聯函數閉包包含返回到它的引用。 因為大物件不正在使用,目的是它會超出範圍,並得到回收 ; 但是,關閉引用讓它和 domObj 在記憶體中活下去。 此循環參考將導致洩漏到 domObj 移除事件攔截器引用,或者獲取音步驟和垃圾回收。 完成某件事情像這樣的正確方法是使用一個函數,返回一個函數,執行您的任務,如中所示圖 6

圖 6 使用函數範圍,以避免封閉的循環參考

function getOnClick(paramA, paramB) {
  // This function's closure contains references to "paramA" and "paramB"
  return function () {
    paramA.doSomething();
    paramB.somethingElse();
  };
}
function addClickHandlerCorrectly(domObj, paramA, paramB, largeObject) {
  domObj.addEventListener(
    "click",
  // Because largeObject isn't passed to getOnClick, no closure reference
  // to it will be created and it won't be leaked
  getOnClick(paramA, paramB),
  false);
}

使用此解決方案,消除了對 domObj 的封閉引用,但對帕爾瑪和 paramB 引用仍然存在,因為他們是事件處理常式執行的必要。 若要確保不漏帕爾瑪或 paramB,仍然需要登出事件監聽器或只是等待他們獲取自動收回時 domObj 獲取垃圾回收。

撤銷所有 Url 創建的 URL.createObjectURL 載入媒體的音訊、 視頻或 img 元素的常用方法是使用 URL.createObjectURL 方法來創建一個元素可以使用的 URL。 當您使用此方法時,它將通知系統保持您的媒體內部參考。 系統使用此內部參考以流到相應元素的物件。 但是,系統不知道當不再需要該資料,所以它保留內部引用活在記憶體中直到它已明確告訴,將其釋放。 這些內部引用會佔用大量的記憶體和容易意外地保留這些不必要地。 有兩種方法來釋放這些引用:

  1. 您可以通過調用 URL.revokeObjectURL 方法,並將它傳遞 URL 明確吊銷該 URL。
  2. 你可以告訴系統 URL.createObjectURL 的 oneTimeOnly 屬性設置為 true 以使用一次後自動撤銷該 URL:
var url = URL.createObjectURL(blob, {oneTimeOnly: true});

對於臨時物件使用弱引用想像一下你有一個大型的物件,您需要在您的應用程式的各個部分中使用的文件物件模型 (DOM) 節點的引用。 現在假設在任何一點可以釋放該物件 (例如,node.innerHTML ="")。 您如何確保避免舉行對物件的引用,所以它可以完全回收在任何點? 值得慶倖的是,Windows 運行庫提供瞭解決這個問題,使您可以存儲到物件的"弱"引用。 弱引用不會阻止從清理它所引用的物件 GC 和時取消引用,它可以返回的物件或返回 null。 為了更好地理解如何,這會很有用,看一看在示例圖 7

圖 7 JavaScript 記憶體洩漏

function addOptionsChangedListener () {
  // A WinRT object
  var query = Windows.Storage.KnownFolders.picturesLibrary.createFileQuery();
  // 'data' is a JS object whose lifetime will be associated with the  
  // behavior of the application.
Imagine it is referenced by a DOM node, which
  // may be released at any point.
// For this example, it just goes out of scope immediately,
  // simulating the problem.
var data = {
    _query: query,
    big: new Array(1000).map(function (i) { return i; }),
    someFunction: function () {
      // Do something
    }
  };
  // An event on the WinRT object handled by a JavaScript callback,
  // which captures a reference to data.
query.addEventListener("optionschanged", function () {
    if (data)
      data.someFunction();
  });
  // Other code ...
}

在此示例中,資料物件不是被回收,因為它正在引用上查詢的事件監聽器。 因為 app 的用意是要清除的資料物件 (和將不會再作嘗試這樣做),這是現在的記憶體洩漏。 若要避免此問題,可以使用下面的語法使用 WeakWinRTProperty API 組:

msSetWeakWinRTProperty(WinRTObj, "objectName", objectToStore);

WinRTObj 是任何支援 IWeakReference 的 WinRT 物件、 物件名稱是要訪問的資料的金鑰和 objectToStore 是要存儲的資料。

若要檢索的資訊,請使用:

var weakPropertyValue = msGetWeakWinRTProperty(WinRTObj, "objectName");

WinRTObj 是 WinRT 物件位置存儲屬性和物件名稱是存儲資料時的關鍵。

傳回值為 null 或值最初存儲 (objectToStore)。

圖 8 演示了一種修復洩漏中 addOptions­ChangedListener 函數。

圖 8 使用弱引用,避免記憶體洩漏

function addOptionsChangedListener() {
  var query = Windows.Storage.KnownFolders.picturesLibrary.createFileQuery();
  var data = {
    big: new Array(1000).map(function (i) { return i; }),
    someFunction: function () {
      // Do something
    }
  };
  msSetWeakWinRTProperty(query, "data", data)
  query.addEventListener("optionschanged", function (ev) {
    var data = msGetWeakWinRTProperty(ev.target, "data");
    if (data) data.someFunction();
  });
}

因為資料物件的引用是弱,移除其他對它的引用時,它將垃圾收集及回收其記憶體。

架構設計使用 JavaScript 的 Windows 存儲應用程式

設計應用程式時考慮到資源的開發利用可以通過使您的應用程式從一開始更耐洩漏減少現貨修復和記憶體管理特定的編碼實踐的需要。 它還使您可以構建保障措施,使它易於識別洩漏發生。 在這一節將討論構建書面與可以單獨使用的 JavaScript 或在一起以創建一個資源高效的應用,易於維護 Windows 存儲應用程式的兩種方法。

處置建築處置體系結構是通過具有一致、 簡單和可靠的方式來回收資源停止在其發病的記憶體洩漏的好方法。 你與記住此模式的應用程式是要確保每個類或大型物件實現回收記憶體的函數 (通常命名為處置) 的設計的第一步與關聯的每個物件它引用。 第二步是作為參數,在實施大致可到達功能 (通常也會命名為處置) 對物件調用 dispose 方法傳遞,然後再使用空值物件本身:

 

var dispose = function (obj) {
  /// <summary>Safe object dispose call.</summary>
  /// <param name="obj">Object to dispose.</param>
  if (obj && obj.dispose) {
    obj.dispose();                
  }
  obj = null;
};

目標是這個程式將花上一個樹狀結構,與每個物件具有內部 dispose 方法能夠釋放出自己的資源,通過調用 dispose 方法對所有物件它引用,依此類推。 這種方式,完全釋放物件和行為的所有引用,您需要做的一切是調用 dispose(obj) !

在應用程式中每個主要方案中過渡,只需調用 dispose 上所有的頂級物件,則不再需要。 如果您想要獲得花式,你可以有所有這些頂級物件是一個主要的"情景"物件的一部分。 當切換場景中,您只需調用具現化一個新場景的切換應用程式和棄置在頂級方案物件上。

膨脹的體系結構 "膨脹"體系結構允許您更輕鬆地識別時通過使物件真的大權利之前您釋放他們發生記憶體洩漏。 這種方式,如果該物件不實際釋放,對您的應用程式 TWS 的影響將明顯。 當然,只應在開發期間使用此模式。 App 永遠不應附帶到位情況下,此代碼突增記憶體使用量 (即使是暫時的) 可以強制使用者機終止其他暫停應用程式。

來人為地膨脹的物件,您可以做一些簡單的作為附加到它的一個很大的陣列。 快速使用聯接的語法填充一些資料,從而使任何物件附加到明顯較大,它的整個陣列:

var bloatArray = [];
bloatArray.length = 50000;
itemToBloat.leakDetector = bloatArray.join("#");

要有效地使用此模式,您需要標識物件應該要釋放的代碼時的好方法。 此您可以手動為每個物件釋放,但有兩個更好的方法。 如果您使用的剛才討論的處置體系結構,只需添加膨脹代碼中所討論的物件的 dispose 方法。 這樣,一旦調用 dispose 時,你會知道是否該物件真正已被刪除或不及其引用的所有。 第二種方法是使用的任何元素的 dom 的 JavaScript 事件 DOMNodeRemoved 因為移除節點之前,將激發此事件,您可膨脹這些物件的大小和查看是否他們真正要回收。

請注意有時 GC 將需要一些時間來實際收回未使用的記憶體。 時有洩漏、 方案進行測試,如果應用程式似乎增長十分迅速,等待一段時間才能確認洩漏 ; GC 可能不做通不過。 如果等待、 後 TWS 是仍然很高,然後再試方案。 如果應用程式的 TWS 仍大,它是極有可能有洩漏。 您可以在源中磨練的通過有系統地從您的應用程式中的物件刪除此膨脹代碼。

前進

我希望我已經確定、 診斷和修復您的 Windows 存儲應用程式中的記憶體洩漏給你一個牢固的基礎。 洩漏往往從資料分配和填海工程是如何發生的誤解的結果。 這些細微差別的知識 — — 結合如顯式地賦空出對大變數的引用簡單技巧 — — 會走很長的路,確保高效的應用程式不減慢使用者的機器,即使在天的使用。 如果您正在尋找的更多資訊,您可以簽出的 Internet Explorer 團隊的涵蓋相關的主題,"理解和解決互聯網瀏覽器洩漏模式,"MSDN 庫文章在 bit.ly/Rrta3P

David Tepper 是在 Windows 應用程式體驗團隊的專案經理。他一直對應用程式模型的設計和應用程式部署 2008 年以來主要集中于 Windows 存儲應用程式的性能和這些應用程式可以將如何擴展 Windows 深感提供集成的功能性*.***

由於下面的技術專家對本文的審閱:Jerry Dunietz、 Mike Hillberg、 恩威假話、 卡門 Moutafov、 布倫特校長和奇帕洛街