Once I did some experiments at programming in Java using only sun.misc.Unsafe for a memory access: https://github.com/xonixx/gc_less. I was able to implement this way basic data structures (array, array list, hash table). I even explicitely set a very small heap and used Epsilon GC to make sure I don't allocate in heap.
Just recently I decided to check if it still works in the latest Java (23) and to my surprise it appears - it is. Now, apparently, this is going to change.
The planned releases for removal are listed on the JEP.
If they fail to provide parity, people will do like with modules, keeping holding to older Java versions longer than they should, use the escape command line options if available, turn back to JNI (the opposite this is trying to achieve), or move to other stack if they aren't that dependent on Java, e.g. ongoing Kafka ecosystem evolution.
I think this is an enormously bad idea. The whole point of using sun.misc.Unsafe is because you need to do something with basically no overhead, regardless of safety. In my opinion, they should just modularize it such that users must enable access to it explicitly. The MemorySegment API is nice, but sometimes you really want to skip bounds checking in a cross platform way.
<<
Over the past several years, we have introduced two standard APIs that are safe and performant replacements for the memory-access methods in sun.misc.Unsafe:
java.lang.invoke.VarHandle, introduced in JDK 9, provides methods to safely and efficiently manipulate on-heap memory: fields of objects, static fields of classes, and elements of arrays.
java.lang.foreign.MemorySegment, introduced in JDK 22, provides methods to safely and efficiently access off-heap memory (sometimes in cooperation with VarHandle).
These standard APIs guarantee no undefined behavior, promise long-term stability, and have high-quality integration with the tooling and documentation of the Java Platform (examples of their use are given below). Given the availability of these APIs, it is now appropriate to deprecate and eventually remove the memory-access methods in sun.misc.Unsafe.
>>
As the comment you replied to indicates, both of those APIs perform bounds-checking. In certain tight loops, this can add up to quite a bit of overhead [1]. However, it's not documented, but if you really know what you are doing you can convince the JIT to elide the bounds checks for MemorySegments [2].
If you really insist on shooting yourself in the foot, you can do MemorySegment.ofAddress(0).reinterpret(Long.MAX_VALUE) to obtain a MemorySegment for the lower half of the address space of your process. Obviously, you set the address to Long.MAX_VALUE for the upper half.
You might be correct, but it's not obvious to me unsafe is really faster. I recall reading that using unsafe invalidates most of the other optimizations because it's opaque to the JIT what you are doing. Also, if there are BCE opportunities, I feel pretty confident OpenJDK will add them, rather than being unsympathetic.
> the story hasn't changed an awful lot since 2004
Um... yeah it has. For starters, hotspot wasn't even a part of the JVM at that point. But further newer JVM additions like the enhanced for loop eliminate a ton of conditions where someone would run into bounds checking. Doing a naked `a[i]` is simply not common java code.
The JVM is far more likely today to remove the bounds check all together than it ever was in 2004.
It's a writing error on my end (clear since I qualify the percentage afterwards), so I think focusing on it distracts from the point (which is why I say it's pedantic).
> Doing a naked `a[i]` is simply not common java code.
It is extremely common in performance sensitive code,
1) graphics & rendering
2) networking
3) buffers
> But further newer JVM additions like the enhanced for loop eliminate a ton of conditions
> The JVM is far more likely today to remove the bounds check all together than it ever was in 2004.
There are more comments in this thread that clarify further, but Java is very commonly unable to eliminate bounds checks. You can test all of these things yourself with a quick benchmark - don't take my word for it! The JIT is not as great at this as common rhetoric claims it is.
2004 had java 1.4 + hotspot. 1.5 (w/ generics and stuff) was about to be released. Hotspot was there, so were the Direct Buffers.
Also accessing byte arrays and direct buffers is extremely common - if you do just "business logic" jazz - it does not happen, though. However, every hashmap needs that, pretty much each hash lookup is a direct a[hash]
There are tricks you can do, but it introduces load on the TLB. You basically restrict accesses to 32 bits of memory and isolate that space in virtual memory with its own prefix.
As a non-java developer, this is surprising to me. I always assumed that bounds checks were "almost free" because the branch predictor will get it right 99% of the time. I can see that being not-true for interpreted runtimes (because the branch predictor gets bypassed, essentially), but I thought Java was JITed by default?
Perhaps the paper answers my question, but I'll admit I'm being lazy here and would much appreciate a tl;dr.
(with bounds checks) "Telling the Rust allocator to avoid zeroing the memory when allocating for Brotli improves the speed to 224 MB/s."
(without bounds checks) "Activating unsafe mode results in another gain, bringing the total speed up to 249MB/s, bringing Brotli to within 82% of the C code."
224MB/s -> 249MB/s (11% Brotli compression perf difference just by eliminating bounds checks)
Couple issues with the linked article, the rust compiler has matured a lot since it was written I didn’t look at the benchmark or the code, but many bounce checks are elided if you use iterators instead of array access and lastly, I would want to see ZSTD benchmark written in normative rust.
I don’t doubt that all of those things in the article were true back then, but that was eight years ago. Wow.
Sure, you're not wrong, but my focus was that the benchmark is indicative of the raw performance impact of bounds checks (when they are unable to elided) in a real world algorithm (as opposed to a micro-benchmark).
With that said, convincing a compiler to elide bounds checks (especially Java's JIT compiler) is a hugely frustrating (and for some algorithms futile) task.
It could be an argument that bounds checks make up a small percentage of total application performance. However, I've profiled production Java servers where >50% of the CPU was encryption/compression. JDK implementations of those algorithms are heavily impacted by (and commonly fail to elide) bounds checks.
>With that said, convincing a compiler to elide bounds checks (especially Java's JIT compiler) is a hugely frustrating (and for some algorithms futile) task.
Adding explicit checks does work to a certain degree but it can change with the compiler, and it requires to keep checking the generated assembly - not fun (no unsafe, either but still)
Some 12-13y back (time does fly) Cliff Click (hotspot architect) had a series of blogs on optimizations, incl. the lattice checks (i.e. within bounds). Was quite insightful. It was called: "Too Much Theory" [0]
>What is optimized today may not be tomorrow.
Exactly. (Also most developers will have exceptionally hard time maintaining such code)
I understand where you are coming from, but without comparing the generated assembly, we are comparing an implementation of bounds checks. I think we should have a bounds check instruction and operates concurrently.
I should play around this with this using a couple RISC-V cores.
Bounds checks are not 'free' but predicted ones are cheap. There are way to convince the JIT to remove them - in cases it can prove the index doesn't exceed the byte array, so explicit checks with the length of the array may improve the performance.
The stuff gets worse with DirectByteBuffers as the JIT has to work harder. Unsafe allows to 'remove' all bound checks, but it may prevent some other optimizations.
I see this mentioned a few times in this thread, but I haven't experienced this in practice (and I've written a lot of unsafe in Java). Are there any examples of this?
Perf impact due to bounds checks can be experienced in statically compiled languages as well (like Go/Rust). Although branch predictor improvements likely narrow the gap, they do not eliminate it.
Code cache is not completely relevant afaik - you can easily replicate in micro-benchmarks.
I don’t think it’s relevant here, the JIT compiler can do the same optimizations here.
If the branch predictor can basically 100% guess correctly (which will be the case in any correct program), it should not have any additional cost, besides taking up place in i$, so I would assume that that is responsible for the difference.
In terms of performance: I realize that this is a somewhat "toy" issue, and it's a sample size of 1, but for the currently ongoing "One Billion Row Challenge"[1] (an ongoing Java performance competition related to parsing and aggregating a 13 GB file), all of the current top-performers are using Unsafe. More specifically, the use of Unsafe appears to have been the change for a few entries that allowed getting below the 3-second barrier in the test.
Submissions to this challenge are also hampered by the lack of prefetch intrinsic, vpshufb intrinsic, aesenc intrinsic, and graal's complete lack of vector intrinsics. As a total outsider to the Java ecosystem, it makes it seem like nobody in a place to make changes really knows or cares about enabling high-throughput code to run in the JVM.
Also, graal can do some vectorization, and definitely has some libraries with vector intrinsics, e.g. truffle’s regex implementation uses similar algorithms to simdjson.
I think I was fairly specific? There's no way for a user to do vpshufb, aesenc, or a prefetch instruction. One would expect the former two things to be in jdk.incubator.vector, where they are not present. The last thing was explicitly removed, here's a link to one part of the process of it being removed: https://bugs.openjdk.org/browse/JDK-8068977
Code that uses jdk.incubator.vector does not actually get compiled to vector instructions under graal. This is why the top submission that uses jdk.incubator.vector is the only top solution that does not use graal.
I don't doubt that "similar algorithms to simdjson" may be used, but simdjson uses instruction sequences that are impossible to generate using the tools provided.
I mention these instructions because vpshufb comes up a lot in string parsing and formatting, because prefetch is useful for hiding latency when doing bulk accesses to a hash table, and because aesenc is useful for building cheap hash functions such as ahash.
For my solution (https://github.com/dzaima/1brc, uses jdk.incubator.vector significantly), switching all array reads/writes to identical Unsafe ones results in the solution running ~100x slower, so there's certainly truth to Unsafe messing with optimization - I suppose it means that the VM must consider all live objects to potentially have mutated. (as for the sibling comment, indeed, lack of vpshufb among other things can be felt significantly while attempting to use jdk.incubator.vector)
The cost of bounds checks, which is already low, could be reduced further the vast majority of uses with even more enhancements to the compiler's inlining decisions, so we're talking about an API whose value is already small and constantly declining. However, those who think they absolutely need to avoid bounds checks could, indeed, use a flag to access the JDK's internals. We don't think that such a small benefit (especially when integrated over all users) merits a supported API.
Thus bringing us back to the same unsafety with extra steps and glue required; what did we gain from this?
Edit: Unless you FFI to Rust / Ada / formally verified C, in which case perhaps we can claw back the performance and safety at the cost of a lot more dev work.
But more “distance” between the two might be better, like, copying some value-like objects and doing the operation on that and copying back the result is much safer, than passing pointers between the two worlds. And this change might push the balance a bit to this side.
You don't need FFI -- you could reach for JDK internals if you conclude you must; all that requires is a flag. What we gain from this is that the vast majority of programs using Unsafe will gain safety with the new supported APIs and will not lose performance.
Also, FFI to Rust will buy you nothing. Safe Rust performs the same bound checks as Java while unsafe Rust would be just as unsafe as using JDK internals. If you want to prove the safety of unsafe code in some other unsafe language, you might as well do the same for unsafe Java code using JDK internals (it would probably even be easier).
This is incorrect. Native calls whether it's via JNI (even with CriticalJNINatives) or Panama still have a transition cost formerly referred to as gc locker. I'm in a field where performance is number one requirement. Removing unsafe is a terrible idea.
Yes, I have no doubt about it. You might be stuck in 1990s perception of Java like a good portion of HN commenters who haven't had any real experience with it in the last 10-20 years.
You mean expedite whole chunks of logic to a different language, then make your build process much more complicated by having to maintain a completely new ecosystem and compiler infrastructure and make testing more cumbersome?
In the very very rare case you might need it, than I guess, yeah.
But if it turns out to be a real problem, it might still be possible that they will add a way to disable checks with the foreign memory API. It would actually allow for a better user experience, you could do “debug” builds with extensive checking for correctness, which could be disabled via some flag.
Yes, 10% would be massive, but that's nowhere near the case. The vast majority of Unsafe-using programs would see no or negligible performance degradation and without the unsafety of Unsafe. The 0.01% of Java programs that would see a performance degradation of 10% or more will still be able to get it all back, if they feel it's absolutely needed at the expense of safety, by using JDK internals. That's a win for everyone.
Citation needed. You make a lot of claims without evidence.
Dropbox's experience https://dropbox.tech/infrastructure/lossless-compression-wit... shows a >10% performance difference caused by bounds checks in a similar context for a real world application. And before you say it: the Java JIT is not capable of optimizing bounds check elimination more than LLVM at this point in time. The JIT is not magical.
I think you misunderstand. There are cases where the performance hit would be significant, but there is absolutely no indication or reason to assume they're widespread. The JDK itself has switched away from Unsafe years ago (mostly except for cases it's not possible, but not due to performance, but technical reasons specific to the JDK alone) and performance overall has largely improved. In the special cases where there might be a performance hit, a single flag can take care of that, but we're concerned with improving the safety and performance of the ecosystem as a whole. If there's something that improves the average but harms some individual cases, we hide it -- as in this case -- behind a flag. Everyone who needs to be convinced that on average the benefit outweighs the cost has been convinced. It is those who think otherwise that would need to make a convincing case that on average Java applications will be harmed rather than helped.
It will be the difference between using Java, or the better suited low level coding features from C#, Go, Rust, assuming library parity for the task being solved.
If after this change, Java is no longer able to compete in "The One Billion Row Challenge", it is a loss for the ecosystem.
The results are significantly behind other languages, in part because of instruction sequences that Java users cannot use. I agree that it's unfortunate to add an additional disadvantage, and this one seems particularly brutal, because people who write automated trading systems commonly use Unsafe to emulate arrays of structs, and MemorySegment is not a good substitute.
Given that automated trading systems are in fierce competition between each other, and thus have a never-ending thirst for tiny performance advantages, maybe the JVM with its emphasis on abstract high-level language constructs and runtime safety isn't the best platform and shouldn't bend itself into unnatural contortions to satisfy this niche?
We've got Rust now, offering both low-level control and safety. No need to take the SUV on the racing track, it is not at home there.
I’m unaware of HFT systems running entirely on a JVM. Do you know of such cases? Safe Rust isn’t fast enough for HFT in my opinion. What kind of HFT?
Amusingly, though somewhat tangential, I observed a rather simple Go implementation (nothing fancy, ordinary channel bound message passing) outperform a fairly sophisticated Rust implementation for the same logic when we were toying with a prototype. But that was at an IO boundary. There are many other variables of course, but an amusing anecdote to share.
I will not pretend to have any inside information, I just picked up some of the related discussions on HN over the years.
I'll easily believe that it can take significant work to outperform Golang (or Java) in Rust where application complexity is high (not just a simple cmd app), they do have an excellent baseline out of the box.
Definitely, microbenchmarks are what’s most striking especially because they seem immediately approachable, as opposed to a full fledged app with a variety of integrations. In that case the quality of the dependencies is much more important to me than any benchmark runs. Great point, totally agreed.
I personally am often quite astonished by the amazing performance Go apps squeeze out of the box, with many efficiency tweaks and tricks we way too often take for granted, which you need to be aware of to come by in C++, Rust, or even Java. But I do also often get disappointed by the sudden inconsistency of that performance in Go after a while, the variance is a bit high, with C++, Rust, and Java runtime behavior being very consistent in general.
Much appreciated indeed. Though I’m unconvinced by that particular piece on Medium. There’s no mention of their purported trading workloads, reads rather like a pile of SEO keywords spiced up by a couple charts and appears more like an attempted product placement for Azul Zing instead of HotSpot Zulu or other distributions. The author doesn’t seem to associate with finance either. I know Disruptor fairly well, it’s been underwhelming.
Yes, Java is often used in HFT, due to one being able to quickly modify the algorithm, while keeping it safe and performant. This is used for mostly the algorithms that have to be fast, but not to the degree of “let’s buy this building which is closer to the bank, because the internet cable length matters”. Do note, that for the latter kind, general purpose CPUs (and thus, programming languages) themselves are not fast enough and is dominated by ASICs.
This is completely backwards. The reason we're now able to do this is that Java's access to low-level coding features has improved so much that the vast majority of programs using Unsafe are now able to get the same functionality and performance using new supported APIs, and without Unsafe's unsafety. That is a huge improvement. Even the small minority of Unsafe-using programs that will see some performance degradation will still be able to get all of it back — if they feel they absolutely need to even at the expense of safety — by using JDK internals.
This is a clear win for low-level coding in Java because today there's no way to tell if a Java program is unsafe as some transitive dependency may be using Unsafe even if it doesn't benefit the program at all. Offering full safety for (nearly) all while still offering maximum performance in special cases is an advantage for Java over other languages.
As long as the deps haven’t all moved to the JDK without Unsafe, I think the ecosystem is tainted by such deps for the foreseeable future.
In case you guys conducted an ecosystem survey, where are we at now? Lots are still running on JDK 1.8, some important deployments are on 1.6 or 1.7, even more are on JDK 11. The transition to JDK 17 has started only recently.
How much more hesitant will this make projects to upgrade the JDK? There’s quite some tearing already due to 1.8, 11, and now 17, particularly with Pivotal EOL support for Spring 2.7 release train. I wish we could wait with it till the transition to 17 is complete.
The JEP says you'll be able to use Unsafe at least until JDK 25. There's surely enough time for libraries to migrate away from unsafe by the time a significant number of the ecosystem is on 25. I don't see why this should have any impact at all on adopting 17 or 21, which are unaffected.
I hope the current state of large organizations staying in Java 8 as long as they can to avoid dealing with modules, is somehow a lesson for how to deliver this change.
Two months ago I did a fresh deploy of Java 8 in production for a SaaS solution, exactly because that is what the product and support team care about.
Staying on 8 has nothing to do with "dealing with modules" (JDK 11 had the exact same accessibility as JDK 8; the JDK's encapsulation wasn't turned on until JDK 16). The reason some need to stay on 8 is because they're using old, non-portable libraries that rely on internals that have since changed. Modularising the JDK has only helped prevent that from reoccurring, and has made version upgrades easier than they've ever been. Not modularising the JDK would not have helped people migrate one iota. The internal changes that broke the old non-portable libraries would still have broken them, only there would have been nothing to prevent this from happening over and over again.
Also, there isn't much new code being written for 8, and there's a whole lot more of Java code yet to be written than the non-portable code still on 8. In fact, even most existing projects are no longer on 8. Sure, if you only look at the pain, there has certainly been some. But if you look at the pain compared to the benefit, the lesson is that it's been a great success, especially compared to other languages (or even frameworks) doing similar significant changes. I think the vast majority of those who've upgraded consider it worthwhile (again, comparing cost/benefit).
Virtual threads, FFM, and the upcoming Valhalla and Leyden are all things that could not have been done without modularisation, and certainly not without significant internal JDK changes, which would have kept the same people on 8 even if there was some way (doubtful and definitely much more expensive) to achieve that without modularisation. We would have been in a far worse place without it: fewer new features and no end to the problem of migration.
While I do totally agree on the necessity of the steps you outlined, I have yet to discover a reliable ETA for Valhalla and Leyden, and Panama, it’s been like a carrot on a stick so far. And I do totally realize that certain consistency bounds must be in place to smoothen the future evolution of JDK, yet the chasm is still there and a quite significant one. I’m sure you are aware. There’s so many things going on at the same time to be aware of in Javaland, it’s getting quite tedious to persuade everyone to keep it up and not jump ship, after getting burned by the previous JDK (and Spring) upgrades.
Panama was delivered in JDK 19 and is final in JDK 23, out this March.
We don't give ETAs as people may make business decisions based on them. As a matter of policy, we only announce a targeted JDK within the 6 months prior to its release.
> it’s getting quite tedious to persuade everyone to keep it up and not jump ship, after getting burned by the previous JDK (and Spring) upgrades.
If nothing else, they should be placated by the fact that upgrades are significantly worse in every other ecosystem.
I think that Java's record on compatibility is unmatched, and users are extremely happy with all the enhancements we've been delivering, which why Java's popularity remains sky-high. But no product can satisfy everyone forever, and if you're unhappy, we'd like to try and address the issues you have; if we can't, we'd be sad to see you go, but we certainly don't wish to hold you hostage.
Yes, absolutely, Java’s compatibility story has been stellar. To the point that people got spoiled by it and no longer want to join the more agile release cycle. I’ve experienced folks being more like “oh finally, now I can express myself at least a bit like I got used to with some other language”, so not sure the enhancements you’re mentioning go as far as to put Java apart from its modern day alternatives, at least some of those concepts finally trickled through though. Still, a lot of Java deployments are deliver once, never touch it again, those users would be happy to never upgrade anything. It’s no fun to revalidate the semantics in a large project.
> those users would be happy to never upgrade anything. It’s no fun to revalidate the semantics in a large project.
Yep, that's why we introduced the LTS service. People may not have noticed before we did away with major releases and changed the version naming scheme, but in the past you had no choice but to upgrade to semi-annual releases with major VM changes, like 8u20 or 7u4 (probably because the name was too similar to patch releases, like 8u30 or 7u5). Now, with LTS, we try to only backport security patches and major bug fixes to make sure that such legacy users get the stability they need.
Gradle is still at JDK 21, albeit not comprehensively as it sometimes still fails to build on anything more recent than JDK 20. Most projects haven’t transitioned to JDK 17 yet. There’s a gap already. I don’t really want to draw parallels but Scala 2 to 3 wasn’t supposed to go the way Python had gone, everyone was certain that wouldn’t be the case, while .NET made a clear cut and somehow managed to move everyone forward, perhaps because of the monocentric organization of the ecosystem. From my experience, I can’t relate with your last sentence however. I do totally appreciate your efforts and investment into the Java community Ron, always have, but your words now feel like a disconnect, or perhaps I am simply no longer part of your target audience.
I don't understand what you're trying to say. Python's migration has clearly been worse and took longer, .NET break their users every 6-7 years on average requiring significant rewrites (except .NET's users have been used to that pain for many years and accept it clear-eyed), and the JS ecosystem is such a mess that people rewrite their programs every couple of years because their libraries keep being abandoned or make incompatible changes as a matter of habit.
No. JDK9/JDK11's adoption issues were due to --add-opens/--add-exports and the JDK developers hostile approach towards reflection.
It's an obsession over "pure" code that has no basis in reality. I'm sorry: Applets don't exist anymore, this obsession is outdated and futile. There is no rational justification behind breaking compatibility with almost every large Java project in existence to the extent that https://java.com still downloads Java 8.
> No. JDK9/JDK11's adoption issues were due to --add-opens/--add-exports and the JDK developers hostile approach towards reflection.
No. JDK 9 and 11 kept all default accessibility the same as in JDK 8 (all you got was a warning) until it was changed in JDK 16: https://openjdk.org/jeps/396
The adoption issues were almost entirely due to non-portable libraries depending on internal implementation details that had changed in 9. Some were due to changes like that to the version string that dropped the "1." prefix for the first time.
> It's an obsession over "pure" code that has no basis in reality. I'm sorry: Applets don't exist anymore, this obsession is outdated and futile.
I don't understand the relationship to Applets, but the emphasis on integrity (a generalisation of safety) is all around the software world. It's the main reason for Rust's existence.
> No. JDK 9 and 11 kept all default accessibility the same as in JDK 8 (all you got was a warning) until it was changed in JDK 16: https://openjdk.org/jeps/396
This is simply not true. This sounds like gaslighting in an attempt to rewrite history, which IMHO shows poor credibility from the JDK developers.
> I don't understand the relationship to Applets, but the emphasis on integrity (a generalisation of safety) is all around the software world. It's the main reason for Rust's existence.
Yes, and Rust has "unsafe". Java still has JNI. If programmers want to write unsafe code, they will write it, to the point of shipping a modified JVM. It's their computers. It's their code. It is a misguided and fruitless effort to push ideology on them.
> That's because java.com is a website for end users downloading the JRE, which was discontinued after 8 (really in 11).
In this release the strong encapsulation of some of the JDK's packages is relaxed by default... --illegal-access=permit opens each package in each module in the run-time image to code in all unnamed modules, i.e., to code on the class path, if that package existed in JDK 8. This enables both static access, i.e., by compiled bytecode, and deep reflective access, via the platform's various reflection APIs... This mode is the default in JDK 9.
> Yes, and Rust has "unsafe". Java still has JNI. If programmers want to write unsafe code, they will write it, to the point of shipping a modified JVM. It's their computers. It's their code. It is a misguided and fruitless effort to push ideology on them.
And Java will still gives you access to unsafe operations (even after this change) albeit with a flag that serves a similar purpose to Rust's unsafe, only done better. The goal is not to impose an ideology on programmers. If you read the JEP, the goal is for them, and the runtime, to know which invariants can be trusted and which can't (e.g. the JIT might want to optimise things based on the assumption that strings are final, but using unsafe code or native code makes that untrue). That's why JNI will also require a flag. As it is today, neither the runtime nor the application developer is able to know whether there's some code that could mess about with their assumptions.
> I don't buy this.
It literally says that on the front page of java.com: "Get Java for desktop applications." OTOH, the developer website, https://dev.java/, lets developers download JDKs only, as JREs no longer exist.
Of course, a classical trade-off. That’s why I’m taking time to discuss that here. The cost incurred by upstream updates reshuffling the hull makes it for hard budget and timeline decisions and risk management. That’s why larger projects with many deps prefer stable upstream, both the runtime and the libs. There’s also compliance and certification that come into play in regulated industries. Lots of moving parts to keep track of. I need to know what the upstream vendors are up to so we can plan in advance. With JDK, a big trouble is the push to JDK 17+ now within the ecosystem. Sonar Cloud for example now wants JDK 17+ starting next Monday. But you can’t even build your JDK 11 bound projects with Gradle that runs on JDK 17. You could separate the build stage from the run stage, of course, but that’s not really enough in the end. Same story with Spring. With .NET by contrast, the ecosystem still supports the legacy .NET Framework target and you can still run your antiquated projects. Not the same with Java anymore though. It’s pulling the rug.
Most Java projects written for JDK 1.2 still run fine, unchanged and without recompilation, on JDK 22, provided that they don't make use of libraries that bypass the spec and reach for internals (or unless they're using discontinued technologies like Applets, although those would still be easy to port). Java's backward compatibility only applies to the spec, not to internal implementation details. Before internals were encapsulated in JDK 16, too many libraries reached for internals, and these non-portable libraries made their client applications non-portable without their knowledge. Those libraries reached for internals to add functionality and to improve performance, but in the process knowingly sacrificed portability but didn't make that tradeoff apparent to consumers.
Now that internals are encapsulated, upgrades are easier than they've ever been at any point in Java's history. Unfortunately, some large applications that depend on non-portable libraries — some of which were since abandoned — are stuck, but support for JDK 8 continues at lest until 2030.
Thanks for these insights, Ron. Indeed, it’s mostly due to the dependencies that it’s not always simple to upgrade. Some Gradle plugins must also be updated. Code generators are rather fragile and some of them bluntly violate javac constraints but have been pulled as dependencies rather widely nonetheless. Yes, from the JDK vendor perspective on backwards compatibility and lifecycle support extensions, OpenJDK’s and Oracle’s track record is stellar, that's been my honest opinion throughout.
Beyond JDK though, the community has been trying to move forward in ways inconsistent with the backward compatibility for projects with dependencies such as those that you describe. This is what's causing most of the hesitation, exacerbated by the modality that there's little clarity on how to replace or upgrade such shenanigans.
From your comments, it turns out I’m quite unaware of the details of the transition to the encapsulation of the internals. I recall you mentioning that some time ago too. I’d appreciate a pointer to some overview, I feel like I’m going to need one before making a dive into the rabbit hole.
Even the JEP admits that there will be performance costs, which the team hopes won't matter for most use cases, hoping has never been a solution for anything.
You're defining "hope" as anything short of absolute certainty, so the same could be said about every significant new feature. Because we can never know with absolute certainty what impact it will have until it's seen some significant duration of use in the field, all we ever have is "hope" (guided by experience) that the benefit will have been worth the cost. Since our record is pretty good, I think, I don't see any more reason for concern in this case than in all others (not that that's ever stopped some people from expressing grave concern with pretty much every significant thing we do).
In fact, why limit ourselves to technology? The same goes for deciding to make any change in anything. Every intentional change has only ever happened because someone had bet, without absolute certainty, that it will make things better. So you could say that "hope" is the only thing that has ever made anything better (or worse, I guess, if you're a glass-half-empty kinda guy).
> We know today how performance will be affected by this.
We know that a small subset of Unsafe-using code will have to continue using internals to keep performance and won't be able to use the supported APIs. There is absolutely no indication about this having any impact of significance on the performance in the ecosystem at large.
> There is no reason to make this change besides misguided ideology. There is zero benefit.
Elimination of undefined behaviour by default (and so fewer crashes) and better security come to mind. Also, better compiler optimisations. This is explained in: https://openjdk.org/jeps/8305968
The fact that you think this is of new benefit to you doesn't make this empty ideology. We have a couple of decades of vulnerability reports, customer support tickets, and challenges of introducing compiler optimisations that have sufficiently convinced us of the benefit. Not every Java user has to be convinced by every change to the JDK. In fact, we rarely make any JDK change that everyone is convinced we should do.
> There is absolutely no indication about this having any impact of significance on the performance in the ecosystem at large.
You seem to be really averse to evidence. In my opinion, the JEP should have benchmarks. It does not have any. I would say that raises concern for lack of credibility and shows dishonesty.
> Elimination of undefined behaviour by default (and so fewer crashes) and better security come to mind.
There is nothing stopping a compiler from omitting conflicting optimizations in functions using "unsafe". Undefined behavior due to optimizations is also already commonly understood in C/C++. If you don't like codebases using unsafe, don't use them!
> In fact, we rarely make any JDK change that everyone is convinced we should do.
That's a very round-about way of saying "we only do what we want and who cares about the users". Funny.
You have evidence that a significant portion of the ecosystem will suffer some performance degradation? If so, it would be immensely helpful if you could share it on the mailing list. We take each and every report seriously.
I don't understand, however, what kind of benchmark could show that most libraries can migrate from Unsafe to FFM without adversely affecting most of their client applications. I'm sure you can find some discussions about performance on panama-dev, where JDK developers have been working with library authors and other Java users on the project for the past years.
> There is nothing stopping a compiler from omitting conflicting optimizations in functions using "unsafe". Undefined behavior due to optimizations is also already commonly understood in C/C++.
Yeah, the world is moving away from undefined behaviour; Java is certainly not going to start moving toward it. Avoiding unspecified behaviour has sort of been one of Java's main selling points from day one. Because we've been delivering significant performance improvements while improving safety at the same time for years (JDK 21 improves the performance of a lot of applications by a significant amount compared to JDK 8, all without changing a line of code), I can't see a reason why we should change our strategy now. We don't need to let undefined behaviour more easily in to give our users the performance they want.
So while we allow undefined behaviour with a flag, allowing it by default would mean it could be everywhere, and most of our users prefer safety to be the default, also in line with the global trend.
> If you don't like codebases using unsafe, don't use them!
The problem is that a Java application has no easy way of knowing whether some transitive dependency may be using Unsafe. The integrity JEP goes into that.
> That's a very round-about way of saying "we only do what we want and who cares about the users". Funny.
It means we have 10M opinionated users, we work very hard to make sure we satisfy most of them with most of what we do (after all, our job depends on Java's continued thriving, which depends on our users' happiness), but even if 1% is unhappy with something we do, and 1% of those express their opinion on social media, that's 1000 angry people saying they're not being listened to. The fact is that different programmers want different things, often those things are contradictory, and it's quite rare to find something that millions of programmers agree on.
I don't know if it's funny or not, but part of doing this job is knowing that you work all day, every day, to make most of your users happy, and no matter what you do or don't do, there will be people yelling at you on social media, because programmers just can't all agree on pretty much anything. But I'm not complaining. The overall satisfaction with our recent features and releases has been palpable.
I’m all for trimming the stdlib and rectifying mistakes, but have you evaluated how much of the ecosystem, particularly the data processing systems, such as Kafka, Cassandra, Elasticsearch, HBase to name a few, depend on it? You say “integrated over all users”—whom are you referring to?
People on those platforms are already adding C++ and Rust written alternatives with compatible networking protocols, if this goes badly, it will only accelerate the transition.
I’ve been using the C++ rewrites for a while and been enjoying it, are there also drop-in replacements in Rust now? Never happened to come across one, would be interested to learn more about it.
> sometimes you really want to skip bounds checking in a cross platform way
What is a use case where this is crucial? Where the difference is not just to win a benchmark pissing contest, but where the delta is so large that it is a factor of the JVM being a valid platform or not, and where the JVM would still be the best overall solution?
Put differently: if you need extreme performance and full low-level control, what is a scenario where you still choose the JVM over eg Rust, and where bounds-checking elimination would be the deciding factor?
Most of sun.misc.Unsafe forwards to jdk.internal, which will still exist and be accessible via --add-exports on the command line. This is a good middle ground for strong encapsulation and making sure users know that they're using internal api's that could change.
First, please clarify whether you are asking about performance impacts vs. no bounds checking or vs. bounds checking in software.
The "vs. no bounds checking" deals with the fact that the bounds must be stored somewhere, so you get an additional memory access, and that's slow (best case, puts some load on the caches).
The "vs. software" is more like RISC vs. CISC in general.
> First, please clarify whether you are asking about performance impacts vs. no bounds checking or vs. bounds checking in software
I was wondering if it would be feasible to have hardware checks with practically no speed difference to performing no check at all.
Yes I imagined it could be seen as RISC vs CISC, but it's such a fundamental and frequent operation that it seems likely it would help overall..!
Of course you'd need to use registers; which yes means higher cost (unless you already have some for sure never in use at bound-checking times), but there seems to be a good chance it would be worthwhile..?
I imagine that the impact of simply reading the additional instructions would be negligible, by the way
The cost is in loading the bounds (e.g. array size) from memory. If you access 100 different arrays, you get 100 load operations for the sizes.
A register would only help if you access the same array in a loop, but the majority of loops doesn't need repeated bounds checking anyway: list-map operations, list-reduce operations, System.arraycopy(), whatever -- these only have to check the bounds once before starting the loop. The JVM specifies that every array access gets checked, but since arrays can't change their size, the JIT compiler can move that check outside the loop.
The main sting here seems to be that MemorySegment when allocated from Java code requires bounds checks on all accesses, which the JVM cannot optimise away on random accesses.
However, MemorySegment itself already supports unbounded access when wrapping a native pointer. It’s necessary for even basic interactions with native code (e.g. reading a c-str).
It should be easy to “wash” an off-heap MemorySegment through native code to obtain an unbounded MemorySegment from it. Something like:
void *vanish_bounds(void *ptr) { return ptr; }
I think it would be nice if the FFI provided a way to get an unbounded MemorySegment without resorting to native code hacks though. It can obviously be made restricted like unbounded MemorySegment’s already are.
That should take most of the sting out of this proposal (although it still sounds like a painful transition for a very modest gain).
123 comments
[ 2.7 ms ] story [ 194 ms ] threadOnce I did some experiments at programming in Java using only sun.misc.Unsafe for a memory access: https://github.com/xonixx/gc_less. I was able to implement this way basic data structures (array, array list, hash table). I even explicitely set a very small heap and used Epsilon GC to make sure I don't allocate in heap.
Just recently I decided to check if it still works in the latest Java (23) and to my surprise it appears - it is. Now, apparently, this is going to change.
If they fail to provide parity, people will do like with modules, keeping holding to older Java versions longer than they should, use the escape command line options if available, turn back to JNI (the opposite this is trying to achieve), or move to other stack if they aren't that dependent on Java, e.g. ongoing Kafka ecosystem evolution.
<< Over the past several years, we have introduced two standard APIs that are safe and performant replacements for the memory-access methods in sun.misc.Unsafe:
java.lang.invoke.VarHandle, introduced in JDK 9, provides methods to safely and efficiently manipulate on-heap memory: fields of objects, static fields of classes, and elements of arrays.
java.lang.foreign.MemorySegment, introduced in JDK 22, provides methods to safely and efficiently access off-heap memory (sometimes in cooperation with VarHandle).
These standard APIs guarantee no undefined behavior, promise long-term stability, and have high-quality integration with the tooling and documentation of the Java Platform (examples of their use are given below). Given the availability of these APIs, it is now appropriate to deprecate and eventually remove the memory-access methods in sun.misc.Unsafe. >>
[1] https://mail.openjdk.org/pipermail/panama-dev/2023-July/0193...
[2] https://mail.openjdk.org/pipermail/panama-dev/2023-July/0194...
Quick publication showing the difference (up to 125%!): https://www2.cs.arizona.edu/~dkl/Publications/Papers/ics.pdf
(and no, the story hasn't changed an awful lot since 2004. The gap still exists.)
Um... yeah it has. For starters, hotspot wasn't even a part of the JVM at that point. But further newer JVM additions like the enhanced for loop eliminate a ton of conditions where someone would run into bounds checking. Doing a naked `a[i]` is simply not common java code.
The JVM is far more likely today to remove the bounds check all together than it ever was in 2004.
It's a writing error on my end (clear since I qualify the percentage afterwards), so I think focusing on it distracts from the point (which is why I say it's pedantic).
I understand the pet peeve though (-:
HotSpot had been part of the JVM for five years at that point.
https://en.m.wikipedia.org/wiki/HotSpot_(virtual_machine)
That said, in the paper they didn't use sun's JVM they used gcj and their own modified version of gcj.
> We examined the performance of both our new Java implementation as well as standard gcj on a variety of Java applications.
So my point still stands, a lot as changed. The researches chose to use static compilation over a JIT or interpreter.
It is extremely common in performance sensitive code, 1) graphics & rendering 2) networking 3) buffers
> But further newer JVM additions like the enhanced for loop eliminate a ton of conditions > The JVM is far more likely today to remove the bounds check all together than it ever was in 2004.
There are more comments in this thread that clarify further, but Java is very commonly unable to eliminate bounds checks. You can test all of these things yourself with a quick benchmark - don't take my word for it! The JIT is not as great at this as common rhetoric claims it is.
Also accessing byte arrays and direct buffers is extremely common - if you do just "business logic" jazz - it does not happen, though. However, every hashmap needs that, pretty much each hash lookup is a direct a[hash]
Perhaps the paper answers my question, but I'll admit I'm being lazy here and would much appreciate a tl;dr.
(with bounds checks) "Telling the Rust allocator to avoid zeroing the memory when allocating for Brotli improves the speed to 224 MB/s."
(without bounds checks) "Activating unsafe mode results in another gain, bringing the total speed up to 249MB/s, bringing Brotli to within 82% of the C code."
224MB/s -> 249MB/s (11% Brotli compression perf difference just by eliminating bounds checks)
If you write a micro-benchmark for only bounds-checks, you'd see the larger difference more inline with the "125%"
I don’t doubt that all of those things in the article were true back then, but that was eight years ago. Wow.
With that said, convincing a compiler to elide bounds checks (especially Java's JIT compiler) is a hugely frustrating (and for some algorithms futile) task.
It could be an argument that bounds checks make up a small percentage of total application performance. However, I've profiled production Java servers where >50% of the CPU was encryption/compression. JDK implementations of those algorithms are heavily impacted by (and commonly fail to elide) bounds checks.
Performance matters!
Adding explicit checks does work to a certain degree but it can change with the compiler, and it requires to keep checking the generated assembly - not fun (no unsafe, either but still)
Especially in Java, because "the assembly" can change as the JIT evolves. What is optimized today may not be tomorrow.
>What is optimized today may not be tomorrow.
Exactly. (Also most developers will have exceptionally hard time maintaining such code)
[0]: https://web.archive.org/web/20120328222841/http://cliffc.org...
I should play around this with this using a couple RISC-V cores.
> Performance matters!
https://www.youtube.com/watch?v=r-TLSBdHe1A by Emery Berger
Another, yesbut, encryption and compression are and will handled by on die accelerators.
The stuff gets worse with DirectByteBuffers as the JIT has to work harder. Unsafe allows to 'remove' all bound checks, but it may prevent some other optimizations.
I see this mentioned a few times in this thread, but I haven't experienced this in practice (and I've written a lot of unsafe in Java). Are there any examples of this?
Code cache is not completely relevant afaik - you can easily replicate in micro-benchmarks.
I don’t think it’s relevant here, the JIT compiler can do the same optimizations here.
If the branch predictor can basically 100% guess correctly (which will be the case in any correct program), it should not have any additional cost, besides taking up place in i$, so I would assume that that is responsible for the difference.
Correct. I was clarifying that the issue can be replicated in a "simpler" statically compiled benchmark.
> If the branch predictor can basically 100% guess correctly (which will be the case in any correct program), it should not have any additional cost
That isn't true. The CPU branch predictor has a cost no-matter what. Of course, it's a complicated story: https://blog.cloudflare.com/branch-predictor
1. https://github.com/gunnarmorling/1brc
Also, graal can do some vectorization, and definitely has some libraries with vector intrinsics, e.g. truffle’s regex implementation uses similar algorithms to simdjson.
Code that uses jdk.incubator.vector does not actually get compiled to vector instructions under graal. This is why the top submission that uses jdk.incubator.vector is the only top solution that does not use graal.
I don't doubt that "similar algorithms to simdjson" may be used, but simdjson uses instruction sequences that are impossible to generate using the tools provided.
I mention these instructions because vpshufb comes up a lot in string parsing and formatting, because prefetch is useful for hiding latency when doing bulk accesses to a hash table, and because aesenc is useful for building cheap hash functions such as ahash.
This is subjective. In my opinion, even 10%~ is massive. Performance matters and this would be a step backwards.
Edit: Unless you FFI to Rust / Ada / formally verified C, in which case perhaps we can claw back the performance and safety at the cost of a lot more dev work.
Also, FFI to Rust will buy you nothing. Safe Rust performs the same bound checks as Java while unsafe Rust would be just as unsafe as using JDK internals. If you want to prove the safety of unsafe code in some other unsafe language, you might as well do the same for unsafe Java code using JDK internals (it would probably even be easier).
Are you sure you should be using Java if your use-case is performance sensitive?
Sorry, but this is just a bad advice.
But if it turns out to be a real problem, it might still be possible that they will add a way to disable checks with the foreign memory API. It would actually allow for a better user experience, you could do “debug” builds with extensive checking for correctness, which could be disabled via some flag.
We are no longer in the Sun days, with Java being the answer for everything.
Dropbox's experience https://dropbox.tech/infrastructure/lossless-compression-wit... shows a >10% performance difference caused by bounds checks in a similar context for a real world application. And before you say it: the Java JIT is not capable of optimizing bounds check elimination more than LLVM at this point in time. The JIT is not magical.
If after this change, Java is no longer able to compete in "The One Billion Row Challenge", it is a loss for the ecosystem.
The matter is one of these tools is being taken away.
We've got Rust now, offering both low-level control and safety. No need to take the SUV on the racing track, it is not at home there.
Amusingly, though somewhat tangential, I observed a rather simple Go implementation (nothing fancy, ordinary channel bound message passing) outperform a fairly sophisticated Rust implementation for the same logic when we were toying with a prototype. But that was at an IO boundary. There are many other variables of course, but an amusing anecdote to share.
I'll easily believe that it can take significant work to outperform Golang (or Java) in Rust where application complexity is high (not just a simple cmd app), they do have an excellent baseline out of the box.
I personally am often quite astonished by the amazing performance Go apps squeeze out of the box, with many efficiency tweaks and tricks we way too often take for granted, which you need to be aware of to come by in C++, Rust, or even Java. But I do also often get disappointed by the sudden inconsistency of that performance in Go after a while, the variance is a bit high, with C++, Rust, and Java runtime behavior being very consistent in general.
https://medium.com/@jadsarmo/why-we-chose-java-for-our-high-...
LMAX Disruptor customers
https://lmax-exchange.github.io/disruptor/
Among many other examples.
The newer, better thing from the same author is aeron.
This is a clear win for low-level coding in Java because today there's no way to tell if a Java program is unsafe as some transitive dependency may be using Unsafe even if it doesn't benefit the program at all. Offering full safety for (nearly) all while still offering maximum performance in special cases is an advantage for Java over other languages.
In case you guys conducted an ecosystem survey, where are we at now? Lots are still running on JDK 1.8, some important deployments are on 1.6 or 1.7, even more are on JDK 11. The transition to JDK 17 has started only recently.
How much more hesitant will this make projects to upgrade the JDK? There’s quite some tearing already due to 1.8, 11, and now 17, particularly with Pivotal EOL support for Spring 2.7 release train. I wish we could wait with it till the transition to 17 is complete.
Two months ago I did a fresh deploy of Java 8 in production for a SaaS solution, exactly because that is what the product and support team care about.
Also, there isn't much new code being written for 8, and there's a whole lot more of Java code yet to be written than the non-portable code still on 8. In fact, even most existing projects are no longer on 8. Sure, if you only look at the pain, there has certainly been some. But if you look at the pain compared to the benefit, the lesson is that it's been a great success, especially compared to other languages (or even frameworks) doing similar significant changes. I think the vast majority of those who've upgraded consider it worthwhile (again, comparing cost/benefit).
Virtual threads, FFM, and the upcoming Valhalla and Leyden are all things that could not have been done without modularisation, and certainly not without significant internal JDK changes, which would have kept the same people on 8 even if there was some way (doubtful and definitely much more expensive) to achieve that without modularisation. We would have been in a far worse place without it: fewer new features and no end to the problem of migration.
We don't give ETAs as people may make business decisions based on them. As a matter of policy, we only announce a targeted JDK within the 6 months prior to its release.
> it’s getting quite tedious to persuade everyone to keep it up and not jump ship, after getting burned by the previous JDK (and Spring) upgrades.
If nothing else, they should be placated by the fact that upgrades are significantly worse in every other ecosystem.
A lot of ecosystems care about their users and care about compatibility. Java is a toxic relationship at this point.
Yep, that's why we introduced the LTS service. People may not have noticed before we did away with major releases and changed the version naming scheme, but in the past you had no choice but to upgrade to semi-annual releases with major VM changes, like 8u20 or 7u4 (probably because the name was too similar to patch releases, like 8u30 or 7u5). Now, with LTS, we try to only backport security patches and major bug fixes to make sure that such legacy users get the stability they need.
It's an obsession over "pure" code that has no basis in reality. I'm sorry: Applets don't exist anymore, this obsession is outdated and futile. There is no rational justification behind breaking compatibility with almost every large Java project in existence to the extent that https://java.com still downloads Java 8.
No. JDK 9 and 11 kept all default accessibility the same as in JDK 8 (all you got was a warning) until it was changed in JDK 16: https://openjdk.org/jeps/396
The adoption issues were almost entirely due to non-portable libraries depending on internal implementation details that had changed in 9. Some were due to changes like that to the version string that dropped the "1." prefix for the first time.
> It's an obsession over "pure" code that has no basis in reality. I'm sorry: Applets don't exist anymore, this obsession is outdated and futile.
I don't understand the relationship to Applets, but the emphasis on integrity (a generalisation of safety) is all around the software world. It's the main reason for Rust's existence.
> to the extent that https://java.com still downloads Java 8.
That's because java.com is a website for end users downloading the JRE, which was discontinued after 8 (really in 11).
This is simply not true. This sounds like gaslighting in an attempt to rewrite history, which IMHO shows poor credibility from the JDK developers.
> I don't understand the relationship to Applets, but the emphasis on integrity (a generalisation of safety) is all around the software world. It's the main reason for Rust's existence.
Yes, and Rust has "unsafe". Java still has JNI. If programmers want to write unsafe code, they will write it, to the point of shipping a modified JVM. It's their computers. It's their code. It is a misguided and fruitless effort to push ideology on them.
> That's because java.com is a website for end users downloading the JRE, which was discontinued after 8 (really in 11).
I don't buy this.
I'm sorry, but default access was not changed until JDK 16. Here's the relevant section from JDK 9's JEP 261 (https://openjdk.org/jeps/261#Relaxed-strong-encapsulation):
In this release the strong encapsulation of some of the JDK's packages is relaxed by default... --illegal-access=permit opens each package in each module in the run-time image to code in all unnamed modules, i.e., to code on the class path, if that package existed in JDK 8. This enables both static access, i.e., by compiled bytecode, and deep reflective access, via the platform's various reflection APIs... This mode is the default in JDK 9.
> Yes, and Rust has "unsafe". Java still has JNI. If programmers want to write unsafe code, they will write it, to the point of shipping a modified JVM. It's their computers. It's their code. It is a misguided and fruitless effort to push ideology on them.
And Java will still gives you access to unsafe operations (even after this change) albeit with a flag that serves a similar purpose to Rust's unsafe, only done better. The goal is not to impose an ideology on programmers. If you read the JEP, the goal is for them, and the runtime, to know which invariants can be trusted and which can't (e.g. the JIT might want to optimise things based on the assumption that strings are final, but using unsafe code or native code makes that untrue). That's why JNI will also require a flag. As it is today, neither the runtime nor the application developer is able to know whether there's some code that could mess about with their assumptions.
> I don't buy this.
It literally says that on the front page of java.com: "Get Java for desktop applications." OTOH, the developer website, https://dev.java/, lets developers download JDKs only, as JREs no longer exist.
Now that internals are encapsulated, upgrades are easier than they've ever been at any point in Java's history. Unfortunately, some large applications that depend on non-portable libraries — some of which were since abandoned — are stuck, but support for JDK 8 continues at lest until 2030.
Beyond JDK though, the community has been trying to move forward in ways inconsistent with the backward compatibility for projects with dependencies such as those that you describe. This is what's causing most of the hesitation, exacerbated by the modality that there's little clarity on how to replace or upgrade such shenanigans.
From your comments, it turns out I’m quite unaware of the details of the transition to the encapsulation of the internals. I recall you mentioning that some time ago too. I’d appreciate a pointer to some overview, I feel like I’m going to need one before making a dive into the rabbit hole.
In fact, why limit ourselves to technology? The same goes for deciding to make any change in anything. Every intentional change has only ever happened because someone had bet, without absolute certainty, that it will make things better. So you could say that "hope" is the only thing that has ever made anything better (or worse, I guess, if you're a glass-half-empty kinda guy).
Java developers didn't just start using Unsafe for fun. They use Unsafe because they benchmarked, measured, and found a real performance gain.
There is no reason to make this change besides misguided ideology. There is zero benefit.
We know that a small subset of Unsafe-using code will have to continue using internals to keep performance and won't be able to use the supported APIs. There is absolutely no indication about this having any impact of significance on the performance in the ecosystem at large.
> There is no reason to make this change besides misguided ideology. There is zero benefit.
Elimination of undefined behaviour by default (and so fewer crashes) and better security come to mind. Also, better compiler optimisations. This is explained in: https://openjdk.org/jeps/8305968
The fact that you think this is of new benefit to you doesn't make this empty ideology. We have a couple of decades of vulnerability reports, customer support tickets, and challenges of introducing compiler optimisations that have sufficiently convinced us of the benefit. Not every Java user has to be convinced by every change to the JDK. In fact, we rarely make any JDK change that everyone is convinced we should do.
You seem to be really averse to evidence. In my opinion, the JEP should have benchmarks. It does not have any. I would say that raises concern for lack of credibility and shows dishonesty.
> Elimination of undefined behaviour by default (and so fewer crashes) and better security come to mind.
> Also, better compiler optimisations. This is explained in: https://openjdk.org/jeps/8305968
There is nothing stopping a compiler from omitting conflicting optimizations in functions using "unsafe". Undefined behavior due to optimizations is also already commonly understood in C/C++. If you don't like codebases using unsafe, don't use them!
> In fact, we rarely make any JDK change that everyone is convinced we should do.
That's a very round-about way of saying "we only do what we want and who cares about the users". Funny.
You have evidence that a significant portion of the ecosystem will suffer some performance degradation? If so, it would be immensely helpful if you could share it on the mailing list. We take each and every report seriously.
> In my opinion, the JEP should have benchmarks.
The Panama project has been publishing and discussing benchmarks for years. Here's one example: https://mail.openjdk.org/pipermail/panama-dev/2020-November/...
I don't understand, however, what kind of benchmark could show that most libraries can migrate from Unsafe to FFM without adversely affecting most of their client applications. I'm sure you can find some discussions about performance on panama-dev, where JDK developers have been working with library authors and other Java users on the project for the past years.
> There is nothing stopping a compiler from omitting conflicting optimizations in functions using "unsafe". Undefined behavior due to optimizations is also already commonly understood in C/C++.
Yeah, the world is moving away from undefined behaviour; Java is certainly not going to start moving toward it. Avoiding unspecified behaviour has sort of been one of Java's main selling points from day one. Because we've been delivering significant performance improvements while improving safety at the same time for years (JDK 21 improves the performance of a lot of applications by a significant amount compared to JDK 8, all without changing a line of code), I can't see a reason why we should change our strategy now. We don't need to let undefined behaviour more easily in to give our users the performance they want.
So while we allow undefined behaviour with a flag, allowing it by default would mean it could be everywhere, and most of our users prefer safety to be the default, also in line with the global trend.
> If you don't like codebases using unsafe, don't use them!
The problem is that a Java application has no easy way of knowing whether some transitive dependency may be using Unsafe. The integrity JEP goes into that.
> That's a very round-about way of saying "we only do what we want and who cares about the users". Funny.
It means we have 10M opinionated users, we work very hard to make sure we satisfy most of them with most of what we do (after all, our job depends on Java's continued thriving, which depends on our users' happiness), but even if 1% is unhappy with something we do, and 1% of those express their opinion on social media, that's 1000 angry people saying they're not being listened to. The fact is that different programmers want different things, often those things are contradictory, and it's quite rare to find something that millions of programmers agree on.
I don't know if it's funny or not, but part of doing this job is knowing that you work all day, every day, to make most of your users happy, and no matter what you do or don't do, there will be people yelling at you on social media, because programmers just can't all agree on pretty much anything. But I'm not complaining. The overall satisfaction with our recent features and releases has been palpable.
What is a use case where this is crucial? Where the difference is not just to win a benchmark pissing contest, but where the delta is so large that it is a factor of the JVM being a valid platform or not, and where the JVM would still be the best overall solution?
Put differently: if you need extreme performance and full low-level control, what is a scenario where you still choose the JVM over eg Rust, and where bounds-checking elimination would be the deciding factor?
Is it inherently difficult to do it without performance impacts? Or nobody cared too much for the prevalence of trust-the-developer c thinking?
I found some information at https://stackoverflow.com/questions/40752436/do-any-cpus-hav...
The "vs. no bounds checking" deals with the fact that the bounds must be stored somewhere, so you get an additional memory access, and that's slow (best case, puts some load on the caches).
The "vs. software" is more like RISC vs. CISC in general.
I was wondering if it would be feasible to have hardware checks with practically no speed difference to performing no check at all.
Yes I imagined it could be seen as RISC vs CISC, but it's such a fundamental and frequent operation that it seems likely it would help overall..!
Of course you'd need to use registers; which yes means higher cost (unless you already have some for sure never in use at bound-checking times), but there seems to be a good chance it would be worthwhile..?
I imagine that the impact of simply reading the additional instructions would be negligible, by the way
A register would only help if you access the same array in a loop, but the majority of loops doesn't need repeated bounds checking anyway: list-map operations, list-reduce operations, System.arraycopy(), whatever -- these only have to check the bounds once before starting the loop. The JVM specifies that every array access gets checked, but since arrays can't change their size, the JIT compiler can move that check outside the loop.
Maybe, if the Devs can replicate the perf with VarHandle and MemorySegment, it could go away eventually.
However, MemorySegment itself already supports unbounded access when wrapping a native pointer. It’s necessary for even basic interactions with native code (e.g. reading a c-str).
It should be easy to “wash” an off-heap MemorySegment through native code to obtain an unbounded MemorySegment from it. Something like:
I think it would be nice if the FFI provided a way to get an unbounded MemorySegment without resorting to native code hacks though. It can obviously be made restricted like unbounded MemorySegment’s already are.That should take most of the sting out of this proposal (although it still sounds like a painful transition for a very modest gain).