C++ các trường hợp nào sẽ dẫn tới memory leak

Javascript là một trong những ngôn ngữ có garbage collection. Những ngôn ngữ lập trình như Javascript thế này sẽ thay developer quản lý bộ nhớ bằng cách kiểm tra định kỳ các vùng nhớ được cấp phát trước đó có có thể được “với tới” bởi các phần khác trong ứng dụng. Có thể nói cách khác là những ngôn ngữ như Javascript sẽ giúp biến vấn đề từ “những vùng nhớ nào vẫn còn cần trong ứng dụng” thành “những vùng nhớ nào có thể được ứng dụng access đến”. Sự khác biệt của 2 vấn đề là không nhiều nhưng lại rất quan trọng: chỉ developer mới có thể biết được là vùng nhớ nào còn cần để chạy tuy nhiên, việc xác định xem một vùng nhớ có thể vươn tới không trong ứng dụng thì có thể làm tự động bởi thuật toán.

Memory leaks trong JS

Lý do chính của memory leaks trong các ngôn ngữ có garbage collection là các reference không mong muốn vào bộ nhớ (unwanted references), tức là một vùng nhớ được trỏ đến mà lại không được sử dụng trong ứng dụng. Để có thể hiểu rõ hơn về nó, trước hết ta cần tìm hiểu các hoạt động của garbage collector, cách nó xác định một vùng nhớ có thể được “với tới” (reach) bởi ứng dụng.

Mark and sweep

Hầu hết các garbage collector đều sử dụng thuật toán

a = "value"; console.log(a);

6 để thực hiện việc giải phóng bộ nhớ. Thuật toán này bao gồm các bước sau:

  1. Đầu tiên, garbage collector sẽ xây dựng một danh sách các a = "value"; console.log(a); 7. a = "value"; console.log(a); 8 thực chất là các biến toàn cục mà có reference được lưu trong code. Trong Javascript, a = "value"; console.log(a); 9 chính là một biến toàn cục như vậy. 1 2 3 0 sẽ luôn hiện hữu trong chương trình nên garbage collector có thể coi nó và tất cả các con của nó luôn hiện hữu.
  2. Tất cả a = "value"; console.log(a); 7 và con của chúng sẽ được đánh dẫu là đang hoạt động. Tất cả những vùng nhớ mà có thể được vươn tới từ a = "value"; console.log(a); 7 thì đều được coi là đang hoạt động và không đánh dấu là rác (garbage).
  3. Tất cả các vùng nhớ mà không được đánh dẫu là rác (garbage) thì bây giớ đều sẽ được coi là rác. Bây giờ thì các collector có thể giải phóng các vùng nhớ này.

Mặc dù thuật toán này được tối ưu bởi các GC (garbage collector) hiện đại tuy nhiên cơ chế của nó vẫn không đổi: những vùng nhớ vươn tói được thì được coi là đang hoạt động, những vùng nhớ khác sẽ được coi là rác.

Những tham chiếu không mong muốn (Unwanted references) là những tham chiếu đến các vùng bộ nhớ mà developer biết là nó không được cần đến nữa nhưng vì lý do nào đó mà nó vẫn được giữ lại trong hệ thống. Trong JS, những tham chiếu không mong muốn này là các biến (variables) được giữ đâu đó trong code mà nó sẽ không được sử dụng đến nữa nhưng lại trỏ đến một vùng nhớ mà cần được giải phóng.

Để hiểu được memory leaks trong JS, ta cần biết được là khi nào thì một tham chiếu bị lãng quên.

3 loại memory leaks trong JS

1: Biến toàn cục

Javascript có một cơ chế là đặt biến mà không cần khai báo. Ví dụ:

1 2

a = "value"; console.log(a);

Khi một biến được khai báo như trên thì JS sẽ tự động gán nó vào

1 2 3

