[关闭]
@JunQiu 2018-09-25T09:58:30.000000Z 字数 5178 阅读 1455

Js_require()源码解读、v8垃圾回收机制

summary_2018/08 language_js v8andWebkit


1、日常

1.1、js:require()源码解读

1.2、v8垃圾回收机制


2、技术

2.1、js:require()源码解读

2.1.1、 执行过程(参考node使用手册
  1. Node 遇到 require(X) 时,按下面的顺序处理。
  2. 1)如果 X 是内置模块(比如 require('http'))
  3.   a. 返回该模块。
  4.   b. 不再继续执行。
  5. 2)如果 X "./" 或者 "/" 或者 "../" 开头
  6.   a. 根据 X 所在的父模块,确定 X 的绝对路径。
  7.   b. X 当成文件,依次查找下面文件,只要其中有一个存在,就返回该文件,不再继续执行。
  8. X
  9. X.js
  10. X.json
  11. X.node
  12.   c. X 当成目录,依次查找下面文件,只要其中有一个存在,就返回该文件,不再继续执行。
  13. X/package.jsonmain字段)
  14. X/index.js
  15. X/index.json
  16. X/index.node
  17. 3)如果 X 不带路径
  18.   a. 根据 X 所在的父模块,确定 X 可能的安装目录。
  19.   b. 依次在每个目录中,将 X 当成文件名或目录名加载。
  20. 4 抛出 "not found"
