Search code examples
javaobjectmemoryjvminstance

Is object fix-sized in java


In the Java programming environment, where each data type has a predetermined size, can it be inferred that objects also possess a fixed size post-initialization?

I've consulted ChatGPT and read numerous articles on the topic, but there seems to be a discrepancy in understanding. While some sources claim that object sizes are not fixed, I remain unconvinced. Could you clarify where my understanding might be lacking?


Solution

  • In java, we have primitives and references.

    What are primitives? It's a hard coded list: double, float, long, int, short, byte, char, and boolean. That is it. They are baked into the language, there are no class files representing these types. They have always been part of java, will always be, and you can't make more.

    They are all fixed size. (double and long are 64-bit, float and int are 32-bit. short/char are 16, byte/bool are 8, but the last 4 are the inferior primitives and many operations, such as 'numeric addition' cannot operate on these last 4; if you attempt to, say, add 2 short values together what actually happens is that both short values are converted to int values, those are added up, and then the result is converted back to a short if your java code requires it to be one - and hence, usually, you need a explicit cast in your java code to make that happen).

    So, what are references? Everything else.

    String x = "foo";
    

    Here, x is a reference. It's not "foo". No, what happens there is that somewhere on the heap (imagine a giant beach), an object is instantiated and it contains "foo" somehow. How? That's an implementation detail; it requires checking the source of java.lang.String and it may change in future versions. (Imagine a treasure chest, buried in the giant beach).

    x is not the treasure chest. It's the map that leads to it. Maps are simple, fit on fixed size bits of paper, are easy to copy around. Same thing with references: They are fixed size (what size? Depends on the JVM and various settings; generally 64-bit, could be 32-bit), and just point at an object, they aren't, themselves, an object.

    Objects have non-static fields and each field requires some space, hence, trivially, objects are not fixed size. They are as large as the fields its class definition contains dictates they must be:

    class Example {
      int a;
      boolean b;
      String z;
    }
    
    ... elsewhere ...
    
    Example example = new Example();
    

    This creates a new Example object. The JVM does this by finding some room on the heap (some room on the beach where no one has been burying treasure chests yet) for 1 instance of Example. This requires at least 13 bytes (9, on a JVM that uses 32-bit refs, such as a 32-bit JVM or one with CompressedOOPS on), though it may take a little more because modern CPUs do not like operating on things that aren't on word boundaries, on 64-bit is the word boundary on modern CPUs - so it's probably 16 bytes no matter what you do here.

    Tha's because it takes 4 bytes to store that int, 1 to store that bool, and 4 (or 8) to store that reference to a String. It doesn't have to, itself, store the string content - just 'the treasure map' that leads to it (yes, an instance of Example is a treasure chest, which contains 1 piece of paper with a number written on it, one with 'true'/'false' written on it, and.. another treasure map that leads to another treasure that contains the value of that string. It can be blank (null)).

    Usually it takes a little more: Objects know what they are, which also takes some storage, but that part, at least, is 'fixed' (all objects have a header). How large? JVM detail; a JVM is free to implement this however it wants, neither the Java Lang Spec nor the Java VM Spec dictates any particular rule here. Just that an object must know what it is (obj.getClass() must 'work').

    Then, the example variable itself takes 4 to 8 bytes, as it is a reference.

    So, we have:

    • An instance of Example, somewhere on the heap. It most likely occupies 32 bytes (16 bytes for the header, 16 to contain the data in its fields). Add 2 long fields to it, and it'd take 16 bytes more. It takes as many bytes as is needed to cover its header and the fields it has, and you can have up to 256 fields in a class. Every field takes a known, fixed amount of bytes to represent, though (because everything is a primitive or a reference, and thus, fixed size). The JVM knows beforehand how big Examples are, so within one running JVM, all Example instances are fixed size - but note that they may contain refs to other objects, and the creation of 1 Example instance can in turn create more objects.

    Consider:

    public class Example {
      private Example next;
    
      public Example(int amount) {
        if (amount < 1) return;
        next = new Example(amount - 1);
      }
    }
    

    If you then call new Example(random.nextInt(100)), then that one line will cause a random amount of memory to be consumed - each individual Example instance is fixed size (probably 24 bytes - 16 for a header, 8 for the one field it has), but a single new Example(100) call makes 100 Example objects.

    When we say 'java is pass by value', what's passed is the primitive or the treasure map. Never the treasure:

    List<String> x = new ArrayList<>();
    int y = 100;
    
    foo(x, y);
    System.out.println(x);
    System.out.println(y);
    
    void foo(List<String> x, int y) {
      x.add("Hello!");
      y = 200;
    }
    

    This prints 'Hello!', and then '100' - the x appears 'modified' but the y appears not. Huh? It makes sense once you realize what references imply. What you pass to foo is a copy of your treasure map, along with a copy of your piece of paper that has '100' on it. That method then follows its copy of your treasure map (. is java for 'follow the map and dig'), opens the treasure chest it finds, puts something inside, closes it, and buries it again in the same place. Then it takes the copy of that paper with '100' on it, wipes out, and writes 200 on it, then ends - and as all methods do, as they end, they burn all the pieces of paper they have. So, that was useless then. It also burns its copy of the map.

    But if you then check your paper with the value on it, it is, of course, still 100, but if you take your treasure map, follow it, dig, and look - there's now a thing inside.