Hacker News new | ask | show | jobs
by jpgvm 1002 days ago
In what world is Go more reliable or faster than Java? Especially modern Java (or other good JVM languages like Kotlin).

It compiles faster but that is about it. Which is a good thing too because it's verbose AF and heavily reliant on code generation even after generics shipped so that compiler lines/s actually matters.

The only knock you can put on Java for reliability is null-safety but lets be real, nil pointers, bad slice initalisation, etc are 10x worse in Go especially these days with @NotNull and powerful editors like IDEA stopping you from doing dumb things. Or simply using Kotlin which is non-nullable by default.

That and all the things Go is good at Java is simply better at. (especially once Java 21 ships in the next few days).

- Low pause GC? ZGC has Go beat, not just on pause times but on throughput and heap size scalability up into the terabytes. - Non-blocking code with synchronous coding style? Virtual Threads are much much better than goroutines. - Channels? ArrayDeque and friends are faster and have a lower learning curve.

Maybe the only area I think I prefer Go to JVM-land is the good parts of the stdlib. Go has lots of shit stdlib cough collections cough but there are some real gems. Namely the x509 and ASN.1 libraries are absolutely top notch.

Learning curve is also lower ofcourse, it's a very very simple language which is good for beginners and those taking their first steps out of JS/Ruby/Python into static types, pointers, etc.

Anyways, my real point was Java is easily faster and more reliable than Go and it seems pretty insane to reach the opposite conclusion if you have been on top of developments in both languages.

11 comments

Go's GC is much more naive than modern Java GCs, and the codegen is not terrific.

That said: A few design decisions in the Java language (that are only slowly getting fixed) harm performance:

1. "Everything is virtually dispatched" reduces the ability to make inlining decisions sensibly. 2. Escape analysis gets harder (and less effective), leading to moving fewer variables onto the stack. 3. The poor support for allocating compound data types on the stack.

All of these conspire to create many more heap allocations than comparable Go code, so even if the GC is much better, the amount of work it has to do is much bigger.

Achieving any form of data locality in Java is also hard because it's really hard to nest structs.

The Java design shows that the memory wall wasn't a thing yet when the language was designed...

Things are slowly getting fixed, and there's a lot of interesting off-mainstream stuff that can be done to get more perf in Java-Land (Azuls JVM, Graals AoT etc) - but the strange thing is that Java does pay for some outdated design choices while Go performs pretty well despite a very simplistic implementation...

> "Everything is virtually dispatched" reduces the ability to make inlining decisions sensibly.

you can use "static" keyword?..

> Escape analysis gets harder (and less effective), leading to moving fewer variables onto the stack.

does go have the same idea of escape analysis with benefits and issues?..

Static == final?
static methods, they won't be virtually dispatchable, and will be potentially inlined.
That is a good point, but soulbadguy was pointing out that if you make a (non-static) method "final" then you can't override it in a subclass, which should have the same effect, without having to change the semantics by making the method "static".

