2011-12-29 34 views
13

như ECMAScriptv5, mỗi lần khi kiểm soát vào một mã, enginge tạo ra một LexicalEnvironment (LE) và một VariableEnvironment (VE), cho chức năng mã, những 2 đối tượng là chính xác tài liệu tham khảo cùng đó là kết quả của gọi NewDeclarativeEnvironment (ECMAScript v5 10.4.3), và tất cả các biến khai báo trong chức năng mã được lưu trữ trong hồ sơ môi trường componentof VariableEnvironment (ECMAScript v5 10.5), và đây là các khái niệm cơ bản cho việc đóng cửa.Về việc đóng cửa, LexicalEnvironment và GC

gì nhầm lẫn tôi là như thế nào rác Thu thập công trình với cách tiếp cận đóng cửa này, giả sử tôi có mã như:

function f1() { 
    var o = LargeObject.fromSize('10MB'); 
    return function() { 
     // here never uses o 
     return 'Hello world'; 
    } 
} 
var f2 = f1(); 

sau dòng var f2 = f1(), đồ thị đối tượng của chúng tôi sẽ là:

global -> f2 -> f2's VariableEnvironment -> f1's VariableEnvironment -> o 

do đó, từ kiến ​​thức nhỏ của tôi, nếu công cụ javascript sử dụng phương pháp tính tham chiếu để thu thập rác, đối tượng o có tại le ase 1 refenrence và sẽ không bao giờ được GCed. Xuất hiện điều này sẽ dẫn đến lãng phí bộ nhớ vì o sẽ không bao giờ được sử dụng nhưng luôn được lưu trữ trong bộ nhớ.

Ai đó có thể cho biết động cơ đều biết rằng f2 của VariableEnvironment không sử dụng f1 của VariableEnvironment, vì vậy VariableEnvironment toàn bộ f1 của sẽ GCed, do đó là một đoạn mã có thể dẫn đến tình hình phức tạp hơn:

function f1() { 
    var o1 = LargeObject.fromSize('10MB'); 
    var o2 = LargeObject.fromSize('10MB'); 
    return function() { 
     alert(o1); 
    } 
} 
var f2 = f1(); 

trong trường hợp này, f2 sử dụng đối tượng o1 mà các cửa hàng trong VariableEnvironment f1 của, vì vậy VariableEnvironme f2 của nt phải giữ một tham chiếu đến f1 Biến số môi trường, kết quả là o2 cũng không thể được GC, điều này dẫn đến lãng phí bộ nhớ.

vì vậy tôi sẽ hỏi, cách công cụ javascript hiện đại (JScript.dll/V8/SpiderMonkey ...) xử lý tình huống như vậy, có quy tắc được chỉ định chuẩn hay không. đồ thị đối tượng như vậy khi thực hiện Bộ sưu tập rác.

Cảm ơn.

+0

