自己动手写Python虚拟机-笔记
自己动手写Python虚拟机 Day1-语言虚拟机的基本结构
今天主要分享的时预言虚拟机的基本结构,包括源代码的执行过程、编译器、字节码、栈帧、静态分配、动态分配、
解释执行、JIT、类加载器、泛型等基础知识。
下面介绍3种关键流程:
java源代码执行过程
.java 通过 javac编译成 .class字节码文件,通过jvm加载到内存中,解释器执行,函数调用多,则翻译成机器码执行
java源码级编译器
javac 源代码--词法分析器--token流--语法分析器--语法树--语义分析器--注解抽象语法树--字节码生成器--jvm字节码
java虚拟机执行引擎
JTI编译器 机器无关优化--中间代码--机器相关优化--中间代码--寄存器分配器--中间代码--目标代码生成器--目标代码
一般我们看到的源码都已经被编译成class字节码文件了,所以我们一般都要通过反编译才能正常阅读,而JDK本身自带了一种javap,可以看到 字节码,常量池,方法定义等,大多数 IDE也带了反编译,可以反编译成.java文件,单其中 语法糖这些还原成基本语法,而有些变量名丢失,会被写成var1 var2等名称,我们阅读的时候,不能以为这样的名字也行,实际写代码,变量名还是要见名知意。
javac编译代码的时候,会做泛型擦除,设定成泛型的上限。泛型是java多态重要的一部分,java 泛型类型使用Object类型代替,不支持基本类型,类型擦除机制、类型限定:上线 下线 编译期间做类型检查。
.java文件会被编译成.class字节码文件,下面是字节码示例:
iload_0 加载0 至栈顶
iconst_1 将1压入栈顶
ifne 6 判断 不满足调制第6行
ireturn 返回栈顶元素
isub 1 2出栈相减在放入栈中
iadd 1 2出栈相加在放入栈中
异常字节码:
异常:Exception table
form to target type
0 3 6 Class
第0行到第三行 发生异常跳到第6行,然后与type比较
finally:生成的字节码 每个分支底下都会生成finally的字节码,这样,不管什么清空,finally的代码都会被执行。
字节码中提到的栈帧其实指的是栈帧中的操作数栈,用来临时保存计算结果,栈帧中还有保存局部变量的栈帧。
解释执行:基于栈的指令集 -- 基于栈的执行过程--解释器hotSpot,运行中翻译成机器码
JIT:被多次调用,直接翻译成机器码。
java7之后是分层编译 五层
静态分派 重载: 编译器确定
动态分派 重写:运行时确定
华为云账号:hw07606522
微信昵称:salad
微信账号:sela001
自己动手写Python虚拟机 Day2-构建对象系统
今天主要分享的虚拟机运行时的对象结构,主要包括:Klass、oop、 Object header、Klass Point、动态类型、弱类型、虚拟机内存结构、类型等相关概念逻辑。
Klass 虚拟机中的类型,代表一类对象,类似于 java做反射等操作时会用到,Klass定义了不同类型的不同操作,例如:Integer 和 String 的+,对应的操作完全不一样。
OOP 普通对象指针。
每个普通对象对应到对应到虚拟机中,都有一个OOP,并且指向Klass。
虚拟机运行时,内存布局跟Klass和OOP息息相关。
对于编程语言来说,分强类型语言和弱类型语言,类似于JS就是弱类型语言,不存在类型概念,对虚机机来说,运行的时候还有动态类型这个概念,动态类型指的是 变量声明时不指定类型,可以使用任意的值为该变量赋值,对象可以在运行时增加或者删除成员变量,对我们来说,python就是典型的动态类型。
动态类型相对而言比较灵活,但是在实际运行中,效率时比较低的, Python在存储对象的属性时,采用的时哈希表来存储,每次都要通过对象的属性名从哈希表中查找,所以性能比较低,而V8采用的二级指针扩展,通过基址+偏移量来操作,避免了查表,所以性能比较高。
虚拟机类型对象,兼备类型和对象的功能,既可以被打印,也能用来创建普通对象,是虚拟机实现反射功能的基础。
对象关联着Klass,Klass又关联着对象,也就是说,我们需要对象的时候可以使用对象,我们需要类型的时候,可以使用Klass,这就是类型对象。
自己动手写Python虚拟机 Day3-内存管理
今天主要分享的虚拟机运行时的内存管理,主要包括:垃圾回收基本概念、内存分配、垃圾识别、内存回收等基本概念、free-list算法、Bump-pointer算法、Tracing、Sweep算法、Compaction算法、Evacuation算法等。
整个内存管理其实都是围绕着垃圾回收进行的,如果内存管理不当,垃圾回收存在问题,那么程序就面临着内存溢出的风险,不能稳定的运行下去,所以内存管理也是一个虚拟机的一个核心,重要的组成部分。而我们熟悉并且了解虚拟机的垃圾回收机制,会使我们的程序更加健壮,避免内存溢出,让程序能够稳定进行下去。
编程语言发展到现在,大部分语言已经从传统的手动内存管理,转成自动内存管理,自动管理的好处有以下几点:
避免开发者的失误,导致内存管理错误
责任分离,使开发者更专注于业务
提高性能,更好的利用多核硬件资源
虚拟机内存管理运行的第一步其实就是内存分配,给运行产生的对象分配内存,本次介绍了三种算法:
Free-list: 空闲列表,把空闲内存记录在列表中,一般按对象大小分配
Bump-pointer: 连续分配,从空闲空间开头开始连续分配
Region-based: 基于区块分配器
给新对象分配内存,也就意味着有来的对象占用内存没有释放,所以接下来,我们要识别垃圾,识别垃圾使用Tracing, 从根开始寻找,如果一个对象不可从根抵达,就说明这个对象是垃圾,根一般指的是访问的变量,堆外到堆内的引用。
找到垃圾对象之后,我们就要清理垃圾,释放内存,一般根据分配内存算法采用不同的垃圾回收算法:、
Free-list:采用Sweep算法,将垃圾空间直接加回到空闲列表
Bump-pointer: 有两种,Compaction压缩 算法,将活动性压缩到一侧
Evacuation撤离 算法,将活对象复制到另一处,原空间全部释放
下来我们看下比较成熟的java虚拟机,将内存直接分为不同区域,不同区域采用不同的垃圾回收策略。
自己动手写Python虚拟机 Day4-函数与方法
今天主要分享的从虚拟机的视角看方法函数,主要包括:高阶函数的概念、高阶函数的实现、变量、比较、闭包 、lambda、方法、decorator。
首先介绍了函数,从虚拟机的角度来看函数,几种关于函数的概念:
函数是第一公民:函数可以是参数,函数可以是返回值,那么函数就是这种编程语言的第一公民。
高阶函数:函数可以接受其他函数为参数,那么这个函数为高阶函数。
重点:尽量避免使用可变对象作为函数默认值。因为使用可变对象作为函数默认值,可变对象会跟函数绑定,这样导致函数多次被调用,可变参数持续再变化。
对于高阶函数而言,不同的编程语言有不同的实现,主要分为两种:
语言类库:通过编译器实现,以java为例,通过Scala实现。
虚拟机:python就是通过虚拟机实现的
还有一个重要的组成部分就是 变量,变量按作用域来说分为四种, local 局部 Enclosing 上下文 Global全局变量 Builtion内建变量,查找顺序是LEGB,作用域局部>外层作用域>当前模块中的全局>python内置作用域,变量作用域是可以改变的。闭包:就是能够读取其他函数内部变量的函数。例如在javascript中,只有函数内部的子函数才能读取局部变量,所以闭包可以理解成“定义在一个函数内部的函数“,python 可以通过nonlocal改变变量作用域来实现。
关于对象的比较,python通过 is 来判断是否是一个对象,通过==判断值相等,而java则是通过==来判断是否是一个对象,equals来判断值相等。其中虚拟机都作用一些优化,类似于小数值对象(-128 到127 之间 一个byte),其实都是一个对象。
lambda表达式,函数式编程,是目前来说比较新的一直方式,来源于 lambda演算。表达式:lambda<变量>:<表达式>
对方法来说,与函数的区别,就是没有绑定owner。
自己动手写Python虚拟机 Day5-高级主题
今天主要分享的从高级主题,主要包括:迭代器、线程、协程、JIT、Profiling 、退优化。
首先讲了迭代器,迭代器是一种设计模式,基本上每种编程语言种都有,并且都已经内置了,
先看下迭代器特殊的几个字节码:
GET_ITER 放到栈顶
FOR_ITER 调用
STORE_FAST 返回值
SETUP_LOOP 跳出循环到的行数
yield:pyhton种可以通过 yield将函数编程迭代器对象,主要是在函数运行到yield时,栈帧不销毁,存储起来。
原来只接触过进程、线程的概念,没有接触过协程这个概念,今天通过这里,了解到了协程,协程可以在程序运行过程中中断,去执行其他内容,执行完了之后再回来执行,协程不是通过线程切换做这些的,而是通过程序控制,避免了线程切换的开销,提高了性能,由于不是多个线程,所以也避免了并发问题,不需要通过加锁来解决变量冲突。
后面对第一章提到的概念,JIT Profiling 退优化做了详细的讲解,原来只知道JIT是把热点函数翻译成机器码,通过学习知道了更深入的东西,首先需要申请一块具备 写权限并且有执行权限的内存,然后将热点函数翻译成机器码,将翻译好的机器码写入我们申请到的内存,当我们需要调用热点函数的时候,就直接调用这块内存,前面我们JIT中提到一个词。热点函数,什么函数叫热点函数呢?这个时候就用到Profiling,它是基于统计对程序的行为特征进行分析,根据调用次数定位出热点函数,JIT就是根据这个热点函数做机器码翻译,当我们已经翻译成机器码的函数假设不成立时,这时候就用到退优化了,解释器可以实时进行翻译,满足我们程序的正常运行,这一整套方案,既满足了效率,有满足了程序的逻辑。
- 点赞
- 收藏
- 关注作者
评论(0)