That said, if there are no actual subclasses of a given class, the JVM will know that, and should be able to inline functions as necessary. That is complicated quite a bit by the fact there is no exhaustive list of which classes are available (the JVM only knows which classes it's seen), and that new classes can be added during the runtime (e.g. Tomcat will load a WAR at startup which will add new classes, or Maven might download a plugin and execute code). But if that _doesn't_ happen, i.e. you load a class and the JVM never sees a subclass and it decides to optimise that part of the code then it will inline what it can (and un-inline it if a subclass shows up later.)

I also read in internet that JVM inlines all method calls by default, and fallbacks to virtual dispatching if finds out it is needed.
> Java is easily faster and more reliable than Go

I work in Java for my day job and I disagree.

1. Cold starts for Java serverless (lambda) are worse than scripting languages

2. For everything else, Java uses 5-10x more memory than Go. The memory overhead difference is really noticeable while the CPU difference is often similar.

1. Ever thought about using class caches, AOT compilation e.g. OpenJ9, GraalVM,...?

2. Most of the time, a signal that the wrong algorithms and data structures are being used

Amazon isn't rushing to rewrite their infrastrure from Java into Go.

Why would they rush? Customer will ultimately pay for infrastructure not Amazon.
Hardly different from what many Go folks are paying as well.
> Cold starts for Java serverless (lambda) are worse than scripting languages

Id be hesitant to use any VM dependant language in serverless... I mean I love elixir and built my startup on it but I'd never consider trying to run it as a lambda function for the same reason. You're fighting against the grain.

> Cold starts for Java serverless (lambda) are worse than scripting languages

I've run plenty of Java applications in Lambda and they are pretty fast (like done in a few minutes fast). I've yet to try SnapStart which can make those workloads even faster.

I have successfully written entirely serverless APIs in Go. It’s not a great fit for everyone, but was for our use-case. Cold-starts became a non-issue compared to Java.
1. Yeah but serverless is completely unnecessary. JVM rewards you for long running processes. CRaC etc are there if you really care about that though.

2. If your Java code is using 5-10x more memory for the same task you are doing it wrong. Java objects do have higher overhead vs Go structs but not an order of magnitude. You could also be tuning the heap wrong, Java will use all the memory you give it and it won't be quick to give it back unless you tell it that it should.

> serverless is completely unnecessary

I don't care for lambda either, but you can't just hand-wave away a huge portion of the current web. Devs use lambda, tens of thousands of them in fact. Java has an expensive startup and it affects them.

> you are doing it wrong

Perhaps, but I've seen it over-and-over again. Maybe you're extra talented, but regardless all us regular people seem to keep producing systems that are much more expensive to run with Java.

I didn't handwave it away. I alluded to there being existing solutions CRaC, AoT, GraalVM, etc. There are ways to entirely eliminate cold-start as a concern if it's important to you

My point was you don't ever -need- serverless. You might like it for whatever reason but it's completely unnecessary when long-lived processes can get the job done and have so many benefits. JIT of course but also connection pools, memory-local caches etc that all much much more important than "being on serverless" which is mostly a net-negative once you look at the picture more wholistically.

I also think it's wrong to go "hey I want to use serverless, what works well with serverless?" insted of going "what is the best tool to solve my problem? Ok now what is the best environment for that to run?".

I feel that you've ignored all the reasons why folks feel that Go is more reliable and faster than Java.

Fighting with the JVM - even in modern versions - is an absolute pain. The practical reality of using _any_ JVM language is wasted memory footprint, classpath issues, weird dependency graphs because a mega-apache-project was used to solve a simple problem, etc. Never mind getting basic things like RPC clients over TLS (fighting with openssl and keystores? in 2023? really?) working.

> my real point was Java is easily faster and more reliable than Go and it seems pretty insane to reach the opposite conclusion

Citation needed. Disagree in practical observation.

As if there aren't plenty of blogs about fighting Go runtime, some of them giving up, and doing rewrites in C++ and Rust.

https://discord.com/blog/why-discord-is-switching-from-go-to...

Oh I am well aware of Java's warts and you hit a few of them but they are clearly -not- performance or reliability.

Yes there are lots of things that suck about Java and the JVM but you can't in good faith argue that it's slow and unreliable.

Once it hits full stride, sure. While not a Java dev, I work with plenty of Java enterprise systems (looking at you, ElasticSearch, and probably big chunks of AWS).

One appreciates the engineering durability of it all.

I do refer to the healthy startup time for the cluster as the "Java pause".

Perhaps that's a cheap shot, blaming the language, given that we're getting rich logging and a predictable approach that makes things boring in a good way, but there you have it.

Yeah the JVM definitely makes tradeoffs that adversely affect startup time, things like autowiring DI, etc all contribute to this - it's not just the runtime. It can be slightly annoying so periodically effort is invested to make it fast again, see recent Spring Boot startup speeds which are pretty damn quick now but can be made super fast with static wiring. (all without needing to resort to CRaC or AoT)

I was really getting at the fact that you can't reasonably call something unreliable or slow when it's probably powering some of your most loved (and performant) distributed systems that your applications rely on - doubly so if you are running on AWS or GCP. If it was either unreliable or slow, let alone both it wouldn't be fit for purpose.

Having worked on one of the largest golang code bases on the planet, I agree completely. I would always think to myself how much simpler and more reliable the programs I saw would have been in Java. But hype is hype I suppose, not to mention so many unsubstantiated claims.
Could you please tell a bit more about your project, I'm very curious.
It was a monorepo, with many hundreds of programs in it. I worked on a handful of them, but I was able to see what other people were writing.
> these days with @NotNull and powerful editors like IDEA stopping you from doing dumb things

I'm so sick of these half-arsed "features" that somehow get adopted into Java code bases.

Stop what you're doing and go write a unit test for @NotNull (or @NonNull). Watch the null value happily bypass those "checks".

Or, use (or write) a notNull() method (I happen to use commons-lang3) and watch it actually work.

I agree - but in a comparison of Java vs Go - in Go there's no support for checking nulls at all, which is surely worse than Java's situation.

I mean you can literally assign nil to a "func" type, and then call that func and it'll panic at runtime. Neither the compiler nor the JetBrains IDE will warn you about that, and there are no @Nonnull type annotations you can add (even if they're not ideal as you rightly point out).

