Nevermind the architecture astronauts saying that the string intern pool is actually global state and global state is always problematic.
Nevermind the architecture astronauts saying that the string intern pool is actually global state and global state is always problematic.
String.intern() also offers some other benefits for certain use cases. Earlier versions of java did not cache the String hashcode, which meant that to use Strings as hash table keys meant hashing a lot more. But, an interned string can be used in an IdentityHashMap, which was faster for a long portion of Java's early life.
(I worked on a moderately popular Java library that targeted Java 1.5 as the minimum version. It does occasionally come up useful, but only in specific, and increasingly rare circumstances)
Now, could we have solved this with other ways other than interns? Absolutely: Just make deserialization code do mostly the very same thing, but done by hand and on the heap.
In today's JVM, if you are using the G1 garbage collector, you can get 99% of the memory savings almost completely for free with a VM parameter: You still pay for an extra object, just pointed to the same character array underneath. I'd argue it's one of the most underutilized flags out there: It's not uncommon to end up with half the memory footprint in many use cases, and memory savings are often runtime savings too. Now that we have this, maybe intern always loses, but it was definitely useful in the right circumstances.
Problem 1: it can easily depend on your input data whether interning is necessary to prevent out of memory exceptions.
Problem 2: there often are better solutions to memory usage than interning strings (for example, by replacing that DOM parser by a streaming one)
Problem 3: if you’re writing a library, you can’t know anything about the application that will use your code, so you have to guess whether interning some of the strings you create will help the user.
And of course, that’s historically. (Some) modern JVMs already deduplicate strings.
If you have (like one our applications did) millions of copies of the string "USA" in memory, that's many megabytes of memory that explicit deduplication can save that the garbage collector can't.
String.intern isn't the way, for all the reasons this post outlines, but just using G1 isn't the right approach either.
Hopefully soon object headers will be negligible with progress from Lilliput though.
Let's say you have a cache with Map<Language, BusinessInfo>. After a while, the cache is quite large, and you want to reduce its memory usage. You realize within BusinessInfo, you have a MailingAddress, but it's not actually localized. You're duplicating millions of address lines for no reason.
You could split this out of your cache, but pulling that thread is a bit tricky. Instead, you decide to store addressLine.intern().
synchronised(bob.intern()) { //This block executes in parallel for requests with different values of bob, but sequentially for each request that has the same value of bob. }
If I didn't have to integrate with a weird 3rd party API where this was necessary, I wouldn't know about String.intern()...
I'm not even sure of the name of the library it came from or what it was called - maybe "ObjectHashMap" or something like that - but its semantics were that it used reference equality instead of the structural equality used by a regular HashMap. As a result map operations depended on base Object.hashCode and reference equality which are constant and presumably very quick operations.
Using a regular HashMap, the map operations would depend on String.hashCode() and String.equals() which are O(n) in the length of the String - although the return value from hashCode is cached.
On reflection and this was an old code-base, I'm somewhat sceptical that this was a win given that String keyed-HashMaps are so common that I'd imagine a lot of JVM optimisation effort has gone into this area. I guess it would depend on the pattern of use. If the universe of String keys was known in advance, then I guess it operated like a poor mans perfect hash. On the other hand, this is the kind of optimization in Java which I've found to be unstable with performance varying significantly between JVM versions and platforms.
Sometimes that's key.
Why not use an int or some other token? Well then you have to garbage collect yourself. Maybe use your own class for the token though instead of a string? But then you still have to do half the garbage collection, as you need a weak map for lookup and possibly some cleaning infrastructure.