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
評論
查看更多