Rendered at 10:34:09 GMT+0000 (Coordinated Universal Time) with Cloudflare Workers.
rf15 3 hours ago [-]
I appreciate the hard work that went into the things that did make it into Valhalla eventually, but:
> The model was powerful, but also mentally heavy
No it isn't! it is this interpretation that kills off the null-safety debate entirely. Saying you have a variable that cannot be null is not a mentally taxing distinction, especially since everything is labelled thoroughly.
> The team, faithful to the lesson “simplify the model for the user, even at the cost of the performance ceiling,” ultimately dismantled this dualism.
but it would have simplified it for the user.
The whole attitude and process around this and the other topics gives me very little faith that Java can be steered in a sensible direction here. The type system of a programming language is supposed to give convenient guarantees to the developer on a CPU that can only do numbers. There is no reason to reduce the optional(!) safety guarantees you can offer with the excuse of "too mentally taxing".
Hell, they even get there half way by recognising:
> the language model and the JVM model don’t have to overlap one hundred percent
21asdffdsa12 53 minutes ago [-]
Nullable is just a different loadout state in Railway Orientated Programming. So, no reason to put different flavours of state into the language directly, when its a solved thing since (checks slides) 2012. There is just rails - going to A or going to B, depending on the trains loadout.
If you have language-wars about a concept going in and out of existence, that is a hint that there is demand and the language does not properly handle the demand or when it handles it, it creates mental overload.
> The whole attitude and process around this and the other topics gives me very little faith that Java can be steered in a sensible direction here.
I agree. The stewardship of Java seems rather lacking - particularly when compared to that of .net, where MS etc. mostly seemed to make the correct decisions from the start.
Does Java even have any value or mindshare at Oracle nowadays? The company seems to be a datacentre/compute business at this point, with appendiges for its legacy activities and a vast overhang of debt.
I sometimes wonder if the only parts of Oracle that are still profitable are the Legal and Lawnmower divisions.
gf000 2 hours ago [-]
> The stewardship of Java seems rather lacking
In what way? If anything Java's main developers (employed by Oracle for the most part, working on the completely open source and free OpenJDK) are extremely knowledgeable and are responsible a big jump in how fast the platform evolves. They have added proper algebraic data types to the language, delivered virtual threads and garbage collectors that decouple pause times from heap size. Like if anything, Java is at the best place it has ever been.
lmm 2 hours ago [-]
> They have added proper algebraic data types to the language
No they haven't. E.g. they added a class that superficially looks like Option but subtly breaks the rules that Option is meant to follow, ensuring that no-one can ever manage to migrate existing codebases away from using `null`.
gf000 2 hours ago [-]
Sealed classes/interfaces and records are proper sum and product types.
The stdlib's Option type predates this language update by a long shot, so it doesn't use sealed classes, but it is now possible to have the usual FP "Maybe" type in Java:
```
sealed class Maybe<T> permits Some, None {
record Some<T>(T obj) {}
record None() {}
}
```
(You will probably have to write Maybe.Some and I might have messed up the generic syntax as I wrote it on my phone, but that's mostly how it looks)
_kidlike 2 hours ago [-]
optional is not how algebraic data types are implemented in Java. Basically it's the combination of sealed types and records.
rf15 2 hours ago [-]
How .net got so many things right where java did not is a mystery to me, but appreciated (it has its own flaws, of course). Java, in my understanding, is still of core relevance to Oracle, and tied into a lot of contracts that require very little effort from them to maintain. But you are correct in observing that they want to be a datacentre/compute business more and more these days; they may have in fact overcomitted to this due to the AI craze, since shareholders are already complaining.
> particularly when compared to that of .net, where MS etc. mostly seemed to make the correct decisions from the start.
Wut? I did worked on .net projects and all it achieved was making me like java a lot more then previously.
andai 21 minutes ago [-]
I had the opposite experience, spent a year with each language, first Java then C#, and to me C# felt like "Java done right". (Which appeared to be the original design goal behind the language!) So I'm curious about your experience.
To me it felt a bit less like a religion and more like a language. It didn't force me to do things a particular way, quite as much. (Still more than I would have liked, though! After all, it's called that[0] for a reason :)
Its just that they have to deliver things incrementally. This PR that introduces value classes/objects is already 200k lines long.
pron 15 minutes ago [-]
> No it isn't! it is this interpretation that kills off the null-safety debate entirely. Saying you have a variable that cannot be null is not a mentally taxing distinction, especially since everything is labelled thoroughly.
I think you've missed what this is referring to. It isn't about nullability (which is orthogonal) but about having reference/value projections analogous to Integer/int.
What the Valhalla team ended up doing is, instead of having two projections for each type, one with identity and one without, value types never have identity and so Integer and int are synonymous, and the memory layout is determined automatically based on context and optimisation decisions.
> There is no reason to reduce the optional(!) safety guarantees you can offer with the excuse of "too mentally taxing".
This is not what happened here.
rzmmm 2 hours ago [-]
This is mainly for performance and memory layouts, it would not have improved safety guarantees of java.
rf15 2 hours ago [-]
It would have implicitly brought some null-safety to java with primitive-like classes that can not be null.
tomaytotomato 2 hours ago [-]
A lot of the comments on here are a bit unfair on what is great work being done and even more awesome work (JEPs) in the pipeline for the future.
If Java was a child, imagine it being brought up by loving parents for the first few years (Sun) then it was thrown in a garage with some other children and neglected by its evil guardian (Oracle)
Neglected and unloved till JDK 8, its basically been playing catch up.
So when people say "oh so its now got structs or value types of X", yes it has but that's because it has been stunted in its development due to big bureaucratic and hostile corporate processes, but its free now and is getting love through the OpenJDK family.
I will continue to enjoy writing once and deploying anywhere!
gf000 2 hours ago [-]
> If Java was a child, imagine it being brought up by loving parents for the first few years (Sun) then it was thrown in a garage with some other children and neglected by its evil guardian (Oracle)
Whether you like oracle or not, this is simply not a correct description of Java's history. It was brought up by loving parents, who due to financial problems had to put Java into a foster home where she was neglected.
But later it was adopted by new, loving parents (Oracle) and she bloomed and become a healthy and stable adult.
Like, it was Oracle that completed the open-sourcing of the platform, making OpenJDK the reference implementation. They also open-sourced the previously proprietary jfr, mission control etc tools.
They also managed to keep many of the original members of the language team, which is quite rare during these acquisitions, and Java has seen a huge improvement both on the language and runtime front.
homebrewer 1 hours ago [-]
It was neglected during its last few years at Sun. Oracle started moving it forward at never before seen pace, while mostly maintaining backward compatibility (unlike .NET that "did things right from the start", which is what .NET Framework/.NET Core/.NET split/rewrite is according to some in this very discussion. And .NET had Java to copy and learn from, but still fucked up.)
Same with MySQL, btw. "Dead" according to this site, risen from the dead under Oracle for those who actually know it.
layer8 2 hours ago [-]
> But careful: == looks at internal state, which isn’t always what the object represents, so for “is this the same data” comparisons keep using equals.
So == for value classes will basically be like memcmp(). That is a bit unfortunate, as it breaks encapsulation, exposing implementation details. Client code can use this to do case distinctions based on how a given value is internally represented. In a way, it’s worse than identity comparison, because identity comparison at least doesn’t expose internal state.
usrusr 2 hours ago [-]
Value types are a concept very far away from the "magic black box organism" school of OOP thinking. It's not a novel way of doing classic OOP (does anyone still do that?), it's a way for a language born in OOP ideology get one step further into the post-OOP world.
layer8 2 hours ago [-]
That’s just not true, you can have a completely value-based language without OOP that still doesn’t leak implementation details of the values, while also supporting UDTs.
jstimpfle 52 minutes ago [-]
OOP isn't just about values vs objects. Yes, the idea that everything needs identity is a big part of the problem. But another big problem is the idea that the implementation and representation of types should be hidden by default. The mindset that there isn't a known and useful data representation for a given type. That everything is done by methods parameterized by a type. It's a misguided idea. There is a place for objects and implementation hiding. But the idea that this should be done on a type granularity is a complete and utter failure.
To see why, consider that to do any useful work, data from different objects (also from different types) has to be combined. To be able to do that in the OOP framework, the encapsulation has to be unwrapped. That's why Java code is littered with getters and setters that don't do any useful work at all, they just make it too painful to get any real work done.
Again, there is a place for objects and implementation hiding, but it's at the highest levels of an architecture where different components get integrated.
ahartmetz 2 hours ago [-]
If your bags of data have internal state, there's something wrong with your bags of data. I assume that the Java guys thought far enough to either exclude padding from comparisons or force padding bytes to be zero.
It should work even for strings: They will surely continue to be heap-allocated, and memcmp-ing pointers (inside the new "structs") is exactly an identity comparison.
layer8 2 hours ago [-]
There’s nothing wrong with having non-normalized representations, that’s why there is equals().
For example, you might have a value class for representing (limited-precision) fractions using two longs internally, for the numerator and denominator. For efficiency trade-off reasons, you don’t want to always shorten the fraction. But now client code can distinguish 2/3 from 4/6 using ==.
Scenarios of that sort are conceivable where this actually leaks sensitive information. In any case, it creates dependencies on implementation details where you don’t want to have them.
When designing a value class, you are now in the dilemma of either always having to normalize the representation, costing performance, or having your class be a funnel for leaking implementation details.
inigyou 22 minutes ago [-]
Java can also distinguish a 2/3 object from a 4/6 object using == when they are not value types. It can even distinguish a 2/3 object from a different 2/3 object.
jstimpfle 45 minutes ago [-]
> There’s nothing wrong with having non-normalized representations
There is a lot wrong with that: complexity, bloat, and slowness.
> But now client code can distinguish 2/3 from 4/6 using ==
That's a great way to obfuscate code. Not a good idea. The right way to do the comparison is, just make a function called CompareRational().
ahartmetz 2 hours ago [-]
Well. I'd be upset if custom operator==() for plain-old-data structs was removed from C++, but Java never had it to begin with, so for Java, it just means that you have to fall back to using traditional classes (or compare using something other than ==) if you need such "fancy" features.
leiroigh 16 minutes ago [-]
I'll be interested in seeing the fallout of the (unavoidable) compat issue:
If I have a function that has a value `x` that erases to `java.lang.Object` (e.g. a parametric function with no lower bound); then it used to be safe to check for nullity and then synchronize on the object.
This is no longer safe: This can now throw `IdentityException` into your face. (it was _never_ a good idea)
In other words, a lot of old code must be reviewed.
I suspect that `-XX:DiagnoseSyncOnValueBasedClasses=2` will need to stay (with the semantics: if user tries to synchronize on identity-less object, then log a JFR event and make it a NOP, don't throw an exception)!
The current JEP text is a little too ambiguous to figure out whether that is the plan, anyways.
DarkNova6 3 hours ago [-]
You could probably a whole tech thriller on the evolution on Value Types in Java.
I’ve been reading the mailing lists and watched all videos on the topic and it is truly inspiring how much they managed to consolidate the design to something that always looked like java.
But while also going far deeper in granularity and understanding what it even means to be a value type and what optimizations can be done where
torginus 3 hours ago [-]
I know its a faux pas in the Java world to acknowledge the existence of .NET, but how does this differ from .NET structs?
Value types, generic specialization, boxing - a quick skim makes it looks like they picked the same choices.
_old_dude_ 2 hours ago [-]
The article has a section about that.
For me, a struct in C/C# can be modified and is passed by copy while a value class can not be modified and is passed by value.
I do not think you can do stack allocation in Java.
layer8 2 hours ago [-]
I don’t see a difference between pass by copy and pass by value.
The mutability difference is that part of a struct can be modified in place, which value classes can’t: the value of a complete value-class variable (or array slot) can only be modified (reassigned) as a whole. This is presumably because object references to value-class objects can be created, and those objects should be immutable so their identity doesn’t matter.
_old_dude_ 2 hours ago [-]
I think pass by copy is a consequence of being modifiable.
The other solution is to stack allocate and pass a pointer but as i said, unlike in C#, i do not think it's possible to do that in Java.
In Go, you can stack allocate but when you send a pointer (that escapes), the compiler will heap allocate the object.
layer8 2 hours ago [-]
My point is that pass by copy and pass by value do the same thing, they copy the value representation. In other words, pass by copy means exactly pass by value.
gf000 1 hours ago [-]
Actually, Java only has pass-by-value, even for reference types. (The same way as C does).
People really misuse/misunderstand this term: Java objects are passed by their pointers ("references") being copied.
The alternative is pass by reference, which is done by e.g. c++, rust, who actually have references (Java doesn't). A good litmus test is whether you can write a swap method that actually changes your local variables.
_old_dude_ 1 hours ago [-]
For me, the difference is that if methods are inlined, the compiler is still required to do a copy for structs but not for value classes.
I do not know how this is called.
gf000 2 hours ago [-]
I think that's mostly a semantic difference - Java avoided the problem of strange lifetimes, captures, tearing by fixing the semantics as immutable value objects, while C# has to deal with these issues.
But under the hood it can (and will) do a modification in place.
rf15 3 hours ago [-]
Functionally they don't - java is just catching up with (by now) ancient practice.
The false dichotomy of
> A struct in C# has identity and mutation, so the semantics of copying on assignment or passing have to be precisely defined, which gives a heavier model for the programmer and less freedom for the runtime.
Doesn't really match with what they're describing. While yes, it will not have identity in a java class ref sense, it of course will still have identity in being a unique structure in memory at a certain address. This is just splitting hairs about Java nomenclature.
oddx 2 hours ago [-]
> it of course will still have identity in being a unique structure in memory
No, it will not. The design allows multiple objects to share one structure in memory across multiple records, or not have such a structure at all (see Scalarization in the article).
rf15 48 minutes ago [-]
> The design allows multiple objects to share one structure in memory across multiple records, or not have such a structure at all
Yes. I fear you are missing the point.
torginus 2 hours ago [-]
I don't wanna badmouth Java people, but how they push the idea that this thing is some sort of genuine breakthrough that took multiple PhDs years of cutting-edge research to implement, when in fact they basically copied what .NET did from basically year 1, is not a good look.
Again, not trying to turn this into a .NET vs Java thing, I'd have been much happier if they reached some new and interesting conclusions.
gf000 2 hours ago [-]
> genuine breakthrough
Well, it is - because they had to make it with almost perfect backwards compatibility for one of the most popular languages with trillions of lines of code produced over decades.
Sure, adding it to a new language is not hard. Adding it to Java which has primitives, generics and boxing, finding ways that seamlessly cover the differences between objects and primitives, while trying to plan for the future is hard.
As a general note, if you come to the conclusion that one of the best designer teams on Earth "basically copied what .NET did from year 1 is not a good look", then maybe your mental model needs adjusting on how these stuff works? Java has a public mailing list, you can browse through the related discussions. Implementation is the least of these things. But I can assure you they most definitely know what they are doing.
sysguest 2 hours ago [-]
idk maybe java should adopt something similar to rust's "edition"?
gf000 2 hours ago [-]
Correct me if I'm wrong, but Rust editions are a source code-level feature. So given you have the source code of newer and older rust code, you can compile them together.
That's materially distinct from Java's model of basically dynamic loading already compiled class files. Though class files do have "editions", and there are extra code to deal with different versions. But still, it should be possible to e.g. send a new value class to an old library's class that has never heard of them, and that should just work.
coldtea 42 minutes ago [-]
>I don't wanna badmouth Java people, but how they push the idea that this thing is some sort of genuine breakthrough that took multiple PhDs years of cutting-edge research to implement, when in fact they basically copied what .NET did from basically year 1, is not a good look.
Oversimplifying a big semantic and backend change to a huge codebase on which some of the most crucial customer and government and business systems depend on, and which has to be made as seamless, correct, and performant as possible, to "they just copied .NET", just because .NET has the same functionality, is an even worse look.
It's a "HN "Dropbox is just rsync + some scripts"-style bad look.
misja111 2 hours ago [-]
This is exactly what made it so difficult. It is much easier to have a feature like this from year 1 than to add it to a language that has grown and evolved for 18 years already.
rf15 2 hours ago [-]
I agree with this sentiment. The work they put in deserves a lot of respect, and took a lot of effort, no doubt. It's just the framing they push to the public that could use some work.
orthoxerox 2 hours ago [-]
> Will I get a fast, flat `ArrayList<Point>`? Not yet.
Sad. Hope they can do this by the next LTS JDK.
piokoch 1 hours ago [-]
Yup. That's a big disappointment they could not cram universal generics faster. But I get the problem - they have to preserve backwards compatibility. I can take 30 y.o. Java 1.0 JAR and run it on Java 27 and it will work.
Alexander-Barth 1 hours ago [-]
I think this is quite similar to julia's handling of a struct. An array of mutable structs is just an array of pointers, where every pointer directs to the underlying structure. However with an array of structs (immutable is the default), there is no such indirection. The value of all fields are stored as array element (unless you have an array of heterogeneous elements).
If you want to change an element of such an array you need to create a new immutable struct which in practice it is quite fast, but a bit verbose to write.
nasso_dev 10 minutes ago [-]
stopped reading when i saw the AI illustration. wholly unnecessary, and it feels insulting to be fed slop like this...
if you really want a fun drawing get a human artist to do it. it doesn't need to be complicated, for example https://www.code-cartoons.com/ is mostly just stick figures and does an excellent job
but you don't even need any of that, a mermaid diagram would have worked perfectly fine too. instead you chose to use a technology that is known to be harmful
geokon 2 hours ago [-]
a few questions for the pros
> "The defining trait: no identity"
I get that this makes objects behave like primitive types. Maybe thats reason enough. But is it necessary for the performance boost and de-fluffing the objects? Seems like an orthogonal objective
> There’s a catch worth knowing about here, though: flattened data has to be readable and writable atomically (otherwise it risks “tearing” under concurrent access).
Isn't this a race condition and "undefined bahvior"..? Having to limit yourself to atomic sizes seems like a huge limitation, to accomodate what is most likely buggy code. Is all the effort only gunna help lil toy ColorRGB examples?
> The points array is a million pointers. Each pointer leads to a separate Point object lying somewhere on the heap.
Does this happen in actuality? One would assume the allocator tries to put stuff sequentially on the heap? Its not a guarantee as with these Value Types, but I'd think you could get similar-ish perf with prefetching in cache. I dunno whats happening under the hood.. But when writing Clojure apps the JVM always reserves absurd amounts of heapspace on my machine (to my annoyance). Id assume it can find some place to do contiguous allocations..
Which i guess gets me to my last question... where are the benchmarks broski? It all sounds great, but does it actually yield the insane speedups promised?
Great article, well written. But a benchmark would have been a nice "punchline"
lmm 2 hours ago [-]
> is it necessary for the performance boost and de-fluffing the objects?
Yes. The one part of the JVM GC that can't run concurrently is heap compaction; objects that can be moved by copying and then deleting would be a huge help for that. And it would be awkward to say the object has an identity but can't be wait/notify'd, at which point you need somewhere for the monitor to go.
> Does this happen in actuality? One would assume the allocator tries to put stuff sequentially on the heap?
Yes. Of course it tries, but semantically the pointers are just pointers and the prefetcher can guess but the system still has to chase them.
rf15 2 hours ago [-]
> I get that this makes objects behave like primitive types. Maybe thats reason enough. But is it necessary for the performance boost and de-fluffing the objects? Seems like an orthogonal objective
It feels like an orthogonal objective and honestly arbitrary distinction, yes.
> Isn't this a race condition and "undefined bahvior"..? Having to limit yourself to atomic sizes seems like a huge limitation, to accomodate what is most likely buggy code.
I think they meant it like the appearance of atomic behavior from a java multithreading view.
> Does this happen in actuality?
Yes, it does happen. Having guarantees on this front leads to better performance.
> But when writing Clojure apps the JVM always reserves absurd amounts of heapspace on my machine (to my annoyance)
Might be a configuration problem?
gf000 2 hours ago [-]
> Is all the effort only gunna help lil toy ColorRGB examples
Arguably flattening mostly makes sense for these only.
And yeah, you are right that allocations happen on something called a thread local allocation buffer, which is basically just a pointer bump in cost and objects allocated one after the other should be physically close in memory for the most part (though an object's creation may require a bunch of other object's creation that would sit in-between). But these have headers, so not as dense as they could be (though due to GCs being generational, they may end up actually closer in the next gen? The in-between temporary objects wouldn't survive for the most part)
pwagland 6 minutes ago [-]
There are plenty of cases where flattening an object that takes 64bits would make sense.
The current code will help with `Integer[]`, `Char []`, etc, as well as combinations of `byte`, `char`, and `int`. Past that it doesn't really help much.
It would be fantastic if we could also flatten something like `Pair` or `Tuple`. However, even with compressed pointers, that is 64 bits, so that, plus the `null` bit, means it can't be flattened, which is a real shame. For various reasons, I have `List<Long>` in numerous places in my code, It would be great if that could also be flattened. However, since a Long is 64 bits, it _also_ can't be flattened. https://openjdk.org/jeps/8316779 would go a long way to to helping here, since then at least the null bit could be thrown away, which would allow more things to be flattened.
And then, if you want to go Wishlist land, something that would allow SSO (Small String Optimisation) would also be awesome, but that would require something akin to unions in Java, which we can _kind_ of do with sealed classes, but, since String is a final class, can't be retrofitted back into the language.
Does anyone know if Valhalla will flatten "simple" sealed classes, where every sealed class is small enough to be flattened? Since that would also be a powerful example to share.
ahartmetz 2 hours ago [-]
From the article:
> In 1995, a memory access cost roughly the same as a CPU operation
It would perhaps make Java look a little bad to say that, in 1995, the prevailing attitude in certain circles was "If it's too slow, just wait for faster hardware - Moore's Law forever baby!"
(Of course, Sun was selling, at the time, relatively fast hardware - the slower the software, the faster the required hardware)
Athas 2 hours ago [-]
Yes, this also stood out to me. I usually think of CPUs and memory having parity in the early 80s, but I never bothered to check for sure. I do remember some early computer architects writing about memory being faster than the CPU!
ahartmetz 2 hours ago [-]
Early 80s is also what I remember, mainly from articles about old CPUs on HN - like the zero page on the 6502 that served as a sort of L2 register file.
rom1v 43 minutes ago [-]
> The difference in the code is exactly one word: value.
What is unclear to me is why the decision to use a Point instance as a value or as a reference is made in the class definition rather than by the caller.
> Point[] point = new Point[10];
For the same class, I might need an array of values in one place and an array of references elsewhere within the same codebase.
tikotus 34 minutes ago [-]
What about the case of just needing one, not a collection? And when a function receives a Point, how does it know if it's a value or a reference?
aykutseker 1 hours ago [-]
I think a lot of people will file this under Java got structs.
That seems off. They're still objects, the new thing is that they can give up identity.
Looking into the negative comments is quite amusing. Not only do most of them contain technical inaccuracies, but of course, they also need to mention how great .NET supposedly has been from the beginning and how Java supposedly copied everything.
Let's take a stroll down memory lane. First of all, .NET literally started as a Java copy. On top of it, a non-cross-platform one for almost two decades! After having shamed Linux for so long Microsoft finally started porting .NET to other platforms in a non-backward compatible way. A lot of .NET proponents will tell you porting from legacy .NET to .NET Core (which was renamed once again to .NET) would be a quick fix, but it isn't. For example, the shop I used to work in had some important cryptographic libraries which were very painful to port. And then, there's .NET's simplistic garbage collector, which can be quite annoying because it tries to be a one-fit-all solution that basically cannot be tweaked at all, often resulting in unresolvable latency problems. There’s a lot of other stuff, like its ghetto-like ecosystem and the insane fragmentation of GUI libraries.
I also don't get the C# praise. Over the years, it has become quite the bloated language. It feels like Microsoft tries to implement every feature possible without realizing that an enterprise language is supposed to be streamlined. Async/await? Very ugly, very annoying. Java has solved this a lot better with virtual threads and structured concurrency.
I could go on, but these "language wars" are silly and pointless. Both platforms have their pros and cons. Besides, I have a lot of bad things to say about the JVM as well, but it's nice to see Valhalla finally beocming reality. Too late for me personally though.
ozim 2 minutes ago [-]
I like how you point out inaccuracies and next paragraph you deliver couple more, finishing with “language wars are silly and pointless”.
> The model was powerful, but also mentally heavy
No it isn't! it is this interpretation that kills off the null-safety debate entirely. Saying you have a variable that cannot be null is not a mentally taxing distinction, especially since everything is labelled thoroughly.
> The team, faithful to the lesson “simplify the model for the user, even at the cost of the performance ceiling,” ultimately dismantled this dualism.
but it would have simplified it for the user.
The whole attitude and process around this and the other topics gives me very little faith that Java can be steered in a sensible direction here. The type system of a programming language is supposed to give convenient guarantees to the developer on a CPU that can only do numbers. There is no reason to reduce the optional(!) safety guarantees you can offer with the excuse of "too mentally taxing".
Hell, they even get there half way by recognising:
> the language model and the JVM model don’t have to overlap one hundred percent
If you have language-wars about a concept going in and out of existence, that is a hint that there is demand and the language does not properly handle the demand or when it handles it, it creates mental overload.
> Value
> Errorstates
https://fsharpforfunandprofit.com/rop/As the pythons said: Get on with it!
I agree. The stewardship of Java seems rather lacking - particularly when compared to that of .net, where MS etc. mostly seemed to make the correct decisions from the start.
Does Java even have any value or mindshare at Oracle nowadays? The company seems to be a datacentre/compute business at this point, with appendiges for its legacy activities and a vast overhang of debt.
I sometimes wonder if the only parts of Oracle that are still profitable are the Legal and Lawnmower divisions.
In what way? If anything Java's main developers (employed by Oracle for the most part, working on the completely open source and free OpenJDK) are extremely knowledgeable and are responsible a big jump in how fast the platform evolves. They have added proper algebraic data types to the language, delivered virtual threads and garbage collectors that decouple pause times from heap size. Like if anything, Java is at the best place it has ever been.
No they haven't. E.g. they added a class that superficially looks like Option but subtly breaks the rules that Option is meant to follow, ensuring that no-one can ever manage to migrate existing codebases away from using `null`.
The stdlib's Option type predates this language update by a long shot, so it doesn't use sealed classes, but it is now possible to have the usual FP "Maybe" type in Java:
``` sealed class Maybe<T> permits Some, None { record Some<T>(T obj) {} record None() {} } ```
(You will probably have to write Maybe.Some and I might have messed up the generic syntax as I wrote it on my phone, but that's mostly how it looks)
Wut? I did worked on .net projects and all it achieved was making me like java a lot more then previously.
To me it felt a bit less like a religion and more like a language. It didn't force me to do things a particular way, quite as much. (Still more than I would have liked, though! After all, it's called that[0] for a reason :)
[0] https://www.reddit.com/r/ProgrammerHumor/comments/ddc4b0/mic...
also, null markers are coming too: https://openjdk.org/jeps/8303099
Its just that they have to deliver things incrementally. This PR that introduces value classes/objects is already 200k lines long.
I think you've missed what this is referring to. It isn't about nullability (which is orthogonal) but about having reference/value projections analogous to Integer/int.
What the Valhalla team ended up doing is, instead of having two projections for each type, one with identity and one without, value types never have identity and so Integer and int are synonymous, and the memory layout is determined automatically based on context and optimisation decisions.
> There is no reason to reduce the optional(!) safety guarantees you can offer with the excuse of "too mentally taxing".
This is not what happened here.
If Java was a child, imagine it being brought up by loving parents for the first few years (Sun) then it was thrown in a garage with some other children and neglected by its evil guardian (Oracle)
Neglected and unloved till JDK 8, its basically been playing catch up.
So when people say "oh so its now got structs or value types of X", yes it has but that's because it has been stunted in its development due to big bureaucratic and hostile corporate processes, but its free now and is getting love through the OpenJDK family.
I will continue to enjoy writing once and deploying anywhere!
Whether you like oracle or not, this is simply not a correct description of Java's history. It was brought up by loving parents, who due to financial problems had to put Java into a foster home where she was neglected.
But later it was adopted by new, loving parents (Oracle) and she bloomed and become a healthy and stable adult.
Like, it was Oracle that completed the open-sourcing of the platform, making OpenJDK the reference implementation. They also open-sourced the previously proprietary jfr, mission control etc tools.
They also managed to keep many of the original members of the language team, which is quite rare during these acquisitions, and Java has seen a huge improvement both on the language and runtime front.
Same with MySQL, btw. "Dead" according to this site, risen from the dead under Oracle for those who actually know it.
So == for value classes will basically be like memcmp(). That is a bit unfortunate, as it breaks encapsulation, exposing implementation details. Client code can use this to do case distinctions based on how a given value is internally represented. In a way, it’s worse than identity comparison, because identity comparison at least doesn’t expose internal state.
To see why, consider that to do any useful work, data from different objects (also from different types) has to be combined. To be able to do that in the OOP framework, the encapsulation has to be unwrapped. That's why Java code is littered with getters and setters that don't do any useful work at all, they just make it too painful to get any real work done.
Again, there is a place for objects and implementation hiding, but it's at the highest levels of an architecture where different components get integrated.
It should work even for strings: They will surely continue to be heap-allocated, and memcmp-ing pointers (inside the new "structs") is exactly an identity comparison.
For example, you might have a value class for representing (limited-precision) fractions using two longs internally, for the numerator and denominator. For efficiency trade-off reasons, you don’t want to always shorten the fraction. But now client code can distinguish 2/3 from 4/6 using ==.
Scenarios of that sort are conceivable where this actually leaks sensitive information. In any case, it creates dependencies on implementation details where you don’t want to have them.
When designing a value class, you are now in the dilemma of either always having to normalize the representation, costing performance, or having your class be a funnel for leaking implementation details.
There is a lot wrong with that: complexity, bloat, and slowness.
> But now client code can distinguish 2/3 from 4/6 using ==
That's a great way to obfuscate code. Not a good idea. The right way to do the comparison is, just make a function called CompareRational().
If I have a function that has a value `x` that erases to `java.lang.Object` (e.g. a parametric function with no lower bound); then it used to be safe to check for nullity and then synchronize on the object.
This is no longer safe: This can now throw `IdentityException` into your face. (it was _never_ a good idea)
In other words, a lot of old code must be reviewed.
I suspect that `-XX:DiagnoseSyncOnValueBasedClasses=2` will need to stay (with the semantics: if user tries to synchronize on identity-less object, then log a JFR event and make it a NOP, don't throw an exception)!
The current JEP text is a little too ambiguous to figure out whether that is the plan, anyways.
I’ve been reading the mailing lists and watched all videos on the topic and it is truly inspiring how much they managed to consolidate the design to something that always looked like java.
But while also going far deeper in granularity and understanding what it even means to be a value type and what optimizations can be done where
Value types, generic specialization, boxing - a quick skim makes it looks like they picked the same choices.
For me, a struct in C/C# can be modified and is passed by copy while a value class can not be modified and is passed by value.
I do not think you can do stack allocation in Java.
The mutability difference is that part of a struct can be modified in place, which value classes can’t: the value of a complete value-class variable (or array slot) can only be modified (reassigned) as a whole. This is presumably because object references to value-class objects can be created, and those objects should be immutable so their identity doesn’t matter.
The other solution is to stack allocate and pass a pointer but as i said, unlike in C#, i do not think it's possible to do that in Java.
In Go, you can stack allocate but when you send a pointer (that escapes), the compiler will heap allocate the object.
People really misuse/misunderstand this term: Java objects are passed by their pointers ("references") being copied.
The alternative is pass by reference, which is done by e.g. c++, rust, who actually have references (Java doesn't). A good litmus test is whether you can write a swap method that actually changes your local variables.
I do not know how this is called.
But under the hood it can (and will) do a modification in place.
The false dichotomy of
> A struct in C# has identity and mutation, so the semantics of copying on assignment or passing have to be precisely defined, which gives a heavier model for the programmer and less freedom for the runtime.
Doesn't really match with what they're describing. While yes, it will not have identity in a java class ref sense, it of course will still have identity in being a unique structure in memory at a certain address. This is just splitting hairs about Java nomenclature.
No, it will not. The design allows multiple objects to share one structure in memory across multiple records, or not have such a structure at all (see Scalarization in the article).
Yes. I fear you are missing the point.
Again, not trying to turn this into a .NET vs Java thing, I'd have been much happier if they reached some new and interesting conclusions.
Well, it is - because they had to make it with almost perfect backwards compatibility for one of the most popular languages with trillions of lines of code produced over decades.
Sure, adding it to a new language is not hard. Adding it to Java which has primitives, generics and boxing, finding ways that seamlessly cover the differences between objects and primitives, while trying to plan for the future is hard.
As a general note, if you come to the conclusion that one of the best designer teams on Earth "basically copied what .NET did from year 1 is not a good look", then maybe your mental model needs adjusting on how these stuff works? Java has a public mailing list, you can browse through the related discussions. Implementation is the least of these things. But I can assure you they most definitely know what they are doing.
That's materially distinct from Java's model of basically dynamic loading already compiled class files. Though class files do have "editions", and there are extra code to deal with different versions. But still, it should be possible to e.g. send a new value class to an old library's class that has never heard of them, and that should just work.
Oversimplifying a big semantic and backend change to a huge codebase on which some of the most crucial customer and government and business systems depend on, and which has to be made as seamless, correct, and performant as possible, to "they just copied .NET", just because .NET has the same functionality, is an even worse look.
It's a "HN "Dropbox is just rsync + some scripts"-style bad look.
Sad. Hope they can do this by the next LTS JDK.
If you want to change an element of such an array you need to create a new immutable struct which in practice it is quite fast, but a bit verbose to write.
if you really want a fun drawing get a human artist to do it. it doesn't need to be complicated, for example https://www.code-cartoons.com/ is mostly just stick figures and does an excellent job
but you don't even need any of that, a mermaid diagram would have worked perfectly fine too. instead you chose to use a technology that is known to be harmful
> "The defining trait: no identity"
I get that this makes objects behave like primitive types. Maybe thats reason enough. But is it necessary for the performance boost and de-fluffing the objects? Seems like an orthogonal objective
> There’s a catch worth knowing about here, though: flattened data has to be readable and writable atomically (otherwise it risks “tearing” under concurrent access).
Isn't this a race condition and "undefined bahvior"..? Having to limit yourself to atomic sizes seems like a huge limitation, to accomodate what is most likely buggy code. Is all the effort only gunna help lil toy ColorRGB examples?
> The points array is a million pointers. Each pointer leads to a separate Point object lying somewhere on the heap.
Does this happen in actuality? One would assume the allocator tries to put stuff sequentially on the heap? Its not a guarantee as with these Value Types, but I'd think you could get similar-ish perf with prefetching in cache. I dunno whats happening under the hood.. But when writing Clojure apps the JVM always reserves absurd amounts of heapspace on my machine (to my annoyance). Id assume it can find some place to do contiguous allocations..
Which i guess gets me to my last question... where are the benchmarks broski? It all sounds great, but does it actually yield the insane speedups promised?
Great article, well written. But a benchmark would have been a nice "punchline"
Yes. The one part of the JVM GC that can't run concurrently is heap compaction; objects that can be moved by copying and then deleting would be a huge help for that. And it would be awkward to say the object has an identity but can't be wait/notify'd, at which point you need somewhere for the monitor to go.
> Does this happen in actuality? One would assume the allocator tries to put stuff sequentially on the heap?
Yes. Of course it tries, but semantically the pointers are just pointers and the prefetcher can guess but the system still has to chase them.
It feels like an orthogonal objective and honestly arbitrary distinction, yes.
> Isn't this a race condition and "undefined bahvior"..? Having to limit yourself to atomic sizes seems like a huge limitation, to accomodate what is most likely buggy code.
I think they meant it like the appearance of atomic behavior from a java multithreading view.
> Does this happen in actuality?
Yes, it does happen. Having guarantees on this front leads to better performance.
> But when writing Clojure apps the JVM always reserves absurd amounts of heapspace on my machine (to my annoyance)
Might be a configuration problem?
Arguably flattening mostly makes sense for these only.
And yeah, you are right that allocations happen on something called a thread local allocation buffer, which is basically just a pointer bump in cost and objects allocated one after the other should be physically close in memory for the most part (though an object's creation may require a bunch of other object's creation that would sit in-between). But these have headers, so not as dense as they could be (though due to GCs being generational, they may end up actually closer in the next gen? The in-between temporary objects wouldn't survive for the most part)
The current code will help with `Integer[]`, `Char []`, etc, as well as combinations of `byte`, `char`, and `int`. Past that it doesn't really help much.
It would be fantastic if we could also flatten something like `Pair` or `Tuple`. However, even with compressed pointers, that is 64 bits, so that, plus the `null` bit, means it can't be flattened, which is a real shame. For various reasons, I have `List<Long>` in numerous places in my code, It would be great if that could also be flattened. However, since a Long is 64 bits, it _also_ can't be flattened. https://openjdk.org/jeps/8316779 would go a long way to to helping here, since then at least the null bit could be thrown away, which would allow more things to be flattened.
And then, if you want to go Wishlist land, something that would allow SSO (Small String Optimisation) would also be awesome, but that would require something akin to unions in Java, which we can _kind_ of do with sealed classes, but, since String is a final class, can't be retrofitted back into the language.
Does anyone know if Valhalla will flatten "simple" sealed classes, where every sealed class is small enough to be flattened? Since that would also be a powerful example to share.
> In 1995, a memory access cost roughly the same as a CPU operation
Uhm... no?!
Here's a CS paper from 1993(!) about prefetching from cache(!!) because the cache was slower than the ALU. https://www.eecs.umich.edu/techreports/cse/93/CSE-TR-152-93....
It would perhaps make Java look a little bad to say that, in 1995, the prevailing attitude in certain circles was "If it's too slow, just wait for faster hardware - Moore's Law forever baby!" (Of course, Sun was selling, at the time, relatively fast hardware - the slower the software, the faster the required hardware)
What is unclear to me is why the decision to use a Point instance as a value or as a reference is made in the class definition rather than by the caller.
> Point[] point = new Point[10];
For the same class, I might need an array of values in one place and an array of references elsewhere within the same codebase.
That seems off. They're still objects, the new thing is that they can give up identity.
Let's take a stroll down memory lane. First of all, .NET literally started as a Java copy. On top of it, a non-cross-platform one for almost two decades! After having shamed Linux for so long Microsoft finally started porting .NET to other platforms in a non-backward compatible way. A lot of .NET proponents will tell you porting from legacy .NET to .NET Core (which was renamed once again to .NET) would be a quick fix, but it isn't. For example, the shop I used to work in had some important cryptographic libraries which were very painful to port. And then, there's .NET's simplistic garbage collector, which can be quite annoying because it tries to be a one-fit-all solution that basically cannot be tweaked at all, often resulting in unresolvable latency problems. There’s a lot of other stuff, like its ghetto-like ecosystem and the insane fragmentation of GUI libraries.
I also don't get the C# praise. Over the years, it has become quite the bloated language. It feels like Microsoft tries to implement every feature possible without realizing that an enterprise language is supposed to be streamlined. Async/await? Very ugly, very annoying. Java has solved this a lot better with virtual threads and structured concurrency.
I could go on, but these "language wars" are silly and pointless. Both platforms have their pros and cons. Besides, I have a lot of bad things to say about the JVM as well, but it's nice to see Valhalla finally beocming reality. Too late for me personally though.