Erlang Doesn’t Fit The JVM

Posted: April 2nd, 2009 | Author: kevin | Filed under: Erlang, Programming Languages | View Comments

There’s been a lot of talk recently about why Erlang should (must?) be ported to the JVM. I happen to think this is a bad idea for several reasons.

But before we get to my list, let me present my Java and Erlang bona fides.

In the not too distant past I spent almost 10 years developing nearly exclusively in Java. I hopped on the Java train when JDK 1.1.8 was released and rode it thru the early JDK 1.5 (aka Java 5) days. I worked on projects both small and large. I was even responsible for implementing two Java standards: JSR-168 and WSRP.

I’ve done extensive performance testing and tuning for entire application suites (think IBM-scale application suites and you’re not far off the mark). I consider myself very familiar with the major parts of the JVM including the JIT and garbage collectors (in all their many forms).

In 2004 I discovered Erlang and toyed with it a bit. Back then there wasn’t much in the way of docs and none of my friends or colleagues were interested in trying out Erlang so I set it aside. A couple of years later, in 2006, the Prags published Programming Erlang and I was hooked. I dove into Erlang and never looked back.

Since 2006 I’ve produced a popular series of Erlang screencasts and put on a training class designed to help developers quickly climb Erlang’s learning curve. Along the way I’ve scored some nice full-time paid Erlang positions, first with the Rails hosting company Engine Yard and now with my own company, Hypothetical Labs.

And now, The List:

  1. The JVM has poor support for distributed programming.

    Erlang’s ability to elegantly extend the actor model across VM boundaries is extremely compelling. With very few caveats you can take code which originally ran on a single VM, break it apart, and run it across many nodes with no code changes.

    There’s nothing even close to this on the JVM. Oh, you can get close using third party libraries but it’s not the same as having the feature baked into the language and runtime. With external libraries there are tradeoffs to be made, configuration to fuddle with, and integration issues to conquer. You could make the argument that these libraries would be subsumed into the Erlang4J runtime and, thus, become transparent to developers but I think you’d wind up with a compromised implementation at best as you deal with the inevitable tradeoffs and integration issues.

  2. The JVM has no credible support for distributed data.

    I can take any Erlang term and serialize it to a binary form using the function term_to_binary/1. I can take that binary form and squirt it across a network to another node. On the receiving node I can call binary_to_term/1 on the binary blob and turn it back into Erlang. Well, that’s not quite true. There’s a very small subset of data, such as file descriptors and sockets, which make little sense to serialize. So I can’t serialize any Erlang term but I can serialize most of ‘em.

    The JVM has the Serializable interface, ObjectInputStream, ObjectOutputStream, and friends which try to do the same thing. I think they fail utterly. Why? Because developers have to incorporate serialization into the design of their classes from the beginning to make it work. It’s been my experience the vast majority of Java developers think about serialization rarely, if at all. Which means they’re left with tons of classes which cannot easily be serialized. Hacking serialization onto established classes is definitely Not Good Times.

    Even if Erlang4J could provide transparent serialization for Erlang terms the problem would still crop up at the integration boundaries between Java and Erlang code. Non-serializable objects are the rule rather than the exception.

    I don’t want to single out a particular project but I’ve seen enough people suggest Terracotta as a possible solution I want to take a second and explain why it’s a poor substitute, at least as compared to Erlang.

    One, Terracotta is an external library and will force certain tradeoffs to be made. See here for an example of the work required to get Clojure running on a Terracotta cluster. Erlang’s serialization and distribution is seamless and deeply integrated into the language.

    Two, Terracotta requires configuration in order to work. See here for an example of the configuration required. I guess Terracotta could be baked into a language runtime but I don’t see how without requiring lots of configuration. Erlang requires minimal configuration — a single cookie value and a call to net_adm:ping/1 to set up a reliable and distributed network. Erlang’s message passing semantics also simplifies inter-node communication and dealing with inevitable node failures. I’m not sure the same claims could be made for Terracotta.

    Three, Terracotta’s “network memory” model represents a shared everything approach which is the opposite of Erlang’s shared nothing model. Once again this is a case of a library creating a hurdle to overcome at best or forcing a compromise in the overall design at worst.


    Observation: Supporting concurrency without also supporting distributed programming diminishes the usefulness of the system overall. In other words, it’s interesting to be able to painlessly deal with lots of threads on a single machine but it’s vastly more interesting to deal with lots and lots of threads and data running across many machines.


  3. The JVM doesn’t support first class closures.

    Period. End of sentence. An implementation of Erlang on the JVM will need to provide closures so the only alternative is to simulate them in the language runtime. This will result in a definite performance hit. See the various JVM Scheme implementations and the hoops they jump thru to implement closures to get an idea of the magnitude of the problem.

  4. The JVM lacks tail recursion.

    See previous point and multiply the impact by 1000. Erlang uses tail recursion all the time. Halfway measures, like Clojure’s recur keyword, will not suffice. Erlang4J will have to implement its own call stack to provide proper tail recursion which means seamless Java interop just got loads harder.

