The biggest win by far IMHO is a performant alternative for io intensive applications for async frameworks.
Not using an async framework tremendously simplifies your code.
Code complexity has an enormous projection on everything else, especially when talking on the business level. First and foremost the cost of development and time to market rises. And then comes the cost of maintenance.
While this is definitely a good new, we need to be very careful. Why? Those new fancy threads are stored on heap and need to be garbage collected. If you buy into ads and believe that you can create for free millions of threads, then, well, it is not gonna work on production.
Before getting too excited I advise to watch Tomasz Nurkiewicz lecture on the subject - https://www.youtube.com/watch?v=n_XRUljffu0, it explains what are the trade-offs here.
1. Platform threads place a heavier burden on the GC. It's true that virtual threads are allocated on the heap, but platform threads are GC roots, which is worse. The GC easily deals with a gazillion heap objects; it's rather unhappy with lots of roots. The number of heap objects that virtual threads occupy is roughly the same as the number of heap objects that async code allocates anyway.
2. The Jetty experiment measured the wrong thing as they misunderstood the origin of the "million thread" scenario. What happens in a real application is that you have some number of threads with deep stacks servicing incoming requests -- say 50K concurrent sessions -- and then each of those fans out to, say, 19 micro services in parallel, each of those outgoing requests is done on a virtual thread with a very small stack, and that's how you get to 1M threads. I.e. when you have a high number of threads, only a small minority of them (5% in this example) have a deep stack.
3. I don't think anyone would claim anything is a silver bullet. All virtual threads do is let a server service the same throughput as asynchronous code does, but the code is much simpler and it is observable, i.e. easily debuggable and profitable, something that async code can't do.
> Those new fancy threads are stored on heap and need to be garbage collected. If you buy into ads and believe that you can create for free millions of threads, then, well, it is not gonna work on production.
A typical request easily creates 100's of objects that needs to be garbage collected. Adding a single thread object on top of that means absolutely nothing. And how much garbage to you think reactive frameworks create? I can you tell you it is a lot more.
> Before getting too excited I advise to watch Tomasz Nurkiewicz lecture on the subjec
Don't waste your time. He is pretty clueless. I remember him complaining about lack of backpressure and composability. But this comes pretty much out of the box with loom.
Based on that I found this reddit discussion where Ron Pressler (Loom project) commented:
"The posts read like they’re leading to a negative conclusion, but at the end of part 2 it turns out that the current Loom prototype does perform as well as async in their experiments ..."
You can even use a million native threads without an issue on Linux. But unless they are mostly idle you probably won't be able to serve a million concurrent clients, wether you use threads or not.
Yes, there's no silver bullet. Writing async code from the beginning is a much better approach to reducing memory footprint. But fibers/virtual threads/whatever-you-call-them will help scale many thread-per-client apps w/o having to pay for a rewrite as async code. That is worth a lot because the size of the thread-per-client Java codebases is enormous.
That's pretty confusing without some more context, yeah. Not the best article in this regard.
> In fact, in very early Java versions, the JVM threads were multiplexed onto OS threads (also known as platform threads), in what were referred to as green threads because those earliest JVM implementations actually used only a single platform thread.
> However, this single platform thread practice died away around the Java 1.2 and Java 1.3 era (and slightly earlier on Sun’s Solaris OS). Modern Java versions running on mainstream OSs instead implement the rule that one Java thread equals exactly one OS thread.
I really hope this is the end of complicated reactive frameworks! I love the old blocking spring controller paradigm, thread local and so on. It makes things much easier. Never liked webflux, so complicated and hard to debug. Simple things become a project!
NIO solved the problems with threads on the network in Java 1.5 (seminal cornerstone API that also had the concurrency package and rewrote the entire JVM memory model), but only in 1.7 the epoll solution became stable.
2004 -> 2011, 7 years!
Now they hopefully can work around the kernel for file descriptors (network and disk) saving 30% CPU globally on all Java servers.
> Virtual threads offer a more efficient alternative to platform threads, allowing developers to handle a large number of tasks with significantly lower overhead.
they should have just called them "Tasks" leaving the already overloaded term "virtual" out of the conversation.
So with this, the last thing Go had going for it over Java is gone, right?
Java has an obviously better type system, while Java originated the billion dollar mistake, Java also at this point has much better practices around handling nulls than Go, Java's jars are more portable than go's binaries, Java's GC performs better, Java has a more mature ecosystem and more libraries... Java has better IDE support and comparable compile times.
I guess at this point the only major difference is that you can teach a 3 year old to write Go more easily, so ChatGPT produces correct Go more easily than Java, and the dependency management story differs a little (though I don't think you can really call a winner or loser on that one, it's just different)
As a language java's not that bad at this point. But it still has cultural issues.
Every java project I encounter tends to be overengineered, has tons of useless boilerplate code and ends up throwing mile-long stack traces as a result. Also, somehow maven manages to be even more unreliable than npm as a package manager. I still frequently encounter situations which seem to only get resolved by throwing away my .m2 folder.
Furthermore, since the language evolved so much in recent years, it's hard for newcomers to know what the best practices are. There's at least 6 different ways to iterate over map values. Which one should I pick?
By the "billion dollar mistake", are you referring to null references [1]? But null references were introduced in 1965 in Algol, by Tony Hoare. They long predate Java.
My experience in small companies told me that younger generation didn’t choose Go because Go is objectively better than Java, but because they actively hate Java for it being out of fashion.
Well technically, you could do async and green threads / co-routines / promises / futures/ etc. (all the same thing technically, it's all callbacks under the hood) on the JVM for many years. Frameworks like Spring, vert.x, and others have supported this for ages. The loom project just makes it a bit easier to use this and provides some JVM level support and optimization for this. With other JVM languages (Scala, Kotlin, Clojure, etc.) this was already quite easy so it's less dramatic if you were already using those languages. My kotlin co-routines will be using loom threads under the hood pretty soon but it's not going to massively change how I use them or what I do with them. Or make a huge difference in performance. My code isn't CPU bottle necked, typically. Like the vast majority of server software (which is IO or memory bottle necked typically).
Go of course has simplicity as its main advantage. That doesn't go away of course. But it's a double edged sword and it can be a bit overly verbose / limited for some stuff.
Maybe more interesting is the trend towards native compilation in the Java world. Graal is a pretty big deal. And with languages like Kotlin, there is also kotlin native and wasm as a relatively new option. That's the other advantage Go has had that is slowly going becoming less relevant: fast start up times and a simpler run-time (i.e. a statically compiled binary that you start).
ChatGPT is fluent in many languages. I've been generating some usable Kotlin code with it, for example. I'm sure it does Java just fine as well.
I would say that Go's error handling is a billion dollar mistake as well.
The JVM is very impressive and a great thing to build upon. The Java standard library is vast and the developers actually care about it (unlike the Python folks, who gave up on having a sane HTTP client library built-in and instead defer to the third-party requests library).
The Java language is not as great, lacking some quality-of-life features (properties and operator overloading, for example). It often suffers from design-by-committee (e.g. https://openjdk.org/jeps/430 ).
And then you get to the web frameworks. The most popular one is Spring, and it's a pain, with abuse of reflection and magic, bad docs, and other issues.
The linked article has zero references to golang, so it's unclear if the java community has other golang features they're planning on buying into.
This change improves Java/JVM capabilities and that's great. Java is a great language and it's nice that it is shaking off the stagnation/perception of stagnation it has acquired over the years. Golang also has really great capabilities and use cases. Having written tons of code in dozens of languages it seems to be folly to expect one language to rule them all. The features that you don't have are almost as important as the features you do have.
If Java is looking for additional golang cherrypicks I would love to see their start times improve (shortlived java processes are painful, Graal doesn't cover all uses). FFI even in Panama is much more boilerplate then CGO. Go's deployment story is so much cleaner and straightforward then Java. The amount of engineering hours saved through language/toolchain standardized formatting is just monumental. Those are just 3 things at random, their are a bunch of other great ideas to steal.
I also wouldn't throw any shade at Golang's vibrant library ecosystem. There are just so many great active projects written in pure go that make spinning up new services a joy.
Java's GC has better throughput, because Go's GC specifically optimises for latency. This may or may not be the right tradeoff for you specifically, but it is a perfectly reasonable tradeoff.
Jars are more portable at the cost of requiring a JVM installed on the target, whereas Go's statically linked binaries and great cross-compilation make portability mostly moot for server applications.
Also, with Java 21, the JVM brings runtime support for virtual threads, at the VM and standard library level, but there is no real language support, whereas Go has actual support with syntactic sugar around goroutines, channels, and select statements.
Tooling and the sheer number of libraries are definitely Java's biggest draws, but it's not enough to justify choosing Java over Go across the board.
> So with this, the last thing Go had going for it over Java is gone, right?
Go still has composition over inheritance, which is vastly more flexible. Go favors explicit (verbose copy-paste, redundancy, use stdlib first, libraries second) over implicit (magic annotations, frameworks everywhere) and I like when I can understand what's happening without holding 10 files in my brain context. Go's memory usage doesn't trigger the OOM killer every 10 minutes. Go favors copy-pasting because what you copy is short and understandable, and function names don't have 10 words in it.
It's always going to be a personal choice, and even though virtual threads bring java closer to where go is, it's still not there for me.
As someone who recently developed in Java (for developing a Jenkins plugin) after not having done so for a long time (~15 years), I feel that Java nowadays is technologically quite interesting with many interesting libraries and tooling. IDE code navigation and debugging support is excellent.
At the same time, the ecosystem can feel messy and overwhelming. There are multiple @Nonnull annotation libraries and it's not clear what the difference is. There are many different testing libraries (some TDD, some BDD) but it seems everybody picks a different one. There are multiple advanced concurrency libraries and concepts, but again not much consensus and everybody uses a different one. Lots of deprecations all over the place.
It's even worse in the Jenkins-specific ecosystem where nearly every plugin uses some deprecated aspect of either Java or Jenkins, and hunting for what the "right" way is supposed to be is a bit of a daunting task.
There are like 20 different JDKs, not sure why.
On the other hand, startup time and memory usage still seems to be a big issue. Starting Jenkins takes forever even with JVM in client mode and with bytecode verification disabled. And when started it uses a huge amount of memory. To date I've never seen one non-hello-world Java app that isn't like that.
C# already had all the advantages of Go (more or less) and more, yet Go is still growing. This has nothing to do with the technical capabilities of the languages.
The top comment in this thread [1] highlights (potential) problems with virtual threads, referring to this PDF [2]. Does anyone know if these actually manifest in the way they are implemented?
Your second link is from 2018, and would indeed need a case study for Loom. For example the concerns
about thread-local storage are addressed, and the base overhead is understood (as in: don't use them for CPU-bound worloads, they'll fare better on IO-bound workloads)
Also, the cases studies in that paper are platform-level when I believe the language has to be involved; as a runtime has more information when resuming to a suspension point. The paper even acknowledges Go as a successful implementation although with C-compatibility call overhead caveat. In the Java world, the vast majority of programs stay in the Java language. So I'd say that paper would list Loom as the best implementation (And maybe revise their recommendation. It'd be useful to have that author's opinion of Loom in 2023)
This is relevant to environments where the user code is statically compiled into native code that depends on some thin runtime library and more or less directly interacts with C code. In case of full blown VM many of these problems are not as significant as the internal thread state representation is non-native anyway and you can dynamically instrument pretty much whatever you want (one issue are system calls and system library functions that do not have non-blocking equivalent, but that could be handled by either having separate OS-level thread for such things or simply ignoring the issue, with real implementations doing some mix of these two approaches).
I haven't touched Java since school and never worked in it professionally and that's been well over a decade. Is there a resource that people recommend that gives a good introduction to modern Java? Preferably succinct but doesn't need to be.
Not familiar with .NET, virtual threads in Java are an alternative to async await in other languages. The approach Java took is similar to green threads in go.
What are these things? It sounds like it is a time-sliced sharing that offers only concurrency and not parallelism. In other words, it's a userland construct and not a kernel thread. Sounds more like GO coroutines but do we really need another name for them?
The main benefit of virtual threads it that they release their carrier thread when they call (non native) blocking code, so that other virtual threads can be executed by the same carrier.
I don't think that it's the case for C# task, which also appear to be an higher level construct.
Conceptually, these are all variations of the same thing. The difference is in how you use them and how much boiler plate you need to use them.
Funnily enough, the first versions of Java did not support OS threads and only had green threads for a while. Supporting real threads was a big deal at the time as it allowed you to use more than 1 processor. Of course, processors were single core at the time and most computers only had one of those anyway. Java 1.1 laid the foundations for proper multi threading and green thread support was eventually removed with Java 1.3. With Java 1.5 we got the java.concurrent package which enabled doing more complicated things with locks and other synchronization primitives that were a bit less primitive & brittle than using the synchronized & volatile keywords. That includes implementing green threads on top of real threads. Which is what frameworks like vert.x and others have been doing for ages.
So, in a way we're coming full circle here with virtual threads re-using the thread API, which in turn reused the original green thread APIs in Java 1.0.
I guess the idea is that you don't have to write tasks explicitly - if you have a sequence of actions you can just write it as a regular code, and the runtime will automatically insert points where your code might be suspended waiting for IO or other threads.
[+] [-] kosolam|2 years ago|reply
[+] [-] piokoch|2 years ago|reply
Because of this server based on new threads does not need to be more performant, Jetty server guys tested this and they were not that happy: https://webtide.com/do-looms-claims-stack-up-part-1/
Before getting too excited I advise to watch Tomasz Nurkiewicz lecture on the subject - https://www.youtube.com/watch?v=n_XRUljffu0, it explains what are the trade-offs here.
No silver bullet, again.
[+] [-] pron|2 years ago|reply
2. The Jetty experiment measured the wrong thing as they misunderstood the origin of the "million thread" scenario. What happens in a real application is that you have some number of threads with deep stacks servicing incoming requests -- say 50K concurrent sessions -- and then each of those fans out to, say, 19 micro services in parallel, each of those outgoing requests is done on a virtual thread with a very small stack, and that's how you get to 1M threads. I.e. when you have a high number of threads, only a small minority of them (5% in this example) have a deep stack.
3. I don't think anyone would claim anything is a silver bullet. All virtual threads do is let a server service the same throughput as asynchronous code does, but the code is much simpler and it is observable, i.e. easily debuggable and profitable, something that async code can't do.
[+] [-] kasperni|2 years ago|reply
A typical request easily creates 100's of objects that needs to be garbage collected. Adding a single thread object on top of that means absolutely nothing. And how much garbage to you think reactive frameworks create? I can you tell you it is a lot more.
> Before getting too excited I advise to watch Tomasz Nurkiewicz lecture on the subjec
Don't waste your time. He is pretty clueless. I remember him complaining about lack of backpressure and composability. But this comes pretty much out of the box with loom.
[+] [-] karussell|2 years ago|reply
"The posts read like they’re leading to a negative conclusion, but at the end of part 2 it turns out that the current Loom prototype does perform as well as async in their experiments ..."
https://www.reddit.com/r/java/comments/kmn6m3/do_looms_claim...
[+] [-] re-thc|2 years ago|reply
This was in 2021. Hopefully things have and will improve in the final version. We have to start somewhere.
> No silver bullet, again.
More like progressive improvement. If you always need things to be a 100% better we'll never have new toys :)
[+] [-] kaba0|2 years ago|reply
[+] [-] akvadrako|2 years ago|reply
[+] [-] cleanchit|2 years ago|reply
[+] [-] cryptonector|2 years ago|reply
[+] [-] pantulis|2 years ago|reply
-- edit myself: no, it can't be. JVM had green threads since eons ago, according to wikipedia.
-- edit again: this SO thread --pun intended!-- explains it
https://stackoverflow.com/questions/74639116/what-is-the-dif...
[+] [-] Valodim|2 years ago|reply
> In fact, in very early Java versions, the JVM threads were multiplexed onto OS threads (also known as platform threads), in what were referred to as green threads because those earliest JVM implementations actually used only a single platform thread.
> However, this single platform thread practice died away around the Java 1.2 and Java 1.3 era (and slightly earlier on Sun’s Solaris OS). Modern Java versions running on mainstream OSs instead implement the rule that one Java thread equals exactly one OS thread.
https://blogs.oracle.com/javamagazine/post/going-inside-java...
[+] [-] HippoBaro|2 years ago|reply
[+] [-] unknown|2 years ago|reply
[deleted]
[+] [-] unknown|2 years ago|reply
[deleted]
[+] [-] Patrol8394|2 years ago|reply
Virtual threads to the rescue!
[+] [-] bullen|2 years ago|reply
2004 -> 2011, 7 years!
Now they hopefully can work around the kernel for file descriptors (network and disk) saving 30% CPU globally on all Java servers.
Many nuclear power plants wasted on the kernel.
[+] [-] kjto|2 years ago|reply
they should have just called them "Tasks" leaving the already overloaded term "virtual" out of the conversation.
[+] [-] TheDong|2 years ago|reply
Java has an obviously better type system, while Java originated the billion dollar mistake, Java also at this point has much better practices around handling nulls than Go, Java's jars are more portable than go's binaries, Java's GC performs better, Java has a more mature ecosystem and more libraries... Java has better IDE support and comparable compile times.
I guess at this point the only major difference is that you can teach a 3 year old to write Go more easily, so ChatGPT produces correct Go more easily than Java, and the dependency management story differs a little (though I don't think you can really call a winner or loser on that one, it's just different)
[+] [-] kabes|2 years ago|reply
Every java project I encounter tends to be overengineered, has tons of useless boilerplate code and ends up throwing mile-long stack traces as a result. Also, somehow maven manages to be even more unreliable than npm as a package manager. I still frequently encounter situations which seem to only get resolved by throwing away my .m2 folder.
Furthermore, since the language evolved so much in recent years, it's hard for newcomers to know what the best practices are. There's at least 6 different ways to iterate over map values. Which one should I pick?
[+] [-] quanticle|2 years ago|reply
[1]: https://www.infoq.com/presentations/Null-References-The-Bill...
[+] [-] hintymad|2 years ago|reply
[+] [-] jillesvangurp|2 years ago|reply
Go of course has simplicity as its main advantage. That doesn't go away of course. But it's a double edged sword and it can be a bit overly verbose / limited for some stuff.
Maybe more interesting is the trend towards native compilation in the Java world. Graal is a pretty big deal. And with languages like Kotlin, there is also kotlin native and wasm as a relatively new option. That's the other advantage Go has had that is slowly going becoming less relevant: fast start up times and a simpler run-time (i.e. a statically compiled binary that you start).
ChatGPT is fluent in many languages. I've been generating some usable Kotlin code with it, for example. I'm sure it does Java just fine as well.
[+] [-] Kwpolska|2 years ago|reply
The JVM is very impressive and a great thing to build upon. The Java standard library is vast and the developers actually care about it (unlike the Python folks, who gave up on having a sane HTTP client library built-in and instead defer to the third-party requests library).
The Java language is not as great, lacking some quality-of-life features (properties and operator overloading, for example). It often suffers from design-by-committee (e.g. https://openjdk.org/jeps/430 ).
And then you get to the web frameworks. The most popular one is Spring, and it's a pain, with abuse of reflection and magic, bad docs, and other issues.
[+] [-] bhawks|2 years ago|reply
This change improves Java/JVM capabilities and that's great. Java is a great language and it's nice that it is shaking off the stagnation/perception of stagnation it has acquired over the years. Golang also has really great capabilities and use cases. Having written tons of code in dozens of languages it seems to be folly to expect one language to rule them all. The features that you don't have are almost as important as the features you do have.
If Java is looking for additional golang cherrypicks I would love to see their start times improve (shortlived java processes are painful, Graal doesn't cover all uses). FFI even in Panama is much more boilerplate then CGO. Go's deployment story is so much cleaner and straightforward then Java. The amount of engineering hours saved through language/toolchain standardized formatting is just monumental. Those are just 3 things at random, their are a bunch of other great ideas to steal.
I also wouldn't throw any shade at Golang's vibrant library ecosystem. There are just so many great active projects written in pure go that make spinning up new services a joy.
[+] [-] pdpi|2 years ago|reply
Jars are more portable at the cost of requiring a JVM installed on the target, whereas Go's statically linked binaries and great cross-compilation make portability mostly moot for server applications.
Also, with Java 21, the JVM brings runtime support for virtual threads, at the VM and standard library level, but there is no real language support, whereas Go has actual support with syntactic sugar around goroutines, channels, and select statements.
Tooling and the sheer number of libraries are definitely Java's biggest draws, but it's not enough to justify choosing Java over Go across the board.
[+] [-] rakoo|2 years ago|reply
Go still has composition over inheritance, which is vastly more flexible. Go favors explicit (verbose copy-paste, redundancy, use stdlib first, libraries second) over implicit (magic annotations, frameworks everywhere) and I like when I can understand what's happening without holding 10 files in my brain context. Go's memory usage doesn't trigger the OOM killer every 10 minutes. Go favors copy-pasting because what you copy is short and understandable, and function names don't have 10 words in it.
It's always going to be a personal choice, and even though virtual threads bring java closer to where go is, it's still not there for me.
[+] [-] FooBarWidget|2 years ago|reply
At the same time, the ecosystem can feel messy and overwhelming. There are multiple @Nonnull annotation libraries and it's not clear what the difference is. There are many different testing libraries (some TDD, some BDD) but it seems everybody picks a different one. There are multiple advanced concurrency libraries and concepts, but again not much consensus and everybody uses a different one. Lots of deprecations all over the place.
It's even worse in the Jenkins-specific ecosystem where nearly every plugin uses some deprecated aspect of either Java or Jenkins, and hunting for what the "right" way is supposed to be is a bit of a daunting task.
There are like 20 different JDKs, not sure why.
On the other hand, startup time and memory usage still seems to be a big issue. Starting Jenkins takes forever even with JVM in client mode and with bytecode verification disabled. And when started it uses a huge amount of memory. To date I've never seen one non-hello-world Java app that isn't like that.
[+] [-] pharmakom|2 years ago|reply
[+] [-] the-smug-one|2 years ago|reply
No. Go does containerized microservices better because of its lower memory footprint. If you can use GraalVM then that might not matter.
[+] [-] moring|2 years ago|reply
[1] https://www.reddit.com/r/rust/comments/xrrjec/virtual_thread...
[2] https://www.open-std.org/JTC1/SC22/WG21/docs/papers/2018/p13...
[+] [-] BenoitP|2 years ago|reply
Also, the cases studies in that paper are platform-level when I believe the language has to be involved; as a runtime has more information when resuming to a suspension point. The paper even acknowledges Go as a successful implementation although with C-compatibility call overhead caveat. In the Java world, the vast majority of programs stay in the Java language. So I'd say that paper would list Loom as the best implementation (And maybe revise their recommendation. It'd be useful to have that author's opinion of Loom in 2023)
[+] [-] dfox|2 years ago|reply
[+] [-] ddorian43|2 years ago|reply
[+] [-] Barrin92|2 years ago|reply
[+] [-] invalidname|2 years ago|reply
[+] [-] noelwelsh|2 years ago|reply
[+] [-] whoisthemachine|2 years ago|reply
[+] [-] andix|2 years ago|reply
[+] [-] francasso|2 years ago|reply
[+] [-] sys_64738|2 years ago|reply
[+] [-] layer8|2 years ago|reply
What is the problem here? Just the per-virtual-thread memory consumption by the variable when it is used (which would be expected)?
[+] [-] jmull|2 years ago|reply
Or is this just an incorrect headline?
[+] [-] DeathArrow|2 years ago|reply
[+] [-] plumarr|2 years ago|reply
I don't think that it's the case for C# task, which also appear to be an higher level construct.
[+] [-] cutler|2 years ago|reply
Java verbosity is alive and well.
[+] [-] vkaku|2 years ago|reply
I guess that will be the biggest impediment to adoption of new features.
[+] [-] alkonaut|2 years ago|reply
[+] [-] jillesvangurp|2 years ago|reply
Funnily enough, the first versions of Java did not support OS threads and only had green threads for a while. Supporting real threads was a big deal at the time as it allowed you to use more than 1 processor. Of course, processors were single core at the time and most computers only had one of those anyway. Java 1.1 laid the foundations for proper multi threading and green thread support was eventually removed with Java 1.3. With Java 1.5 we got the java.concurrent package which enabled doing more complicated things with locks and other synchronization primitives that were a bit less primitive & brittle than using the synchronized & volatile keywords. That includes implementing green threads on top of real threads. Which is what frameworks like vert.x and others have been doing for ages.
So, in a way we're coming full circle here with virtual threads re-using the thread API, which in turn reused the original green thread APIs in Java 1.0.
[+] [-] killerstorm|2 years ago|reply