Likely because the (JIT-)compiler performs some optimizations:
1) It sees that you're doing absolutely nothing with the created strings, so the VM just doesn't allocate them.
2) It sees that no objects escape the method call, so it might allocate all of them on the stack. I'm not sure if freeMemory() includes stack memory.
Try the following and see if it makes any difference:
Note the following changes:
Use an instance field to keep track of the objects, so they don't get allocated on the stack.
Use Object instead of String, because the compiler may optimize strings more aggressively.
Run the garbage collector before executing the test.
Let the current thread sleep for a while to increase chances of the garbage collector actually running.
Clear the array after running the test.
Run the test multiple times to see if there is a difference between the runs.
Here's the output for one particular execution of this program on my machine:
Interestingly, when I run this code my first test run doesn't seem to consume any memory, but the amount of free memory is very different from the two other test runs. That implies to me that the first few calls to freeMemory() are inaccurate and maybe the JIT compiler must run before it starts returning accurate results. This is just conjecture though. There could be any number of reasons it returns different results. Note that the documentation says that freeMemory() just returns an approximation.
The other two test runs consistently return a memory usage of 41952 bytes. That is slightly under 42 bytes per object. The fact that the number isn't evenly divisible by 1000 implies that some hidden bookkeeping is done somewhere in the VM.
The freeMemory() method is a bit vague about it, but you are probably right that it gives you available heap space. I thought the same as you, that the allocations are probably optimised away, and such objects being used only in restricted scope would be created on the stack. I thought escape analysis became a standard optimisation in Java6.
I tried you code and got −41952 too. I also noticed JShell's heap became smaller after the first run.