<audio id="audio" title="10 | 语言的实现：运行时，软件设计的地基" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/39/3d/395ce527f5e6b8eff09c195adc9da53d.mp3"></audio>

你好！我是郑晔。

通过前两讲的学习，相信你已经对程序设计语言有了全新的认识。我们知道了，在学习不同的程序设计语言时，可以相互借鉴优秀的设计。但是要借鉴，除了模型和接口，还应该有实现。所以，这一讲，我们就来谈谈程序设计语言的实现。

程序设计语言的实现就是支撑程序运行的部分：运行时，也有人称之为运行时系统，或运行时环境，它主要是为了实现程序设计语言的执行模型。

相比于语法和程序库，我们在学习语言的过程中，对运行时的关注较少。因为不理解语言的实现依然不影响我们写程序，那我们为什么还要学习运行时呢？

因为**运行时，是我们做软件设计的地基。**你可能会问，软件设计的地基不应该是程序设计语言本身吗？并不是，一些比较基础的设计，仅仅了解到语言这个层面是不够的。

我用个例子来进行说明，我曾经参与过一个开源项目：在JVM上运行Ruby。这种行为肯定不是 Java语言支持的，为了让Ruby能够运行在JVM上，我们将Ruby的代码编译成了Java的字节码，而字节码就属于运行时的一部分。

你看，**做设计真正的地基，并不是程序设计语言，而是运行时，有了对于运行时的理解，我们甚至可以做出语言本身不支持的设计**。而且理解了运行时，我们可以成为一个更好的程序员，真正做到对自己编写的代码了如指掌。

不过，运行时的知识很长一段时间内都不是显学，我初学编程时，这方面的资料并不多。不过，近些年来，这方面明显得到了改善，各种程序设计语言运行时的资料开始多了起来。尤其在Java社区，JVM相关的知识已经成为很多程序员面试的重要组成部分。没错，JVM就是一种运行时。

接下来，我们就以JVM为例，谈谈怎样了解运行时。

## 程序如何运行

首先，我们要澄清一点，对于大部分普通程序员来说，学习运行时并不是为了成为运行时的开发者，我们只是为了更好地理解自己写的程序是如何运行的。

运行时的相关知识很多，而**“程序如何运行”**本身就是一条主线，将各种知识贯穿起来。程序能够运行，前提条件是，它是一个可执行的文件，我们就从这里开始。

一般来说，可执行的程序都是有一个可执行文件的结构，对应到JVM上，就是类文件的结构。然后，可执行程序想要执行，需要有一个加载器将它加载到内存里，这就是JVM类加载器的工作。

加载是一个过程，那么加载的结果是什么呢？就是按照程序运行的需求，将加载过来的程序放到对应的位置上，这就需要了解JVM的内存布局，比如，程序动态申请的内存都在堆上，为了支持方法调用要有栈，还要有区域存放我们加载过来的程序，诸如方法区等等。

到这里，程序完成了加载，做好了运行的准备，但这只是静态的内容。接下来，你就需要了解程序在运行过程中的表现。一般来说，执行过程就是设置好程序计数器（Program Counter，PC），然后开始按照指令一条一条地开始执行。所以，重点是要知道这些指令到底做了什么。

在Java中，我们都知道，程序会编译成字节码，对于Java来说，字节码就相当于汇编，其中的指令就是Java程序执行的基础。所以，突破口就在于**了解指令是如何执行的**。

其实，大部分JVM指令理解起来都很简单，尤其在你了解内存布局之后。比如，加法指令就是在栈上取出两个数，相加之后，再放回栈里面。

我要提一个看上去很简单的指令，它是一根拴着牛的绳子，这就是new，没错，就是创建对象的指令。那头牛就是内存管理机制，也就是很多人熟悉的GC，这是一个很大的主题，如果展开来看的话，也是一个庞杂的知识体系。

有了对指令的理解，就对Java程序执行有了基本的理解。剩下的就可以根据自己的需要，打开一些被语法和程序库隐藏起来的细节。比如，synchronized是怎样实现的，顺着这条线，我们可以走到内存模型（Java Memory Model，JMM）。

当然，这里的内容并不是为了与你详细讨论JVM的实现，无论是哪个知识点真正展开后，实际上都还会有很多的细节。

这里只是以JVM为例进行讲解，学习其他语言的运行时也是类似的，带着“程序如何运行”这个问题去理解就好了。只不过，每种语言的执行模型是不同的，需要了解的内容也是有所差异的。比如，理解C的运行时，你需要知道更多计算机硬件本身的特性，而理解一些动态语言的运行时，则需要我们对语法树的结构有一定认识。

有了对运行时的理解，我们就可以把一些好的语言模型借鉴到自己的语言中，比如，使用C语言编程时，我们可以实现多态，做法就是自己实现一个虚拟表，这就是面向对象语言实现多态的一种方案。

**运行时的编程接口**