3 object (

a = "value"; console.log(a);

9 trên browser). Nếu như biến này chỉ hoạt động trên phạm vi toàn cục (global scope) thì cũng không có sự khác biệt cho lắm. Tuy nhiên, nếu nó được định nghĩa trong một hàm thì đó lại là chuyện khác. Ví dụ:

1 2 3

function foo(){ bar = "đây là biến toàn cục ẩn" }

Đoạn code trên sẽ tương đương với đoạn code sau trên browser:

1 2 3

function foo(){ window.bar = "đây là biến toàn cục" }

Nếu khai báo

1 2 3

5 trong phạm vi của hàm

1 2 3

6 mà lại không sử dụng

1 2 3

7 để khai báo thì biến

1 2 3

5 sẽ được tạo với phạm vi toàn cục, và đây là một ví dụ điển hình về memory leaks.

Một cách khác mà có thể vô tình tạo ra biến toàn cục đó là thông qua

1 2 3

9:

1 2 3 4 5

function foo() { this.variable = "có thể là biến toàn cục"; }

foo();

1 2 3

9 trong hàm sẽ trỏ đến biến root toàn cục (

a = "value"; console.log(a);

  1. nếu hàm đó được gọi trực tiếp không thông qua object nào khác nên ở ví dụ trên, biến

function foo(){ bar = "đây là biến toàn cục ẩn" }

2 sẽ được gắn vào phạm vi toàn cục.

Một cách để giảm thiểu những lỗi trên đó là thêm

function foo(){ bar = "đây là biến toàn cục ẩn" }

3 vào dòng đầu tiên của file JS. Nó sẽ giúp ngăn chặn việc khai báo biến toàn cục như trên.

Chú ý khi làm việc với biến toàn cục

Biến toàn cục không bao giờ được giải phóng bộ nhớ tự động theo thuật toán

a = "value"; console.log(a);

6 ở trên. Vì thế, biến toàn cục chỉ nên được sử dụng để lưu tạm dữ liệu để xử lý. Nếu cần lưu một lượng lớn dữ liệu vào biến toàn cục thì cần đảm bạo là nó sẽ bị gán về null hoặc gán lại dữ liệu khi mà bạn đã sử dụng xong nó.

2: Callback và timer bị lãng quên

Sau đây là một ví dụ dẫn đến memory leak khi sử dụng

function foo(){ bar = "đây là biến toàn cục ẩn" }

5:

1 2 3 4 5 6 7

var data = getData(); setInterval(function(){ var node = document.getElementById("Node"); if(node){ node.innerHTML = JSON.stringify(someResource)); } }, 1000);

Đây là một ví dụ về một timer bị treo. Timẻ bị treo tức là khi timer tham chiếu đến các node hoặc dữ liệu mà không còn được sử dụng nữa. Ở ví dụ trên, nếu như

function foo(){ bar = "đây là biến toàn cục ẩn" }

6 bị xóa ở một lúc nào đấy thì toàn bộ đoạn code xử lý trong hàm callback của interval sẽ không cần đến nữa. Tuy nhiên, vì interval vẫn còn hoạt động nên các vùng nhớ được sử dụng trong hàm callback của interval cũng không được giải phóng (muốn giải phóng cần dừng interval lại). Tiếp đó, các object từ bên ngoài mà được hàm callback của interval tham chiếu đến cũng không thể được giải phóng vì vẫn có thể vươn tới được thông qua hàm callback kia. Theo ví dụ trên thì đó là

function foo(){ bar = "đây là biến toàn cục ẩn" }

7.

Một trường hợp có thể dẫn đến leaks đó là do các observers object (DOM và event listener của chúng). Điều này chỉ ảnh hưởng đến các trình duyệt cũ (vd: IE6) vì các trình duyệt mới sẽ tự động làm điều này cho chúng ta. Đây là một bug của GC của IE6 và dẫn đến việc tham chiếu quay vòng.

3: Tham chiếu tới các DOM đã bị xóa

