As far as the abstract machine called JVM (not to be confused with the various pieces of software which go by the same name and implement that abstract machine by mapping it onto real hardware) is concerned, A.a = B.b
does indeed load the value of B.b
on the stack, then stores it to A.a
.1
However, as the name abstract machine may tell you, this is only a way of thinking about semantics. An implementation may do whatever it pleases, as long as it preserves the effect of the program. As you should know, most implementations don't actually interpret JVM instructions most of the time, but instead compile it to machine code for the CPU they run on. If you're concerned about performance or memory traffic, you need to go deeper.
When compiling, the JVM's stack is mostly discarded in favor of the registers most physical CPUs use. If there aren't enough registers available, the hardware stack (which is distinct from the JVM stack!) may also be used. But I digress. On most architectures, there's no instruction for moving from one memory location to the other (see Assembly: MOVing between two memory addresses). However, the hardware stack is also memory, so it's actually impossible to go heap -> stack -> heap. Instead, you'll find that the code loads the value from memory into a register, then stores it to memory from a register.
Finally, if the objects A and B are short-lived and aren't aliased, they may even be elided with their fields ending up on the stack or in registers. Then, this operation becomes even simpler (or may even be removed entirely if it has no effect).
1 These two steps actually take several JVM instructions each, but that's not important here.