Go 语言令人印象深刻
不同于 Java,Go 编译生成机器码,并被直接执行,非常类似 C。因为它不是一个虚拟机,这与 Java 有着天壤之别。Go 支持面向对象,并在一定程度上支持函数式编程,因此它不仅仅是一种具备自动垃圾回收机制的类 C 语言。如果我们将程序语言发展看作线性的话(事实上不是),Go 介于 C 和 C++ 之间的某种状态。在 Java 开发者看来,Go 是如此的与众不同,以至于学习它本身就是一种挑战。通过对 Go 的学习,可以更深入理解程序语言的构造,对象及类等等都是如何实现的。这些知识在 Java 中同样适用。
我相信,如果你知道 Go 是如何实现面向对象的,你也会明白 Java 以不同的途径实现的一些原因。
免得你觉得我絮絮叨叨,简言之吧:不要被 Go 中看起来怪异的结构吓到,即便你没有项目要用 Go 开发,也去了解它,这会增加你的知识和理解。
GC 还是不 GC,这是个问题
内存管理对于编程语言至关重要。汇编允许你操作所有东西,或者说要求你必须全权处理所有细节更合适。C 语言中虽然标准库函数提供一些内存管理支持,但是对于之前调用 malloc 申请的内存,还是依赖于你亲自 free 掉。从C++、Python、Swift 和 Java 开始,才在不同程度上支持内存管理,Go 语言也是他们中的一员。
Python 和 Swift 采用引用计数方案。当存在一个对象引用时,对象自身持有一个计数器,用于统计有多少个引用指向当前对象。对象中并没有反向引用或指针。当一个引用获取对象的值,并指向这个对象时,计数器自增;当一个引用变为 null/nil/其他值 时,计数器自减。很显然,当计数器为0时,这个对象就没有被引用,可以被作废了。这种方法的问题是,计数器大于0,但是对象却可能已失效。当对象彼此形成环形引用时,通过静态、局部或者其他有效引用释放环中最后一个对象时,整个引用环就悬在内存中,就像气泡悬浮在水中:所有对象的计数器都大于 0,但是所有对象都已失效。Swift 教程对这种情况做了很好的解释,并说明了避免的方法。可惜,结论还是那样:你始终需要在某种程度上关心内存管理。
对于 Java 和其他语言的 JVM (包括 JVM 的Python实现),内存是完全由 JVM 管理的。与工作线程同时运行着 1 个或者多个线程,周期性的运行全局垃圾回收,或者暂停所有线程(众所周知的 stop the world),标记所有失效对象,清理它们,并压缩可能存在的内存碎片。你唯一需要操心的是性能问题。
Go 语言与上述情况大同,又有点小异。Go 中没有引用,只有指针,这是非常重要的区别。Go 语言可以被外部 C 代码集成,出于性能考虑,Go 运行时中也没有类似引用表之类的东西。真实的指针对调用者是不可知的。申请到的内存依然被分析,以获得对象有效性相关信息,无用“对象”依然可被标记和清理,但是内存不能通过移动实现压缩。我在文档中没有找到太多相关信息,由于我理解指针的处理机制,我一直期待 Go 语言存在某种实现内存压缩的天才魔法。我很失望的了解到,它根本没有内存压缩。毕竟,魔法不常有。
Go 包含垃圾回收机制,但是不是跟 Java 一样完整的垃圾回收机制,它不能进行内存压缩。这也未尝是一件坏事。它可以持续运行服务很长一段时间,而且不会产生内存碎片。某些 JVM 垃圾回收器也会跳过内存压缩,以减少垃圾回收造成的服务停顿,直到必要时才执行。Go 语言中,必要时才进行的这一步没有了,在个别情况下可能会引起一些问题。不过在你学习该语言时,不大可能需要考虑这个问题。 |
|