When an instance of a derived class is created the heap allocation will be something like (*):
- Standard JVM object header (with pointer to Class object for 
DerivedClass) 
- Instance fields for class 
Object 
- Instance fields for class 
BaseClass 
- Instance fields for class 
DerivedClass 
So in effect, if you ignore the DerivedClass instance fields the object looks remarkably like an instance of BaseClass, and the JVM can reference the object as if it were an instance of BaseClass and have no difficulty doing so.
Similarly, in the Class object for DerivedClass is a "virtual method table" with:
- Virtual method pointers for 
Object 
- Virtual method pointers for 
BaseClass 
- Virtual method pointers for 
DerivedClass 
The JVM makes virtual calls by indexing into this table to find a specific method, knowing that, say, hashValue is method number 5 and printTheGroceryList is method number 23.  The number needed to call a method is determined when a class is loaded and cached in the method reference data in calling classes, so calling a method is:  Get the number, go the the Class object pointed to by the instance header, index into the virtual method table, pull out the pointer, and branch to the method.
But when you look closely you will see, eg, that the pointer in the Object group that points to the hashValue method actually points to the one in BaseClass (if BaseClass overrides hashValue).  So the JVM can treat the object as if it were an Object, invoke hashValue, and seamlessly get the method in BaseClass (or DerivedClass, if it also overrides the method).
(*) In practice the instance fields may be intermingled to a degree, since "aligning" fields from a superclass may leave gaps in the heap allocation that fields from a subclass can fill.  This is simply a trick to minimize object size.