理解不透彻,实在是无法写清楚,记录下过程吧
为了理解堆栈区别,
我对比 c++,java,APP,javascipt(vue,v8) ,node.js, solidity,
都提到一个共同概念-虚拟机.
1 以太坊 - 深入浅出虚拟机,
2 javaScript(V8 )的工作原理:引擎
3 c++虚拟内存。epoll_wait
4 Java虚拟机精讲
在一层一层构建过程中.各自实现了自己的 堆栈和loop
我初步判断 虚拟机2个重要组成部分就是--堆 栈和loop循环。
耗时 一个周末+每个晚上2个小时 累计18个小时。
具体过程如下:
我粗罗搜集整理了如下信息。
从c++ 到Java虚拟机精讲,这2本书内容和相关网络文档
对应产品:
从 redis(epoll),memache (libevent)源码设计的io实现部分。
然后扩展到 结合JavaScript(v8)单线程工作原理和运行机制,
node.js (libuv)单线程实现,
甚至参考 以太坊存 智能合约solidity -EVM是一个基于栈的虚拟机 。如何理解如下:
要想理解堆栈区别-->必须理解经典5中经典io模型(同步,异步,阻塞和阻塞)
要想理解同步,异个该你--->必须知道进程和线程各种区别
要想阻塞,非阻塞--->操作系统对进程状态
然后回过堆的内存管理各种方式 中断,缺页,swap,映射。
然后回顾到基于栈的和寄存 的vm设计。尼玛 最后没理解 , 高楼大厦不是凭空出来的,是一层层该你的 ,
因此我不纠结堆栈这2个 概念。
目录
1.JavaScript(V8 )的工作原理:引擎,运行时和调用堆栈的概述
JavaScript 是如何工作的:在 V8 引擎里 5 个优化代码的技巧
-
再谈Event Loop(JavaScript,redis,memache,node.js 比较)
Event Loop 以太坊 - 深入浅出虚拟机
-
java - 深入浅出虚拟机(主要是book)
image.png
- doc
- QA 堆栈区
https://github.com/xitu/gold-miner/blob/master/TODO/how-javascript-works-inside-the-v8-engine-5-tips-on-how-to-write-optimized-code.md
脉脉上回答:
https://maimai.cn/web/gossip_detail?encode_id=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpZCI6MjEyMDI1NjksImlhdCI6MTU1NTM4MTEwM30.KX-CshMubqjcxd5dN_N54VrMOTm6_n9JzEXV_cBBO2U
第一部分:JavaScript的工作原理:引擎,运行时和调用堆栈的概述
原文:https://blog.sessionstack.com/how-does-javascript-actually-work-part-1-b0bacc073cf
As JavaScript is getting more and more popular, teams are leveraging its support on many levels in their stack - front-end, back-end, hybrid apps, embedded devices and much more.
随着 JavaScript 越来越受欢迎,许多团队在他们技术栈的多个方面使用它:前端,后端,混合应用程序,嵌入式设备等等。
As shown in the GitHut stats, JavaScript is at the top in terms of Active Repositories and Total Pushes in GitHub. It doesn’t lag behind much in the other categories either.
如 GitHut统计 所示,JavaScript 在 GitHub 中的活跃仓库和总推送方面位于顶部,在其他类别中也没有落后很多。
If projects are getting so much dependent on JavaScript, this means that developers have to be utilizing everything that the language and the ecosystem provide with deeper and deeper understanding of the internals, in order to build amazing software.
如果项目越来越依赖 JavaScript,这意味着开发人员必须利用这门语言和生态系统提供的所有内容,并且深入地了解其内部内容,以便构建出惊人的软件。
As it turns out, there are a lot of developers that are using JavaScript on a daily basis but don’t have the knowledge of what happens under the hood.
事实证明,有很多开发人员每天都在使用JavaScript,但并不知道什么情况下会发生什么。
概述(Overview)
Almost everyone has already heard of the V8 Engine as a concept, and most people know that JavaScript is single-threaded or that it is using a callback queue.
几乎所有人都已经听说过V8引擎的概念,大多数人都知道JavaScript是单线程的,或者是使用回调队列。
In this post, we’ll go through all these concepts in detail and explain how JavaScript actually runs. By knowing these details, you’ll be able to write better, non-blocking apps that are properly leveraging the provided APIs.
在这篇文章中,我们将详细介绍所有这些概念,并解释 JavaScript 如何运行。了解这些细节,你将能够正确利用提供的 API 编写更好的非阻塞性应用程序。
If you’re relatively new to JavaScript, this blog post will help you understand why JavaScript is so “weird” compared to other languages.
如果你是 JavaScript 初学者,此博客文章将帮助你了解为什么 JavaScript 与其他语言相比是如此“奇怪”。
And if you’re an experienced JavaScript developer, hopefully, it will give you some fresh insights on how the JavaScript Runtime you’re using every day actually works.
如果你是一位经验丰富的 JavaScript 开发人员,希望能为你提供一些关于你每天使用的 JavaScript 运行时实际工作的新见解。
JavaScript引擎(The JavaScript Engine)
A popular example of a JavaScript Engine is Google’s V8 engine. The V8 engine is used inside Chrome and Node.js for example. Here is a very simplified view of what it looks like:
JavaScript 引擎的一个流行示例是 Google 的 V8 引擎。V8 引擎被 Chrome 和 Node.js 使用。这是一个该引擎非常简化的视图:
The Engine consists of two main components:
Memory Heap - this is where the memory allocation happens
Call Stack - this is where your stack frames are as your code executes
引擎由两个主要组成部分组成:
- 内存堆 - 这是内存分配发生的地方
- 调用栈 - 这是你的代码执行时堆栈帧的位置
每当一个函数被调用时,js 会为其创建执行环境,js引擎就会把这个执行环境 放入一个栈中 来处理。
这个栈,我们称之为函数调用栈(call stack)
运行时(The Runtime)
There are APIs in the browser that have been used by almost any JavaScript developer out there (e.g. “setTimeout”). Those APIs, however, are not provided by the Engine.
浏览器中已经有一些几乎被所有 JavaScript 开发人员使用的API(例如“setTimeout”)。然而,引擎不提供这些API。
So, where are they coming from?
It turns out that the reality is a bit more complicated.
那么他们从哪里来? 事实上,这有点复杂。
So, we have the Engine but there is actually a lot more. We have those things called Web APIs which are provided by browsers, like the DOM, AJAX, setTimeout and much more.
所以,我们有引擎,但实际上还有更多内容。有一些被称为 Web API 的东西,由浏览器提供,如 DOM,AJAX,setTimeout 等等。
And then, we have the so popular event loop and the callback queue.
然后,还有受欢迎的事件循环和回调队列。
调用堆栈(The Call Stack)
JavaScript is a single-threaded programming language, which means it has a single Call Stack. Therefore it can do one thing at a time.
JavaScript是一种单线程编程语言,这意味着它有一个单一的调用堆栈。因此,它一次只可以做一件事。
The Call Stack is a data structure which records basically where in the program we are.
If we step into a function, we put it on the top of the stack. If we return from a function, we pop off the top of the stack. That’s all the stack can do.
调用堆栈是一个数据结构,它记录了我们在程序的基本位置。
如果我们进入一个函数,我们把它放在堆栈的顶部。如果我们从一个函数返回,我们弹出堆栈的顶部。这就是堆栈做的事情。
Let’s see an example. Take a look at the following code:
我们来看一个例子。看看下面的代码:
function multiply(x, y) {
return x * y;
}
function printSquare(x) {
var s = multiply(x, x);
console.log(s);
}
printSquare(5);
When the engine starts executing this code, the Call Stack will be empty. Afterwards, the steps will be the following:
当引擎开始执行此代码时,调用堆栈将为空。之后,步骤如下
Each entry in the Call Stack is called a Stack Frame.
进入调用堆栈中的每个条目称为堆栈帧。
And this is exactly how stack traces are being constructed when an exception is being thrown — it is basically the state of the Call Stack when the exception happened. Take a look at the following code:
这正是在抛出异常时构造堆栈跟踪的方式 — 当异常发生时,它基本上是调用堆栈的状态。看看下面的代码:
function foo() {
throw new Error('SessionStack will help you resolve crashes :)');
}
function bar() {
foo();
}
function start() {
bar();
}
start();
If this is executed in Chrome (assuming that this code is in a file called foo.js), the following stack trace will be produced:
如果这是在 Chrome 中执行的(假设此代码位于一个名为foo.js的文件中),则会产生以下堆栈跟踪:
Blowing the stack — this happens when you reach the maximum Call Stack size. And that could happen quite easily, especially if you’re using recursion without testing your code very extensively. Take a look at this sample code:
Blowing the stack — 当你达到最大调用堆栈尺寸时,会发生这种情况。这可能会非常容易发生,特别是如果你在不经过很大程度测试代码的情况下使用递归。看看这个示例代码:
function foo() {
foo();
}
foo();
When the engine starts executing this code, it starts with calling the function “foo”. This function, however, is recursive and starts calling itself without any termination conditions. So at every step of the execution, the same function gets added to the Call Stack over and over again. It looks something like this:
当引擎开始执行这个代码时,它首先调用 “foo” 函数。然而,此函数是递归的,并且开始调用自身而没有任何终止条件。所以在执行的每个步骤中,相同的函数都被一次又一次地添加到调用堆栈中。看起来像这样:
At some point, however, the number of function calls in the Call Stack exceeds the actual size of the Call Stack, and the browser decides to take action, by throwing an error, which can look something like this:
然后,在调用堆栈中的函数调用次数超过了调用堆栈的实际大小的时候,浏览器决定采取行动,抛出一个错误,看起来像这样:
Running code on a single thread can be quite easy since you don’t have to deal with complicated scenarios that are arising in multi-threaded environments — for example, deadlocks.
在单线程上运行代码可能非常容易,因为你不必处理在多线程环境中出现的复杂场景,例如死锁。
But running on a single thread is quite limiting as well. Since JavaScript has a single Call Stack, what happens when things are slow?
但在单线程上运行也是非常受限的。由于JavaScript有一个调用堆栈,当事情开始缓慢时会发生什么?
并发和事件循环(Concurrency & the Event Loop)
What happens when you have function calls in the Call Stack that take a huge amount of time in order to be processed? For example, imagine that you want to do some complex image transformation with JavaScript in the browser.
当你在调用堆栈中进行函数调用需要大量时间才能进行处理时会发生什么?例如,假设你想在浏览器中使用 JavaScript 进行一些复杂的图像转换。
You may ask — why is this even a problem? The problem is that while the Call Stack has functions to execute, the browser can’t actually do anything else — it’s getting blocked. This means that the browser can’t render, it can’t run any other code, it’s just stuck. And this creates problems if you want nice fluid UIs in your app.
你可能会问 - 为什么这是一个问题?问题在于,当调用堆栈有函数在执行的时候,浏览器实际上不能做任何事情 - 它被阻塞了。这意味着浏览器无法渲染任何内容,它也不能运行任何其他代码,它卡住了。如果你想要的UI流畅,这会产生问题。
And that’s not the only problem. Once your browser starts processing so many tasks in the Call Stack, it may stop being responsive for quite a long time. And most browsers take action by raising an error, asking you whether you want to terminate the web page.
这不是唯一的问题。一旦你的浏览器开始处理“调用堆栈”中的许多任务,它可能会停止响应很长时间。大多数浏览器通过提出错误来采取行动,询问你是否要终止网页。
1.2 演示1
我们先来看一个有意思的现象,我运行一段代码,大家觉得输出的顺序是什么:
setTimeout(() => {
console.log('setTimeout')
}, 22)
for (let i = 0; i++ < 2;) {
i === 1 && console.log('1')
}
setTimeout(() => {
console.log('set2')
}, 20)
for (let i = 0; i++ < 100000000;) {
i === 99999999 && console.log('2')
}
- 首先,文件入栈
- 开始执行文件,读取到第一行代码,当遇到
setTimeout
的时候,执行引擎将其添加到栈中。(由于字体太细我调粗了一点。。。)
- 调用栈发现
setTimeout
是Webapis
中的API
,因此将其交给浏览器的timer
模块进行处理,同时处理下一个任务。
- 第二个
setTimeout
入栈
- 同上所示,异步请求被放入
异步API
进行处理,同时进行下一个入栈操作:
- 在进行异步的同时,
app.js
文件调用完毕,弹出调用栈,异步执行完毕后,会将回调函数放入任务队列:
- 任务队列通知调用栈,我这边有任务还没有执行,调用栈则会执行任务队列里的任务:
上面的流程解释了浏览器遇到 setTimeout
之后究竟如何执行的,其实总结下来就是以下几点:
- 调用栈顺序调用任务
- 当调用栈发现异步任务时,将异步任务交给其他模块处理,自己继续进行下面的调用
- 异步执行完毕,异步模块将任务推入任务队列,并通知调用栈
- 调用栈在执行完当前任务后,将执行任务队列里的任务
- 调用栈执行完任务队列里的任务之后,继续执行其他任务
来源:https://juejin.im/post/5bc1adc45188255c82553921
第二部分:JavaScript 运行机制详解:再谈Event Loop
1 什么是 Event Loop
Event Loop 是一个很重要的概念,指的是计算机系统的一种运行机制。
Event Loop是一个程序结构,用于等待和发送消息和事件。
(a programming construct that waits for and dispatches events or messages in a program.)
JavaScript语言就采用这种机制,来解决单线程运行带来的一些问题。
视频:
- 菲利普·罗伯茨:到底什么是Event Loop呢? | 欧洲 JSConf 2014
https://www.youtube.com/watch?v=8aGhZQkoFbQ
- You Need to Know About Node.js Event Loop - Bert Belde
https://www.youtube.com/watch?v=PNa9OMajw9w
2 Javascript Event Loop 如何实现的
Javascript是单线程的,所有的同步任务都会在主线程中执行。
- 主线程之外,还有一个任务队列。每当一个异步任务有结果了,就往任务队列里塞一个事件。
- 当主线程中的任务,都执行完之后,系统会 “依次” 读取任务队列里的事件。与之相对应的异步任务进入主线程,开始执行。
- 异步任务之间,会存在差异,所以它们执行的优先级也会有区别。大致分为 微任务(micro task,如:Promise、MutaionObserver等)和宏任务(macro task,如:setTimeout、setInterval、I/O等)。同一次事件循环中,微任务永远在宏任务之前执行。
- 主线程会不断重复上面的步骤,直到执行完所有任务。
主线程从"任务队列"中读取事件,这个过程是循环不断的,所以整个的这种运行机制又称为Event Loop(事件循环)。
为了更好地理解Event Loop,请看下图(转引自Philip Roberts的演讲《Help, I'm stuck in an event-loop》)。
3 Node.js的Event Loop
https://jsblog.insiderattack.net/event-loop-and-the-big-picture-nodejs-event-loop-part-1-1cb67a182810
Node.js也是单线程的Event Loop,但是它的运行机制不同于浏览器环境。
Node的event是基于libuv(代码库)
请看下面的示意图(作者@BusyRich)。
根据上图,Node.js的运行机制如下。
(1)V8引擎解析JavaScript脚本。
(2)解析后的代码,调用Node API。
(3)libuv库负责Node API的执行。它将不同的任务分配给不同的线程,形成一个Event Loop(事件循环),以异步的方式将任务的执行结果返回给V8引擎。
https://github.com/libuv/libuv
ibuv is a multi-platform support library with a focus on asynchronous I/O. It was primarily developed for use by Node.js, but it's also used by Luvit, Julia, pyuv, and others.
image.png
(4)V8引擎再将结果返回给用户。
对事件的统一管理
timers:执行setTimeout() 和 setInterval()中到期的callback。
I/O callbacks:上一轮循环中有少数的I/Ocallback会被延迟到这一轮的这一阶段执行
idle, prepare:仅内部使用
poll:最为重要的阶段,执行I/O callback,在适当的条件下会阻塞在这个阶段
check:执行setImmediate的callback
close callbacks:执行close事件的callback,例如socket.on("close",func)
https://acemood.github.io/2016/02/01/event-loop-in-javascript/
3 redis Event Loop 对应浏览器js的
4 Memcached Event Loop 对应node.js
node.js使用libuv库 ,采用Memcached是libevent库,采用相同模式设计
FQA:谈谈你对堆栈的理解。
参考书籍 c++使用操作系统虚拟空间 和java的虚拟机 对应起来了
1 Java虚拟机精讲 第8章节
https://www.youtube.com/watch?v=6a4Id3lj7Sw
2 程序员的自我修养—链接、装载与库 第一章 和第六章
所以,多线程环境下,引出的两个问题:
1.乱序访问 out of order execution
2.内存可见性 memory visibility
-
线程安全
线程间私有及共享数据
- 多线程的执行问题:加锁也不一定安全
![编译优化和指令交换]](https://upload-images.jianshu.io/upload_images/1837968-ef8a3c64bec5dcf7.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
#define barier() __asm__ __volatile__ ("" : : : "memory")
T* getInstance()
{
//如果申请了,直接返回这样就没有问题吗?-有问题
if(m_ptr!=NULL)
{
return m_ptr;
}
lock();
//2个线程竞争,一个线程释放,二个线程获取之后,依然要执行申请操作 m_ptr=new T()执行二次
if(m_ptr ==NULL)
{
//m_ptr=new T();
//1 申请内存
//2 待用构造
//3赋值
//2和3乱序后,m_ptr虽然有地址,但是没有执行构造函数。可能被第三个线程"可见"
T* temp=new T();
// insert some memory barier
barier()
m_ptr=temp;
}
unlock
}
5 文章
进程的内存剖析
原文:anatomy-of-a-program-in-memory
https://manybutfinite.com/post/anatomy-of-a-program-in-memory/
翻译:http://www.gaccob.com/publish/2014-06-15-process-memory.html-
https://yangrz.github.io/blog/2017/12/20/ptmalloc/
image.png
3 以太坊 - 深入浅出虚拟机
参考:https://learnblockchain.cn/2019/04/09/easy-evm/
以太坊虚拟机(environment virtual machine,简称EVM),作用是将智能合约代码编译成可在以太坊上执行的机器码,并提供智能合约的运行环境
EVM是一种基于栈的虚拟机(区别于基于寄存器的虚拟机),用于编译、执行智能合约
- 什么是基于栈的虚拟机
以太坊虚拟机是一种基于栈的虚拟机,所以要弄清以太坊虚拟机原理,我们就必须了解- -
什么是基于栈的虚拟机。首先我们来介绍下虚拟机需要实现的功能:
image.png
取指令,其中指令来源于内存
译码,决定指令类型(执行何种操作)。另外译码的过程要包括从内存中取操作数
执行。指令译码后,被虚拟机执行(其实最终都会借助于物理机资源)
送入解释器执行
代码跟输入都有了,就可以送入解释器执行了。EVM是基于栈的虚拟机,解释器中需要操作四大组件:
- PC:类似于CPU中的PC寄存器,指向当前执行的指令
- Stack:执行堆栈,位宽为256 bits,最大深度为1024
- Memory:内存空间
- Gas:油费池,耗光邮费则交易执行失败
具体解释执行的流程参见下图:
EVM的每条指令称为一个OpCode,占用一个字节,所以指令集最多不超过256,具体描述参见:https://ethervm.io 。比如下图就是一个示例(PUSH1=0x60, MSTORE=0x52):
首先PC会从合约代码中读取一个OpCode,然后从一个JumpTable中检索出对应的operation,也就是与其相关联的函数集合。接下来会计算该操作需要消耗的油费,如果油费耗光则执行失败,返回ErrOutOfGas错误。如果油费充足,则调用execute()执行该指令,根据指令类型的不同,会分别对Stack、Memory或者StateDB进行读写操作。
Input数据通常分为两个部分:
前面4个字节被称为“4-byte signature”,是某个函数签名的Keccak哈希值的前4个字节,作为该函数的唯一标识。(可以在该网站查询目前所有的函数签名)
-
后面跟的就是调用该函数需要提供的参数了,长度不定。
c++的符号表