8 ms·
the author leaves out Kotlin which adds support for coroutines on the language level and still compiles to java bytecode. These are not classic continuations be
by eggsnbacon1 6y ago
the author leaves out Kotlin which adds support for coroutines on the language level and still compiles to java bytecode. These are not classic continuations because they cannot be cancelled, but they're still very useful and true fibers.
There's also the Quasar library that adds fiber support to existing Java projects, but its mostly unmaintained since the maintainers were pulled in to work on Project Loom.
Then there's Project Loom, an active branch of of OpenJDK with language support for continuations and a fiber threading model. The prototype is done and they're in the optimization phase. I expect fibers to land in the Java spec somewhere around JDK 17.
I figure its fair to mention these as the authors criticisms are somewhat valid but will not be for very long (few years max?)
In summary:
Java will have true fiber support "soon". This will invalidate the arguments for Erlang concurrency model. They are already outdated if you are okay using mixed java/kotlin coroutines or Quasar library
The newer Java GC's Shenandoah and ZGC address authors criticisms of pause times. They already exist, are free, and are in stable releases. Dare I say they are almost certainly better than Erlang's GC. They are truly state of the art, arguably far superior to the GC's used in Go, .NET, etc. Pause times are ~10 milliseconds at 99.5%ile latency for multi terabyte heaps, with average pause times well below 1 millisecond. No other GC'ed language comes close to my knowledge. His points 1 and 2 no longer exist with these collectors. You don't need 2X memory for the copy phase and the collectors quickly return unused memory to the OS. This has been the case for several years.
Hot code reloading. JVM supports this extensively and its used all the time. Look into ByteBuddy, CGLIB, ASM, Spring AOP if you want to know more. Java also supports code generation at build time using Annotation Processors. This is also extensively used/abused to get rid of language cruft
- throw51319 6y agoIs Java the best out there?
- The_rationalist 6y agoKotlin* but yes JVM is basically the best platform
- deleted 6y ago[deleted]
- The_rationalist 6y agoThese are not classic continuations because they cannot be cancelled, but they're still very useful and true fibers. what? Coroutines do support cancelation
- dnautics 6y ago> This will invalidate the arguments for Erlang concurrency model. What about failure domains? As far as I'm concerned, this is the strongest reason for actor-based concurrency. I can design my architecture so that groups of processes that need to die together die together. And it's usually one or two lines of code, if any. Here's a real life example. I have a process that maintains an SSH connection to a host machine, and that ssh connection is used to query information about running VMs on that host machine. If the SSH connection dies, it kills the process that is tracking the host machine, which in turn kills the processes tracking the associated VMs, without perturbing any of the other hosts' processes or vms. This triggers the host process to be restarted by a supervisor, which then creates a new SSH connection to query for information (possibly repopulating VM processes for tracking information). All of this I wrote zero lines of code for (which, importantly, means I made no mistakes), just one or two configuration options. More importantly, the system doesn't get stuck in an undefined state where complex query failures can cause logjams in the running system.
- eggsnbacon1 6y agoYou can tie the fates of threads together in Java using thread groups. If you need more flexibility, or want it to be managed for you, Akka framework offers this. I believe Akka gives you a model very similar to Erlang. In Java you would create a thread pool and configure it to restart the threads if they die. Each thread would wake up every so often to query SSH and dump their results into a queue. If the query threads die, the processes reading the queue at the other end have nothing to do so they won't execute. Its easy to make a consumer queue that executes some code on another thread whenever data arrives. Java's exposure of the underlying OS threads and cheap transfer of data between threads lets people build libraries on top that offer memory models used by Erlang and others. Its not built in or quite as convenient, but you can use actors and fibers in Java if you want to.
- dnautics 6y agoYeah that's exactly the problem. It's an afterthought in the system. How certain can you be that the system you're using is compostable with any other code brought in to your system, even from libraries outside? In erlang, failure domains are the raison d'etre of the language, so everything in the ecosystem will play nice. Ultimately, systems like akka are extremely complicated to get right, even for experts, because you have to think about all of the vm bits underneath. I can (and have) teach a junior programmer basic OTP concepts with the confidence that they can't mess things up. Now, they wouldn't be able to come up with the architecture I designed as a good idea, but I could tell them to implement it (with tests!) and expect them to get it right.
- sa1 6y agoNon-preemptive concurrency doesn't invalidate any argument. Erlang's GC is per user thread, even a primitive GC per user thread will have lower latency than Java's GC.
- eggsnbacon1 6y agoI want to see a source on this. Golang's GC is often touted as better than Java's but every real world benchmark I've seen shows that it sacrifices a lot of throughput for low pause times, essentially by running much more often. Java's new garbage collectors, ZGC and Shenandoah, have average pause times of 0.3 milliseconds on heaps less than 4GB. I find it unlikely that another language has pause times shorter than that given the sheer amount of work put into Java GC over the years
- sa1 6y ago> have average pause times of 0.3 milliseconds on heaps less than 4GB The answer is that one user thread would have a lot less memory than 4GB, and the GC only needs to work on heap sizes of KBs to MBs for most cases. There is no shared memory(well, mostly). It's comparing apples and oranges.
- Skinney 6y agoJava's GC has to be best in class because of shared memory. In a shared nothing world doing GC in one "thread" doesn't stop other threads from executing, it also means that each heap can be very small so you might not even need to perform gc before the thread is done executing. It's truly amazing what Java is doing, but keep in mind that Erlang has worked this way for _decades_. And still, a classic web server that spins up one thread/process per request, can still potentially end up responding to the request with zero garbage collection in the best case, irrespective of load. This will not be true for Shenendoa or ZGC. Does Java's Hot code reloading support data migration? One benefit of Erlangs model is that you can execute hooks when HCR is performed to make sure your data in memory is migrated to a new format. But really, the most important thing about Erlangs actor model is error handling. If I spin up a process in Erlang and it fails, it won't corrupt the state of my other processes. In Java this can only be attained through disipline since all memory is shared. Also, I can very easily specify which processes should work together as units, such that if one fails, they all fail, and can be restarted together from a known working state. This, again, requires discipline in Java.
- eggsnbacon1 6y agoPer thread GC is definitely a different approach than Java takes. The trade-off is that shared memory between Java threads is nearly free. Basically the same approach C++ uses, except Java has better concurrency primitives because its VM. Not sure about Erlang but data sharing between processes on JS and Python is very expensive and a frequent criticism of those languages. You can achieve zero garbage per request in Java. Typically high performance web frameworks like Undertow and Vert.X are designed this way. User code rarely does it but its definitely possible. Not sure what you mean by data migration on code reloading. I suspect the mechanisms are different enough that it can't be compared. With Java you can load arbitrary new code, but changes to existing code are limited in ways that prevent data incompatibilities. For example you can add fields to existing object but you can't change the type of existing ones. Data corruption from threading is rare in Java. I can't remember the last time I ran into it. Its easy to do but everyone is used to threads and the concurrency implementation is one of the best I've used. Java also supports thread groups to ensure that threads die and get restarted together. Its not automatic, you need to manage the groups, but I think it achieves the same.
- rojeee 6y agoFair point - all this is true. As a counter-point, I've been working on a platform for the last few years which uses Kotlin and Quasar in production. Quasar was cool at first but now it's just a nightmare and I wish we never opted to use it. It leaks abstractions all over the place with @Suspendable annotations and users of the platform find the quasar related errors super confusing. Debugging is also very difficult because of Quasar. On the other hand, Kotlin is great! If I could turn back the time, I'd build the messaging/async workflow part of the platform using Erlang. I've mentioned this to a few people but they all think I'm mad... "Erlang... are you on drugs?!", which is disappointing because it's literally perfect for our use case.
- eggsnbacon1 6y agoI have actually heard the same about Quasar so I have avoided using it. It hacks up the bytecode so evil bugs appear common based on my glances at issue tracker. Why didn't you use Kotlin coroutines? My understanding is that they achieve the same as Quasar without the insanity. You may also want to look at Vert.X. Its evolved into a lot more than a REST framework. It uses thread-per-core and nonblocking to achieve high performance instead of green threads. It theoretically performs better because there's not a lot of stacks hanging around and only 1 thread per core. There's a lot of callbacks though, so if you're not used to RxJava style chaining its hard to get used to. Its very much like Node. Erlang or Go would be the easiest if you need a lot of threads. If you just need high performance with a lot of connections, Vert.X may suffice. Java IO in recent years is fully non-blocking so you don't need a lot of threads for high concurrency. Vert.X can handle millions of concurrent clients, enough that you will need to adjust your kernel to hit its limits. And its built on Netty which is rock solid.
- rojeee 6y agoSounds like you made the right decision! When we started the project four years ago, coroutines were still quite experimental so wasn't a feasible option for us. If I were to consider all the trade-offs, moving to coroutines not something we should do now as I believe it would yield only marginal benefits but would break compatibility for customers. One of the main problems with the Quasar/coroutine based model is that the semantics are quite hard to undersand for developers who are not very familiar with concurrency. They write code that _looks_ synchronous but is actually async. We get a lot of support tickets claiming there's a bug in the platform when the reality is that they don't understand what's going on. I sympathise with them and we probably need to do a better job of hiding the complexities. As you note, the bytecode instrumentation is a bit of a pain but not only that... It also has quite a big impact on performance! There has been talk of doing some experiments with Akka and that's something I'm interested in exploring. But I think, hypothetically, that writing parts of the platform again in Erlang/OTP would yield huge productivity benefits... gen_fsm offers exactly what we need out of the box. From the little playing around I've done with Erlang, it feels like you can get a small, competent team, up to speed fairly quickly.
- haspok 6y agoDon't forget ZIO (and other Cats Effect based libraries) which is the new kid on the block, and has it's own take on fibers and concurrent programming. The biggest problem with Erlang is that hardly anything out there needs this level of concurrency and robustness in a single system - in the new world of microservices and serverless architectures there are other ways to cope with scaling. This is the main selling point, and unfortunately in all other areas Erlang is significantly outdated and refuses to evolve - even less so than the Java language which is a dinosaur in itself. Having said that I think Erlang is a fantastic teaching tool and should be on everyone's bucket list of "things to learn in this life as a software engineer".
- conradfr 6y agoYes, that's a problem, you can't be the only one responsible for a core part of the stack. I did an interview at a major streaming company and one critical part was written in Erlang. It has been working great but the guy who wrote it had left for some time and nobody knew Erlang there, so they would have to rewrite it if an update was needed.
- toast0 6y agoOr, someone could take a week to learn Erlang and make whatever change is needed. You won't be an Erlang expert in a week, but it's a pretty small language, so editing existing code isn't that hard. And that existing code can't be that bad, since it's still working.
- anko 6y ago> The biggest problem with Erlang is that hardly anything out there needs this level of concurrency and robustness in a single system - in the new world of microservices and serverless architectures there are other ways to cope with scaling. I wondered about this myself before I started using Elixir. In practice, it turns out when it's cheap to make things concurrent more services take advantage of this feature. Tests and the elixir compiler are extremely fast because of this, and it makes the whole development experience better. Because the primitives are so simple, people experiment more which makes better software. Nobody would come up with phoenix live view for Play framework in their spare time because play framework is so overly complicated.