Erlang和Java的内存模型比较

Published: 26 Feb 2014 Category: Java, Erlang

Erlang和Java的内存模型比较

我读到一篇相当相当有趣的关于Erlang VM内存管理策略的文章。它是Jesper Wilhelmsson写的一篇论文,我觉得有必要讨论一下Erlang和Oracle的Java虚拟机在内存管理方面的不同之处。

对于从没听说过Erlang的人来说,有必要简单的介绍一下,它是一门函数式语言,并且使用异步消息传递作为它并发的基石。消息传递使用的是拷贝的机制,并且在不同的Erlang虚拟机中传播,甚至是在不同的机器上(不过这个对程序员来说是透明的)。

Erlang和Java都通过虚拟机将底层硬件抽象成了一个可移植的平台。两门语言都采用了独立于机器的字节码。两者的运行时系统都通过垃圾回收来将程序员从内存管理中解放出来。

Erlang的线程开销非常的低,我认为Erlang的一个线程大概只需要512字节。在Java里线程大概需要512KB,差不多是1000倍。对于Erlang程序员来说,创建线程来异步处理些任务简直太平常不过了。通常来说Erlang的系统都成千上万的线程在同时工作着。不像Java那样,我们得在线程池和executors上面浪费时间。

通过我对Erlang的一点点玩票,我发现Erlang在一门函数式语言和一门可以实际写程序的语言里面作了一个很好的折中(我知道这么说肯定会被喷的很惨)。健壮的分布式错误处理令人惊喜,随便编写什么网络服务器都变得非常简单。WEB服务器的状态机使得错误回滚看起来如此自然。

不过这篇文章并不是要讲Erlang的编程模型的。它是要介绍Erlang虚拟机如何来管理内存。

现在的Java虚拟机用的是一种Erlang程序员称之为共享堆的机制。所有线程共享一个大的堆。大部分内存都是在那个堆上分配的。除了这个堆外,JVM也使用了一些专用的数据区域比如代码缓存还有持久代。这些也是所有线程共享的。

相反的,Erlang用的是一个私有堆的拓扑结构。每一个线程都有一个专属的小堆,里面包含了这个线程用到的所有数据,线程的栈也在这里面。线程的所有数据都在这个本地堆上。当线程创建的时候这个堆就预留出来了。如果这个线程结束了,就把整个堆都扔回到空闲内存的池子里面。

除了私有堆外,所有线程都共享一个称为二进制堆的以及一个消息堆。这些都是有特殊用途的堆。二进制堆用来分配有可能在线程间共享的任何大块数据。比如文件或者网络缓存都在这个区域。

消息堆是指消息数据的堆。消息也是在进程间共享的。消息在线程间传递只是从发送线程拷贝了一个消息指针传到接收线程。消息数据是存储在消息堆里的。

Erlang的内存模型令我印象深刻。和Java的单个堆的模型相比,它的伸缩性要强得太多了,这点让我触动很深。语言的语义和内存模型完美的结合到了一起。

比如说,线程拥有私有堆可以使得线程从各种检查自身数据的锁中解放出来。更进一步的,破坏性的写入也成为了过去,也不再需要加锁来检查共享数据了。

最近一版的Erlang虚拟机还做了一件事是,引入了不止一个的调度器。每个物理处理器使用一个调度器来确保精确,这样避免了使用全局锁来进行检查。除非有个调度器太闲了,才会去获取个锁,然后从别的调度器那抢点活过来干干。

不过在Java,还有很多是值得Erlang借鉴的。就是说,Java里有一些好的特性,而Erlang现在还没有。

当线程的堆积累的数据过多的时候,Erlang虚拟机会重新分配堆并增加它的大小。然而重新分配的算法会导致堆迅速的膨胀。在高负载下,Erlang的虚拟机能在短短数分钟内吃掉16G的内存。每个发布的版本都应该严格进行压测以确保能够合理的使用内存。

Erlang的虚拟机里还没有机制能够抑制内存过快的增长。虚拟机很乐于为分配内存而效劳,以至于系统瞬间就进入到交换分区,也就是说虚拟内存也被榨干了。这会导致机器无法响应,甚至连KVM的控制台也无法访问。之前我们只能重启机器电源才能重新访问。

基于队列的编程模型使得Erlang编程变得非常有趣,不过在生产环境中,这也是它的致命的弱点。Erlang中的队列都是无限大小的。虚拟机不会抛出异常也不会限制队列中消息的数量。有的时候线程会由于出现BUG而无法继续处理,有的时候线程的处理速度赶不上消息的发送速度。万一出现了这种情况的话,Erlang只会放任队列不断增长,直到虚拟机被杀死或者机器锁住,就看哪个先来了。

这就意味着当你在生产环境中运行大型的Erlang应用的时候,你得在操作系统层面来确保如果内存出现井喷能够把进程杀掉。在运行大型Erlang虚拟机的机器上,远程控制器或者远程访问卡都是必备良药。

总而言之,我相信Erlang里的私有堆的内存模型对于日常的性能而言是一个非常强大的工具。它从运行时系统里砍掉了大量的锁的机制,这样在同样的应用下,Erlang系统的伸缩性要比Java要强得多。而Java在内存上的硬性的限制会让你的应用在面临DDoS攻击的时候能让你幸免于难。

最后说一点,Erlang有个命令行开关能让它从私有堆切换到共享堆。

Erlang和Java我都很喜欢。对于开发者而言它们很难进行比较,因为它们几乎没有什么共同之处。不过在通常情况下,我还是会用Java来开发系统。因为它在工具支持方面要更好,同时还有多的让人难以置信的第三方库。如果有个面向流的消息系统,我会考虑使用Erlang。这才是Erlang编程模型大放异彩的时刻。

原创文章转载请注明出处:Erlang和Java的内存模型比较

英文原文链接