Có những lúc bạn muốn lưu các DOM vào một số cấu trúc dữ liệu như mảng hoặc object trong JS code để làm một loạt các tác vụ nào đấy. Ví dụ bạn muốn update dữ liệu của một vài element nào đấy thì việc lưu các element này vào một mảng là hoàn toàn hợp lý. Khi điều này xảy ra thì sẽ có 2 tham chiếu đên DOM element này: một là từ DOM tree, hai là từ đối tượng mảng của JS. Nếu bạn muốn xóa các element này thì bạn cần phải xóa toàn bộ các tham chiếu tới chúng để có thể giải phóng bộ nhớ.

Ví dụ:

a = "value"; console.log(a);

0

a = "value"; console.log(a);

1

Còn một vấn đề quan trọng nữa là khi tham chiếu đến một node lá hoặc một inner node của DOM tree, ví dụ như một ô trong bảng (

function foo(){ bar = "đây là biến toàn cục ẩn" }

8 của

function foo(){ bar = "đây là biến toàn cục ẩn" }

9). Nếu bạn tham chiếu đến

function foo(){ bar = "đây là biến toàn cục ẩn" }

8 này trong JS code thì khi bạn xóa

function foo(){ bar = "đây là biến toàn cục ẩn" }

9 chứa node này thì GC sẽ không giải phóng được cả table chứ không phải là chỉ mỗi

function foo(){ bar = "đây là biến toàn cục ẩn" }

8 node không được giải phóng. Vì node con còn tham chiếu đến node cha nên nó sẽ được GC coi là vẫn được tham chiếu và bỏ qua nó. Vì thế nên cẩn thận khi tham chiếu đến các DOM.

4: Closures

Closures có nghĩa đơn giản là hàm nằm trong phạm vi của một hàm khác có thể tham chiếu tới các biến của hàm bao nó. Vì sao

1 2 3

3 có thể gây ra leak, hãy xem ví dụ sau:

a = "value"; console.log(a);

2

a = "value"; console.log(a);

3

Ví dụ này cho ta thấy mỗi khi

1 2 3

4 được gọi,

1 2 3

5 sẽ tạo ra một object mới chứa một mảng và một closures (

1 2 3

6). Cùng lúc đó, biến

1 2 3

7 cũng lưu một closures tham chiếu đên

1 2 3

8 (là object

1 2 3

5 được tạo ra từ việc gọi

1 2 3

4 ở bước trước đó). Một điều quan trọng nữa là khi một scope được tạo ra cho các closures mà có cùng scope cha, chúng sẽ cùng chia sẻ scope đó. Trong ví dụ này thì

1 2 3

6 và

1 2 3

7 đều chia sẻ cùng một scope. Mặc dù

1 2 3

7 không được gọi đến nhưng vì nó có tham chiếu đến

1 2 3

