9 ms·
I’ve been out of the Java scene for a really long time, but will be coming back to it soon. I’m curious - these performance issues described here, are they inhe
by pseudoramble 2y ago
I’ve been out of the Java scene for a really long time, but will be coming back to it soon. I’m curious - these performance issues described here, are they inherit to how Java itself? Is it baggage from Spring/Boot? Are there ways to get more bang for the buck with some careful choices in a system like this?
The closest I’ve done to Java recently is C#, which I think may have similar challenges, but overall didn’t seem quite as bad if you avoided lots of framework extras. It also wasn’t something I was digging into deeply though, so perhaps I’m mistaken.
- xxs 2y agoSpring by far - Spring is effectively a build tool running at run time (scanning, enhance, code generation, etc.) - most of it is just startup as JVM does a decent job at optimizing the cruft. In most cases the boot times don't matter, though - at least for most people, esp. when it comes to production. (it's mostly developers time wasted) I have some personal experience optimizing Spring to record previous runs and effectively cache resolution and code generation, etc. for massive boot latency improvements. Never got around contributing it back, though (not a real spring user)
- jamesfinlayson 2y ago> most of it is just startup Yep, I have some Spring code in AWS ECS and it hits 100% CPU usage on start-up before dropping back to 1.5% when idling (this is with 1 vCPU I think). But yeah I remember reading one of the Spring devs say that some (a lot?) of the runtime reflection could be done at compile time but isn't.
- xxs 2y ago> Spring devs say that some (a lot?) of the runtime reflection It's a lot more than reflection, if it'd have been reflection alone - it'd be markedly better. (and yes, lots and lots can be optimized). Spring effectively: scans the classpath for resources - that includes jars, file system loads every single class matching the scanned directories as a byte array parses it in java (not by JVM) to check what annotations it has (it doesn't load the classes actually) builds dependency tree enhances the previously loaded byte arrays, i.e. generates different byte code loads the newly enhanced classes and create instances (usually through standard reflection) makes calls like PostInit (life cycle) in some cases it uses the standard java reflection to set fields/call methods; in lots of cases java reflection is generating (and loading) a new class (byte code) to carry the process all the steps above can be recorded on run time (or be a step in the build process) and let the JVM just load the classes organically. as for the 100%, spring initialization is mostly single threaded - so likely you dont have many cpus dedicated to the java process. (or you meant just a single core 100%)
- jamesfinlayson 2y agoAh okay - yeah I'm not across the internals at all. And yeah I assume it's a single core, but definitely a heavy start.
- trallnag 2y agoThis makes recycling nodes, be it ECS or Kubernetes nodes, that run tons of Spring apps, a huge pain.
- voidfunc 2y agoYou can write clean beautiful Java and make it perform beautifully if you're careful with what you pull in for dependencies... but then you're not really using Java to its full potential as an ecosystem for basically building massive line of business apps and services that you can spin up everything from junior to offshore to 30 years of experience developers. The tradeoff for this is performance and it's a thing a lot of companies are willing to trade off on. Things get messy the moment you involve large frameworks, reflection, ten different approaches to parallelism etc. There's also the problem of code base evolution in Java. Code before JDK-8 feels different than code after it and there's a lot of Pre-JDK 8 code out there in the wild. And that's just it at the end of the day... it's a cultural difference in the ecosystems.
- gred 2y agoMy experience with very simple Jersey web apps is ~1.5 seconds to start up. Much less than his reported ~8 seconds with Spring Boot, but still not in the 100 ms range he reports with Go. I assume one second or so is about as low as you can go with a mainstream Java framework without AOT, though I'd be happy to be corrected.
- maccard 2y agoI worked on an app in kotlin a while back, and am currently working in a dotnet app. We can run our entire unit test suite faster than the JVM started up on that project. Also, 8 seconds is quick in my experience for Java - I’ve seen more like 15-30
- nelup20 2y agoYou can do AOT compilation with GraalVM to reduce both startup times and memory usage, but then you don't have the JIT.
- e44858 2y agoI think this upcoming feature will let you have both AOT + JIT without needing GraalVM: https://wiki.openjdk.org/display/crac https://wiki.openjdk.org/display/crac
- tomohawk 2y agoHaving done a lot of Java and Go, Go has much better mechanical sympathy between the language, libraries, and vm than Java does. The JIT GC in Java are marvels of engineering, but they have to be. As an example, in Java, everything is a pointer, so pointer chasing all the time, which is not good for cpu cache, etc. In Go, there is first class support for composition. The other main adjustment, if coming from Java, is reduced cognitive overhead. It usually only takes a week or two for an experienced Java dev to be reasonably effective in Go, but it takes a few months to break the mental habits of overthinking everything.
- codr7 2y agoThere's nothing forcing you to write EE style code in Java though, or depend on frameworks written in that style.
- re-thc 2y ago> As an example, in Java, everything is a pointer, so pointer chasing all the time, which is not good for cpu cache, etc Strictly speaking that's not true. It's everything is a pointer in theory to make it easier to reason with and JIT / JVM optimizing in the background. There are primitive types and there are lots of tricks in the JVM e.g. escape analysis that places objects on the heap/stack etc.
- PhilipRoman 2y agoDespite all the advances in JIT, I've literally never seen it correctly optimize a HashMap<Integer> (happy to be proven wrong). Hopefully the renewed focus on value types can finally bring some sanity.
- neonsunset 2y agoIn C#, all struct generics are monomorphized and struct-based abstractions are zero-cost :)
- jeroenhd 2y agoIn my experience, if you write Java like you'd write to, you'd probably get similar performance. Maybe AOT performs a bit better, maybe it doesn't because it disables live optimizations. Java doesn't lend itself well to writing Go style code, though. You quickly end up writing very "Java" Java code. That has some advantages (code deduplication, less boilerplate) but also downsides (allocation overhead, performance impacts). In terms of Java vs C#, I don't think you'll notice too many new issues. If you opt into using heavy frameworks like Spring Boot to solve annoying problems for you, you'll see RAM usage increase massively, but it'll also take care of a lot of annoying grunt work for you. Startup times overall are pretty similar. The biggest downside in my experience is the lack of nullable types (@Nullable annotations are a poor substitute) and some syntax improvements that are new to Java but have been part of stable C# for many years. As long as you stick with small libraries and avoid writing too much ObjectInjectorFactoryProducerResolver style code, performance of the JVM is fine. All of that goes as long as you're able to use modern toolkits. If you're stuck in the world of Enterprise Java with Oracle Java 8, you'll notice old Java's shortcomings a lot more.