I’m aware of two cases where an allocated page’s physical address can change, and thus where a subsequent allocation can re-use a previously-used physical page:
- swap-out (as you mention)
- compaction
The former can be prevented in all cases by locking memory with mlock() or mlockall(). For the latter, the vm.compact_unevictable_allowed sysctl also needs to be set to 0 (it’s enabled by default if compaction is enabled).
Transparent huge page defragmentation uses both swap-out and compaction, but it adds a number of sysctl entries of its own; I don’t know whether they would be needed on top of disabling compaction globally and locking the memory being tested.