8 nên nó sẽ được GC coi là vẫn đang hoạt động. Khi đoạn code này chạy thì bộ nhớ của chương trình sẽ tăng đều đặn và có thể nhìn thấy ngay được. Về bản chất, một linked-list của closures được tạo (với root là

1 2 3

  1. khi đoạn code trên được chạy và đó là lý do bộ nhớ bị tăng dần theo thời gian.

Garbage Collectors (bộ dọn rác)

Mặc dù GCs giúp chúng ta không phải quản lý bộ nhớ bằng tay nữa, tuy nhiên ta cũng sẽ phải đánh đổi lại một vài thứ. Một trong số đó là việc các GCs hoạt động theo một cách khó đoán biết. Thông thường rất khó có thể chắc chắn rằng một hoạt động thu thập các vùng nhớ không được sử dụng được thực thi hay không. Điều này cũng có nghĩa là trong một số trường hợp, số lượng vùng nhớ của một chương trình nhiều hơn số bộ nhớ mà chương trình đó cần. Trong một số trường hợp khác, ứng dụng sẽ bị ảnh hưởng bởi một khoảng thời gian nhỏ chương trình bị delay để thực hiện công việc thu thập bộ nhớ. Hiện nay, hầu hết GC đều hoạt động theo cách là chỉ thực hiện việc thu thập bộ nhớ khi cấp phát bộ nhớ cho chương trình. Nếu không cần cấp phát bộ nhớ, GCs sẽ không hoạt động. Chúng ta sẽ xem xét các tình huống sau:

  1. Chương trình đã cấp phát một số lượng nhỏ bộ nhớ.
  2. Sau đó, hầu hết (hoặc toàn bộ) các phần tử được đánh dấu là không thể vươn tới nữa.
  3. Chương trình không thực hiện việc cấp phát bộ nhớ nữa.

Trong tình huống này, hầu như tất cả các GC sẽ không thực hiện việc thu thập bộ nhớ nữa. Nói cách khác, mặc dù có những phần tử không thể vươn tới được nữa trong chương trình, chúng sẽ không được thu hồi lại bộ nhớ. Đây không hẳn là leaks, tuy nhiên nó vẫn dẫn đến việc chương trình ngốn bộ nhớ.

function foo(){ window.bar = "đây là biến toàn cục" }

6 cung cấp một tập các công cụ để kiểm tra tình trạng sử dụng bộ nhớ của code JS. Có 2 view quan trọng liên quan đến bộ nhớ đó là: timeline và profiles.

Timeline View

function foo(){ window.bar = "đây là biến toàn cục" }

7 có thể giúp ta biết được mô hình sử dụng bộ nhớ của chương trình. Từ đây ta có thể nhìn được việc rò rỉ bộ nhớ, việc bộ nhớ sử dụng tăng liên tục theo thời gian mà không giảm xuống sau mỗi lần GC được chạy. Ví dụ:

C++ các trường hợp nào sẽ dẫn tới memory leak

Ta có thể thấy được việc bộ nhớ rò rỉ được thể hiện thông qua việc JS heap tăng dần theo thời gian. Mặc dù sau khi được thu thập với một số lượng lớn tại đoạn cuối thì chương trình vẫn sử dụng số lượng bộ nhớ nhiều hơn so với lúc bắt đầu. Số lượng Node cũng cao hơn. Đây là dấu hiệu của việc các node DOM bị rò rỉ đâu đó trong code.o

Profiles view

C++ các trường hợp nào sẽ dẫn tới memory leak

Đây là công cụ sẽ luôn gắn bó với bạn khi phải điều tra về rò rỉ bộ nhớ.

function foo(){ window.bar = "đây là biến toàn cục" }

8 cho phép bạn lấy ảnh chụp (snapshot) về việc sử dụng bộ nhớ của một chương trình Javascript. Nó cũng cho phép bạn ghi lại những lần cấp phát bộ nhớ theo thời gian. Mỗi một loại kết quả sẽ có các danh sách liệt kê khác nhau được đưa ra, tuy nhiên những thứ mà bạn cần quan tâm đó là danh sách tổng hợp (summary list) và danh sách so sánh (comparision list).

function foo(){ window.bar = "đây là biến toàn cục" }

9 sẽ cho ta thấy được tổng quan về các loại objects được khởi tạo và cấp phát cùng với các kích thước tổng hợp (aggregated size): kich thước nông (Shallow size) là tổng kích thước của tất cả các object của một loại cụ thể nào đó và kích thước giữ lại (retained size) bao gồm

1 2 3 4 5

0 và kích thước của các object được lưu lại bởi object này. Nó cũng cho ta một thông tin về khoảng cách giữa một object với root.

1 2 3 4 5

1 cũng cung cấp cùng một thông tin như

1 2 3 4 5

2 nhưng nó cho phép ta so sánh giữa các snapshot khác nhau.

Ví dụ: Tìm kiếm rò rỉ dữ liệu trong Chrome

Có 2 kiểu rò rỉ dữ liệu chủ yếu là: rỏ rỉ dẫn đến việc bộ nhớ bị tăng một cách đều đặn theo thời gian và rò rỉ chỉ xảy ra một lần duy nhất và không gây ra việc bộ nhớ bị tăng trong tương lai nữa. Việc tìm rò rỉ dữ liệu mà bộ nhớ bị tăng dần theo thời gian khá là đơn giản và rõ ràng (sử dụng

1 2 3 4 5

3). Tuy nhiên thì đây lại là rò rỉ gây ra nhiều rắc rối nhất: nếu bộ nhớ cứ tăng dần theo thời gian, nó sẽ khiến trình duyệt chạy chậm dận và cuối cùng sẽ dẫn đến việc script bị ngừng chạy. Rò rỉ mà không dẫn đến việc bộ nhớ bị tăng theo thời gian có thể dễ dàng được tìm ra khi bộ nhớ lớn đến một mức độ nào đó. Thông thường những rò rỉ kiểu này không được chú ý quả nhiều. Nói theo một cách khác, những rò rỉ nhỏ mà chỉ xảy ra một lần thường được coi là một vấn đề để tối ưu code. Tuy nhiên, những rò rỉ mà làm bộ nhớ tăng dần theo thời gian thì được coi là bug và nó cần được fix.

Ở đây ta sẽ sử dụng một ví dụ từ Chrome. Toàn bộ đoạn code như sau:

a = "value"; console.log(a);

4

a = "value"; console.log(a);

5

Khi

1 2 3 4 5

4 được gọi, nó sẽ bắt đầu tạo một

1 2 3 4 5

5 và gán nó vào DOM. Nó cũng sẽ khởi tạo một mảng lớn (1 triệu phần tử) và gán nó vào một mảng được tham chiếu bỏi một biến toàn cục (

1 2 3 4 5

6). Việc này sẽ dẫn đến việc bộ nhớ bị tăng đều đặn và có thể nhận biết được với

1 2 3 4 5

7.

Phát hiện việc bộ nhớ bị tăng đều đặn trong Chrome

Ta sẽ bắt đầu với ví dụ sau của chrome. Sau khi click vào ví dụ của Chrome, mở Dev Tools, click vào tab

1 2 3 4 5

8, tích chọn

1 2 3 4 5

9 và click vào nút

function foo() { this.variable = "có thể là biến toàn cục"; }

foo();

0. Tiếp đó quay lại trang ví dụ và click vào

function foo() { this.variable = "có thể là biến toàn cục"; }

foo();

1 để bắt đầu việc rò rỉ bộ nhớ. Sau một khoảng thời gian thì dừng lại việc record và xem kết quả:

C++ các trường hợp nào sẽ dẫn tới memory leak

Note: Ví dụ này sẽ khiến bộ nhớ bị tăng mỗi giây. Sau khi dừng việc record thì các bạn có thể đặt breakpoint vào

1 2 3 4 5

4 để dừng việc thực thi script.

Có 2 dấu hiệu lớn trong bức ảnh trên cho thấy việc rò rỉ bộ nhớ: biểu đồ cho

function foo() { this.variable = "có thể là biến toàn cục"; }

foo();

3 (đường kẻ màu xanh lá) và biểu đồ cho JS heap (đường kẻ màu xanh đậm). Số lượng node luôn luôn tăng và không bao giờ giảm. Đây là dấu hiệu cảnh báo lớn.

JS heap cũng tăng dần theo thời gian tuy nhiên điều này khó nhìn ra hơn do hiệu ứng từ GC. Các bạn có thể thấy là bộ nhớ tăng sau lại giảm một cách liên tục. Điểm quan trọng cần chú ý ỏ đây là sau mỗi lần bộ nhớ được giảm thì kích thước của JS heap vẫn lớn hơn so với lần giảm trước đấy. Nói cách khác, mặc dù GC đã thành công thu thập được rất nhiều bộ nhớ, một vài trong số đó bị rò rỉ.

Bây giờ ta đã chắc chắn chương trình của mình bị rò rỉ bộ nhớ, ta cần phải tìm ra nguyên nhân của nó.

Tạo 2 snapshot

Để tìm ra nguyên nhân rò rỉ, ta sẽ sử dụng đến công cụ

function foo() { this.variable = "có thể là biến toàn cục"; }

foo();

4 của Chrome. Cụ thể hơn, ta sẽ sử dụng tính năng

function foo() { this.variable = "có thể là biến toàn cục"; }

foo();

5.

Đầu tiên, reload lại trang và tạo một snapshot ngay sau khi load xong trang. Ta sẽ sử dụng snapshot này làm cơ sở. Sau đó, click vào

function foo() { this.variable = "có thể là biến toàn cục"; }

foo();

1 một lần nũa, chờ khoảng một vài giây, tạo một snapshot khác. Sau đó tạo breakpoint để dừng việc rò rỉ bộ nhớ lại.

Có 2 cách mà ta có thể sử dụng để kiểm tra sự khác nhau giữa 2 snapshot. Thứ nhất là sử dụng chức năng

function foo() { this.variable = "có thể là biến toàn cục"; }

foo();

7 rồi bắt đầu từ phía bên phải chọn

function foo() { this.variable = "có thể là biến toàn cục"; }

foo();

8. Hoặc chọn

function foo() { this.variable = "có thể là biến toàn cục"; }

foo();

9 thay cho

function foo() { this.variable = "có thể là biến toàn cục"; }

foo();

7. Trong cả 2 trường hợp, ta sẽ thấy một danh sách các object được khởi tạo giữa 2 snapshot.

C++ các trường hợp nào sẽ dẫn tới memory leak

Trong trường hợp này thì việc tìm ra leaks rất đơn giản. Hãy xem

1 2 3 4 5 6 7

1 của

1 2 3 4 5 6 7

2. 8MB với 58 object mới. Điều này rất đáng nghi ngờ: object mới được tạo nhưng không được giải phóng và 8MB bị chiếm mất.

Nếu ta mở danh sách khởi tạo của

1 2 3 4 5 6 7

2 ta sẽ thấy có một vài object lớn được khởi tạo bên cạnh các object nhỏ. Nếu ta chọn một trong số các object lớn này thì ta sẽ thấy một vài điểm thú vị trong mục

1 2 3 4 5 6 7

4:

C++ các trường hợp nào sẽ dẫn tới memory leak

Ta thấy rằng object được chọn là một phần tử của mảng. Tiếp đó ta biết được mảng này được tham chiếu bởi biến

1 2 3 4 5

6 nằm ở trong

a = "value"; console.log(a);

9. Điều này cho ta thấy được toàn bộ con đường từ object lớn của chúng ta liên kết thế nào với root (

a = "value"; console.log(a);

9). Ta đã tìm được một nguyên nhân dẫn đến rò rỉ và nơi nó được tham chiếu.

Vi dụ này khá đơn giản: object lớn được khỏi tạo thế này không thường xuyên xuất hiện trong chương trinh. Tuy nhiên trong chương trình này cũng có xuất hiện việc rò rỉ DOM có kích cỡ nhỏ hơn. Những node này có thể tìm thấy đươc thông qua snapshot tuy nhiên đối với những site lớn, mọi chuyện sẽ trở nên rắc rối hơn nhiều. Các phiên bản Chrome hiên tại có cung cấp một tính năng đó là:

1 2 3 4 5 6 7

8

Record Heap Allocations

Ta se bắt đầu với viêc để cho đoạn script tiếp tục được chạy và quay lại tab

1 2 3 4 5 6 7

9 của Chrome Dev Tools. Ấn nút

1 2 3 4 5 6 7

8. Khi mà tool đang chạy, các bạn sẽ thấy một vài vạch xanh trên biểu đồ ở phía trên đầu. Nó thể hiện việc khởi tạo object khi chạy chương trình.

C++ các trường hợp nào sẽ dẫn tới memory leak

Ta có thể thấy được tính năng của công cụ này: chọn một khoảng thời gian để xem object nào được khởi tạo trong khoảng thời gian này. Ta đặt khoảng thời gian này gần các vạch xanh đậm nhất có thể. Chỉ có 3 hàm khởi tạo được show trong danh sách: một trong số đó liên quan đến rò rỉ do

1 2 3 4 5 6 7

2 ở phía trên, tiếp theo là liên quan đến việc khởi tạo DOM và cái cuối cùng là khởi tạo

var data = getData(); setInterval(function(){ var node = document.getElementById("Node"); if(node){ node.innerHTML = JSON.stringify(someResource)); } }, 1000);

