当今流行的编程语言,大多具备垃圾回收(Garbage Collection,以下简称GC)功能 。它能够将不再使用的内存区域收回并重新分配 。
这一功能可以说,将程序员的注意力从内存的分配/释放工作中解放了出来,可以专注于业务逻辑的实现 。但这并不意味着说,程序员在写代码的时候就可以无所顾忌了 。
因为他们面对的环境里,资源毕竟是有限的,而GC也不能包办一切工作 。尤其是程序需要运行时性能的时候,对代码的编写就有更高的要求了 。
而在优化程序性能时,也不能凭着猜想去实施,这就需要对编程语言的内存布局与管理有清楚的了解 。这样才能做到有的放矢,事半而功倍 。
下面我们先从编译技术的基本概念说起 。
文章插图
编译技术编译器方式,这种方式是将代码经过预处理、编译、汇编、链接之后,得到一个可执行文件 。这个文件里面包含的都是二进制的机器指令,它的优点是程序执行速度快,能将硬件性能充分发挥出来 。
它的缺点则是编译过程需要耗费时间,程序修改之后必须重新编译才能使用 。在早些年硬件性能不高的时候,编译一个大型的程序需要一两个小时是很平常的事 。
此类语言的典型代表是C/C++,以及现在十分流行的Go语言 。
解释器方式,程序代码直接运行在一个解释器中,没有编译的过程 。优点则是可以立即运行,且可移植性好,代码编写一次即可在任何平台上运行,而且预期效果也一样 。而编译器方式则要麻烦的多,它需要为每一个平台单独编译一次 。
不过解释器方式的缺点也同样明显,就是它的性能受限 。毕竟是隔着一层解释器去执行,远远比不了翻译成机器指令的二进制可执行文件 。
此类语言的代表则有Python、ruby、php、JAVAscript等 。可以认为,脚本类语言都属于解释器方式执行 。
中间代码方式,这是一种折衷式的方案,它会先对代码有一次编译过程,但不是编译成可执行文件,而是一份中间代码 。然后这份中间代码会放到一个虚拟机里去执行 。以这样的方式既获得了良好的可移植性,也能够拥有高于解释器的速度 。
java语言即是最佳代表 。它会先编译出一个字节码文件,然后Java Virtual machine(JVM)通过读取字节码来运行程序 。
微软的.NET也是类似的结构,它使用的是Common Language Runtime(CLR),以此支持多种语言 。例如C#、VB.net等 。
文章插图
基础知识不论一个程序用何种语言编写,它的运行时内存布局都是一致的 。我们先从一个程序的三种基本内存区域说起 。
【程序运行后性能总会下降?你应该先了解编程语言的内存布局与管理】静态区:这个区域主要存放的是程序的全局变量、常量数据,以及编译成二进制指令的代码 。可以看到,这个区域存放的,主要是贯穿于程序整个生命周期所要使用到的数据与指令 。
栈区:熟悉数据结构的朋友们都知道,栈(stack)是一个后入先出(LIFO)的队列 。在程序运行中,它用来实现函数的调用 。程序执行函数调用时,会在栈上依次压入参数,局部变量、返回位置等,执行完成后再依次将数据出栈 。所以,栈上的数据都是临时性的,只在调用时可用 。
堆区:所有动态申请的内存都从堆区分配 。在使用C/C++语言时,程序员对待内存的申请与释放就必须特别小心,一个疏忽就会造成内存泄漏 。而后来的java、C#等,语言内置了GC技术,情况相对改善,但也要养成良好的编程习惯 。
对于程序来说,静态区和堆区都是全局存在的,即所有线程共享这二者 。而栈区则是为每个线程单独准备一个,这一点程序员要记住 。因为栈区的数据在函数调用之后就会失效,如果还引用栈区的数据,则会产生不可预料的问题 。
文章插图
程序运行时内存布局
OOP语言的内存结构因为现在市场上面向对象编程语言(OOP)占据主流地位,所以接下来的讨论也将以OOP语言的典型内存结构进行讲解 。我们了解清楚对象的存储区域,方法的调用之后,就会更加明白编程时应当注意哪些方面 。
我们以使用较为广泛的Java语言进行说明,先要厘清一个总是争论不休的问题 。就是Java语言中究竟有没有指针?
Java中的一系列逻辑功能,都是通过对象的间的消息传递和方法调用来实现的 。对象是实现功能的最小单元,而一个对象是怎么来的,它存放在哪里?
推荐阅读
- 使用Docker来构建、运行、发布微服务
- 小程序云开发支持公众号网页开发了
- 清太祖努尔哈赤的皇后 康熙德妃生平
- 一行代码让你的python运行速度提高100倍
- 孝宣皇帝的皇后 孝钦皇太后是谁
- 武则天为什么要杀上官婉儿 上官婉儿死后李隆基对她做了什么
- 善用沙盒虚拟机,测试有风险的程序让你无视木马病毒
- 李莲英长相奇丑 李莲英和太后的关系
- 汉武帝长公主是否腰斩 卫长公主腰斩后大便
- 喝咖啡后能喝茶吗,胃病能不能喝茶