That literally happened to me last week, just a stupid coding mistake, but took quite a while to find. In Java IntelliJ would have probably warned me about that, and certainly if I'd added @Nonnull annotations.

Here's my last week story. I was trying to debug an NPE, so I added the `notNull(` line in the follwing:

  @PUT
  @Path("/{rest}/path")
  @LogBody
  public Answer controllerMethod(
    @PathParam("param1") UUID param1,
    @ApiParam(required = true, value = "body") @NotNull @Valid Body1 body1) {
        notNull(body1, "Annotations are a joke. This null value made it through reqired=true, NotNull, and Valid");
        return service.serviceMethod(param1, body1);
  }
And sure enough that line was hit:

  2023-09-08T07:34:34,511 ${env:K8S_POD_IP}  WARN  org.eclipse.jetty.server.HttpChannel - /api/v3/service/rest/path
    javax.servlet.ServletException: javax.servlet.ServletException: org.jboss.resteasy.spi.UnhandledException: java.lang.NullPointerException: Annotations are a joke. This null value made it through reqired=true, NotNull, and Valid
      at org.eclipse.jetty.server.handler.HandlerCollection.handle(HandlerCollection.java:162) ~[jetty-server-9.4.44.v20210927.jar:9.4.44.v20210927]
      at org.eclipse.jetty.server.handler.StatisticsHandler.handle(StatisticsHandler.java:179) ~[jetty-server-9.4.44.v20210927.jar:9.4.44.v20210927]
      at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:127) ~[jetty-server-9.4.44.v20210927.jar:9.4.44.v20210927]
I just don't know what is to be done at this point. I don't know how the median Java programmer doesn't walk away after getting burned by this one.
I get what you mean, it's certainly confusing.

There are various different @NotNull so I'm not sure which you're using, some (but not all) of them are more compile-time contract type things. So if you called that method with null then you'd get an error from a code analysis tools. But that @PUT method is called by the framework at runtime based on an incoming REST request, there's no line in your code which calls the method with null, therefore nowhere for the code analysis tool to see the error and report it.

So yeah, all really confusing, and doesn't always do what you want. Nevertheless, I feel that at least there is a chance to use them and they do sometimes find bugs, which is better than Go's situation of having no way to specify that a method shouldn't be called with null. So if you do call such methods with nil in Go, even in a way that could never work, and which would theoretically be detectable at compile time, you just always get a runtime crash.

Perhaps another factor the article was referring to is that Go compiles ahead of time to static binaries. Java is typically JIT compiled which means extra overhead and unpredictability at runtime as code warms up.
> the only knock you can put on Java for reliability is null-safety

FWIW, nullity control has been added to the Valhalla project, so it might be fixed going forward.

https://youtu.be/Ma0NtbG0mHY?feature=shared&t=1076

The usual Java bashing from Go folks, meanwhile Google keeps mostly using Java, Kotlin and C++, with only a couple of key projects using Go, like Kubernetes or Google downloader.
I was a witness to a decision on whether to migrate an old Java project to Go. A proof of concept (a Go rewrite of a core part of the engine) convincingly showed that the Go version would be somewhat more performant, would have vastly better startup time, and would have a smaller and simpler codebase, since the Java version's caches and latency-hiding complexity would not be needed.

The decision made was to stick with Java.

Why? Because rewriting the long feature tail of the Java version in Go and ensuring bug-for-bug compatibility with the Java version would have taken multiple engineer-years. During the rewrite, from the perspective of the project's users, development would have stalled. It made more business sense to instead use those engineer-years to implement some new features that users were demanding. And after all, while the Java version's performance wasn't amazing, it was acceptable.

Golden question being what would a rewrite in Java achieve.
Java has been awesome for a long time so it’s boring to write blog articles about it and it doesn’t signal any hipness on your resume or GitHub profile. The problem with Java is really…almost all the Java code that has ever been written.
I work in cloud and every single one of our new projects is in go
Also work in cloud and we only do .NET, Java, and node, coupled with C++ when needed.

Anecdotes.

That's surprising to me, I don't think .NET or node are approved languages at Google. Are you part of some acquisition?
I work in cloud, as in, cloud native development, targeting AWS and Azure based infrastructure.
Ah got it. I was talking about Google Cloud
This particular thread is about golang use in google. So your comment doesn't appear to apply.
It is called Go, and whatever.
I’ve heard three main arguments in favor of using Go. First, that it has a standard library that is world class for building a worldwide ads serving network. If your use case is that or something that’s technically similar, then it’s great. Second, Go has extremely limited facilities for abstraction. Frankly, I dislike that about it, but the argument is that forcing programmers to use a quite limited set of abstractions makes code more readable. I have my doubts, but the argument isn’t prima facie absurd. Third, it produces statically linked binaries that you can copy and run on any machine of the same architecture.
> that it has a standard library that is world class for building a worldwide ads serving network

