In-Memory Cache (Bellek Önbelleği)
Memory cache en hızlı okuma yoludur, ama mobilde RAM sınırlıdır ve işletim sistemi bellek baskısında uygulamayı öldürebilir. Bu yüzden cache boyutu dinamik, temizlenebilir ve ölçülebilir olmalıdır.
Hızlı Karar
| Durum | Memory Cache Kullan | Dikkat |
|---|---|---|
| Küçük ve sık okunan veri | Evet | LRU/LFU yeterli olabilir |
| Büyük bitmap/listeler | Sınırlı | Memory pressure izlenmeli |
| Hassas veri | Kaçın veya kısa ömürlü tut | Dump/log riski |
| Offline kalıcılık | Hayır, disk gerekir | App kill sonrası kaybolur |
Üretim Kontrol Listesi
- Problem: Memory cache hangi latency sorununu çözüyor?
- Çözüm: Max size, eviction, memory warning handling, key ve lifecycle net mi?
- Trade-off: Çok cache hızlıdır; çok bellek kullanımı crash ve OS kill riskini artırır.
- Hata durumu: Memory leak, stale object, unbounded map ve bitmap OOM ele alınmalı.
- Ölçüm: Hit ratio, memory footprint, eviction count, GC pressure ve OOM/crash oranı izlenmeli.
- Güvenlik/maliyet: Token/PII memory'de gereksiz tutulmamalı; düşük RAM cihazlar ayrıca test edilmeli.
Memory Cache Fundamentals
Temel Prensipler
- Amaç: En hızlı veri erişimi ile anında yanıt
- Trade-offs: Bellek kullanımı vs hız vs cache hit oranı
- Mobil Kısıtlamalar: Sınırlı RAM, arka plan uygulama askıya alma
- Cache Boyut Optimizasyonu:
- Kullanılabilir belleğe göre dinamik boyutlandırma
- Bellek baskısı izleme
- Uyarlanabilir cache limitleri
- Cache Entry Türleri:
- Strong references: Sık erişilen öğeler
- Soft references: Bellek baskısına duyarlı öğeler
- Weak references: Geçici cache girdileri
LRU (Least Recently Used) Cache Implementation
Platform-Özel LRU Implementasyonları
Android LruCache
kotlin
class ImageMemoryCache {
private val maxMemory = (Runtime.getRuntime().maxMemory() / 1024).toInt()
private val cacheSize = maxMemory / 8 // Toplam belleğin 1/8'i
private val cache = object : LruCache<String, Bitmap>(cacheSize) {
override fun sizeOf(key: String, bitmap: Bitmap): Int {
// Bitmap boyutunu KB cinsinden döndür
return bitmap.byteCount / 1024
}
override fun entryRemoved(
evicted: Boolean,
key: String,
oldValue: Bitmap,
newValue: Bitmap?
) {
// Çıkarılan bitmap'i temizle
if (evicted && !oldValue.isRecycled) {
oldValue.recycle()
}
}
}
fun put(key: String, bitmap: Bitmap) {
cache.put(key, bitmap)
}
fun get(key: String): Bitmap? {
return cache.get(key)
}
fun evictAll() {
cache.evictAll()
}
// Cache istatistikleri
fun getCacheStats(): String {
return "Hit: ${cache.hitCount()}, Miss: ${cache.missCount()}, Size: ${cache.size()}"
}
}iOS NSCache
swift
class ImageMemoryCache {
private let cache = NSCache<NSString, UIImage>()
init() {
// Toplam maliyet limiti (bytes)
cache.totalCostLimit = 50 * 1024 * 1024 // 50MB
// Maksimum obje sayısı
cache.countLimit = 100
// Bellek uyarılarını dinle
NotificationCenter.default.addObserver(
self,
selector: #selector(clearCache),
name: UIApplication.didReceiveMemoryWarningNotification,
object: nil
)
}
func setImage(_ image: UIImage, forKey key: String) {
let cost = image.jpegData(compressionQuality: 1.0)?.count ?? 0
cache.setObject(image, forKey: key as NSString, cost: cost)
}
func image(forKey key: String) -> UIImage? {
return cache.object(forKey: key as NSString)
}
@objc private func clearCache() {
cache.removeAllObjects()
}
deinit {
NotificationCenter.default.removeObserver(self)
}
}Flutter Memory Cache
dart
class MemoryCache<K, V> {
final int _maxSize;
final Map<K, _CacheEntry<V>> _cache = <K, _CacheEntry<V>>{};
final Queue<K> _accessOrder = Queue<K>();
MemoryCache(this._maxSize);
V? get(K key) {
final entry = _cache[key];
if (entry != null) {
// Access sırasını güncelle
_accessOrder.remove(key);
_accessOrder.addLast(key);
return entry.value;
}
return null;
}
void put(K key, V value) {
if (_cache.containsKey(key)) {
// Mevcut girdiyi güncelle
_cache[key] = _CacheEntry(value, DateTime.now());
_accessOrder.remove(key);
_accessOrder.addLast(key);
} else {
// Yeni girdi ekle
if (_cache.length >= _maxSize) {
_evictLeastRecentlyUsed();
}
_cache[key] = _CacheEntry(value, DateTime.now());
_accessOrder.addLast(key);
}
}
void _evictLeastRecentlyUsed() {
if (_accessOrder.isNotEmpty) {
final lruKey = _accessOrder.removeFirst();
_cache.remove(lruKey);
}
}
void clear() {
_cache.clear();
_accessOrder.clear();
}
int get length => _cache.length;
bool get isEmpty => _cache.isEmpty;
}
class _CacheEntry<V> {
final V value;
final DateTime timestamp;
_CacheEntry(this.value, this.timestamp);
}Gelişmiş LRU Stratejileri
Size-Based Eviction (Boyut Tabanlı Çıkarma)
kotlin
class SizeAwareLruCache<K, V>(
private val maxSize: Long,
private val sizeCalculator: (V) -> Long
) {
private val cache = LinkedHashMap<K, CacheEntry<V>>(16, 0.75f, true)
private var currentSize = 0L
data class CacheEntry<V>(
val value: V,
val size: Long,
val timestamp: Long = System.currentTimeMillis()
)
@Synchronized
fun put(key: K, value: V) {
val size = sizeCalculator(value)
val entry = CacheEntry(value, size)
// Önceki girdiyi kontrol et
cache[key]?.let { oldEntry ->
currentSize -= oldEntry.size
}
cache[key] = entry
currentSize += size
// Boyut limitini aş tıksa eski girdileri çıkar
while (currentSize > maxSize && cache.isNotEmpty()) {
val eldestEntry = cache.entries.first()
cache.remove(eldestEntry.key)
currentSize -= eldestEntry.value.size
}
}
@Synchronized
fun get(key: K): V? {
return cache[key]?.value
}
fun getSize(): Long = currentSize
fun getCount(): Int = cache.size
}Access Pattern Learning (Erişim Kalıpları Öğrenimi)
swift
class SmartCache<Key: Hashable, Value> {
private var cache: [Key: CacheEntry<Value>] = [:]
private let maxSize: Int
private let queue = DispatchQueue(label: "smart.cache.queue", attributes: .concurrent)
struct CacheEntry<Value> {
let value: Value
var accessCount: Int
var lastAccess: Date
var accessPattern: AccessPattern
}
struct AccessPattern {
var hourlyAccess: [Int] = Array(repeating: 0, count: 24)
var dayOfWeekAccess: [Int] = Array(repeating: 0, count: 7)
}
init(maxSize: Int) {
self.maxSize = maxSize
}
func get(_ key: Key) -> Value? {
return queue.sync {
guard var entry = cache[key] else { return nil }
// Erişim istatistiklerini güncelle
entry.accessCount += 1
entry.lastAccess = Date()
updateAccessPattern(&entry.accessPattern)
cache[key] = entry
return entry.value
}
}
func set(_ key: Key, value: Value) {
queue.async(flags: .barrier) {
if self.cache.count >= self.maxSize {
self.evictLeastValuable()
}
let entry = CacheEntry(
value: value,
accessCount: 1,
lastAccess: Date(),
accessPattern: AccessPattern()
)
self.cache[key] = entry
}
}
private func evictLeastValuable() {
let now = Date()
let currentHour = Calendar.current.component(.hour, from: now)
let currentDay = Calendar.current.component(.weekday, from: now)
let leastValuable = cache.min { entry1, entry2 in
let score1 = calculateValue(entry1.value, at: currentHour, day: currentDay)
let score2 = calculateValue(entry2.value, at: currentHour, day: currentDay)
return score1 < score2
}
if let keyToRemove = leastValuable?.key {
cache.removeValue(forKey: keyToRemove)
}
}
private func calculateValue(_ entry: CacheEntry<Value>, at hour: Int, day: Int) -> Double {
let timeFactor = 1.0 / (Date().timeIntervalSince(entry.lastAccess) + 1)
let frequencyFactor = Double(entry.accessCount)
let patternFactor = Double(entry.accessPattern.hourlyAccess[hour] +
entry.accessPattern.dayOfWeekAccess[day])
return timeFactor * frequencyFactor * patternFactor
}
private func updateAccessPattern(_ pattern: inout AccessPattern) {
let now = Date()
let hour = Calendar.current.component(.hour, from: now)
let day = Calendar.current.component(.weekday, from: now) - 1
pattern.hourlyAccess[hour] += 1
pattern.dayOfWeekAccess[day] += 1
}
}Image Caching Solutions
Android Image Caching
Glide Architecture
kotlin
class OptimizedImageLoader(private val context: Context) {
private val glide: RequestManager by lazy {
Glide.with(context)
.applyDefaultRequestOptions(
RequestOptions()
.diskCacheStrategy(DiskCacheStrategy.AUTOMATIC)
.format(DecodeFormat.PREFER_RGB_565) // Bellek tasarrufu
.skipMemoryCache(false)
)
}
fun loadImage(
url: String,
imageView: ImageView,
placeholder: Int? = null,
errorPlaceholder: Int? = null
) {
var request = glide
.load(url)
.thumbnail(0.1f) // %10 boyutunda thumbnail
.transition(DrawableTransitionOptions.withCrossFade())
placeholder?.let { request = request.placeholder(it) }
errorPlaceholder?.let { request = request.error(it) }
request.into(imageView)
}
fun preloadImage(url: String, width: Int, height: Int) {
glide
.load(url)
.override(width, height)
.preload()
}
fun clearMemoryCache() {
Glide.get(context).clearMemory()
}
fun clearDiskCache() {
// Arka plan thread'de çalıştır
Thread {
Glide.get(context).clearDiskCache()
}.start()
}
// Custom cache key generation
fun loadImageWithCustomKey(
url: String,
imageView: ImageView,
customKey: String
) {
glide
.load(url)
.signature(ObjectKey(customKey))
.into(imageView)
}
}
// Custom memory cache configuration
class CustomGlideModule : AppGlideModule() {
override fun applyOptions(context: Context, builder: GlideBuilder) {
val calculator = MemorySizeCalculator.Builder(context)
.setMemoryCacheScreens(2f) // 2 ekran boyutunda cache
.setBitmapPoolScreens(3f)
.build()
builder
.setMemoryCache(LruResourceCache(calculator.memoryCacheSize.toLong()))
.setBitmapPool(LruBitmapPool(calculator.bitmapPoolSize.toLong()))
.setDiskCache(
InternalCacheDiskCacheFactory(
context,
"image_cache",
100 * 1024 * 1024 // 100MB
)
)
}
}Picasso Features
kotlin
class PicassoImageLoader(private val context: Context) {
private val picasso: Picasso by lazy {
Picasso.Builder(context)
.memoryCache(LruCache(calculateMemoryCacheSize()))
.diskCache(createDiskCache())
.indicatorsEnabled(BuildConfig.DEBUG) // Debug modda cache göstergeleri
.build()
}
fun loadImage(
url: String,
imageView: ImageView,
transformation: Transformation? = null
) {
var request = picasso
.load(url)
.placeholder(R.drawable.placeholder)
.error(R.drawable.error_placeholder)
transformation?.let { request = request.transform(it) }
request.into(imageView, object : Callback {
override fun onSuccess() {
// Başarılı yükleme
}
override fun onError(e: Exception?) {
// Hata durumu
logError("Image load failed: ${e?.message}")
}
})
}
private fun calculateMemoryCacheSize(): Int {
val maxMemory = Runtime.getRuntime().maxMemory()
return (maxMemory / 8).toInt() // Toplam belleğin 1/8'i
}
private fun createDiskCache(): Cache {
val cacheDir = File(context.cacheDir, "picasso_cache")
return Cache(cacheDir, 50 * 1024 * 1024) // 50MB
}
// Custom transformation example
class CircleTransformation : Transformation {
override fun transform(source: Bitmap): Bitmap {
val size = minOf(source.width, source.height)
val x = (source.width - size) / 2
val y = (source.height - size) / 2
val squaredBitmap = Bitmap.createBitmap(source, x, y, size, size)
if (squaredBitmap != source) {
source.recycle()
}
val bitmap = Bitmap.createBitmap(size, size, source.config)
val canvas = Canvas(bitmap)
val paint = Paint()
val shader = BitmapShader(squaredBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP)
paint.shader = shader
paint.isAntiAlias = true
val radius = size / 2f
canvas.drawCircle(radius, radius, radius, paint)
squaredBitmap.recycle()
return bitmap
}
override fun key(): String = "circle()"
}
}iOS Image Caching
SDWebImage
swift
class SDWebImageManager {
private let imageManager = SDWebImageManager.shared
func loadImage(
url: URL,
into imageView: UIImageView,
placeholder: UIImage? = nil,
completion: ((UIImage?, Error?) -> Void)? = nil
) {
imageView.sd_setImage(
with: url,
placeholderImage: placeholder,
options: [.progressiveLoad, .retryFailed],
progress: { receivedSize, expectedSize, targetURL in
// Progress tracking
let progress = Double(receivedSize) / Double(expectedSize)
DispatchQueue.main.async {
// Update progress indicator
}
},
completed: { image, error, cacheType, imageURL in
completion?(image, error)
if let error = error {
print("Image load failed: \(error.localizedDescription)")
} else {
print("Image loaded from: \(cacheType)")
}
}
)
}
func preloadImages(urls: [URL]) {
let prefetcher = SDWebImagePrefetcher.shared
prefetcher.prefetchURLs(urls) { completedCount, skippedCount in
print("Preloaded: \(completedCount), Skipped: \(skippedCount)")
}
}
func configureCache() {
let cache = SDImageCache.shared
// Memory cache configuration
cache.config.maxMemoryCost = 50 * 1024 * 1024 // 50MB
cache.config.maxMemoryCount = 100
// Disk cache configuration
cache.config.maxDiskAge = 7 * 24 * 60 * 60 // 7 days
cache.config.maxDiskSize = 100 * 1024 * 1024 // 100MB
// Background cache cleanup
cache.config.shouldDecompressImages = true
cache.config.shouldCacheImagesInMemory = true
}
func clearCache() {
SDImageCache.shared.clearMemory()
SDImageCache.shared.clearDisk()
}
}Kingfisher (Swift)
swift
import Kingfisher
import SwiftUI
class KingfisherImageLoader: ObservableObject {
func configureKingfisher() {
let cache = ImageCache.default
// Memory cache limits
cache.memoryStorage.config.totalCostLimit = 50 * 1024 * 1024 // 50MB
cache.memoryStorage.config.countLimit = 100
// Disk cache limits
cache.diskStorage.config.sizeLimit = 100 * 1024 * 1024 // 100MB
cache.diskStorage.config.expiration = .days(7)
// Network configuration
KingfisherManager.shared.downloader.downloadTimeout = 15.0
}
func loadImage(
url: URL,
into imageView: UIImageView,
placeholder: UIImage? = nil
) {
let processor = DownsamplingImageProcessor(size: imageView.bounds.size)
let options: KingfisherOptionsInfo = [
.processor(processor),
.scaleFactor(UIScreen.main.scale),
.transition(.fade(1.0)),
.cacheOriginalImage
]
imageView.kf.setImage(
with: url,
placeholder: placeholder,
options: options
) { result in
switch result {
case .success(let value):
print("Image loaded: \(value.cacheType)")
case .failure(let error):
print("Image load failed: \(error.localizedDescription)")
}
}
}
}
// SwiftUI integration
struct AsyncImageView: View {
let url: URL
let placeholder: Image
var body: some View {
KFImage(url)
.placeholder {
placeholder
.foregroundColor(.gray)
}
.retry(maxCount: 3, interval: .seconds(5))
.onSuccess { result in
print("Image loaded from: \(result.cacheType)")
}
.onFailure { error in
print("Image load failed: \(error.localizedDescription)")
}
.resizable()
.aspectRatio(contentMode: .fit)
}
}Flutter Image Caching
cached_network_image Package
dart
class FlutterImageCache {
static const String _cacheKey = 'image_cache';
Widget buildCachedImage({
required String imageUrl,
Widget? placeholder,
Widget? errorWidget,
BoxFit? fit,
double? width,
double? height,
}) {
return CachedNetworkImage(
imageUrl: imageUrl,
width: width,
height: height,
fit: fit,
placeholder: (context, url) => placeholder ??
const Center(child: CircularProgressIndicator()),
errorWidget: (context, url, error) => errorWidget ??
const Icon(Icons.error),
memCacheWidth: width?.toInt(),
memCacheHeight: height?.toInt(),
cacheManager: DefaultCacheManager(),
fadeInDuration: const Duration(milliseconds: 300),
fadeOutDuration: const Duration(milliseconds: 300),
);
}
// Custom cache manager
static CacheManager get customCacheManager {
return CacheManager(
Config(
_cacheKey,
stalePeriod: const Duration(days: 7),
maxNrOfCacheObjects: 100,
repo: JsonCacheInfoRepository(databaseName: _cacheKey),
fileService: HttpFileService(),
),
);
}
// Preload images
Future<void> preloadImages(List<String> imageUrls) async {
for (String url in imageUrls) {
try {
await DefaultCacheManager().downloadFile(url);
} catch (e) {
debugPrint('Failed to preload image: $url, Error: $e');
}
}
}
// Clear cache
Future<void> clearCache() async {
await DefaultCacheManager().emptyCache();
}
// Get cache info
Future<void> getCacheInfo() async {
final cacheManager = DefaultCacheManager();
final cacheObjects = await cacheManager.store.getAllObjects();
int totalSize = 0;
for (var obj in cacheObjects) {
final file = await cacheManager.getFileFromCache(obj.key);
if (file != null) {
totalSize += await file.file.length();
}
}
debugPrint('Cache objects: ${cacheObjects.length}');
debugPrint('Total cache size: ${totalSize / (1024 * 1024)} MB');
}
}
// Advanced image widget with custom caching
class AdvancedCachedImage extends StatefulWidget {
final String imageUrl;
final double? width;
final double? height;
final BoxFit fit;
const AdvancedCachedImage({
Key? key,
required this.imageUrl,
this.width,
this.height,
this.fit = BoxFit.cover,
}) : super(key: key);
@override
_AdvancedCachedImageState createState() => _AdvancedCachedImageState();
}
class _AdvancedCachedImageState extends State<AdvancedCachedImage> {
final MemoryCache<String, Uint8List> _memoryCache = MemoryCache<String, Uint8List>(50);
@override
Widget build(BuildContext context) {
// First check memory cache
final cachedData = _memoryCache.get(widget.imageUrl);
if (cachedData != null) {
return Image.memory(
cachedData,
width: widget.width,
height: widget.height,
fit: widget.fit,
);
}
return CachedNetworkImage(
imageUrl: widget.imageUrl,
width: widget.width,
height: widget.height,
fit: widget.fit,
imageBuilder: (context, imageProvider) {
// Cache in memory for faster access
_cacheImageInMemory(widget.imageUrl, imageProvider);
return Image(
image: imageProvider,
width: widget.width,
height: widget.height,
fit: widget.fit,
);
},
placeholder: (context, url) => Container(
width: widget.width,
height: widget.height,
color: Colors.grey[300],
child: const Center(child: CircularProgressIndicator()),
),
errorWidget: (context, url, error) => Container(
width: widget.width,
height: widget.height,
color: Colors.grey[300],
child: const Icon(Icons.error),
),
);
}
void _cacheImageInMemory(String url, ImageProvider imageProvider) async {
try {
final completer = Completer<Uint8List>();
final stream = imageProvider.resolve(const ImageConfiguration());
stream.addListener(ImageStreamListener((ImageInfo info, bool _) async {
final byteData = await info.image.toByteData(format: ImageByteFormat.png);
if (byteData != null) {
completer.complete(byteData.buffer.asUint8List());
}
}));
final bytes = await completer.future;
_memoryCache.put(url, bytes);
} catch (e) {
debugPrint('Failed to cache image in memory: $e');
}
}
}Memory Management Integration
Garbage Collection Awareness
Android Memory Management
kotlin
class MemoryAwareCache<K, V> {
private val cache = ConcurrentHashMap<K, SoftReference<V>>()
private val hardReferences = LinkedHashMap<K, V>(16, 0.75f, true)
private val maxHardReferences: Int
init {
// Kullanılabilir belleğin %25'i kadar hard reference
val runtime = Runtime.getRuntime()
val maxMemory = runtime.maxMemory()
maxHardReferences = (maxMemory / (4 * 1024)).toInt() // Rough estimation
// Memory pressure listener
registerMemoryPressureListener()
}
fun put(key: K, value: V) {
// Hard reference cache'e ekle
synchronized(hardReferences) {
hardReferences[key] = value
if (hardReferences.size > maxHardReferences) {
val eldest = hardReferences.entries.first()
hardReferences.remove(eldest.key)
}
}
// Soft reference cache'e de ekle
cache[key] = SoftReference(value)
}
fun get(key: K): V? {
// Önce hard reference'larda ara
synchronized(hardReferences) {
hardReferences[key]?.let { value ->
// LRU order'ı güncelle
hardReferences.remove(key)
hardReferences[key] = value
return value
}
}
// Sonra soft reference'larda ara
val softRef = cache[key]
val value = softRef?.get()
if (value == null) {
// Soft reference garbage collect edilmiş
cache.remove(key)
} else {
// Hard reference cache'e geri ekle
synchronized(hardReferences) {
if (hardReferences.size < maxHardReferences) {
hardReferences[key] = value
}
}
}
return value
}
private fun registerMemoryPressureListener() {
// ComponentCallbacks2 kullanarak memory pressure'ı dinle
val context = /* get application context */
context.registerComponentCallbacks(object : ComponentCallbacks2 {
override fun onConfigurationChanged(newConfig: Configuration) {}
override fun onLowMemory() {
clearHardReferences()
}
override fun onTrimMemory(level: Int) {
when (level) {
ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE -> {
// Bellek baskısı artıyor, cache'i küçült
trimCache(0.5f)
}
ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW -> {
// Yüksek bellek baskısı
trimCache(0.25f)
}
ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL -> {
// Kritik bellek durumu
clearHardReferences()
}
}
}
})
}
private fun trimCache(retainFactor: Float) {
synchronized(hardReferences) {
val targetSize = (hardReferences.size * retainFactor).toInt()
while (hardReferences.size > targetSize) {
val eldest = hardReferences.entries.first()
hardReferences.remove(eldest.key)
}
}
}
private fun clearHardReferences() {
synchronized(hardReferences) {
hardReferences.clear()
}
}
}iOS Memory Management
swift
class MemoryAwareCache<Key: Hashable, Value: AnyObject> {
private var cache: [Key: Value] = [:]
private let queue = DispatchQueue(label: "memory.cache.queue", attributes: .concurrent)
private var memoryWarningObserver: NSObjectProtocol?
init() {
// Memory warning observer
memoryWarningObserver = NotificationCenter.default.addObserver(
forName: UIApplication.didReceiveMemoryWarningNotification,
object: nil,
queue: nil
) { [weak self] _ in
self?.handleMemoryWarning()
}
}
deinit {
if let observer = memoryWarningObserver {
NotificationCenter.default.removeObserver(observer)
}
}
func set(_ value: Value, forKey key: Key) {
queue.async(flags: .barrier) {
self.cache[key] = value
// Memory pressure check
if self.cache.count > self.calculateOptimalCacheSize() {
self.trimCache()
}
}
}
func object(forKey key: Key) -> Value? {
return queue.sync {
return cache[key]
}
}
private func calculateOptimalCacheSize() -> Int {
let info = mach_task_basic_info()
var count = mach_msg_type_number_t(MemoryLayout<mach_task_basic_info>.size)/4
let result = withUnsafeMutablePointer(to: &info) {
$0.withMemoryRebound(to: integer_t.self, capacity: 1) {
task_info(mach_task_self_, task_flavor_t(MACH_TASK_BASIC_INFO), $0, &count)
}
}
if result == KERN_SUCCESS {
let usedMemory = info.resident_size
let totalMemory = ProcessInfo.processInfo.physicalMemory
let memoryPressure = Double(usedMemory) / Double(totalMemory)
// Bellek baskısına göre cache boyutunu ayarla
let baseCacheSize = 100
let adjustmentFactor = max(0.1, 1.0 - memoryPressure)
return Int(Double(baseCacheSize) * adjustmentFactor)
}
return 50 // Default fallback
}
private func trimCache() {
let targetSize = calculateOptimalCacheSize()
let keysToRemove = cache.keys.prefix(cache.count - targetSize)
for key in keysToRemove {
cache.removeValue(forKey: key)
}
}
private func handleMemoryWarning() {
queue.async(flags: .barrier) {
// Memory warning durumunda cache'in %75'ini temizle
let targetSize = self.cache.count / 4
let keysToRemove = self.cache.keys.prefix(self.cache.count - targetSize)
for key in keysToRemove {
self.cache.removeValue(forKey: key)
}
}
}
func removeAll() {
queue.async(flags: .barrier) {
self.cache.removeAll()
}
}
}Thread-Safe Cache Design
Concurrent Access Patterns
kotlin
class ConcurrentLruCache<K, V>(
private val maxSize: Int
) {
private val cache = ConcurrentHashMap<K, CacheEntry<V>>()
private val accessOrder = ConcurrentLinkedQueue<K>()
private val lock = ReentrantReadWriteLock()
private val readLock = lock.readLock()
private val writeLock = lock.writeLock()
data class CacheEntry<V>(
val value: V,
@Volatile var accessTime: Long = System.nanoTime()
)
fun get(key: K): V? {
readLock.lock()
try {
val entry = cache[key] ?: return null
// Access time'ı güncelle (write lock gerekmez, volatile)
entry.accessTime = System.nanoTime()
// Access order'ı güncelle
updateAccessOrder(key)
return entry.value
} finally {
readLock.unlock()
}
}
fun put(key: K, value: V) {
writeLock.lock()
try {
// Önce boyut kontrolü
if (cache.size >= maxSize && !cache.containsKey(key)) {
evictLeastRecentlyUsed()
}
val entry = CacheEntry(value)
cache[key] = entry
accessOrder.offer(key)
} finally {
writeLock.unlock()
}
}
private fun updateAccessOrder(key: K) {
// Non-blocking access order update
accessOrder.remove(key)
accessOrder.offer(key)
}
private fun evictLeastRecentlyUsed() {
// En eski erişilen elemanı bul ve çıkar
var oldestKey: K? = null
var oldestTime = Long.MAX_VALUE
for ((key, entry) in cache) {
if (entry.accessTime < oldestTime) {
oldestTime = entry.accessTime
oldestKey = key
}
}
oldestKey?.let { key ->
cache.remove(key)
accessOrder.remove(key)
}
}
fun size(): Int {
readLock.lock()
try {
return cache.size
} finally {
readLock.unlock()
}
}
fun clear() {
writeLock.lock()
try {
cache.clear()
accessOrder.clear()
} finally {
writeLock.unlock()
}
}
}Performance Monitoring
Cache Metrics Collection
kotlin
class CacheMetrics {
private val hitCount = AtomicLong(0)
private val missCount = AtomicLong(0)
private val putCount = AtomicLong(0)
private val evictionCount = AtomicLong(0)
fun recordHit() = hitCount.incrementAndGet()
fun recordMiss() = missCount.incrementAndGet()
fun recordPut() = putCount.incrementAndGet()
fun recordEviction() = evictionCount.incrementAndGet()
fun getHitRate(): Double {
val totalRequests = hitCount.get() + missCount.get()
return if (totalRequests == 0L) 0.0 else hitCount.get().toDouble() / totalRequests
}
fun getStats(): CacheStats {
return CacheStats(
hitCount = hitCount.get(),
missCount = missCount.get(),
putCount = putCount.get(),
evictionCount = evictionCount.get(),
hitRate = getHitRate()
)
}
fun reset() {
hitCount.set(0)
missCount.set(0)
putCount.set(0)
evictionCount.set(0)
}
}
data class CacheStats(
val hitCount: Long,
val missCount: Long,
val putCount: Long,
val evictionCount: Long,
val hitRate: Double
)Bu kapsamlı bellek cache implementasyonu, mobil uygulamalarda performans optimizasyonu için gerekli tüm temel bileşenleri içermektedir. Platform-özel optimizasyonlar ve gerçek dünya kullanım senaryoları göz önünde bulundurularak tasarlanmıştır.
