Memory-efficient object pooling system for reusing expensive objects like byte arrays, reducing garbage collection pressure in high-performance scenarios.
Core pooling abstraction for managing reusable objects.
/**
* Pool for reusing objects of type T to reduce allocation overhead
*/
interface ObjectPool<T : Any> : AutoCloseable {
/** Maximum number of objects this pool can hold */
val capacity: Int
/** Borrow an object from the pool */
fun borrow(): T
/** Return an object to the pool for reuse */
fun recycle(instance: T)
/** Dispose of the pool and all its objects */
fun dispose()
/** AutoCloseable implementation that calls dispose() */
override fun close()
}
/** Use an object from the pool within a block and automatically recycle it */
inline fun <T : Any, R> ObjectPool<T>.useInstance(block: (T) -> R): RUsage Examples:
import io.ktor.utils.io.pool.*
fun basicPoolingExample() {
// Create a custom pool implementation
val stringPool = object : ObjectPool<StringBuilder> {
private val pool = mutableListOf<StringBuilder>()
override val capacity = 10
override fun borrow(): StringBuilder {
return if (pool.isNotEmpty()) {
pool.removeAt(pool.size - 1).apply { clear() }
} else {
StringBuilder()
}
}
override fun recycle(instance: StringBuilder) {
if (pool.size < capacity) {
pool.add(instance)
}
}
override fun dispose() {
pool.clear()
}
}
// Use the pool
val result = stringPool.useInstance { builder ->
builder.append("Hello")
builder.append(" World")
builder.toString()
}
println(result) // "Hello World"
// StringBuilder is automatically recycled
}Built-in pool implementations for different use cases.
/** Pool implementation with zero capacity - always creates new objects */
abstract class NoPoolImpl<T : Any> : ObjectPool<T> {
override val capacity: Int = 0
override fun recycle(instance: T) { /* no-op */ }
override fun dispose() { /* no-op */ }
}
/** Pool implementation that reuses a single instance */
abstract class SingleInstancePool<T : Any> : ObjectPool<T> {
override val capacity: Int = 1
}
/** Platform-specific default pool implementation */
expect abstract class DefaultPool<T : Any>(capacity: Int) : ObjectPool<T>Usage Examples:
import io.ktor.utils.io.pool.*
// No-pool implementation for objects that shouldn't be pooled
class StringNoPool : NoPoolImpl<String>() {
override fun borrow(): String = ""
}
// Single instance pool for expensive objects
class ExpensiveObjectPool(private val factory: () -> ExpensiveObject) : SingleInstancePool<ExpensiveObject>() {
private var instance: ExpensiveObject? = null
override fun borrow(): ExpensiveObject {
return instance ?: factory().also { instance = it }
}
override fun recycle(instance: ExpensiveObject) {
this.instance = instance
instance.reset() // Reset object state
}
override fun dispose() {
instance?.cleanup()
instance = null
}
}
// Platform-specific default pool
class BufferPool : DefaultPool<ByteArray>(capacity = 100) {
override fun produceInstance(): ByteArray = ByteArray(8192)
override fun clearInstance(instance: ByteArray): ByteArray = instance.apply { fill(0) }
}Pre-built pool for commonly used byte arrays.
/** Global pool for ByteArray objects */
val ByteArrayPool: ObjectPool<ByteArray>Usage Examples:
import io.ktor.utils.io.pool.*
suspend fun byteArrayPoolExample() {
// Use the global byte array pool
val result = ByteArrayPool.useInstance { buffer ->
// Use the buffer for operations
val data = "Hello, Pool!".toByteArray()
data.copyInto(buffer, 0, 0, data.size)
// Process buffer content
processBuffer(buffer, data.size)
}
// Buffer is automatically recycled
// Manual borrow and recycle
val buffer = ByteArrayPool.borrow()
try {
// Use buffer
fillBuffer(buffer)
processBuffer(buffer, buffer.size)
} finally {
ByteArrayPool.recycle(buffer)
}
}
fun processBuffer(buffer: ByteArray, length: Int) {
// Process buffer content
println("Processing ${length} bytes")
}
fun fillBuffer(buffer: ByteArray) {
// Fill buffer with data
for (i in buffer.indices) {
buffer[i] = (i % 256).toByte()
}
}Using pools with byte channel operations for memory efficiency.
// Pool integration is implicit in channel operations
// Channels may use pools internally for buffer managementUsage Examples:
import io.ktor.utils.io.*
import io.ktor.utils.io.pool.*
suspend fun poolChannelIntegration() {
// Custom channel operations using pooled buffers
val channel = ByteChannel()
// Write using pooled buffer
ByteArrayPool.useInstance { buffer ->
val data = "Pooled data transfer".toByteArray()
data.copyInto(buffer, 0, 0, data.size)
channel.writeByteArray(buffer.sliceArray(0 until data.size))
}
// Read using pooled buffer
val content = ByteArrayPool.useInstance { buffer ->
val bytesRead = channel.readAvailable(buffer, 0, buffer.size)
if (bytesRead > 0) {
buffer.sliceArray(0 until bytesRead)
} else {
byteArrayOf()
}
}
println("Read: ${String(content)}")
}Creating custom pools for specific object types.
// Custom pool implementation pattern
abstract class CustomPool<T : Any>(capacity: Int) : DefaultPool<T>(capacity) {
abstract fun produceInstance(): T
open fun clearInstance(instance: T): T = instance
open fun validateInstance(instance: T): Boolean = true
}Usage Examples:
import io.ktor.utils.io.pool.*
// Custom pool for StringBuilder objects
class StringBuilderPool(capacity: Int = 50) : DefaultPool<StringBuilder>(capacity) {
override fun produceInstance(): StringBuilder = StringBuilder(1024)
override fun clearInstance(instance: StringBuilder): StringBuilder {
instance.clear()
return instance
}
override fun validateInstance(instance: StringBuilder): Boolean {
// Ensure capacity hasn't grown too large
return instance.capacity() <= 10240
}
}
// Custom pool for network buffers
class NetworkBufferPool(
private val bufferSize: Int = 8192,
capacity: Int = 20
) : DefaultPool<ByteArray>(capacity) {
override fun produceInstance(): ByteArray = ByteArray(bufferSize)
override fun clearInstance(instance: ByteArray): ByteArray {
instance.fill(0) // Clear sensitive data
return instance
}
override fun validateInstance(instance: ByteArray): Boolean {
return instance.size == bufferSize
}
}
// Usage
suspend fun customPoolExample() {
val stringPool = StringBuilderPool()
val bufferPool = NetworkBufferPool(bufferSize = 4096)
// Use string builder pool
val text = stringPool.useInstance { builder ->
builder.append("Generated at: ")
builder.append(System.currentTimeMillis())
builder.toString()
}
// Use buffer pool
val processedData = bufferPool.useInstance { buffer ->
// Simulate network operation
val data = "Network data".toByteArray()
data.copyInto(buffer, 0, 0, data.size)
// Process buffer
processNetworkData(buffer, data.size)
}
// Clean up pools when done
stringPool.dispose()
bufferPool.dispose()
}
fun processNetworkData(buffer: ByteArray, length: Int): ByteArray {
// Process network data and return result
return buffer.sliceArray(0 until length)
}// Create pool
val pool = CustomPool<MyObject>(capacity = 50)
// Use throughout application lifecycle
repeat(1000) {
pool.useInstance { obj ->
// Use object
}
}
// Dispose when shutting down
pool.dispose()// Always use try-finally or useInstance
val pool = ByteArrayPool
// Preferred: automatic recycling
pool.useInstance { buffer ->
// Use buffer safely
processData(buffer)
}
// Manual: ensure recycling
val buffer = pool.borrow()
try {
processData(buffer)
} finally {
pool.recycle(buffer)
}// Configure pools based on use case
val smallBufferPool = NetworkBufferPool(bufferSize = 1024, capacity = 100)
val largeBufferPool = NetworkBufferPool(bufferSize = 64 * 1024, capacity = 10)
// High-frequency, small objects
val tokenPool = StringBuilderPool(capacity = 200)
// Low-frequency, expensive objects
val connectionPool = CustomPool<DatabaseConnection>(capacity = 5)