2.

Chon một trong những hàm khởi tạo của

var data = getData(); setInterval(function(){ var node = document.getElementById("Node"); if(node){ node.innerHTML = JSON.stringify(someResource)); } }, 1000);

3 trong danh sách và chọn

var data = getData(); setInterval(function(){ var node = document.getElementById("Node"); if(node){ node.innerHTML = JSON.stringify(someResource)); } }, 1000);

4.

C++ các trường hợp nào sẽ dẫn tới memory leak

Từ ảnh trên ta thấy được là phần tử được khởi tạo bởi

1 2 3 4 5

4 ->

var data = getData(); setInterval(function(){ var node = document.getElementById("Node"); if(node){ node.innerHTML = JSON.stringify(someResource)); } }, 1000);

6. Nếu ta để ý kỹ mỗi vạch trên biểu đồ, ta sẽ thấy là hàm khởi tạo

var data = getData(); setInterval(function(){ var node = document.getElementById("Node"); if(node){ node.innerHTML = JSON.stringify(someResource)); } }, 1000);

3 được gọi nhiều lần. Nếu ta quay trở lại với snapshot

var data = getData(); setInterval(function(){ var node = document.getElementById("Node"); if(node){ node.innerHTML = JSON.stringify(someResource)); } }, 1000);

8, ta sẽ thấy là nó chỉ khởi tạo object mà không xóa chúng đi. Nói cách khác là nó luôn khởi tạo object mà không cho phép GC thu thập một vài trong số chúng. Giờ khi ta đã biết objects bị rò rỉ ở đâu (

var data = getData(); setInterval(function(){ var node = document.getElementById("Node"); if(node){ node.innerHTML = JSON.stringify(someResource)); } }, 1000);

