In-Out-Process Java cache (L1/L2 off-heap, scalable, ZeroGC) with full SSD support
113
457 commits
updated Dec 24, 2025
Documentation 🥕🥕 Forum & Discussions 🥕🥕
The Carrot Cache (CC) project aims to modernize data caching and enable users to build custom caching solutions through pluggable components within the CC framework. CC is a 100% Java solution that extensively utilizes off-heap memory, minimizing the impact on Java garbage collection (GC).
You can download Carrot Cache Jar - file with dependencies from the Maven Repo Releases page.
Alternatively you can use Maven or Gradle to add carrot-cache to your project.
Maven
<dependency>
<groupId>io.carrotdata</groupId>
<artifactId>carrot-cache</artifactId>
<version>${carrot-cache.version}</version>
</dependency>
Gradle
dependencies {
implementation "io.carrotdata:carrot-cache:${carrotCacheVersion}"
}
Carrot Cache binaries already support many platforms (you can find the list on zstd-jni page).
import com.carrotdata.cache.*;
protected Cache createInMemoryCache(String cacheName) throws IOException {
// Data directory is needed even for in-memory cache; this is where
// data from memory can be saved to
Path dataDirPath = Files.createTempDirectory(null);
String dataDir = dataDirPath.toFile().getAbsolutePath();
Builder builder = new Builder(cacheName);
builder
.withCacheDataSegmentSize(16_777_216) // 16MB
.withCacheMaximumSize(8_589_934_592) // 8GB
.withRecyclingSelector(MinAliveRecyclingSelector.class.getName())
.withDataDir(dataDir)
.withMainQueueIndexFormat(SubCompactBaseWithExpireIndexFormat.class.getName()) // This index format supports cache expiration
.withAdmissionController(ExpirationAwareAdmissionController.class.getName()); // This controller does some smart things :)
return builder.buildMemoryCache();
}
import com.carrotdata.cache.*;
protected Cache createDiskCache(String cacheName) throws IOException {
Path dataDirPath = Files.createTempDirectory(null);
String dataDir = dataDirPath.toFile().getAbsolutePath();
Builder builder = new Builder(cacheName);
builder
.withCacheDataSegmentSize(67_108_864) // 64MB
.withCacheMaximumSize(687_194_767_360) // 640GB
.withRecyclingSelector(MinAliveRecyclingSelector.class.getName()) // Specify recycling selector type
.withDataDir(dataDir)
.withMainQueueIndexFormat(SubCompactBaseWithExpireIndexFormat.class.getName()) // This index format supports cache expiration
.withAdmissionController(ExpirationAwareAdmissionController.class.getName()); // Specify cache admission controller
return builder.buildDiskCache();
}
protected Cache createHybridCache(String ramCacheName, String diskCacheName) throws IOException {
Cache ramCache = createInMemoryCache(ramCacheName);
Cache diskCache = createDiskCache(diskCacheName);
ramCache.setVictimCache(diskCache);
return ramCache;
}
import com.carrotdata.cache.*;
protected Cache createInMemoryCompressedCache(String cacheName) throws IOException {
// Data directory is needed even for in-memory cache; this is where
// data from memory can be saved to
Path dataDirPath = Files.createTempDirectory(null);
String dataDir = dataDirPath.toFile().getAbsolutePath();
Builder builder = new Builder(cacheName);
builder
.withCacheDataSegmentSize(16_777_216) // 16MB
.withCacheMaximumSize(34_359_738_368) // 32GB
.withCacheCompressionEnabled(true) // Enable compression
.withRecyclingSelector(MinAliveRecyclingSelector.class.getName())
.withDataDir(dataDir)
.withMainQueueIndexFormat(SubCompactBaseWithExpireIndexFormat.class.getName()) // This index format supports cache expiration
.withAdmissionController(ExpirationAwareAdmissionController.class.getName()); // This controller does some smart things :)
return builder.buildMemoryCache();
}
protected Cache createTandemCache(String ramCacheName, String ramCompressedCacheName) throws IOException {
Cache ramCache = createInMemoryCache(ramCacheName);
Cache compCache = createInMemoryCompressedCache(ramCompressedCacheName);
ramCache.setVictimCache(compCache);
return ramCache;
}
At a minimum, you need to provide the maximum cache size, the data segment size (if you do not like the default - 4MB), and the data directory. All other parameters will use their default values. It is a good idea to read the com.carrotdata.cache.util.CacheConfig class, which contains all configuration parameters with annotations and default values.
Cache cache = createInMemoryCache("ram1");
byte[] key1 = "key1".getBytes(StandardCharsets.UTF_8);
byte[] value1 = "value1".getBytes(StandardCharsets.UTF_8);
// Put key-value without expiration time
cache.put(key1, value1, 0);
byte[] key2 = "key2".getBytes(StandardCharsets.UTF_8);
byte[] value2 = "value2".getBytes(StandardCharsets.UTF_8);
// Put key-value with expiration time of 1 minute
cache.put(key2, value2, System.currentTimeMillis() + 60 * 1000);
byte[] buffer = new byte[value2.length];
int size = cache.get(key2, 0, key2.length, buffer, 0);
String result = new String(buffer, 0, size, StandardCharsets.UTF_8);
System.out.printf("Value for key %s is %s", key2, result);
... because the code has direct access to the native memory via the sun.misc.Unsafe class, and this is a beta version.
To debug possible core dumps, you need to activate the debug mode in the memory allocator:
UnsafeAccess.setMallocDebugEnabled(true);
This will prevent core dumps and will throw an exception on memory corruption, but it will run significantly slower. Use this only for small-scale tests.
To track potential memory leaks, such as allocations of size 64, you need to additionally enable the allocations stack trace monitoring:
UnsafeAccess.setMallocDebugStackTraceEnabled(true);
UnsafeAccess.setStackTraceRecordingFilter(x -> x == 64);
UnsafeAccess.setStackTraceRecordingLimit(100); // Record first 100 allocations only
Bear in mind that allocations tracking is expensive and slows down the application by a factor of 20-30. To check the code for memory leaks, you need to enable debug mode (as described above) and use the provided API to print the memory allocator statistics:
UnsafeAccess.mallocStats.printStats();
Contact: vlad@trycarrots.io
Copyright (c) Carrot Data, Inc. 2024
In-Out-Process Java cache (L1/L2 off-heap, scalable, ZeroGC) with full SSD support
113
457 commits
updated Dec 24, 2025
Documentation 🥕🥕 Forum & Discussions 🥕🥕
The Carrot Cache (CC) project aims to modernize data caching and enable users to build custom caching solutions through pluggable components within the CC framework. CC is a 100% Java solution that extensively utilizes off-heap memory, minimizing the impact on Java garbage collection (GC).
You can download Carrot Cache Jar - file with dependencies from the Maven Repo Releases page.
Alternatively you can use Maven or Gradle to add carrot-cache to your project.
Maven
<dependency>
<groupId>io.carrotdata</groupId>
<artifactId>carrot-cache</artifactId>
<version>${carrot-cache.version}</version>
</dependency>
Gradle
dependencies {
implementation "io.carrotdata:carrot-cache:${carrotCacheVersion}"
}
Carrot Cache binaries already support many platforms (you can find the list on zstd-jni page).
import com.carrotdata.cache.*;
protected Cache createInMemoryCache(String cacheName) throws IOException {
// Data directory is needed even for in-memory cache; this is where
// data from memory can be saved to
Path dataDirPath = Files.createTempDirectory(null);
String dataDir = dataDirPath.toFile().getAbsolutePath();
Builder builder = new Builder(cacheName);
builder
.withCacheDataSegmentSize(16_777_216) // 16MB
.withCacheMaximumSize(8_589_934_592) // 8GB
.withRecyclingSelector(MinAliveRecyclingSelector.class.getName())
.withDataDir(dataDir)
.withMainQueueIndexFormat(SubCompactBaseWithExpireIndexFormat.class.getName()) // This index format supports cache expiration
.withAdmissionController(ExpirationAwareAdmissionController.class.getName()); // This controller does some smart things :)
return builder.buildMemoryCache();
}
import com.carrotdata.cache.*;
protected Cache createDiskCache(String cacheName) throws IOException {
Path dataDirPath = Files.createTempDirectory(null);
String dataDir = dataDirPath.toFile().getAbsolutePath();
Builder builder = new Builder(cacheName);
builder
.withCacheDataSegmentSize(67_108_864) // 64MB
.withCacheMaximumSize(687_194_767_360) // 640GB
.withRecyclingSelector(MinAliveRecyclingSelector.class.getName()) // Specify recycling selector type
.withDataDir(dataDir)
.withMainQueueIndexFormat(SubCompactBaseWithExpireIndexFormat.class.getName()) // This index format supports cache expiration
.withAdmissionController(ExpirationAwareAdmissionController.class.getName()); // Specify cache admission controller
return builder.buildDiskCache();
}
protected Cache createHybridCache(String ramCacheName, String diskCacheName) throws IOException {
Cache ramCache = createInMemoryCache(ramCacheName);
Cache diskCache = createDiskCache(diskCacheName);
ramCache.setVictimCache(diskCache);
return ramCache;
}
import com.carrotdata.cache.*;
protected Cache createInMemoryCompressedCache(String cacheName) throws IOException {
// Data directory is needed even for in-memory cache; this is where
// data from memory can be saved to
Path dataDirPath = Files.createTempDirectory(null);
String dataDir = dataDirPath.toFile().getAbsolutePath();
Builder builder = new Builder(cacheName);
builder
.withCacheDataSegmentSize(16_777_216) // 16MB
.withCacheMaximumSize(34_359_738_368) // 32GB
.withCacheCompressionEnabled(true) // Enable compression
.withRecyclingSelector(MinAliveRecyclingSelector.class.getName())
.withDataDir(dataDir)
.withMainQueueIndexFormat(SubCompactBaseWithExpireIndexFormat.class.getName()) // This index format supports cache expiration
.withAdmissionController(ExpirationAwareAdmissionController.class.getName()); // This controller does some smart things :)
return builder.buildMemoryCache();
}
protected Cache createTandemCache(String ramCacheName, String ramCompressedCacheName) throws IOException {
Cache ramCache = createInMemoryCache(ramCacheName);
Cache compCache = createInMemoryCompressedCache(ramCompressedCacheName);
ramCache.setVictimCache(compCache);
return ramCache;
}
At a minimum, you need to provide the maximum cache size, the data segment size (if you do not like the default - 4MB), and the data directory. All other parameters will use their default values. It is a good idea to read the com.carrotdata.cache.util.CacheConfig class, which contains all configuration parameters with annotations and default values.
Cache cache = createInMemoryCache("ram1");
byte[] key1 = "key1".getBytes(StandardCharsets.UTF_8);
byte[] value1 = "value1".getBytes(StandardCharsets.UTF_8);
// Put key-value without expiration time
cache.put(key1, value1, 0);
byte[] key2 = "key2".getBytes(StandardCharsets.UTF_8);
byte[] value2 = "value2".getBytes(StandardCharsets.UTF_8);
// Put key-value with expiration time of 1 minute
cache.put(key2, value2, System.currentTimeMillis() + 60 * 1000);
byte[] buffer = new byte[value2.length];
int size = cache.get(key2, 0, key2.length, buffer, 0);
String result = new String(buffer, 0, size, StandardCharsets.UTF_8);
System.out.printf("Value for key %s is %s", key2, result);
... because the code has direct access to the native memory via the sun.misc.Unsafe class, and this is a beta version.
To debug possible core dumps, you need to activate the debug mode in the memory allocator:
UnsafeAccess.setMallocDebugEnabled(true);
This will prevent core dumps and will throw an exception on memory corruption, but it will run significantly slower. Use this only for small-scale tests.
To track potential memory leaks, such as allocations of size 64, you need to additionally enable the allocations stack trace monitoring:
UnsafeAccess.setMallocDebugStackTraceEnabled(true);
UnsafeAccess.setStackTraceRecordingFilter(x -> x == 64);
UnsafeAccess.setStackTraceRecordingLimit(100); // Record first 100 allocations only
Bear in mind that allocations tracking is expensive and slows down the application by a factor of 20-30. To check the code for memory leaks, you need to enable debug mode (as described above) and use the provided API to print the memory allocator statistics:
UnsafeAccess.mallocStats.printStats();
Contact: vlad@trycarrots.io
Copyright (c) Carrot Data, Inc. 2024