What does the golang standard lib have for that use case that Java doesn't have, or do better?

> but the argument is that forcing programmers to use a quite limited set of abstractions makes code more readable

There's a good balance. Sure you can probably argue that Scala opens the door for many different ways to write a program. But golang took the extreme opposite approach, resulting in very verbose code that is difficult to decipher. Java has a very solid middle ground here.

I agree that Java definitely strikes a middle ground. It has enough features you can write code in the ways you want (generics, first class functions) but not so many you can create unintelligible messes (implicits).

I generally tend to think of Java is "truly general purpose" because it has enough speed/scalability to implement a database while being generally easy enough for beginners to learn and providing enough scope and structure for building big line of business applications (where only really C# competes). Outside of embedded (excluding smart cards) and the browser mono-language you would be hard pressed to find a field that you can't use JVM to effectively solve a problem.

Java I would still call verbose, it's more verbosity than I tolerated when I was greener but I have come to appreciate a certain amount of ceremony isn't necessarily bad - if it helps to efficiently communicate the intent of the programmer.

I guess what I am trying to say is Java is easy to read, easier even than Go IMO because Go's verbosity is in the wrong places, i.e error handling, loop iteration ceremony, struct construction, etc. while Java's is more inherent to class/application structure which does more to communicate intent.

One thing I did like about Go though from a readability perspective though was their take on visibility. By eliminating the public/private/internal visibility modifiers as seen in JVM code it becomes simple at a glance no matter where you are in the codebase if a method is part of a public API or not just based on it's capitalisation.

> Java I would still call verbose, it's more verbosity than I tolerated when I was greener but I have come to appreciate a certain amount of ceremony isn't necessarily bad - if it helps to efficiently communicate the intent of the programmer.

In my experience the trick with Java is to use a purpose built editor like Eclipse or IntelliJ. Once you've achieved competence with an appropriate tool the syntax kind of fades from your attention and you focus on the important parts. Kind of like how experienced Lispers using an appropriate editing mode don't really pay much attention to the parentheses.

100% agree.

It was tough coming from a VIM only workflow to IntelliJ but I am really glad I toughed out the transition.

The power is in how all the tooling comes together but it requires a certain investment to get there.

Yeah those are all good reasons to consider Go.

I think the static binary argument is a bit flat though.

With Java I don't even need to compile for multiple architectures, I can copy a fat-JAR onto any machine with a JVM and it will execute. i.e an artifact with exactly the same hash and no worries about Mac OS X DNS resolver weirdness or CGO nonsense.

but now you have the nonsense of dealing with jvm versions and choosing the right one
That is fairly overblown, especially post Java 9 (2017).

Generally speaking you just target a minimum version if you want to not distribute the JVM itself but these days most Java software is backend software, most of which is containerized, packaging the runtime with the code itself.

But bad news if you actually need a decent interface to C libraries.
> Non-blocking code with synchronous coding style? Virtual Threads are much much better than goroutines.

That's going to need some justification. I work with Java and Go. I haven't worked with Virtual Threads yet but my understanding is Go's approach to goroutines and Java's approach to Virtual Threads are pretty much equivalent. (Channels, however, are quite different.) Let me know if I've missed something.

The runtime is similar, both are continuation based under the hood etc.

What sets VirtualThread apart is that all the support you get from the Java Stdlib. i.e ExecutorService and friends and now ThreadGroup with the improvements in Java 21. Not to mention the structured concurrency features that are planned to complement them. Another key difference is how much easier they interact with interrupts for cancelation without having to resort to context, channel and select hacks.

Compare this to Golang where this is very little tools for managing groups of goroutines. You have the sync package, specifically WaitGroup which get you some of the way there but it's still massively behind the equivalent JVM stdlib support for real concurrency. JVM has equivalents to everything in the sync package but in addition to that it for instance the collections library which contains ConcurrentHashMap, which naturally works perfectly with VirtualThreads vs Golang which map is not goroutine safe and you have to go outside the stdlib to get concurrent safe structures.

So yeah, if you are working in Java already you should checkout VirtualThread for your i/o intensive needs, especially if you were using Go for those tasks before.

Embedded devices?
Older versions of ARM had hardware support for translating and executing Java bytecode, see Jazelle.