我们前面说过，做软件设计的地基是运行时，那怎样把我们的设计构建在运行时之上呢？这就要依赖于运行时给我们提供的接口了。所以，我们学习运行时，除了要理解运行时本身的机制之外，还要掌握运行时提供的编程接口。

在Java中，最简单的运行时接口就是运行时类型识别的能力，也就是很多人熟悉的getClass。通过这个接口，我们可以获取到类的信息，一些程序库的实现就会利用类自身声明的信息。比如，之前说过，有些程序库会利用Annotation进行声明式编程，这样的程序库往往会在运行的过程中，以getClass为入口进行一系列操作将Annotation取出来，然后做相应的处理。

当然，这样的接口还有很多，一部分是以标准库的方式提供的，比如，动态代理。通过阅读JDK的文档，我们很容易学会怎么去运用这些能力。还有一部分接口是以规范的方式提供的，需要你对JVM有着更好的理解才能运用自如，比如，字节码。

前面我们说了，通过了解指令的执行方式，可以帮助我们更好地理解运行时的机制。有了这些理解，再来看字节码，理解的门槛就大幅度地降低了。

如果站在字节码的角度思考问题，我们甚至可以创造出一些Java语言层面没有提供的能力，比如，有的程序库给Java语言扩展AOP（Aspect-oriented programming，面向切面编程）的能力。这样一来，你写程序的极限就不再是语言本身了，而是变成了字节码的能力极限。

给你举个例子，比如，Java 7发布的时候，字节码定义了InvokeDynamic这个新指令，当时语言层面上并没有提供任何的语法。如果你需要，就可以自己编写字节码使用这个新指令，像JRuby、Jython、Groovy 等一些基于JVM的语言，开发者就可以利用这个指令改善自己的运行时实现。当然InvokeDynamic的诞生，本身就是为了在JVM上更好地支持动态语言。

好消息是，操控字节码这件事的门槛也在逐渐降低。最开始，操作字节码是一件非常神秘的事情，在许多程序员看来，那是只有 SUN 工程师才能做的事情（那时候，Java还属于 SUN）。

后来，出现了一个叫[ASM](https://asm.ow2.io/)的程序库，把字节码拉入了凡间，越来越多的程序员开始拥有操作字节码的能力。不过，使用ASM，依然要对类文件的结构有所理解，用起来还是比较麻烦。后来又出现了各种基于ASM的改进，现在我个人用得比较多的是[ByteBuddy](https://bytebuddy.net/)。

有了对于字节码的了解，在Java这种静态的语言上，就可以做出动态语言的一些效果。比如，Java语言的一些Mock框架，为什么可以只声明接口就能够执行，因为背后常常是动态生成了一个类。

一些动态语言为了支持自己的动态特性，也提供了一些运行时的接口给开发者。比如，Ruby里面很著名的method_missing，很多框架用它实现了一些效果，即便你未定义方法也能够执行的。你也许想到了，我们提到过的Ruby on Rails中各种find_by方法就可以用它来实现。

method_missing其实就是一个回调方法，当运行时在进行方法查找时，如果找不到对应方法时就调用语言层面的这个方法。所以，你看出来了，这就是运行时和语言互相配合的产物。如果你能够对于方法查找的机制有更具体的了解，使用起来就可以更加地得心应手，就能实现出一些非常好的设计。

## 总结时刻

今天，我们讨论了程序设计语言的实现：运行时。**对于运行时的理解，我们甚至可以做出语言本身不支持的设计。所以，做设计真正的地基，并不是程序设计语言，而是运行时。**

理解运行时，可以将**“程序如何运行”**作为主线，将相关的知识贯穿起来。我们从了解可执行文件的结构开始，然后了解程序加载机制，知道加载器的运作和内存布局。然后，程序开始运行，我们要知道程序的运行机制，了解字节码，形成一个整体认识。最后，还可以根据需要展开各种细节，做深入的了解。

有一些语言的运行时还提供了一些语言层面的编程接口，程序员们可以与运行时进行交互，甚至拥有超过语言本身的能力。这些接口有的是以程序库的方式提供，有的则是以规范的方式提供。如果你是一个程序库的开发者，这些接口可以帮助你写出更优雅的程序。

关于程序设计语言的介绍，我用了三讲分别从模型、接口和实现等不同的角度给你做了介绍。目的无非就是一个，想做好设计，不仅仅要有设计的理念，更要有实际落地的方式。下一讲，我们来讲一个你可以在项目中自己设计的语言：DSL。

如果今天的内容你只能记住一件事，那请记住：**把运行时当作自己设计的地基，不受限于语言。**

<img src="https://static001.geekbang.org/resource/image/0a/d9/0ac9b789f401a370ff91b4bd495417d9.jpg" alt="">

## 思考题

最后，我想请你分享一下，你知道哪些程序库的哪些特性是利用运行时交互的接口实现的？欢迎在留言区分享你的想法。

感谢阅读，如果你觉得这一讲的内容对你有帮助的话，也欢迎把它分享给你的朋友。
