参考: http://slides.com/gruizdevilla/memory

内存是一张图

  • 原始类型,只能作为叶子。原始类型不能引用其他类型
    • 数字
    • 字符串
    • 布尔值
  • 除了原始类型之外,其他类型都是对象,其实就是键值对
  • 数组是一种特殊对象,它的键是连续的数字

image.png

内存从根开始

  • 在浏览器中,根对象是window
  • 在nodejs中,根对象是global
  • 任何从根无法到达的对象,都会被GC回收,例如下图的节点9和10
  • 根节点的GC是无法控制的

image.png

路径

  • 从根节点开始到特定对象的路径,如下图的1-2-4-6-8

image.png

支配项

  • 每个对象有且仅有一个支配项,支配项对对象可能不是直接引用
  • 举例子
    • 节点1支配节点2
    • 节点2支持节点3、4、6
    • 节点3支配节点5
    • 节点6支配节点7
    • 节点5支配节点8
    • 上面的例子有个不好理解的是节点2为什么支配了节点6?如果节点A存在于从根节点到节点B的每一个路径中,那么A就是B的支配项。2存在于1-2-4-6,也存在于1-2-3-6,所以节点2支配节点6

image.png

V8 新生代与老生代

  • v8内存分为新生代和老生代内存,两块内存使用不同的内存GC策略
  • 相比而言,新生代GC很快,老生代则较慢
  • 新生代的内存在某些条件下会被转到老生代内存区
  • GC发生时,用可能应用会暂停

解除引用的一些错误

  1. var a = {name: 'wdd'}
  2. delete a.name // 这回让对象a变成慢对象
  3. var a = {name: 'wdd'}
  4. a.name = null // 这个则更好

关于slow Object

  • V8 optimizing compiler makes assumptions on your code to make optimizations.
  • It transparently creates hidden classes that represent your objects.
  • Using this hidden classes, V8 works much faster. If you “delete” properties, these assumptions are no longer valid, and the code is de-optimized, slowing your code.
  1. // Fast Object
  2. function FastPurchase(units, price) {
  3. this.units = units;
  4. this.price = price;
  5. this.total = 0;
  6. this.x = 1;
  7. }
  8. var fast = new FastPurchase(3, 25);
  9. // Slow Object
  10. function SlowPurchase(units, price) {
  11. this.units = units;
  12. this.price = price;
  13. this.total = 0;
  14. this.x = 1;
  15. }
  16. var slow = new SlowPurchase(3, 25);
  17. //x property is useless
  18. //so I delete it
  19. delete slow.x;

image.png

Timers内存泄露

  1. //
  2. var buggyObject = {
  3. callAgain: function () {
  4. var ref = this;
  5. var val = setTimeout(function () {
  6. console.log('Called again: '
  7. + new Date().toTimeString());
  8. ref.callAgain();
  9. }, 1000);
  10. }
  11. };
  12. buggyObject.callAgain();
  13. buggyObject = null;

闭包内存泄露

  1. var a = function () {
  2. var largeStr =
  3. new Array(1000000).join('x');
  4. return function () {
  5. return largeStr;
  6. };
  7. }();
  8. var a = function () {
  9. var smallStr = 'x',
  10. largeStr =
  11. new Array(1000000).join('x');
  12. return function (n) {
  13. return smallStr;
  14. };
  15. }();
  16. var a = function () {
  17. var smallStr = 'x',
  18. largeStr =
  19. new Array(1000000).join('x');
  20. return function (n) {
  21. eval(''); //maintains reference to largeStr
  22. return smallStr;
  23. };
  24. }();

DOM 内存泄露

leaf maintains a reference to it’s parent (parentNode), and recursively up to #tree, so only when leafRef is nullified is the WHOLE tree under #tree candidate to be GC

image.png

  1. var select = document.querySelector;
  2. var treeRef = select("#tree");
  3. var leafRef = select("#leaf");
  4. var body = select("body");
  5. body.removeChild(treeRef);
  6. //#tree can't be GC yet due to treeRef
  7. treeRef = null;
  8. //#tree can't be GC yet, due to
  9. //indirect reference from leafRef
  10. leafRef = null;
  11. //NOW can be #tree GC

守则

  • Use appropiate scope
    • Better than de-referencing, use local scopes.
  • Unbind event listeners
    • Unbind events that are no longer needed, specially if the related DOM objects are going to be removed.
  • Manage local cache
    • Be careful with storing large chunks of data that you are not going to use.

分析内存泄漏的工具

  • 浏览器: performance.memory
  • devtool memory profile

关于闭包的提示

  • 给闭包命名,这样在内存分析时,就可以按照函数名找到对应的函数