6), ta có thể quay trở lại code để sửa lại nó.

Các tính năng hữu ích khác

Thay vì sử dụng

a = "value"; console.log(a);

00, ta có thể sử dụng

a = "value"; console.log(a);

01:

C++ các trường hợp nào sẽ dẫn tới memory leak

Giao diện này cho ta thấy một danh sách các hàm và bộ nhớ khởi tạo liên quan đến chúng. Ta có thể thấy ngay là

1 2 3 4 5

4 và

var data = getData(); setInterval(function(){ var node = document.getElementById("Node"); if(node){ node.innerHTML = JSON.stringify(someResource)); } }, 1000);

6 là nổi bật hơn cả. Khi chọn

1 2 3 4 5

4 ta sẽ thấy đối tượng khởi tạo liên quan được gọi đến. Ta có thể để ý thấy

1 2 3 4 5 6 7

2

var data = getData(); setInterval(function(){ var node = document.getElementById("Node"); if(node){ node.innerHTML = JSON.stringify(someResource)); } }, 1000);

3 và

var data = getData(); setInterval(function(){ var node = document.getElementById("Node"); if(node){ node.innerHTML = JSON.stringify(someResource)); } }, 1000);

2 là những hàm khởi tạo của các đối tượng bị rò rỉ.

Note: để sử dụng được tính năng này, vào

a = "value"; console.log(a);

08 -> Settings và enable

a = "value"; console.log(a);

09 trước khi record.