我读了一篇非常非常有趣的文章(JesperWilhelmsson的一篇论文),是关于Erlang虚拟机(Erlang VM)内存管理策略的。我相信对比一下Erlang和java的虚拟机内存管理策略,一定很有意思。

       先给从来没有听说过Erlang的同学做个简短的介绍。Erlang是一门函数语言,通过异步消息传递(asynchronous message passing)来处理并发,使用语义拷贝(copysemantics)传递消息。即使Erlang分布在多个虚拟机上,运行在多台机器上,对程序员来说也是透明的。

       在某种意义上Erlang和java是相似的,他们都通过虚拟机来获得可移植性,都采用独立于操作系统的字节码技术,都使用垃圾回收机制来解脱程序员自己管理内存的麻烦。

       Erlang中线程的开销是非常低的。我相信在Erlang中,一个线程只需要大约512字节的内存。而java线程则需要512K字节的内存,大约是Erlang的一千倍多。对程序员来说,这意味着创建一个线程非常轻便。典型的Erlang系统中可以有上万的线程。所以Erlang不需要做线程池、executors等等,那些我们写java多线程要用到的东西。

       在我过去很少涉猎的语言中,我发现Erlang既保持了函数语言的特性,又能够做出真正的应用来。Erlang健壮的分布式错误处理非常惊艳,让编写任何一种网络服务变得相当容易。这种状态机的处理方式使web服务在出错时,处理回滚非常自然。

       不过这篇文章不打算讨论Erlang的编程模型,这里主要想讨论Erlang虚拟机的内存管理方式。

       目前java虚拟机采用了一种被Erlang程序员称作“共享堆”的机制,虚拟机维护了一个可以被所有线程共享和使用的大堆(堆和栈什么区别)。这个堆占用了虚拟机的大部分内存。在这个大堆里,也包括了虚拟机的一些特殊数据区域,例如代码缓存和永久区。这些特殊数据区也是被所有线程共享的。

       相反的,Erlang使用一种私有堆的技术。每个线程都有一个只属于自己的小堆,里面包含了这个线程用到的所有数据以及线程栈。这个堆是在线程被创建的时候分配的。当这个线程结束了,它的私有堆内存就被虚拟机收回了。

       除了私有堆,Erlang中所有的线程都能访问两个特殊的堆,二进制堆和消息堆。二进制堆被分配了大量的数据块,以便线程线间共享数据。例如文件输入或是网络缓冲区。

       消息堆里放的是消息(messages)数据。这些消息也可以在进程间共享。线程之间传递消息的方式,是从发送线程复制一个指针到接收线程。这些消息数据就被存放在消息堆里。

       我对Erlang的内存模型印象深刻。被它比java更具扩展性的内存模型给震撼了。而且这门语言的语法和他的内存模型结合的非常漂亮。

       因为有私有堆,Erlang线程对自己的数据检查不需要采用任何形式的锁。并且私有堆也避免了破坏性的写,这样也就没有了对共享数据加锁的需求。

        最新版的Erlang又往前走了一步,采用了多个调度器(scheduler)。每个物理处理器有一个调度器可以保证更精确。而且这也消除了另一种需要检查的锁。仅当一个调度器无用了,才会用到锁,以便从其他处理器上获得一个新的调度器。

       关于java,我们仍然有很多东西要去学习。也是就说,java里还是有些好东西的,也正是因为这点,我才没有使用大型的Erlang系统。

       当Erlang线程积累了大量数据的时候,Erlang虚拟机会重新分配空间,扩大私有堆。然而,这个重新分配的算法会导致堆空间急速增长。在高负载下,我们看到Erlang虚拟机在几分钟内吃掉了16G的内存!每次发布版本都要小心的做负载测试,看看它的内存需求是否能被满足。

       Erlang虚拟机没有抑制内存增长的机制。虚拟机不断的分配内存,迫使系统不得不使用交换区(swap),直到虚拟内存被耗尽。这可能会导致用KVM控制台访问系统变得很迟钝。使我们不得不重启系统,才能够再次访问它。

       基于队列的编程模型让Erlang编程变得非常愉快,但这也是Elang系统设计上的致命缺陷。Erlang的每一个队列都是无界的。虚拟机不会抛出异常,也不会限制一个队列的消息数量。有时候会出现,一个进程可能由于bug而停止工作,或者进程消费消息的速度跟不上消息发送速度的情况。在这种情况下,Erlang还是允许这个进程的消息队列不断的增长,直到虚拟机被杀掉,或是这个机器被锁死了。

       这意味着当你在生产环境运行Erlang虚拟机时,要配备一个系统级的检测,以便在Erlang内存使用量飞涨的时候能够杀死进程。所以,运行大型Erlang应用的机器需要能被远程访问和操作(是不是意味着需要经常上去处理问题??)。

       总的来说,我认为在Erlang的工具箱里,“私有堆”是一个非常强大的工具。它避免了实时系统里的锁机制,这个意味着它将比java更具扩展性。而java的硬性限制内存的模型,则能在你的系统压力剧增,或是遭受DDOS攻击的时候保持稳定。

       有一个命令行,可以将Erlang的虚拟模型从“私有堆”改成“共享堆”。

       我喜欢Erlang和java。但他们很难进行比较,因为对开发者来说,他们的共同点很少。一般情况下,我在大多数系统里使用java。因为它有很好的工具支持,而且有大量的lib包可以使用。当我需要一个面向信息流的系统时,我会使用Erlang。这才是Erlang模型真正放光芒的时候。


Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