bản sao có thể có của [thu thập rác với node.js] (http://stackoverflow.com/questions/5326300/garbage-collection-with-node-js) – Bergi

Trả lời

7

tl; dr trả lời:"Only variables referenced from inner fns are heap allocated in V8. If you use eval then all vars assumed referenced.". Trong ví dụ thứ hai của bạn, o2 có thể được cấp phát trên ngăn xếp và bị vứt bỏ sau khi thoát khỏi số f1.


Tôi không nghĩ rằng họ có thể xử lý.Ít nhất chúng ta biết rằng một số công cụ có thể không, vì điều này được biết đến là nguyên nhân của nhiều rò rỉ bộ nhớ, như ví dụ:

function outer(node) { 
    node.onclick = function inner() { 
     // some code not referencing "node" 
    }; 
} 

nơi inner đóng trên node, tạo thành một tham chiếu vòng tròn inner -> outer's VariableContext -> node -> inner, mà sẽ không bao giờ được giải phóng ví dụ IE6, ngay cả khi nút DOM bị xóa khỏi tài liệu. Một số trình duyệt xử lý điều này rất tốt mặc dù: tham khảo vòng tròn bản thân không phải là một vấn đề, đó là việc thực hiện GC trong IE6 đó là vấn đề. Nhưng bây giờ tôi đã đào từ chủ đề.

Cách phổ biến để phá vỡ tham chiếu vòng tròn là loại bỏ tất cả các biến không cần thiết ở cuối outer. I.e., đặt node = null. Câu hỏi đặt ra là liệu các công cụ javascript hiện đại có thể làm điều này cho bạn hay không, liệu chúng có thể suy ra bằng cách nào đó rằng một biến không được sử dụng trong vòng inner?

Tôi nghĩ câu trả lời là không, nhưng tôi có thể được chứng minh là sai. Lý do là mã sau đây thực thi tốt:

function get_inner_function() { 
    var x = "very big object"; 
    var y = "another big object"; 
    return function inner(varName) { 
     alert(eval(varName)); 
    }; 
} 

func = get_inner_function(); 

func("x"); 
func("y"); 

Tự mình sử dụng this jsfiddle example. Không có tham chiếu đến x hoặc y bên trong inner nhưng chúng vẫn có thể truy cập được bằng cách sử dụng eval. (Thật ngạc nhiên, nếu bạn bí danh eval đối với một cái gì đó khác, hãy nói myeval và gọi myeval, bạn KHÔNG nhận được ngữ cảnh thực thi mới - điều này thậm chí trong đặc tả, xem phần 10.4.2 và 15.1.2.1.1 trong ECMA-262.)


Edit: Theo nhận xét của bạn, dường như một số công cụ hiện đại thực sự làm một số thủ thuật thông minh, vì vậy tôi đã cố gắng để đào nhiều hơn một chút. Tôi đã xem qua số này forum thread thảo luận vấn đề và đặc biệt, liên kết đến a tweet about how variables are allocated in V8. Nó cũng đặc biệt chạm vào vấn đề eval. Dường như nó phải phân tích mã trong tất cả các hàm bên trong. và xem những biến nào được tham chiếu, hoặc nếu eval được sử dụng, và sau đó xác định xem mỗi biến có nên được cấp phát trên vùng heap hay trên ngăn xếp hay không. Khá gọn gàng. Đây là another blog có chứa rất nhiều chi tiết về việc triển khai ECMAScript.

Điều này có ngụ ý rằng ngay cả khi chức năng bên trong không bao giờ "thoát" cuộc gọi, nó vẫn có thể buộc các biến được cấp phát trên heap. Ví dụ:

function init(node) { 

    var someLargeVariable = "..."; 

    function drawSomeWidget(x, y) { 
     library.draw(x, y, someLargeVariable); 
    } 

    drawSomeWidget(1, 1); 
    drawSomeWidget(101, 1); 

    return function() { 
     alert("hi!"); 
    }; 
} 

Bây giờ, khi init đã kết thúc cuộc gọi của nó, someLargeVariable không còn tham chiếu và phải có đủ điều kiện để xóa, nhưng tôi nghi ngờ rằng nó không phải là, trừ khi chức năng bên trong drawSomeWidget đã được tối ưu hóa đi (inlined?). Nếu vậy, điều này có thể xảy ra khá thường xuyên khi sử dụng các hàm tự thực hiện để bắt chước các lớp với các phương thức riêng/công khai.


Trả lời nhận xét của Raynos bên dưới. Tôi đã thử kịch bản trên (được sửa đổi một chút) trong trình gỡ lỗi và kết quả như tôi dự đoán, ít nhất là trong Chrome:

Screenshot of Chrome debugger Khi chức năng bên trong đang được thực hiện, một sốLargeVariable vẫn nằm trong phạm vi.

Nếu tôi nhận xét ra các tài liệu tham khảo để someLargeVariable trong phương pháp nội drawSomeWidget, sau đó bạn sẽ có được một kết quả khác nhau:

Screenshot of Chrome debugger 2 Bây giờ someLargeVariable không nằm trong phạm vi, bởi vì nó có thể được cấp phát trên stack.

+1

Cảm ơn rất nhiều, 'eval' thực sự là một con quái vật GC, gần đây tôi đã được một người bạn nói rằng một số động cơ (SpiderMonkey trong trường hợp của anh ta) có thể xử lý một GC nâng cao cho các biến đóng-ed nếu ** không có 'eval' trong' inner' ** vì javascript engine có thể thu thập tất cả các tên biến sẽ được sử dụng trong 'inner' bằng cách chỉ cần inpect tất cả ** Identifier ** s trong mã hàm, nhưng khi' inner' chứa bất kỳ lệnh gọi nào đến 'eval', nó không thực hiện bất kỳ tối ưu hóa nào. Theo tôi, xử lý 'eval' khác nhau thực sự là một chiến lược được thiết kế tốt :) – otakustay

+0

Ồ, thú vị. Tôi đã thấy trong một số thư viện rằng họ sử dụng hàm dựng hàm 'new Function (code-as-string)' như là một thay thế cho 'eval', mà không bị" vấn đề "kế thừa phạm vi, theo các bài kiểm tra của tôi. Tôi tự hỏi nếu 'setInterval' và' setTimeout' phải được xử lý khác nhau không? – waxwing

+0

@otakustay: Đã cập nhật câu trả lời. – waxwing

0

nếu động cơ javascript sử dụng một phương pháp tính tham khảo

Hầu hết javascript động cơ của sử dụng một số biến thể của một nhà sưu tập compacting mark and sweep rác, không phải là một đơn giản tính tham khảo GC, vì vậy chu kỳ tài liệu tham khảo không gây ra vấn đề.