2.1.2、具体整个流程:删改的部分源码
  1. ## Node 定义了一个构造函数 Module,所有的模块都是 Module 的实例。
  2. function Module(id, parent) {
  3. this.id = id; // 位置
  4. this.exports = {}; // ??
  5. this.parent = parent; // 调用者
  6. this.filename = null;
  7. this.loaded = false; // 是否加载完成
  8. this.children = []; // 是否加载其它模块
  9. }
  10. module.exports = Module;
  11. ## 每个模块实例都有require方法
  12. Module.prototype.require = function(path) {
  13. return Module._load(path, this);
  14. }
  15. // Module._load的实现
  16. Module._load = function(request, parent, isMain) {
  17. // 计算绝对路径
  18. var filename = Module._resolveFilename(request, parent);
  19. // 第一步:如果有缓存,取出缓存
  20. var cachedModule = Module._cache[filename];
  21. if (cachedModule) {
  22. return cachedModule.exports;
  23. // 第二步:是否为内置模块
  24. if (NativeModule.exists(filename)) {
  25. return NativeModule.require(filename);
  26. }
  27. // 第三步:生成模块实例,存入缓存
  28. var module = new Module(filename, parent);
  29. Module._cache[filename] = module;
  30. // 第四步:加载模块
  31. try {
  32. module.load(filename);
  33. hadException = false;
  34. } finally {
  35. if (hadException) {
  36. delete Module._cache[filename];
  37. }
  38. }
  39. // 第五步:输出模块的exports属性
  40. return module.exports;
  41. };
  42. ## 上面的主要的两个方法
  43. Module._resolveFilename() :确定模块的绝对路径
  44. module.load():加载模块
  45. // Module._resolveFilename()实现
  46. Module._resolveFilename = function(request, parent) {
  47. // 第一步:如果是内置模块,不含路径返回
  48. if (NativeModule.exists(request)) {
  49. return request;
  50. }
  51. // 第二步:确定所有可能的路径
  52. var resolvedModule = Module._resolveLookupPaths(request, parent);
  53. var id = resolvedModule[0];
  54. var paths = resolvedModule[1];
  55. // 第三步:确定哪一个路径为真
  56. var filename = Module._findPath(request, paths);
  57. if (!filename) {
  58. var err = new Error("Cannot find module '" + request + "'");
  59. err.code = 'MODULE_NOT_FOUND';
  60. throw err;
  61. }
  62. return filename;
  63. };
  64. Tips:上面有Module.resolveLookupPaths() Module._findPath()两个方法 ,前者用来列出可能的路径,后者用来确认哪一个路径为真。
  65. // Module._resolveLookupPaths()结果:
  66. [ '/home/ruanyf/tmp/node_modules',
  67. '/home/ruanyf/node_modules',
  68. '/home/node_modules',
  69. '/node_modules'
  70. '/home/ruanyf/.node_modules',
  71. '/home/ruanyf/.node_libraries'
  72. '$Prefix/lib/node' ]
  73. Tips:如果给出了具体路径,会直接返回
  74. // Module._findPath()实现
  75. Module._findPath = function(request, paths) {
  76. // 列出所有可能的后缀名:.js,.json, .node
  77. var exts = Object.keys(Module._extensions);
  78. // 如果是绝对路径,就不再搜索
  79. if (request.charAt(0) === '/') {
  80. paths = [''];
  81. }
  82. // 是否有后缀的目录斜杠
  83. var trailingSlash = (request.slice(-1) === '/');
  84. // 第一步:如果当前路径已在缓存中,就直接返回缓存
  85. var cacheKey = JSON.stringify({request: request, paths: paths});
  86. if (Module._pathCache[cacheKey]) {
  87. return Module._pathCache[cacheKey];
  88. }
  89. // 第二步:依次遍历所有路径
  90. for (var i = 0, PL = paths.length; i < PL; i++) {
  91. var basePath = path.resolve(paths[i], request);
  92. var filename;
  93. if (!trailingSlash) {
  94. // 第三步:是否存在该模块文件
  95. filename = tryFile(basePath);
  96. if (!filename && !trailingSlash) {
  97. // 第四步:该模块文件加上后缀名,是否存在
  98. filename = tryExtensions(basePath, exts);
  99. }
  100. }
  101. // 第五步:目录中是否存在 package.json
  102. if (!filename) {
  103. filename = tryPackage(basePath, exts);
  104. }
  105. if (!filename) {
  106. // 第六步:是否存在目录名 + index + 后缀名
  107. filename = tryExtensions(path.resolve(basePath, 'index'), exts);
  108. }
  109. // 第七步:将找到的文件路径存入返回缓存,然后返回
  110. if (filename) {
  111. Module._pathCache[cacheKey] = filename;
  112. return filename;
  113. }
  114. }
  115. // 第八步:没有找到文件,返回false
  116. return false;
  117. };
  118. ## module.load()
  119. 模块的加载实质上就是,注入exportsrequiremodule三个全局变量,然后执行模块的源码,然后将模块的 exports 变量的值输出。
  120. 确定后缀->根据后缀选择加载方式
2.1.3、引用参考

2.2、v8垃圾回收机制

2.2.1、简述
2.2.2、Scavenge算法:新生代的处理
2.2.3、Mark-Sweep 和 Mark-Compact:老生代的处理
2.2.4、v8新垃圾回收机制:并发标记
  1. ## 在标记进行中应用程序暂停(stop-the-world marking)
  2. 三色标记(白色-灰色-黑色) + 标记工作表(变为灰色)

  1. ## 减少标记停顿
  2. 增量标记:在增量标记期间,GC将标记工作分解为更小的模块,并允许应用程序在模块之间交替运行,垃圾回收的最大停顿时间可以减少到原来的1/6左右。
  3. 增量标记不是免费的。应用程序必须通知垃圾收集器有关更改对象图的所有操作。V8使用Dijkstra风格的写屏障实现通知。object.field = valueJavaScript 中对表单执行每次写操作之后,V8会插入写屏障代码:
  4. // Called after `object.field = value`.
  5. write_barrier(object, field_offset, value) {
  6. // 如果对象是黑色,引用一个白色的对象,则需要加到marking_worklist
  7. if (color(object) == black && color(value) == white) {
  8. set_color(value, grey);
  9. marking_worklist.push(value);
  10. }
  11. }

  1. ## 写屏障:由于使用三色标记法,允许用户代码跟垃圾回收同时运行,那么需要保证被黑色对象引用的白色对象不被回收,这就是写屏障的作用。(由于在三色标记算法中,黑色对象已经处理完毕,它不会被重复扫描。那么,这个对象引用的白色对象将没有机会被着色,最终会被误当作垃圾清理。)
  2. ## 由于写屏障成本,增量标记可能会降低应用程序的吞吐量。通过使用额外的工作线程,可以改善吞吐量和暂停时间,即并行标记。
  3. 并行标记发生在主线程和工作线程上。在整个并行标记阶段暂停应用程序。它是stop-the-world marking的多线程版本。

  1. ### 并发标记:主要发生在工作线程上。并发标记正在进行时,应用程序可以继续运行。
  2. ### 主线程的每个改变对象图表的操作将会是竞态数据的潜在来源。在某些操作上,主线程需要与工作线程同步。同步代价和复杂度是操作而定。(???解决)

2.2.5、参考
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注