We use object ids to decide on whether objects have been invalidated, from the client side, and whether server needs to be called. The ids are located in the object headers and are never used for something else, and therefore are safe to use from the client side as an object invalidation marker.
Sure, that's what we need to guarantee from server, but that doesn't require exposing entire mapping layout. Changing index to offset in your patch wouldn't take away anything from the client that it needs, locking could stay the same.
That said, a ref counted session object in client feels like an unneeded complexity. If you simply never unmapped memory, the whole thing wouldn't be needed. If you'd map only new chunks when the mapping needs to be enlarged, it wouldn't really affect VM usage.
Changing object layouts to be fully variable in size would require knowledge from the server side, of whether objects are still referenced from the client side or not, before they can be actually reused. This introduce an additional synchronization requirement that would IMO be better to avoid.
It sounds to me as simple as using a separated free lists for allocations (to guarantee that it will be reused only by something following the same synchronization protocol).
Then TBH I don't like much the idea of implementing a shared heap on the server side, it introduces a lot of complexity and potential source of failures, and I avoided it here for that reason.
TBH, I think you're exaggerating that complexity.