src/java.base/share/classes/java/nio/Direct-X-Buffer.java.template contains this, unfortunately without further comments:
| public $Type$Buffer slice() { | int pos = this.position(); | int lim = this.limit(); | assert (pos <= lim); | int rem = (pos <= lim ? lim - pos : 0); | int off = (pos << $LG_BYTES_PER_VALUE$); | assert (off >= 0); | return new Direct$Type$Buffer$RW$$BO$(this, -1, 0, rem, rem, off); | } ByteBuffer::duplicate and ByteBuffer::asReadOnlyBuffer have similar code. The constructor invoked: | // For duplicates and slices | // | Direct$Type$Buffer$RW$$BO$(DirectBuffer db, // package-private | int mark, int pos, int lim, int cap, | int off) | { | #if[rw] | super(mark, pos, lim, cap); | address = db.address() + off; | #if[byte] | cleaner = null; | #end[byte] | att = db; | #else[rw] | super(db, mark, pos, lim, cap, off); | this.isReadOnly = true; | #end[rw] | } The key part is the assignment to the att member. If I understand this correctly, it is needed to keep the backing object alive during the lifetime of this buffer. However, it causes the creation of a long chain of buffer objects. With -Xmx100m or so, the following test will OOM fairly quickly for this reason: | volatile ByteBuffer buffer; | … | buffer = ByteBuffer.allocateDirect(16384); | while (true) { | buffer = buffer.duplicate(); | } I wonder if it would be possible to change the setting of the att member to this instead: | if (db.att == null) { | att = db; | } else { | att = db.att; | } This would only keep the object alive which actually owns the backing storage, as if Buffer::slice had been invoked on it directly.