Erlang与java的内存架构比较
Erlang是一门函数语言,通过异步消息传递来处理并发,使用语义拷贝传递消息。即使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模型真正放光芒的时候。
erlang与java构建的节点通讯
我们知道,erlang在开源社区的活跃度远远不及java社区,在java社区中有很多优秀的开源框架,比如struts、hibernate、 spring、hadoop、hbase等,为了让erlang和java社区的众多开源框架相结合,让他们优势互补,可以让erlang来调用java写的中间件。
erlang提供了一个Jinterface代码包,java可以和erlang建立节点通信。通过这种方式,erlang可以将 java构建的节点也当作erlang的一个节点,java可以接收erlang传过来的消息,并且处理之后以异步的方式发送处理结果。
erlang与java构建的节点通讯
安装erlang后,可以在安装目录找到这个Jinterface代码包 OtpErlang.jar,路径是 erl5.10.3\lib\jinterface-1.5.8\priv\OtpErlang.jar
Java的代码如下:
package erljava;
import com.ericsson.otp.erlang.OtpErlangObject;
import com.ericsson.otp.erlang.OtpErlangPid;
import com.ericsson.otp.erlang.OtpErlangTuple;
import com.ericsson.otp.erlang.OtpMbox;
import com.ericsson.otp.erlang.OtpNode;
public class ErlJavaNode{
public static void main(String[] args) throws Exception {
String cookie = “123456”;
OtpNode node = new OtpNode(“java_node@127.0.0.1”, cookie);
OtpMbox mbox = node.createMbox();
mbox.registerName(“java_node_name”);
System.out.println(“java node start”);
OtpErlangObject o;
OtpErlangTuple msg;
OtpErlangPid from;
while (true) {
try {
o = mbox.receive();
System.out.println(“recv:” + o);
if (o instanceof OtpErlangTuple) {
msg = (OtpErlangTuple) o;
from = (OtpErlangPid) (msg.elementAt(0));
mbox.send(from, msg.elementAt(1)); // 原消息返回
}
} catch (Exception e) {
System.out.println(“” + e);
}
}
}
}
erlang给java节点发送消息:
C:\>erl -name erl_node@127.0.0.1 -setcookie 123456
Eshell V5.10.3 (abort with ^G)
(erl_node@127.0.0.1)1> {java_node_name,‘java_node@127.0.0.1’} ! {self(),{a,bc,d}}。
{《0.37.0>,{a,bc,d}}
(erl_node@127.0.0.1)2> flush()。
Shell got {a,bc,d}
ok
分析erlang与java节点无法通讯
1、epmd服务未启动
先在本机启动一个erlang节点,没有的话,如果先执行Java代码系统会抛出异常。原因是所有erlang节点之间的通讯都要依赖一个底层的epmd的服务,这个模块的主要功能是提供通过相互通过name来识别机器的机制。这个机制类似dns功能,先通过一个名称服务中间件,使用name获取所对应的ip地址,然后再利用这个ip地址建立连接。
另一种方式是手动启动epmd服务,在erlang安装目录下找到epmd进程,执行命令 epmd -daemon
2、erlang版本不一致
java使用的OtpErlang包和shell使用的erlang版本不一致,erlang是不允许主版本不同的erlang节点进行连接。
erlang与java节点通讯的好处
erlang节点通讯也是依靠socket实现的,虽然erlang和java可以直接用socket连接,但使用节点通讯的有很多好处,和erlang节点通讯一样,支持erlang所有的数据结构,而且还不用定义传输协议,多节点通讯也会变得复杂。做成erlang节点,能极大程度利用到erlang分布式的好处。
java判断erlang节点是否连接成功
if (node.ping(“erl_node@127.0.0.1”, 2000)) {
System.out.println(“connect ok”);
} else {
System.out.println(“connect fail”);
}
java主动与erlang节点通讯实例
前面说到是erlang连接java节点的情况,现在讨论java主动连接erlang节点的情况。
erlang建立节点,注册进程名字
C:\>erl -name erl_node@127.0.0.1 -setcookie 123456
Eshell V5.10.3 (abort with ^G)
(erl_node@127.0.0.1)1> register(erl_node_name, self())。
true
java代码如下:
package erljava;
import com.ericsson.otp.erlang.OtpConnection;
import com.ericsson.otp.erlang.OtpErlangAtom;
import com.ericsson.otp.erlang.OtpErlangObject;
import com.ericsson.otp.erlang.OtpErlangTuple;
import com.ericsson.otp.erlang.OtpPeer;
import com.ericsson.otp.erlang.OtpSelf;
public class ErlJavaNode {
public static void main(String[] args) throws Exception {
String cookie = “123456”;
OtpSelf self = new OtpSelf(“java_node@127.0.0.1”, cookie);
OtpPeer other = new OtpPeer(“erl_node@127.0.0.1”);
OtpConnection connection = self.connect(other);
System.out.println(“java node start”);
if (connection.isConnected()) {
System.out.println(“connect ok”);
OtpErlangObject[] msg = new OtpErlangObject[2];
msg[0] = self.pid();
msg[1] = new OtpErlangAtom(“hello, world”);
OtpErlangTuple tuple = new OtpErlangTuple(msg);
connection.send(“erl_node_name”, tuple);
} else {
System.out.println(“connect fail”);
}
}
}
erlang刷新信箱,就可以看到java发来的消息:
(erl_node@127.0.0.1)2> flush()。
Shell got {《6872.1.0>,‘hello, world’}
ok
评论
查看更多