Mergesort, instrumented for counting reads and writes
A minor "bug"

One of the main advantages of mergesort is that it is a stable sorting algorithm.  That is, it guarantees that two elements will not be swapped unnecessarily if they have the same value.

For that reason, the comparison in merge() should be implemented with:

if (leftArray[lIndex] &lt;= rightArray[rIndex])


… so that it prefers taking from leftArray first if there is a tie.

For ints, it makes no practical difference whether the sort is stable, because two ints that are equal are indistinguishable.  But it's still a good idea to use &lt;= rather than &lt; for the comparison, to stay true to the mergesort algorithm.

Bounds convention

In Java, a common convention is to specify a range using inclusive lower indexes and exclusive upper indexes.  Examples of this convention can be seen in Arrays.copyOfRange(original, from, to) and string.substring(beginIndex, endIndex).  A main advantage of this convention is that endIndex - beginIndex is the number of elements in that range, which often leads to fewer ± 1 adjustments in the code.

For example, with that convention, you would get rid of the awkward array.length - 1 in doMergeSort(), and the couple instances of (middle + 1) in merge().  Also, populateArray() would have j &lt; end as the loop condition, which is more idiomatic.  (To be fair, you would have to change the test in doMergeSort() to if (low + 1 &lt; high).)

Copying array elements

populateArray() should be replaced by Arrays.copyOfRange() (or perhaps a wrapper around Arrays.copyOfRange() if you need the instrumentation to count reads and writes).  Similarly, the loops to copy the leftover leftArray and rightArray elements could be replaced by System.arraycopy().  Both techniques would not only save a few lines of code, but might also be slightly faster than a hand-written loop.

The post-increment operator is handy for updating index counters with less verbosity:

if (leftArray[lIndex] &lt;= rightArray[rIndex]) {
    parentArray[pIndex++] = leftArray[lIndex++];
} else {
    parentArray[pIndex++] = rightArray[rIndex++];
}


In summary, I don't see anything that can be done about the number of reads and writes, because that's just how the mergesort algorithm works.  But you might be able to shorten the code a bit, and squeeze out a tiny (likely imperceptible) performance gain.