At it’s heart the JVM is designed to run OO languages very efficiently. This is why languages like Ruby and Python are (mostly) easily
ported to the platform. It should also be no surprise these languages also see significant performance improvements on the JVM due to the platform’s maturity and overall high quality.

However, the JVM is less suited to running non-OO languages. Languages like Erlang, Haskell, and Scheme provide features, like tail recursion, closures, and continuations, which are not prominent in the mainstream OO world the JVM targets. They depart far enough from the OO model to make the JVM a poor platform choice.

I believe we are at the cusp of a major shift in our industry. FP is in the beginning stages of its ascendancy. It seems only natural to me for new platforms to increase in popularity as new languages come to the fore.


  • @28 jesus m. rodriguez: Keep dreaming.
  • SCALA IS THE FUTURE! :)
  • Robert Virding
    Sorry to get into this discussion so very late. TCO is central and critical for Erlang! An implementation without it is a toy which at best could be used to run some toy apps. Trampolining might work but the resulting language would not be Erlang.
  • @bwtaylor Killim - impressive performance but cooperative tasking? Seriously?
    X10 - nice but not production ready (as Killim too), same Java like syntax which leads to same SLOC ratio to Erlang.

    Maybe you not noticed but Erlang/OTP is matured, industry proven and reliable platform. It takes about 5~18 less SLOC compared to Java depend on task which means far less bugs and easier maintenance. It provides reliability which is hard to achieve by any JVM implementation which I know or I heard about. JVM doesn't fit typical Erlang users expectations. One size doesn't fit all. It's true for HW, RDBMS, OS, Language, IDE, ... cars, clothes, shoes, ... why should not be for VM?
  • @nigdje Devil lies in details. True. It's hard to implement all of Erlang features to JVM including Hot Code Swap including non-stop of all processes and keeping their states, well performance of typical Erlang program, distribution with easy and Reliability!!! (share nothing semantic - shared heap, each non Erlang process in same JVM breaks it) and so. Including any Java process into JVM will broke it. When you avoid include Java into Erlang4J environment there is no reason to port Erlang to JVM and vice versa.

    ad TCO: Are you sure you can involve below case:
    a(X)->b:b(X).
    b(X)->c:c(X).
    c(X)->a:a(X).
    when functions a, b and c are in its own module and each of this module can be any time recompiled and loaded without stopping any of process performing any of those functions? Show me! You will end up with full reimplementation of BEAM in Java or JVM byte-code instead of C. IMHO It's worthless work.
  • nigdje
    Just an amendment to the post above. When I stated that tail optimization is doable my thoughts were this: you could write piece of code which would introduce tail optimizations to a bytecode methods and apply this code to all classes during their loading, bringing tail optimizations to all jvms.
    I don’t imply that erlang functions are easily mapped to bytecode methods.
  • nigdje
    @kevin – I never implied you are bashing java or jvm. I am just curious about your statements why erlang4j isn't viable.

    I am not an Erlang programmer and I am not pretending to be one. But I do have experience in JVM internals (I customized Kaffe Virtual Machine few years ago) and I do find your arguments somewhat missing.

    Take tail recursion for example. This java function:
    static void foo(int n) { foo(n+1); }
    will be translated by java compiler to this bytecode sequence:

    ILOAD 0; ICONST_1; IADD; INVOKESTATIC foo(int)

    Straightforward tail recursion of this code could look like this

    Label0: ILOAD 0; ICONST_1; IADD; ISTORE 0; GOTO Label0;

    Some JVM-s out there have this functionality, some don’t. Now your reasoning goes somewhat like this: “because JVM doesn’t demand this functionality, the essential thing for any erlang implementation, it is impossible to have good performing erlang code on majority of jvms (HotSpot being most interesting)”. Now this isn’t quite true. Lots of things out there aren’t demanded by JVM specification and nevertheless they are doable by existing JVMs. GC for example (JVM specification s agnostic when it comes to GC).

    Naive implementation of erlang4j compiler/interpreter will have this problem. On other hand proper implementation certainly could have this kind optimization. It can be done in AOT fashion using erlang2bytecode compiler, or during runtime using bytecode rewrite techniques which are supported by any JVM and are commonly used for years. There is even a framework for this kind of optimizations called Soot.

    Of course best solution is to delegate this job to JVM. All I am saying is that “not having it in JVM” doesn’t immediately imply “not possible to do”.
    Now the devil lies in details. It is certainly possible that there is quite mismatch between notion of erlang “function” and of bytecode “method”. It is possible that that kind of mismatch exists and it is hard to translate one to other for any nontrivial job, but the tail optimization per-se is doable.
  • @nigdje I stand by my comments re: TCO. The fact is the most widely used JVM doesn't do TCO. J9 does, and that's great. But for the majority of environments "JVM" equates to the Sun JVM. Also, if TCO isn't in the spec how can one claim the JVM, as whole, supports TCO?

    As for distributed data, the whole point is there are a large number of classes which are not serializable. Any implementation of "Erlang4J" would want to foster Erlang <-> Java interop. I don't see how this works well when so many Java classes haven't been designed with serialization in mind.

    Here's an example. There's a Java class, we'll call it Foo, which lacks a SERIALUID declaration and proper transient declarations. An Erlang tuple contains a references to an instance of Foo. This tuple is marshalled to it's binary form and squirted across the network to another node and rehydrated into an Erlang term. What's the state of the Java object? And what does that mean for the condition of the Erlang program?
  • @Bariole - Again, I'm not putting down the JVM or Java. My whole point was to point out why the JVM is a poor target for an Erlang port. Have you used JINI and Erlang? JINI was an awesome framework in its day but I don't see how one could incorporate it into a mythical Erlang port without introducing limitations which don't exist in the current Erlang runtime.

    Distributed data != distributed state. Distributed data is the ability to easily transmit data between nodes in a cluster, for example. Erlang has excellent support since all Erlang terms, with the exception of things like socket references and file handles, are serializable and therefore transmittable. The real power here is, as a developer, I do not have to even think about serialization for my program to work. The same cannot be said for Java and JVM-based languages. This is a problem for "Erlang4J" since one of the goals would be Erlang <-> Java interop. Seamless serialization would break as soon as an Erlang datastructure contained a reference to one of these unserializable JVM objects.

    As for closures and GC, these problems are mutually reinforcing. Closures on the JVM equates to anonymous inner classes. Additional instances of these objects will stress the GC as it introduces more live objects into the object graph. And, since the JVM has a single GC for the whole VM, this represents a bottleneck. Erlang works around this problem by assigning a collector per Erlang process. This mostly eliminates GC as a whole-VM bottleneck and reduces the scope of GC operations thus speeding up GC as well.
  • nigdje
    Number 4. is wrong. Jvm specification neither requires neither forbids tail recursion. Some jvms do it, some don’t. J9 is widely used jvm which has tail recursion optimisation. There are others.
    I don't see a point in 1. and 2. What exactly prevents one to serialize erlang4j code and send it to other jvm? Described scenario (send some code and state, evaluate and run the code on receiver end) is doable even with plain java code.
    Similar goes to non-serializable classes. There isn’t any easy solution to serialize these objects. These classes just aren’t designed for that kind of usage. We can all agree on this issue. Why suddenly erlang4j has to solve this problem? So what is the exact issue here? What is the point? Nobody looked at erlang and thinker to himself: “Finally! What a great solution for marshalling java.”.
    Also terracotta looks out of place in this scenario. Yes you can use it to send events. It doesn’t seem to fit. As you did notice terracotta is“distributed heap” technology. Jini is probably better thing to look at.
  • re #17 - I have often wandered about that. Why on earth does everything have to be ported to the JVM. Many people talk about the JVM like it's the one true god and all should use it. Which I don't get. People talk about shoehorning in this feature and that. Yeah I hear the JVM is very optimized and so on, but I fail to see why Erlang needs the JVM.

    I see why people made JRuby. Because Ruby needed something better and the JVM was one of the solutions possible and the one that could be ready the fastest.

    I just don't see Erlang's need for the JVM. Erlang is doing fine and most of the problems are really just something that is being worked on and will be fixed. I just fail to see the need for the JVM and feel like it's mostly coming from Java programmers refusing to use anything else than the JVM. I can't beleive any of this is coming from Erlang programmers.
  • Bariole
    The JVM has poor support for distributed programming
    I don’t agree. JVM had all what it’s needed for distributed services since it was designed in early 90s (JINI for example). “Distributed computing” was one of principal goals of oak. Oak was runtime for small, embedded networked devices since its making. As far I know infrastructure needed to implement standard erlang model is in JVM.
    1. The JVM has no credible support for distributed data
    True. The JVM hasn’t. Nor does it Erlang. As I know Erlang doesn’t have shared state concept even if we are talking about sharing state between two processes within same Erlang VM. So issue is more like can do something like send a code trough a network/ipc bridge and run it on other JVM. Yes you can. It’s actually quite trivial to implement and doesn’t need terracotta. Just remember Terracota is more about sharing state than sending code.
    3. i 4. The JVM doesn’t support first class closures, The JVM lacks tail recursion
    Again so what? Closures are not bytecode feature. Closures are compile/runtime feature. Bytecode is lowlevel assembler abstraction. Use bytecode to construct closers.
    Bytecode does support notions of objects, classes and methods. And it can’t do tail recursion on bytecode methods (by implementation of existing JMV’s, not by bytecode/JMV design). And while bytecode methods will not be optimised with tail recursion by existing JVMs there isn’t any specific reason why any hypothetic erlang-to-bytecode interpreter/compiler couldn’t support this feature in translation of erlang methods to bytecode. Or there are reasons why this isn’t possible?
    @James Sanlder
    Cost of doing GC is traversing live objects and copying them during defragmentation. Count of dead objects is never a bottleneck of any decent GC algorithm.
  • Erlang's VM is one of the primary reasons for it being interesting and personally I think the crufty syntax isn't that great. So, why the hell are people talking about porting it to the JVM?
  • @bwtaylor - I'm not going to spend time refuting each of your points. It's obvious you've spent no time exploring other languages and runtimes or you would understand why Kilim and Scala aren't the same as Erlang's concurrency and distributed facilities. Similarly, Terracotta and the messaging libraries you mention pale in comparison to the comparable features in Erlang.

    "Erlang's wasteful immutability" :) It might be wasteful on the JVM's single GC per JVM model but I can assure you it's anything but wasteful on Erlang's VM. I won't even get into the entire class of bugs immutability completely eliminates.

    Notice I never said the JVM was "bad" or Java was "crap". I said the JVM was a poor fit for Erlang and listed several supporting reasons. Why are you getting so defensive? Is there not room in the world for more than one computing platform? Languages and platforms are like tools. Each one is suitable for some problems and not for others. To forget that is to consign yourself to eventual irrelevance as your favored platform is inevitably replaced by something better.
  • bwtaylor
    @Neal -- Not so. Kilim brings true microthread to Java, while relaxing Erlang's wasteful immutability demands. Check it out: http://www.malhar.net/sriram/kilim/

    The original points are all weak.

    - poor distributed programming, Erlang actor model.

    Scala implements the Erlang actor model on the JVM.

    Kilim implements something superior to the Erlang actor model: the actor model with concurrency safe mutable messages.

    - no credible support for distributed data

    Do you actually believe your own crap? eBay runs on java. Which are they not: "credible" or "distributed"?

    You refute your own argument when you note that things like files and socket should not be serialized. How exactly can I know whether or not the an entity I'm considering moving over the wire includes a reachable element that can't travel? The only viable solution is to have the programmer explicitly answer this question.

    The fact is that Java has tons of distributed data management solutions. Terracotta is one, but simpler things like the dozens of 2nd level persistence caches work just fine. Hell, XML marshalling works just fine (and is cross platform). Spring remoting works fine. GWT distributes java objects to and from browsers that don't even run a JVM. Look at all the grid, clustering, and map reduce tools in java. GridGain, Oracle Coherence, Hadoop, ZooKeeper, JGroups, many DHT implementations, etc...


    - The JVM doesn't support closures.

    Groovy. JRuby. Scala.

    You mean "Java" doesn't support closures with language syntax. Except that anonymous inner classes are functionally equivalent. The only objection is they aren't "nice" syntax. But you are advocating Erlang, so you shouldn't throw stones because of the lack of "nice" syntax.

    - no tail recursion

    Of your points, this is the only one with any basis in fact, as it is true that the JVM does not support the tail recursion optimization. This is fundamentally a performance issue, and here too, Erlang is not on solid ground to criticize Java for performance. Java's much faster for most things, and Erlang imposing immutability is a far greater negative performance impact (too much copying) than the JVM not supporting tail recursion optimization in the runtime.

    I'd like it if the JVM added tail recursion optimizations, but it doesn't have too as they can also be addressed at compile time by generating a while loop instead of using recursion. I think compile time optimization is the approach both scala and groovy will take.

    -----------

    I give Erlang it's credit. It's innovations are the actor model and runtime managed microthreads. Both have been very influential on the trends to concurrent programming. But I respectfully submit that the ideas in Kilim and X10 http://x10.codehaus.org/ are the state of the art here, and Erlang will be to concurrency what smalltalk was to object orientation. It's simply easier for the JVM to be enhanced to add improvements to Erlang's innovations than vice versa.
  • BlogReader
    There’s nothing even close to this on the JVM. Oh, you can get close using third party libraries but it’s not the same as having the feature baked into the language and runtime.

    What about Scala? That has distributed actor support baked in.
  • David Budworth
    the processes vs threads thing always ticks me off in java/erlang comparisons. everyone ignores the runtime performance aspect of it. they just focus on memory / context switches.

    the one thing you can't do in the jvm, without a terrible performance impact is the green "thread" style with reduction counts for switching.

    this, to me, is what makes the beam platform so awesome for high transaction computing. if I have an infinite loop, i'm guaranteed to get swapped out every 32k reductions (or whatever the default is). emulating that in the jvm would require some kind of JavaAgent instrumentation (for the standard java libs) to inject (between every N bytecode instructions) a call to some kind of "reduction" function that would then use exceptions to kick the thread out. That, or you only apply it to the erlang code and toss interrop out the door (which would make this a pointless experiment).

    all the java agent libraries are neat but only a partial implementation. a bad message (or set of messages) get in to the queue and your thread pool is exhausted with agent 'react' methods (ie: scala) consuming all the resources and never giving them up.
  • @Neal - Good point. My assumptions about Erlang4J included some sort of green thread-y layer which would multiplex Erlang "processes" onto real Java threads. This is another area where there's a real mismatch between the JVM and Erlang. I should've spelled this out explicitly.
  • An important reason that you didn't mention is that threads are much too expensive on the JVM to be used as they are in Erlang.
  • How's about we port Java to BEAM -- seems just as ludicrous as porting Erlang to JVM. Seriously, though, I would love to hear a valid and meaningful argument about why porting any one language on a VM to a different VM other than "its a great idea" or "because you can get the libraries" or the old "Great Science Experiment" all of which wash out to nothing.
  • It appears every language is being ported to the .NET VM or JVM these days.

    Enhancements for dynamic languages is one of the main goals for the Java 7. TCO is included in JSR 292:
    http://mail.openjdk.java.net/pipermail/mlvm-dev...
  • You can probably work around tail recursion using trampolining or better techniques. I haven't read it in detail, but it looks like Felleisen et al. have written a paper about this: http://www.ccs.neu.edu/scheme/pubs/esop2003-cf.pdf

    And closures can still be done if the compiler is good enough. For example, there is a good Haskell to GRIN compiler which efficiently translates high-level functional code into low-level assembly-like code. Boquist has written his PhD thesis on that.
  • @ben - I _am_ familiar with Java serialization and it is fraught with issues. Class versioning, proper declaration of internal members (transient anyone?), and SERIALUIDs are just some of the problems. As for the other suggestions, jboss-serialization is the only potentially viable choice. I'd sooner stick knives into my eyes than deal with XML parsing for object serialization.

    @Stephen Schmidt - I'm not saying Terracotta doesn't work. I'm sure it's a great solution for its intended audience. My point is Terracotta's audience is _not_ Erlang. My understanding is Erlang's distributed capabilities are more flexible and dynamic than what Terracotta can provide. Also -- you are correct about tail recursion. In my defense, tail recursion without TCO doesn't make much sense, IMHO.

    The reason why recur-style solutions won't work is Erlang has no other looping mechanisms besides TCO recursion. TCO needs to be available at all times in an Erlang4J runtime. Hence, the language would have to implement its own call stack which creates performance and Java interop issues.

    @Morton Johnson - I disagree. Any distribution mechanism will have to be bolted on and will come with its own set of constraints.

    Ditto for serialization. Assuming Erlang4J gets Erlang serialization working you still have the problem of Erlang <-> Java integration. How do you serialize Erlang code which contains references to Java objects? What if those objects are not easily serializable?

    True, you can use anonymous inner classes to emulate closures. Erlang uses closures frequently. I think an object-per-closure instance, which would be the model forced by anonymous inner classes, represents a problem as you'll be stressing the garbage collector with the creation and destruction of lots of closures. This is a place where Erlang's GC-per-process works really well and Java's GC-per-VM doesn't.
  • John Stauffer
    How about this for a Twitter-sized summary:

    Porting Erlang to the JVM misses the point: Erlang's VM is it's best point. Let's port Java to the Erlang VM.
  • ben
    On the serialization issue, if you had looked at the inners of java serialization, you would quickly realize that it's arbitrarily limited (there is actually an if(o instanceof Serializable) check).

    See xstream, jsx, jboss-serialization and many others for seamless, configuration free serialization.
  • Strongly agree with you even you doesn't mentioned reliability ;-) I think JVM can't take Erlang advantages as reliability, seamless distribution, soft real-time characteristics (GC) because JVM design. Contrary for example, Erlang can improve speed in sequential operation introducing some kind of JIT (may be trace trees? http://www.ics.uci.edu/~franz/Site/pubs-pdf/ICS...) without big impact. It is philosophical question if trade some reliability, real-time characteristics and hot code swap complexity for this improvement.
  • We use Terracotta, it's nice, it works, it helped us a lot.

    Some observations:

    "There’s nothing even close to this on the JVM"

    [...]

    "Two, Terracotta requires configuration in order to work."

    [...]

    "Erlang requires minimal configuration"

    About closures:

    "This will result in a definite performance hit."

    Scala looks quite good performing to me. From my - limited - understanding closures can be transformed to objects (see Functional Java for details), and there is no order of magnitude performance hit.

    "The JVM lacks tail recursion."

    Sorry to be picky, it doesn't
    lack tail recursion but tail recursion optimization. There are patches which do TCO, Scala will support annotations for TCO,

    "Clojure’s recur keyword, will not suffice."

    Could you explain? Clojure works nice with explicit recur and the patches (see OpenJDK projects) might make it into the VM. Right now I think I'd prefer the recur solution for the JVM. There would be no seamless running of Erlang on the JVM without TCO though - you're right in this regard.

    Cheers
    Stephan
    http://twitter.com/codemonkeyism
  • James Sadler
    There's another major feature missing form the JVM - per-process heaps.

    Erlang has this, and sending data between two processes is a copy (at least semantically; except for binaries which can be safely reference counted). The nice side effect of this is that terms in one process _cannot_ reference terms in another process, so this make it real easy to implement garbage collection efficiently.

    There is no way the JVM can do this: it's garbage collector is heap-wide. It cannot know that once a thread terminates, it can free all the objects referenced by that thread; hence it is less memory efficient and will not scale as well as Erlang in that regard.
  • Morton Johnson
    1) Distribution support can be added in as a Java library. It does not need to be baked into the JVM to make it work.

    2) It's trivial to write Java serialization support for the handful of types supported in Erlang terms. There's already serialization support for the closest types in the Java libraries.

    3) This is easily implemented in a number of ways. Java programmers regularly use anonymous inner classes which can be thought of as a superset of closure functionality.

    4) This is the real issue.
blog comments powered by Disqus