The bytecode interpreted by JVM in not necessarily the same bytecode written in .class
file. Many JVMs perform so-called bytecode rewriting on different stages of execution.
So does HotSpot JVM. When a class is initialized, HotSpot rewrites ldc
bytecodes refering to String entries in the constant pool with JVM-specific fast_aldc
bytecode which refers to objects (i.e. java.lang.String
instances) in CP cache. When such fast_aldc
bytecode is executed for the first time, JVM resolves the constant pool entry, creates a String in Java Heap and populates the CP cache with the reference to this String. Upon further executions of the same bytecode JVM will instantly get the reference from CP cache and push it to Java stack.
After the interpretation of ldc
bytecode (or its rewritten form) the top-of-stack will contain a valid reference to an object in Java Heap. The same kind of reference is produced by new
bytecode. So there is no need to distinguish reference types.
That's how interpreter works. Of course, after a method gets JIT-compiled, there is no more bytecodes, constant pool references etc. All of these are just abstractions. Just a model.