Họ cũng có xu hướng thực hiện một số thủ thuật để các chu kỳ liên quan đến các nút DOM (được tham chiếu do trình duyệt tính bên ngoài vùng heap JavaScript) không giới thiệu các chu kỳ không thể thu thập được. The XPCOM cycle collector thực hiện điều này cho Firefox.

Bộ thu chu trình dành phần lớn thời gian của nó để tích lũy (và quên) các đối tượng XPCOM có thể tham gia vào chu kỳ rác. Đây là giai đoạn nhàn rỗi của hoạt động của nhà sưu tập, trong đó các biến thể đặc biệt của nsAutoRefCnt đăng ký và hủy đăng ký rất nhanh chóng với người thu thập, khi họ đi qua một sự kiện "đáng ngờ" (từ N + 1 đến N, cho nonzero N).

Định kỳ bộ thu thức dậy và kiểm tra bất kỳ con trỏ đáng ngờ nào đã được lưu trong bộ đệm trong một thời gian. Đây là giai đoạn quét của hoạt động của nhà sưu tập. Trong giai đoạn này, nhà sưu tập liên tục yêu cầu mỗi ứng cử viên cho một lớp trợ giúp thu thập chu kỳ đơn, và nếu người trợ giúp đó tồn tại, người thu thập yêu cầu người trợ giúp mô tả trẻ em (thuộc sở hữu) của ứng cử viên. Bằng cách này, nhà sưu tập xây dựng một hình ảnh của đồ thị sở hữu có thể truy cập từ các đối tượng đáng ngờ.

Nếu trình thu thập tìm thấy một nhóm đối tượng mà tất cả tham chiếu đến nhau, và xác định rằng số lượng tham chiếu của đối tượng được tính toán bởi con trỏ nội bộ trong nhóm, nó xem xét rằng rác nhóm theo chu kỳ. miễn phí. Đây là giai đoạn hủy liên kết của thao tác thu thập. Trong giai đoạn này, nhà sưu tập đi qua các đối tượng rác mà nó đã tìm thấy, một lần nữa tham khảo với các đối tượng trợ giúp của họ, yêu cầu các đối tượng trợ giúp "hủy liên kết" từng đối tượng khỏi các con ngay lập tức của nó.

Lưu ý rằng người thu thập cũng biết cách đi qua vùng heap JS và có thể định vị các chu kỳ quyền sở hữu đi vào và ra khỏi nó.

Sự hài hòa EcmaScript có thể bao gồm ephemerons cũng như cung cấp các tham chiếu bị giữ yếu.

Bạn có thể tìm thấy "The future of XPCOM memory management" thú vị.

1

Không có thông số kỹ thuật chuẩn của việc triển khai cho GC, mọi công cụ đều có triển khai riêng. Tôi biết một chút khái niệm về v8, nó có một bộ thu gom rác rất ấn tượng (stop-the-world, thế hệ, chính xác). Như trên ví dụ 2, động cơ v8 có bước sau:

  1. tạo đối tượng biến đổi f1 của f1 được gọi là f1.
  2. sau khi được tạo mà đối tượng V8 tạo ra một lớp ẩn ban đầu của f1 được gọi là H1.
  3. cho biết điểm của f1 là f2 ở cấp cơ sở.
  4. tạo một lớp ẩn khác H2, dựa trên H1, sau đó thêm thông tin vào H2 mô tả đối tượng là có một thuộc tính, o1, lưu nó ở độ lệch 0 trong đối tượng f1.
  5. cập nhật f1 điểm đến H2 được chỉ định f1 nên sử dụng H2 thay vì H1.
  6. tạo một lớp ẩn H3 khác, dựa trên H2 và thêm thuộc tính, o2, lưu trữ nó ở độ lệch 1 trong đối tượng f1.
  7. cập nhật f1 điểm đến H3.
  8. tạo đối tượng VariableEnvironment ẩn danh được gọi là a1.
  9. tạo một lớp ẩn ban đầu của a1 được gọi là A1.
  10. cho biết 1 phụ huynh là f1.

Khi chức năng phân tích chữ, nó tạo hàm chức năng. Chỉ phân tích FunctionBody khi chức năng là called.The mã sau đây cho thấy nó không ném lỗi khi thời gian phân tích cú pháp

function p(){ 
    return function(){alert(a)} 
} 
p(); 

Vì vậy, tại GC thời gian H1, H2 sẽ được quét, bởi vì không có điểm tham chiếu that.In tâm trí của tôi nếu mã là lazily biên dịch, không có cách nào để chỉ ra biến o1 khai báo trong a1 là một tham chiếu đến f1, Nó sử dụng JIT.

+0

V8 sử dụng cả Dọn dẹp và Đánh dấu quét nhỏ gọn, Dọn dẹp là một tham chiếu đến thuật toán của Cheney http://en.wikipedia.org/wiki/Cheney%27s_algorithm –