Mastering Application Performance through Strategic State Management
Article
Snapshotting is a powerful technique for performance optimization that involves capturing the state of an application at specific points in time. This enables performance analysis, debugging, state restoration, and optimization opportunities. In Java, snapshotting can be implemented at various levels - from object state capture to full JVM state preservation.
Snapshotting Architecture Overview
Key Concepts:
- State Capture: Recording application state at a specific moment
- Incremental Snapshots: Only capturing changed data
- Lazy Snapshots: Deferred state capture for performance
- Snapshot Compression: Reducing storage overhead
- Snapshot Analytics: Analyzing captured data for insights
Snapshot Types:
- Memory Snapshots: Heap dumps and object graphs
- Performance Snapshots: Metrics and profiling data
- Application State Snapshots: Business object states
- Distributed Snapshots: Coordinated state across services
1. Core Snapshot Framework
Let's start with a foundational snapshot framework:
package com.snapshot.core;
import java.io.*;
import java.lang.management.*;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
import java.util.zip.GZIPOutputStream;
// Snapshot metadata
public class SnapshotMetadata {
private final String snapshotId;
private final LocalDateTime timestamp;
private final String description;
private final Map<String, Object> tags;
private final long sizeBytes;
private final SnapshotType type;
public SnapshotMetadata(String snapshotId, LocalDateTime timestamp,
String description, long sizeBytes, SnapshotType type) {
this.snapshotId = snapshotId;
this.timestamp = timestamp;
this.description = description;
this.sizeBytes = sizeBytes;
this.type = type;
this.tags = new HashMap<>();
}
// Getters
public String getSnapshotId() { return snapshotId; }
public LocalDateTime getTimestamp() { return timestamp; }
public String getDescription() { return description; }
public long getSizeBytes() { return sizeBytes; }
public SnapshotType getType() { return type; }
public Map<String, Object> getTags() { return tags; }
public void addTag(String key, Object value) {
tags.put(key, value);
}
}
// Snapshot types
enum SnapshotType {
MEMORY,
PERFORMANCE,
APPLICATION_STATE,
DISTRIBUTED,
INCREMENTAL
}
// Snapshot result
class SnapshotResult {
private final boolean success;
private final String snapshotId;
private final String errorMessage;
private final long durationMs;
private final long sizeBytes;
public SnapshotResult(boolean success, String snapshotId, String errorMessage,
long durationMs, long sizeBytes) {
this.success = success;
this.snapshotId = snapshotId;
this.errorMessage = errorMessage;
this.durationMs = durationMs;
this.sizeBytes = sizeBytes;
}
public static SnapshotResult success(String snapshotId, long durationMs, long sizeBytes) {
return new SnapshotResult(true, snapshotId, null, durationMs, sizeBytes);
}
public static SnapshotResult failure(String errorMessage) {
return new SnapshotResult(false, null, errorMessage, 0, 0);
}
// Getters
public boolean isSuccess() { return success; }
public String getSnapshotId() { return snapshotId; }
public String getErrorMessage() { return errorMessage; }
public long getDurationMs() { return durationMs; }
public long getSizeBytes() { return sizeBytes; }
}
// Base snapshot interface
public interface SnapshotTaker<T> {
SnapshotResult takeSnapshot(String description, Map<String, Object> context);
SnapshotResult restoreSnapshot(String snapshotId, Map<String, Object> context);
SnapshotMetadata getSnapshotMetadata(String snapshotId);
List<SnapshotMetadata> listSnapshots();
boolean deleteSnapshot(String snapshotId);
}
// Snapshot storage interface
interface SnapshotStorage {
void store(String snapshotId, byte[] data, SnapshotMetadata metadata) throws IOException;
byte[] retrieve(String snapshotId) throws IOException;
void delete(String snapshotId) throws IOException;
boolean exists(String snapshotId);
List<String> listAll();
}
// File-based snapshot storage
class FileSnapshotStorage implements SnapshotStorage {
private final File storageDirectory;
public FileSnapshotStorage(String basePath) {
this.storageDirectory = new File(basePath);
if (!storageDirectory.exists()) {
storageDirectory.mkdirs();
}
}
@Override
public void store(String snapshotId, byte[] data, SnapshotMetadata metadata) throws IOException {
File snapshotFile = getSnapshotFile(snapshotId);
File metadataFile = getMetadataFile(snapshotId);
// Compress data before storage
byte[] compressedData = compressData(data);
try (FileOutputStream fos = new FileOutputStream(snapshotFile)) {
fos.write(compressedData);
}
// Store metadata
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(metadataFile))) {
oos.writeObject(metadata);
}
}
@Override
public byte[] retrieve(String snapshotId) throws IOException {
File snapshotFile = getSnapshotFile(snapshotId);
if (!snapshotFile.exists()) {
throw new IOException("Snapshot not found: " + snapshotId);
}
byte[] compressedData = java.nio.file.Files.readAllBytes(snapshotFile.toPath());
return decompressData(compressedData);
}
@Override
public void delete(String snapshotId) throws IOException {
File snapshotFile = getSnapshotFile(snapshotId);
File metadataFile = getMetadataFile(snapshotId);
if (snapshotFile.exists()) {
snapshotFile.delete();
}
if (metadataFile.exists()) {
metadataFile.delete();
}
}
@Override
public boolean exists(String snapshotId) {
return getSnapshotFile(snapshotId).exists();
}
@Override
public List<String> listAll() {
File[] files = storageDirectory.listFiles((dir, name) -> name.endsWith(".snapshot"));
if (files == null) return Collections.emptyList();
return Arrays.stream(files)
.map(file -> file.getName().replace(".snapshot", ""))
.collect(java.util.stream.Collectors.toList());
}
public SnapshotMetadata getMetadata(String snapshotId) throws IOException {
File metadataFile = getMetadataFile(snapshotId);
if (!metadataFile.exists()) {
throw new IOException("Metadata not found for snapshot: " + snapshotId);
}
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(metadataFile))) {
return (SnapshotMetadata) ois.readObject();
} catch (ClassNotFoundException e) {
throw new IOException("Failed to read metadata", e);
}
}
private File getSnapshotFile(String snapshotId) {
return new File(storageDirectory, snapshotId + ".snapshot");
}
private File getMetadataFile(String snapshotId) {
return new File(storageDirectory, snapshotId + ".metadata");
}
private byte[] compressData(byte[] data) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try (GZIPOutputStream gzos = new GZIPOutputStream(baos)) {
gzos.write(data);
}
return baos.toByteArray();
}
private byte[] decompressData(byte[] compressedData) throws IOException {
try (java.util.zip.GZIPInputStream gzis = new java.util.zip.GZIPInputStream(
new ByteArrayInputStream(compressedData));
ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
byte[] buffer = new byte[1024];
int len;
while ((len = gzis.read(buffer)) > 0) {
baos.write(buffer, 0, len);
}
return baos.toByteArray();
}
}
}
2. Memory Snapshot Implementation
package com.snapshot.memory;
import com.snapshot.core.*;
import java.io.*;
import java.lang.management.*;
import java.lang.ref.WeakReference;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
public class MemorySnapshotTaker implements SnapshotTaker<MemoryStats> {
private final SnapshotStorage storage;
private final MemoryMXBean memoryMXBean;
private final ThreadMXBean threadMXBean;
private final AtomicLong snapshotCounter;
// Cache for object graphs
private final Map<String, WeakReference<Object>> objectCache;
public MemorySnapshotTaker(SnapshotStorage storage) {
this.storage = storage;
this.memoryMXBean = ManagementFactory.getMemoryMXBean();
this.threadMXBean = ManagementFactory.getThreadMXBean();
this.snapshotCounter = new AtomicLong(0);
this.objectCache = new ConcurrentHashMap<>();
}
@Override
public SnapshotResult takeSnapshot(String description, Map<String, Object> context) {
long startTime = System.currentTimeMillis();
String snapshotId = generateSnapshotId();
try {
// Capture memory statistics
MemoryStats memoryStats = captureMemoryStats();
// Capture thread information
ThreadStats threadStats = captureThreadStats();
// Capture garbage collection stats
GCStats gcStats = captureGCStats();
// Create snapshot data
MemorySnapshotData snapshotData = new MemorySnapshotData(
memoryStats, threadStats, gcStats, LocalDateTime.now()
);
// Serialize snapshot data
byte[] serializedData = serializeSnapshotData(snapshotData);
// Create metadata
SnapshotMetadata metadata = new SnapshotMetadata(
snapshotId, LocalDateTime.now(), description,
serializedData.length, SnapshotType.MEMORY
);
metadata.addTag("heapUsed", memoryStats.getHeapUsed());
metadata.addTag("nonHeapUsed", memoryStats.getNonHeapUsed());
metadata.addTag("threadCount", threadStats.getThreadCount());
// Store snapshot
storage.store(snapshotId, serializedData, metadata);
long duration = System.currentTimeMillis() - startTime;
return SnapshotResult.success(snapshotId, duration, serializedData.length);
} catch (Exception e) {
return SnapshotResult.failure("Memory snapshot failed: " + e.getMessage());
}
}
@Override
public SnapshotResult restoreSnapshot(String snapshotId, Map<String, Object> context) {
// Memory snapshots are typically for analysis, not restoration
// But we can restore some state if needed
return SnapshotResult.failure("Memory snapshot restoration not supported");
}
@Override
public SnapshotMetadata getSnapshotMetadata(String snapshotId) {
try {
if (storage instanceof FileSnapshotStorage) {
return ((FileSnapshotStorage) storage).getMetadata(snapshotId);
}
throw new UnsupportedOperationException("Metadata retrieval not supported");
} catch (IOException e) {
throw new RuntimeException("Failed to get metadata", e);
}
}
@Override
public List<SnapshotMetadata> listSnapshots() {
List<SnapshotMetadata> snapshots = new ArrayList<>();
for (String snapshotId : storage.listAll()) {
try {
snapshots.add(getSnapshotMetadata(snapshotId));
} catch (Exception e) {
// Skip corrupted metadata
}
}
return snapshots;
}
@Override
public boolean deleteSnapshot(String snapshotId) {
try {
storage.delete(snapshotId);
return true;
} catch (IOException e) {
return false;
}
}
// Advanced: Object graph snapshot
public SnapshotResult takeObjectGraphSnapshot(Object root, String description) {
long startTime = System.currentTimeMillis();
String snapshotId = generateSnapshotId();
try {
ObjectGraphSnapshot graphSnapshot = captureObjectGraph(root);
byte[] serializedData = serializeObjectGraph(graphSnapshot);
SnapshotMetadata metadata = new SnapshotMetadata(
snapshotId, LocalDateTime.now(), description,
serializedData.length, SnapshotType.MEMORY
);
metadata.addTag("objectCount", graphSnapshot.getObjectCount());
metadata.addTag("rootClass", root.getClass().getName());
storage.store(snapshotId, serializedData, metadata);
long duration = System.currentTimeMillis() - startTime;
return SnapshotResult.success(snapshotId, duration, serializedData.length);
} catch (Exception e) {
return SnapshotResult.failure("Object graph snapshot failed: " + e.getMessage());
}
}
private MemoryStats captureMemoryStats() {
MemoryUsage heapUsage = memoryMXBean.getHeapMemoryUsage();
MemoryUsage nonHeapUsage = memoryMXBean.getNonHeapMemoryUsage();
return new MemoryStats(
heapUsage.getUsed(),
heapUsage.getMax(),
heapUsage.getCommitted(),
nonHeapUsage.getUsed(),
nonHeapUsage.getMax(),
nonHeapUsage.getCommitted(),
Runtime.getRuntime().freeMemory(),
Runtime.getRuntime().totalMemory(),
Runtime.getRuntime().maxMemory()
);
}
private ThreadStats captureThreadStats() {
int threadCount = threadMXBean.getThreadCount();
long totalStarted = threadMXBean.getTotalStartedThreadCount();
int peakThreadCount = threadMXBean.getPeakThreadCount();
int daemonThreadCount = threadMXBean.getDaemonThreadCount();
// Capture thread details
Map<Thread.State, Integer> threadStateCounts = new HashMap<>();
ThreadInfo[] threadInfos = threadMXBean.dumpAllThreads(false, false);
for (ThreadInfo info : threadInfos) {
Thread.State state = info.getThreadState();
threadStateCounts.merge(state, 1, Integer::sum);
}
return new ThreadStats(
threadCount, peakThreadCount, daemonThreadCount,
totalStarted, threadStateCounts
);
}
private GCStats captureGCStats() {
List<GarbageCollectorMXBean> gcBeans = ManagementFactory.getGarbageCollectorMXBeans();
List<GCBeanStats> gcBeanStats = new ArrayList<>();
for (GarbageCollectorMXBean gcBean : gcBeans) {
gcBeanStats.add(new GCBeanStats(
gcBean.getName(),
gcBean.getCollectionCount(),
gcBean.getCollectionTime()
));
}
return new GCStats(gcBeanStats);
}
private ObjectGraphSnapshot captureObjectGraph(Object root) {
ObjectGraphSnapshot graph = new ObjectGraphSnapshot();
captureObjectRecursive(root, graph, new IdentityHashMap<>());
return graph;
}
private void captureObjectRecursive(Object obj, ObjectGraphSnapshot graph,
Map<Object, String> visited) {
if (obj == null || visited.containsKey(obj)) {
return;
}
String objectId = "obj_" + System.identityHashCode(obj);
visited.put(obj, objectId);
ObjectNode node = new ObjectNode(
objectId,
obj.getClass().getName(),
System.identityHashCode(obj),
estimateObjectSize(obj)
);
graph.addNode(node);
// Recursively process fields (simplified - in reality, use reflection carefully)
if (obj instanceof Collection) {
processCollection((Collection<?>) obj, graph, visited, objectId);
} else if (obj instanceof Map) {
processMap((Map<?, ?>) obj, graph, visited, objectId);
}
// Add more type-specific processing as needed
}
private void processCollection(Collection<?> collection, ObjectGraphSnapshot graph,
Map<Object, String> visited, String parentId) {
int index = 0;
for (Object element : collection) {
if (element != null) {
String elementId = "elem_" + System.identityHashCode(element);
graph.addEdge(parentId, elementId, "[" + index + "]");
captureObjectRecursive(element, graph, visited);
}
index++;
}
}
private void processMap(Map<?, ?> map, ObjectGraphSnapshot graph,
Map<Object, String> visited, String parentId) {
for (Map.Entry<?, ?> entry : map.entrySet()) {
if (entry.getKey() != null) {
String keyId = "key_" + System.identityHashCode(entry.getKey());
graph.addEdge(parentId, keyId, "key");
captureObjectRecursive(entry.getKey(), graph, visited);
}
if (entry.getValue() != null) {
String valueId = "value_" + System.identityHashCode(entry.getValue());
graph.addEdge(parentId, valueId, "value");
captureObjectRecursive(entry.getValue(), graph, visited);
}
}
}
private long estimateObjectSize(Object obj) {
// Simplified size estimation
// In production, use Instrumentation or more sophisticated estimation
if (obj == null) return 0;
if (obj instanceof String) {
return ((String) obj).length() * 2L + 24; // Rough estimate
} else if (obj instanceof Integer) {
return 16;
} else if (obj instanceof Long) {
return 24;
} else if (obj instanceof Collection) {
return 64 + ((Collection<?>) obj).size() * 16; // Rough estimate
} else {
return 32; // Default object overhead
}
}
private byte[] serializeSnapshotData(MemorySnapshotData data) throws IOException {
try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos)) {
oos.writeObject(data);
return baos.toByteArray();
}
}
private byte[] serializeObjectGraph(ObjectGraphSnapshot graph) throws IOException {
try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos)) {
oos.writeObject(graph);
return baos.toByteArray();
}
}
private String generateSnapshotId() {
return "MEM-" + System.currentTimeMillis() + "-" + snapshotCounter.incrementAndGet();
}
// Data classes
public static class MemoryStats implements Serializable {
private final long heapUsed;
private final long heapMax;
private final long heapCommitted;
private final long nonHeapUsed;
private final long nonHeapMax;
private final long nonHeapCommitted;
private final long runtimeFreeMemory;
private final long runtimeTotalMemory;
private final long runtimeMaxMemory;
public MemoryStats(long heapUsed, long heapMax, long heapCommitted,
long nonHeapUsed, long nonHeapMax, long nonHeapCommitted,
long runtimeFreeMemory, long runtimeTotalMemory, long runtimeMaxMemory) {
this.heapUsed = heapUsed;
this.heapMax = heapMax;
this.heapCommitted = heapCommitted;
this.nonHeapUsed = nonHeapUsed;
this.nonHeapMax = nonHeapMax;
this.nonHeapCommitted = nonHeapCommitted;
this.runtimeFreeMemory = runtimeFreeMemory;
this.runtimeTotalMemory = runtimeTotalMemory;
this.runtimeMaxMemory = runtimeMaxMemory;
}
// Getters
public long getHeapUsed() { return heapUsed; }
public long getHeapMax() { return heapMax; }
public long getHeapCommitted() { return heapCommitted; }
public long getNonHeapUsed() { return nonHeapUsed; }
public long getNonHeapMax() { return nonHeapMax; }
public long getNonHeapCommitted() { return nonHeapCommitted; }
public long getRuntimeFreeMemory() { return runtimeFreeMemory; }
public long getRuntimeTotalMemory() { return runtimeTotalMemory; }
public long getRuntimeMaxMemory() { return runtimeMaxMemory; }
}
public static class ThreadStats implements Serializable {
private final int threadCount;
private final int peakThreadCount;
private final int daemonThreadCount;
private final long totalStartedThreadCount;
private final Map<Thread.State, Integer> threadStateCounts;
public ThreadStats(int threadCount, int peakThreadCount, int daemonThreadCount,
long totalStartedThreadCount, Map<Thread.State, Integer> threadStateCounts) {
this.threadCount = threadCount;
this.peakThreadCount = peakThreadCount;
this.daemonThreadCount = daemonThreadCount;
this.totalStartedThreadCount = totalStartedThreadCount;
this.threadStateCounts = new HashMap<>(threadStateCounts);
}
// Getters
public int getThreadCount() { return threadCount; }
public int getPeakThreadCount() { return peakThreadCount; }
public int getDaemonThreadCount() { return daemonThreadCount; }
public long getTotalStartedThreadCount() { return totalStartedThreadCount; }
public Map<Thread.State, Integer> getThreadStateCounts() { return threadStateCounts; }
}
public static class GCStats implements Serializable {
private final List<GCBeanStats> gcBeans;
public GCStats(List<GCBeanStats> gcBeans) {
this.gcBeans = new ArrayList<>(gcBeans);
}
// Getters
public List<GCBeanStats> getGcBeans() { return gcBeans; }
}
public static class GCBeanStats implements Serializable {
private final String name;
private final long collectionCount;
private final long collectionTime;
public GCBeanStats(String name, long collectionCount, long collectionTime) {
this.name = name;
this.collectionCount = collectionCount;
this.collectionTime = collectionTime;
}
// Getters
public String getName() { return name; }
public long getCollectionCount() { return collectionCount; }
public long getCollectionTime() { return collectionTime; }
}
public static class MemorySnapshotData implements Serializable {
private final MemoryStats memoryStats;
private final ThreadStats threadStats;
private final GCStats gcStats;
private final LocalDateTime timestamp;
public MemorySnapshotData(MemoryStats memoryStats, ThreadStats threadStats,
GCStats gcStats, LocalDateTime timestamp) {
this.memoryStats = memoryStats;
this.threadStats = threadStats;
this.gcStats = gcStats;
this.timestamp = timestamp;
}
// Getters
public MemoryStats getMemoryStats() { return memoryStats; }
public ThreadStats getThreadStats() { return threadStats; }
public GCStats getGcStats() { return gcStats; }
public LocalDateTime getTimestamp() { return timestamp; }
}
public static class ObjectGraphSnapshot implements Serializable {
private final Map<String, ObjectNode> nodes;
private final Map<String, List<GraphEdge>> edges;
public ObjectGraphSnapshot() {
this.nodes = new HashMap<>();
this.edges = new HashMap<>();
}
public void addNode(ObjectNode node) {
nodes.put(node.getId(), node);
}
public void addEdge(String fromId, String toId, String relationship) {
edges.computeIfAbsent(fromId, k -> new ArrayList<>())
.add(new GraphEdge(fromId, toId, relationship));
}
// Getters
public Map<String, ObjectNode> getNodes() { return nodes; }
public Map<String, List<GraphEdge>> getEdges() { return edges; }
public int getObjectCount() { return nodes.size(); }
}
public static class ObjectNode implements Serializable {
private final String id;
private final String className;
private final int identityHashCode;
private final long estimatedSize;
public ObjectNode(String id, String className, int identityHashCode, long estimatedSize) {
this.id = id;
this.className = className;
this.identityHashCode = identityHashCode;
this.estimatedSize = estimatedSize;
}
// Getters
public String getId() { return id; }
public String getClassName() { return className; }
public int getIdentityHashCode() { return identityHashCode; }
public long getEstimatedSize() { return estimatedSize; }
}
public static class GraphEdge implements Serializable {
private final String fromId;
private final String toId;
private final String relationship;
public GraphEdge(String fromId, String toId, String relationship) {
this.fromId = fromId;
this.toId = toId;
this.relationship = relationship;
}
// Getters
public String getFromId() { return fromId; }
public String getToId() { return toId; }
public String getRelationship() { return relationship; }
}
}
3. Performance Snapshot Implementation
package com.snapshot.performance;
import com.snapshot.core.*;
import java.io.*;
import java.lang.management.*;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
public class PerformanceSnapshotTaker implements SnapshotTaker<PerformanceStats> {
private final SnapshotStorage storage;
private final AtomicLong snapshotCounter;
private final Map<String, PerformanceMetric> activeMetrics;
// Performance counters
private long totalRequests = 0;
private long totalErrors = 0;
private long totalDuration = 0;
public PerformanceSnapshotTaker(SnapshotStorage storage) {
this.storage = storage;
this.snapshotCounter = new AtomicLong(0);
this.activeMetrics = new ConcurrentHashMap<>();
}
@Override
public SnapshotResult takeSnapshot(String description, Map<String, Object> context) {
long startTime = System.currentTimeMillis();
String snapshotId = generateSnapshotId();
try {
// Capture comprehensive performance metrics
PerformanceStats performanceStats = capturePerformanceStats();
// Capture system metrics
SystemStats systemStats = captureSystemStats();
// Capture application-specific metrics
ApplicationStats appStats = captureApplicationStats();
// Create snapshot data
PerformanceSnapshotData snapshotData = new PerformanceSnapshotData(
performanceStats, systemStats, appStats, LocalDateTime.now()
);
// Serialize and store
byte[] serializedData = serializeSnapshotData(snapshotData);
SnapshotMetadata metadata = new SnapshotMetadata(
snapshotId, LocalDateTime.now(), description,
serializedData.length, SnapshotType.PERFORMANCE
);
metadata.addTag("throughput", performanceStats.getCurrentThroughput());
metadata.addTag("errorRate", performanceStats.getErrorRate());
metadata.addTag("avgResponseTime", performanceStats.getAverageResponseTime());
storage.store(snapshotId, serializedData, metadata);
long duration = System.currentTimeMillis() - startTime;
return SnapshotResult.success(snapshotId, duration, serializedData.length);
} catch (Exception e) {
return SnapshotResult.failure("Performance snapshot failed: " + e.getMessage());
}
}
@Override
public SnapshotResult restoreSnapshot(String snapshotId, Map<String, Object> context) {
// Performance snapshots are for analysis, not restoration
return SnapshotResult.failure("Performance snapshot restoration not supported");
}
// Metric recording methods
public void recordRequest(long duration, boolean success) {
totalRequests++;
totalDuration += duration;
if (!success) {
totalErrors++;
}
}
public void registerMetric(String name, PerformanceMetric metric) {
activeMetrics.put(name, metric);
}
public void recordCustomMetric(String name, double value) {
activeMetrics.computeIfAbsent(name, k -> new SimplePerformanceMetric())
.recordValue(value);
}
private PerformanceStats capturePerformanceStats() {
double throughput = calculateThroughput();
double errorRate = totalRequests > 0 ? (double) totalErrors / totalRequests : 0;
double avgResponseTime = totalRequests > 0 ? (double) totalDuration / totalRequests : 0;
// Capture custom metrics
Map<String, Double> customMetrics = new HashMap<>();
for (Map.Entry<String, PerformanceMetric> entry : activeMetrics.entrySet()) {
customMetrics.put(entry.getKey(), entry.getValue().getValue());
}
return new PerformanceStats(
totalRequests,
totalErrors,
totalDuration,
throughput,
errorRate,
avgResponseTime,
customMetrics
);
}
private SystemStats captureSystemStats() {
OperatingSystemMXBean osBean = ManagementFactory.getOperatingSystemMXBean();
RuntimeMXBean runtimeBean = ManagementFactory.getRuntimeMXBean();
double systemLoad = osBean.getSystemLoadAverage();
int availableProcessors = osBean.getAvailableProcessors();
// Capture additional system metrics if available
if (osBean instanceof com.sun.management.OperatingSystemMXBean) {
com.sun.management.OperatingSystemMXBean sunOsBean =
(com.sun.management.OperatingSystemMXBean) osBean;
return new SystemStats(
systemLoad,
availableProcessors,
sunOsBean.getProcessCpuLoad(),
sunOsBean.getSystemCpuLoad(),
sunOsBean.getCommittedVirtualMemorySize(),
sunOsBean.getFreePhysicalMemorySize(),
sunOsBean.getTotalPhysicalMemorySize()
);
}
return new SystemStats(systemLoad, availableProcessors, -1, -1, -1, -1, -1);
}
private ApplicationStats captureApplicationStats() {
// Capture application-specific performance data
Runtime runtime = Runtime.getRuntime();
return new ApplicationStats(
runtime.totalMemory(),
runtime.freeMemory(),
runtime.maxMemory(),
Thread.activeCount(),
getUptime()
);
}
private double calculateThroughput() {
// Calculate requests per second (simplified)
// In reality, you'd track this over time windows
return totalRequests / (getUptime() / 1000.0);
}
private long getUptime() {
return ManagementFactory.getRuntimeMXBean().getUptime();
}
private byte[] serializeSnapshotData(PerformanceSnapshotData data) throws IOException {
try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos)) {
oos.writeObject(data);
return baos.toByteArray();
}
}
private String generateSnapshotId() {
return "PERF-" + System.currentTimeMillis() + "-" + snapshotCounter.incrementAndGet();
}
// Other interface methods similar to MemorySnapshotTaker...
@Override
public SnapshotMetadata getSnapshotMetadata(String snapshotId) {
// Implementation similar to MemorySnapshotTaker
return null;
}
@Override
public List<SnapshotMetadata> listSnapshots() {
// Implementation similar to MemorySnapshotTaker
return Collections.emptyList();
}
@Override
public boolean deleteSnapshot(String snapshotId) {
// Implementation similar to MemorySnapshotTaker
return false;
}
// Data classes
public static class PerformanceStats implements Serializable {
private final long totalRequests;
private final long totalErrors;
private final long totalDuration;
private final double currentThroughput;
private final double errorRate;
private final double averageResponseTime;
private final Map<String, Double> customMetrics;
public PerformanceStats(long totalRequests, long totalErrors, long totalDuration,
double currentThroughput, double errorRate, double averageResponseTime,
Map<String, Double> customMetrics) {
this.totalRequests = totalRequests;
this.totalErrors = totalErrors;
this.totalDuration = totalDuration;
this.currentThroughput = currentThroughput;
this.errorRate = errorRate;
this.averageResponseTime = averageResponseTime;
this.customMetrics = new HashMap<>(customMetrics);
}
// Getters
public long getTotalRequests() { return totalRequests; }
public long getTotalErrors() { return totalErrors; }
public long getTotalDuration() { return totalDuration; }
public double getCurrentThroughput() { return currentThroughput; }
public double getErrorRate() { return errorRate; }
public double getAverageResponseTime() { return averageResponseTime; }
public Map<String, Double> getCustomMetrics() { return customMetrics; }
}
public static class SystemStats implements Serializable {
private final double systemLoad;
private final int availableProcessors;
private final double processCpuLoad;
private final double systemCpuLoad;
private final long committedVirtualMemory;
private final long freePhysicalMemory;
private final long totalPhysicalMemory;
public SystemStats(double systemLoad, int availableProcessors,
double processCpuLoad, double systemCpuLoad,
long committedVirtualMemory, long freePhysicalMemory,
long totalPhysicalMemory) {
this.systemLoad = systemLoad;
this.availableProcessors = availableProcessors;
this.processCpuLoad = processCpuLoad;
this.systemCpuLoad = systemCpuLoad;
this.committedVirtualMemory = committedVirtualMemory;
this.freePhysicalMemory = freePhysicalMemory;
this.totalPhysicalMemory = totalPhysicalMemory;
}
// Getters
public double getSystemLoad() { return systemLoad; }
public int getAvailableProcessors() { return availableProcessors; }
public double getProcessCpuLoad() { return processCpuLoad; }
public double getSystemCpuLoad() { return systemCpuLoad; }
public long getCommittedVirtualMemory() { return committedVirtualMemory; }
public long getFreePhysicalMemory() { return freePhysicalMemory; }
public long getTotalPhysicalMemory() { return totalPhysicalMemory; }
}
public static class ApplicationStats implements Serializable {
private final long totalMemory;
private final long freeMemory;
private final long maxMemory;
private final int activeThreads;
private final long uptime;
public ApplicationStats(long totalMemory, long freeMemory, long maxMemory,
int activeThreads, long uptime) {
this.totalMemory = totalMemory;
this.freeMemory = freeMemory;
this.maxMemory = maxMemory;
this.activeThreads = activeThreads;
this.uptime = uptime;
}
// Getters
public long getTotalMemory() { return totalMemory; }
public long getFreeMemory() { return freeMemory; }
public long getMaxMemory() { return maxMemory; }
public int getActiveThreads() { return activeThreads; }
public long getUptime() { return uptime; }
}
public static class PerformanceSnapshotData implements Serializable {
private final PerformanceStats performanceStats;
private final SystemStats systemStats;
private final ApplicationStats applicationStats;
private final LocalDateTime timestamp;
public PerformanceSnapshotData(PerformanceStats performanceStats, SystemStats systemStats,
ApplicationStats applicationStats, LocalDateTime timestamp) {
this.performanceStats = performanceStats;
this.systemStats = systemStats;
this.applicationStats = applicationStats;
this.timestamp = timestamp;
}
// Getters
public PerformanceStats getPerformanceStats() { return performanceStats; }
public SystemStats getSystemStats() { return systemStats; }
public ApplicationStats getApplicationStats() { return applicationStats; }
public LocalDateTime getTimestamp() { return timestamp; }
}
}
// Performance metric interfaces
interface PerformanceMetric {
void recordValue(double value);
double getValue();
void reset();
}
class SimplePerformanceMetric implements PerformanceMetric {
private double sum = 0;
private long count = 0;
@Override
public void recordValue(double value) {
sum += value;
count++;
}
@Override
public double getValue() {
return count > 0 ? sum / count : 0;
}
@Override
public void reset() {
sum = 0;
count = 0;
}
}
class ThroughputMetric implements PerformanceMetric {
private final long windowSizeMs;
private final Queue<Long> timestamps;
public ThroughputMetric(long windowSizeMs) {
this.windowSizeMs = windowSizeMs;
this.timestamps = new LinkedList<>();
}
@Override
public void recordValue(double value) {
long now = System.currentTimeMillis();
timestamps.add(now);
cleanupOldEntries(now);
}
@Override
public double getValue() {
long now = System.currentTimeMillis();
cleanupOldEntries(now);
return timestamps.size() / (windowSizeMs / 1000.0); // per second
}
@Override
public void reset() {
timestamps.clear();
}
private void cleanupOldEntries(long currentTime) {
long cutoff = currentTime - windowSizeMs;
while (!timestamps.isEmpty() && timestamps.peek() < cutoff) {
timestamps.poll();
}
}
}
4. Application State Snapshot Implementation
package com.snapshot.application;
import com.snapshot.core.*;
import java.io.*;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
public class ApplicationStateSnapshotTaker implements SnapshotTaker<ApplicationState> {
private final SnapshotStorage storage;
private final AtomicLong snapshotCounter;
private final Map<String, StateProvider> stateProviders;
private final Set<String> excludedComponents;
public ApplicationStateSnapshotTaker(SnapshotStorage storage) {
this.storage = storage;
this.snapshotCounter = new AtomicLong(0);
this.stateProviders = new ConcurrentHashMap<>();
this.excludedComponents = ConcurrentHashMap.newKeySet();
}
public void registerStateProvider(String componentName, StateProvider provider) {
stateProviders.put(componentName, provider);
}
public void excludeComponent(String componentName) {
excludedComponents.add(componentName);
}
@Override
public SnapshotResult takeSnapshot(String description, Map<String, Object> context) {
long startTime = System.currentTimeMillis();
String snapshotId = generateSnapshotId();
try {
// Capture state from all registered providers
Map<String, ComponentState> componentStates = new HashMap<>();
for (Map.Entry<String, StateProvider> entry : stateProviders.entrySet()) {
String componentName = entry.getKey();
if (!excludedComponents.contains(componentName)) {
try {
ComponentState state = entry.getValue().captureState();
componentStates.put(componentName, state);
} catch (Exception e) {
// Log but continue with other components
System.err.println("Failed to capture state for " + componentName + ": " + e.getMessage());
componentStates.put(componentName, new ErrorComponentState(e.getMessage()));
}
}
}
// Create application state
ApplicationState appState = new ApplicationState(
componentStates,
LocalDateTime.now(),
description
);
// Serialize and store
byte[] serializedData = serializeApplicationState(appState);
SnapshotMetadata metadata = new SnapshotMetadata(
snapshotId, LocalDateTime.now(), description,
serializedData.length, SnapshotType.APPLICATION_STATE
);
metadata.addTag("componentCount", componentStates.size());
metadata.addTag("totalSize", serializedData.length);
storage.store(snapshotId, serializedData, metadata);
long duration = System.currentTimeMillis() - startTime;
return SnapshotResult.success(snapshotId, duration, serializedData.length);
} catch (Exception e) {
return SnapshotResult.failure("Application state snapshot failed: " + e.getMessage());
}
}
@Override
public SnapshotResult restoreSnapshot(String snapshotId, Map<String, Object> context) {
long startTime = System.currentTimeMillis();
try {
// Retrieve snapshot data
byte[] serializedData = storage.retrieve(snapshotId);
ApplicationState appState = deserializeApplicationState(serializedData);
// Restore state to components
int restoredCount = 0;
int failedCount = 0;
for (Map.Entry<String, ComponentState> entry : appState.getComponentStates().entrySet()) {
String componentName = entry.getKey();
ComponentState componentState = entry.getValue();
StateProvider provider = stateProviders.get(componentName);
if (provider != null && !excludedComponents.contains(componentName)) {
try {
provider.restoreState(componentState);
restoredCount++;
} catch (Exception e) {
System.err.println("Failed to restore state for " + componentName + ": " + e.getMessage());
failedCount++;
}
}
}
long duration = System.currentTimeMillis() - startTime;
return new SnapshotResult(
failedCount == 0,
snapshotId,
failedCount > 0 ? failedCount + " components failed to restore" : null,
duration,
serializedData.length
);
} catch (Exception e) {
return SnapshotResult.failure("Snapshot restoration failed: " + e.getMessage());
}
}
// Incremental snapshot - only capture changed components
public SnapshotResult takeIncrementalSnapshot(String description,
Set<String> changedComponents) {
long startTime = System.currentTimeMillis();
String snapshotId = generateSnapshotId();
try {
Map<String, ComponentState> componentStates = new HashMap<>();
for (String componentName : changedComponents) {
if (stateProviders.containsKey(componentName) &&
!excludedComponents.contains(componentName)) {
try {
ComponentState state = stateProviders.get(componentName).captureState();
componentStates.put(componentName, state);
} catch (Exception e) {
System.err.println("Failed to capture incremental state for " + componentName);
}
}
}
ApplicationState appState = new ApplicationState(
componentStates,
LocalDateTime.now(),
description + " (Incremental)"
);
byte[] serializedData = serializeApplicationState(appState);
SnapshotMetadata metadata = new SnapshotMetadata(
snapshotId, LocalDateTime.now(), description,
serializedData.length, SnapshotType.INCREMENTAL
);
metadata.addTag("changedComponentCount", componentStates.size());
metadata.addTag("incremental", true);
storage.store(snapshotId, serializedData, metadata);
long duration = System.currentTimeMillis() - startTime;
return SnapshotResult.success(snapshotId, duration, serializedData.length);
} catch (Exception e) {
return SnapshotResult.failure("Incremental snapshot failed: " + e.getMessage());
}
}
private byte[] serializeApplicationState(ApplicationState state) throws IOException {
try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos)) {
oos.writeObject(state);
return baos.toByteArray();
}
}
private ApplicationState deserializeApplicationState(byte[] data) throws IOException {
try (ByteArrayInputStream bais = new ByteArrayInputStream(data);
ObjectInputStream ois = new ObjectInputStream(bais)) {
return (ApplicationState) ois.readObject();
} catch (ClassNotFoundException e) {
throw new IOException("Failed to deserialize application state", e);
}
}
private String generateSnapshotId() {
return "APP-" + System.currentTimeMillis() + "-" + snapshotCounter.incrementAndGet();
}
// Other interface methods...
@Override
public SnapshotMetadata getSnapshotMetadata(String snapshotId) {
// Implementation
return null;
}
@Override
public List<SnapshotMetadata> listSnapshots() {
// Implementation
return Collections.emptyList();
}
@Override
public boolean deleteSnapshot(String snapshotId) {
// Implementation
return false;
}
// State provider interface
public interface StateProvider {
ComponentState captureState();
void restoreState(ComponentState state);
}
// Data classes
public static class ApplicationState implements Serializable {
private final Map<String, ComponentState> componentStates;
private final LocalDateTime timestamp;
private final String description;
public ApplicationState(Map<String, ComponentState> componentStates,
LocalDateTime timestamp, String description) {
this.componentStates = new HashMap<>(componentStates);
this.timestamp = timestamp;
this.description = description;
}
// Getters
public Map<String, ComponentState> getComponentStates() { return componentStates; }
public LocalDateTime getTimestamp() { return timestamp; }
public String getDescription() { return description; }
}
public static class ComponentState implements Serializable {
private final String componentName;
private final Map<String, Object> stateData;
private final LocalDateTime capturedAt;
public ComponentState(String componentName, Map<String, Object> stateData) {
this.componentName = componentName;
this.stateData = new HashMap<>(stateData);
this.capturedAt = LocalDateTime.now();
}
// Getters
public String getComponentName() { return componentName; }
public Map<String, Object> getStateData() { return stateData; }
public LocalDateTime getCapturedAt() { return capturedAt; }
public void put(String key, Object value) {
stateData.put(key, value);
}
public Object get(String key) {
return stateData.get(key);
}
}
public static class ErrorComponentState extends ComponentState {
private final String errorMessage;
public ErrorComponentState(String errorMessage) {
super("ERROR", Collections.singletonMap("error", errorMessage));
this.errorMessage = errorMessage;
}
public String getErrorMessage() { return errorMessage; }
}
}
5. Snapshot Manager and Orchestration
package com.snapshot.manager;
import com.snapshot.core.*;
import com.snapshot.memory.MemorySnapshotTaker;
import com.snapshot.performance.PerformanceSnapshotTaker;
import com.snapshot.application.ApplicationStateSnapshotTaker;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
@Component
public class SnapshotManager {
private static final Logger logger = LoggerFactory.getLogger(SnapshotManager.class);
private final Map<String, SnapshotTaker<?>> snapshotTakers;
private final SnapshotStorage storage;
private final SnapshotConfig config;
private final ScheduledExecutorService scheduler;
private final Map<String, LocalDateTime> lastSnapshotTimes;
private final Map<String, List<SnapshotEventListener>> eventListeners;
public SnapshotManager(SnapshotStorage storage, SnapshotConfig config) {
this.storage = storage;
this.config = config;
this.snapshotTakers = new ConcurrentHashMap<>();
this.scheduler = Executors.newScheduledThreadPool(config.getThreadPoolSize());
this.lastSnapshotTimes = new ConcurrentHashMap<>();
this.eventListeners = new ConcurrentHashMap<>();
initializeSnapshotTakers();
scheduleAutomaticSnapshots();
}
private void initializeSnapshotTakers() {
// Register built-in snapshot takers
registerSnapshotTaker("memory", new MemorySnapshotTaker(storage));
registerSnapshotTaker("performance", new PerformanceSnapshotTaker(storage));
registerSnapshotTaker("application", new ApplicationStateSnapshotTaker(storage));
logger.info("Initialized {} snapshot takers", snapshotTakers.size());
}
public void registerSnapshotTaker(String name, SnapshotTaker<?> taker) {
snapshotTakers.put(name, taker);
logger.info("Registered snapshot taker: {}", name);
}
public void addEventListener(String eventType, SnapshotEventListener listener) {
eventListeners.computeIfAbsent(eventType, k -> new ArrayList<>()).add(listener);
}
private void notifyEventListeners(String eventType, SnapshotEvent event) {
List<SnapshotEventListener> listeners = eventListeners.get(eventType);
if (listeners != null) {
for (SnapshotEventListener listener : listeners) {
try {
listener.onSnapshotEvent(event);
} catch (Exception e) {
logger.error("Error notifying event listener", e);
}
}
}
}
public SnapshotResult takeSnapshot(String takerName, String description) {
return takeSnapshot(takerName, description, Collections.emptyMap());
}
public SnapshotResult takeSnapshot(String takerName, String description,
Map<String, Object> context) {
SnapshotTaker<?> taker = snapshotTakers.get(takerName);
if (taker == null) {
return SnapshotResult.failure("Unknown snapshot taker: " + takerName);
}
logger.info("Taking snapshot with taker: {}, description: {}", takerName, description);
try {
SnapshotResult result = taker.takeSnapshot(description, context);
// Update last snapshot time
lastSnapshotTimes.put(takerName, LocalDateTime.now());
// Notify listeners
SnapshotEvent event = new SnapshotEvent(
takerName, description, result, context
);
notifyEventListeners("SNAPSHOT_TAKEN", event);
if (result.isSuccess()) {
logger.info("Snapshot completed successfully: {}", result.getSnapshotId());
} else {
logger.error("Snapshot failed: {}", result.getErrorMessage());
SnapshotEvent errorEvent = new SnapshotEvent(
takerName, description, result, context
);
notifyEventListeners("SNAPSHOT_FAILED", errorEvent);
}
return result;
} catch (Exception e) {
logger.error("Unexpected error during snapshot", e);
return SnapshotResult.failure("Unexpected error: " + e.getMessage());
}
}
public SnapshotResult takeComprehensiveSnapshot(String description) {
Map<String, Object> context = new HashMap<>();
context.put("comprehensive", true);
context.put("initiatedAt", LocalDateTime.now());
List<SnapshotResult> results = new ArrayList<>();
// Take snapshots sequentially to avoid resource contention
for (String takerName : snapshotTakers.keySet()) {
if (config.isTakerEnabled(takerName)) {
logger.info("Taking comprehensive snapshot with taker: {}", takerName);
SnapshotResult result = takeSnapshot(takerName, description + " - " + takerName, context);
results.add(result);
// Add small delay between snapshots
try {
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
}
// Analyze results
long successCount = results.stream().filter(SnapshotResult::isSuccess).count();
long totalSize = results.stream().mapToLong(SnapshotResult::getSizeBytes).sum();
if (successCount == snapshotTakers.size()) {
return SnapshotResult.success("COMPREHENSIVE", 0, totalSize);
} else {
return SnapshotResult.failure(successCount + " of " + snapshotTakers.size() + " snapshots succeeded");
}
}
public SnapshotResult restoreSnapshot(String takerName, String snapshotId) {
return restoreSnapshot(takerName, snapshotId, Collections.emptyMap());
}
public SnapshotResult restoreSnapshot(String takerName, String snapshotId,
Map<String, Object> context) {
SnapshotTaker<?> taker = snapshotTakers.get(takerName);
if (taker == null) {
return SnapshotResult.failure("Unknown snapshot taker: " + takerName);
}
logger.info("Restoring snapshot: {} with taker: {}", snapshotId, takerName);
try {
SnapshotResult result = taker.restoreSnapshot(snapshotId, context);
SnapshotEvent event = new SnapshotEvent(
takerName, "Restore: " + snapshotId, result, context
);
notifyEventListeners("SNAPSHOT_RESTORED", event);
return result;
} catch (Exception e) {
logger.error("Snapshot restoration failed", e);
return SnapshotResult.failure("Restoration failed: " + e.getMessage());
}
}
@Scheduled(fixedRate = 300000) // Every 5 minutes
public void scheduledMemorySnapshot() {
if (config.isScheduledSnapshotEnabled("memory")) {
takeSnapshot("memory", "Scheduled memory snapshot");
}
}
@Scheduled(fixedRate = 60000) // Every minute
public void scheduledPerformanceSnapshot() {
if (config.isScheduledSnapshotEnabled("performance")) {
takeSnapshot("performance", "Scheduled performance snapshot");
}
}
@Scheduled(fixedRate = 300000) // Every 5 minutes
public void scheduledApplicationSnapshot() {
if (config.isScheduledSnapshotEnabled("application")) {
takeSnapshot("application", "Scheduled application snapshot");
}
}
private void scheduleAutomaticSnapshots() {
// Schedule based on configuration
for (SnapshotConfig.ScheduleConfig scheduleConfig : config.getScheduleConfigs()) {
if (scheduleConfig.isEnabled()) {
scheduler.scheduleAtFixedRate(
() -> takeSnapshot(scheduleConfig.getTakerName(),
"Auto-scheduled snapshot"),
scheduleConfig.getInitialDelay(),
scheduleConfig.getInterval(),
TimeUnit.MILLISECONDS
);
logger.info("Scheduled automatic snapshots for {} every {} ms",
scheduleConfig.getTakerName(), scheduleConfig.getInterval());
}
}
}
public SnapshotAnalytics getAnalytics() {
return new SnapshotAnalytics(lastSnapshotTimes, getSnapshotStatistics());
}
private Map<String, Object> getSnapshotStatistics() {
Map<String, Object> stats = new HashMap<>();
for (Map.Entry<String, SnapshotTaker<?>> entry : snapshotTakers.entrySet()) {
String takerName = entry.getKey();
List<SnapshotMetadata> snapshots = entry.getValue().listSnapshots();
Map<String, Object> takerStats = new HashMap<>();
takerStats.put("totalSnapshots", snapshots.size());
takerStats.put("lastSnapshot", lastSnapshotTimes.get(takerName));
if (!snapshots.isEmpty()) {
long totalSize = snapshots.stream()
.mapToLong(SnapshotMetadata::getSizeBytes)
.sum();
takerStats.put("totalSizeBytes", totalSize);
takerStats.put("averageSizeBytes", totalSize / snapshots.size());
}
stats.put(takerName, takerStats);
}
return stats;
}
public void shutdown() {
logger.info("Shutting down snapshot manager");
scheduler.shutdown();
try {
if (!scheduler.awaitTermination(10, TimeUnit.SECONDS)) {
scheduler.shutdownNow();
}
} catch (InterruptedException e) {
scheduler.shutdownNow();
Thread.currentThread().interrupt();
}
}
// Configuration class
public static class SnapshotConfig {
private final Map<String, Boolean> enabledTakers;
private final Map<String, Boolean> scheduledSnapshots;
private final List<ScheduleConfig> scheduleConfigs;
private final int threadPoolSize;
public SnapshotConfig() {
this.enabledTakers = new HashMap<>();
this.scheduledSnapshots = new HashMap<>();
this.scheduleConfigs = new ArrayList<>();
this.threadPoolSize = 4;
// Default configuration
enabledTakers.put("memory", true);
enabledTakers.put("performance", true);
enabledTakers.put("application", true);
scheduledSnapshots.put("memory", true);
scheduledSnapshots.put("performance", true);
scheduledSnapshots.put("application", false);
// Default schedules
scheduleConfigs.add(new ScheduleConfig("memory", 300000, 300000, true)); // 5 minutes
scheduleConfigs.add(new ScheduleConfig("performance", 60000, 60000, true)); // 1 minute
}
public boolean isTakerEnabled(String takerName) {
return enabledTakers.getOrDefault(takerName, true);
}
public boolean isScheduledSnapshotEnabled(String takerName) {
return scheduledSnapshots.getOrDefault(takerName, false);
}
public List<ScheduleConfig> getScheduleConfigs() {
return scheduleConfigs;
}
public int getThreadPoolSize() {
return threadPoolSize;
}
public static class ScheduleConfig {
private final String takerName;
private final long initialDelay;
private final long interval;
private final boolean enabled;
public ScheduleConfig(String takerName, long initialDelay, long interval, boolean enabled) {
this.takerName = takerName;
this.initialDelay = initialDelay;
this.interval = interval;
this.enabled = enabled;
}
// Getters
public String getTakerName() { return takerName; }
public long getInitialDelay() { return initialDelay; }
public long getInterval() { return interval; }
public boolean isEnabled() { return enabled; }
}
}
// Event classes
public interface SnapshotEventListener {
void onSnapshotEvent(SnapshotEvent event);
}
public static class SnapshotEvent {
private final String takerName;
private final String description;
private final SnapshotResult result;
private final Map<String, Object> context;
private final LocalDateTime timestamp;
public SnapshotEvent(String takerName, String description,
SnapshotResult result, Map<String, Object> context) {
this.takerName = takerName;
this.description = description;
this.result = result;
this.context = new HashMap<>(context);
this.timestamp = LocalDateTime.now();
}
// Getters
public String getTakerName() { return takerName; }
public String getDescription() { return description; }
public SnapshotResult getResult() { return result; }
public Map<String, Object> getContext() { return context; }
public LocalDateTime getTimestamp() { return timestamp; }
}
public static class SnapshotAnalytics {
private final Map<String, LocalDateTime> lastSnapshotTimes;
private final Map<String, Object> statistics;
public SnapshotAnalytics(Map<String, LocalDateTime> lastSnapshotTimes,
Map<String, Object> statistics) {
this.lastSnapshotTimes = new HashMap<>(lastSnapshotTimes);
this.statistics = new HashMap<>(statistics);
}
// Getters
public Map<String, LocalDateTime> getLastSnapshotTimes() { return lastSnapshotTimes; }
public Map<String, Object> getStatistics() { return statistics; }
}
}
6. Usage Examples and Integration
package com.snapshot.examples;
import com.snapshot.application.ApplicationStateSnapshotTaker;
import com.snapshot.core.SnapshotStorage;
import com.snapshot.manager.SnapshotManager;
import com.snapshot.memory.MemorySnapshotTaker;
import com.snapshot.performance.PerformanceSnapshotTaker;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;
@Component
public class SnapshotExamples {
private final SnapshotManager snapshotManager;
public SnapshotExamples(SnapshotManager snapshotManager) {
this.snapshotManager = snapshotManager;
setupExampleStateProviders();
}
private void setupExampleStateProviders() {
// Get the application state snapshot taker
ApplicationStateSnapshotTaker appStateTaker =
(ApplicationStateSnapshotTaker) snapshotManager.getSnapshotTaker("application");
// Register example state providers
appStateTaker.registerStateProvider("UserSessionManager", new UserSessionStateProvider());
appStateTaker.registerStateProvider("CacheManager", new CacheStateProvider());
appStateTaker.registerStateProvider("DatabaseConnectionPool", new ConnectionPoolStateProvider());
}
public void demonstrateSnapshots() {
// Take a memory snapshot
snapshotManager.takeSnapshot("memory", "Pre-load test snapshot");
// Take a performance snapshot with custom context
Map<String, Object> perfContext = new HashMap<>();
perfContext.put("testScenario", "load_test");
perfContext.put("userCount", 1000);
snapshotManager.takeSnapshot("performance", "Load test performance", perfContext);
// Take comprehensive snapshot
snapshotManager.takeComprehensiveSnapshot("System health check");
// Take application state snapshot
snapshotManager.takeSnapshot("application", "Application state before maintenance");
}
public void demonstrateIncrementalSnapshots() {
ApplicationStateSnapshotTaker appStateTaker =
(ApplicationStateSnapshotTaker) snapshotManager.getSnapshotTaker("application");
// Simulate changed components
java.util.Set<String> changedComponents = java.util.Set.of("UserSessionManager", "CacheManager");
appStateTaker.takeIncrementalSnapshot("After user activity", changedComponents);
}
// Example state providers
public static class UserSessionStateProvider implements ApplicationStateSnapshotTaker.StateProvider {
private final Map<String, Object> activeSessions = new HashMap<>();
@Override
public ApplicationStateSnapshotTaker.ComponentState captureState() {
Map<String, Object> stateData = new HashMap<>();
stateData.put("activeSessionCount", activeSessions.size());
stateData.put("sessions", new HashMap<>(activeSessions));
stateData.put("timestamp", System.currentTimeMillis());
return new ApplicationStateSnapshotTaker.ComponentState("UserSessionManager", stateData);
}
@Override
public void restoreState(ApplicationStateSnapshotTaker.ComponentState state) {
Map<String, Object> stateData = state.getStateData();
// Restore session state (simplified)
System.out.println("Restoring user sessions: " + stateData.get("activeSessionCount"));
}
public void addSession(String sessionId, Object sessionData) {
activeSessions.put(sessionId, sessionData);
}
}
public static class CacheStateProvider implements ApplicationStateSnapshotTaker.StateProvider {
private final Map<String, Object> cache = new HashMap<>();
@Override
public ApplicationStateSnapshotTaker.ComponentState captureState() {
Map<String, Object> stateData = new HashMap<>();
stateData.put("cacheSize", cache.size());
stateData.put("cacheKeys", new ArrayList<>(cache.keySet()));
stateData.put("memoryUsage", estimateMemoryUsage());
return new ApplicationStateSnapshotTaker.ComponentState("CacheManager", stateData);
}
@Override
public void restoreState(ApplicationStateSnapshotTaker.ComponentState state) {
Map<String, Object> stateData = state.getStateData();
// Restore cache state (in reality, you might reload data)
System.out.println("Restoring cache with size: " + stateData.get("cacheSize"));
}
private long estimateMemoryUsage() {
return cache.values().stream()
.mapToLong(obj -> obj.toString().length() * 2L)
.sum();
}
public void put(String key, Object value) {
cache.put(key, value);
}
}
public static class ConnectionPoolStateProvider implements ApplicationStateSnapshotTaker.StateProvider {
private int activeConnections = 0;
private int idleConnections = 0;
@Override
public ApplicationStateSnapshotTaker.ComponentState captureState() {
Map<String, Object> stateData = new HashMap<>();
stateData.put("activeConnections", activeConnections);
stateData.put("idleConnections", idleConnections);
stateData.put("totalConnections", activeConnections + idleConnections);
return new ApplicationStateSnapshotTaker.ComponentState("DatabaseConnectionPool", stateData);
}
@Override
public void restoreState(ApplicationStateSnapshotTaker.ComponentState state) {
Map<String, Object> stateData = state.getStateData();
// Reset connection pool state
System.out.println("Restoring connection pool state");
}
public void connectionBorrowed() {
activeConnections++;
if (idleConnections > 0) {
idleConnections--;
}
}
public void connectionReturned() {
if (activeConnections > 0) {
activeConnections--;
idleConnections++;
}
}
}
}
Best Practices for Snapshotting
1. Performance Considerations:
- Use incremental snapshots for large state
- Implement lazy loading for object graphs
- Compress snapshot data before storage
- Schedule snapshots during low-load periods
2. Memory Management:
- Use weak references for object caching
- Implement size limits for snapshots
- Clean up old snapshots automatically
- Monitor snapshot storage usage
3. Error Handling:
- Implement graceful degradation
- Continue snapshotting even if components fail
- Provide detailed error reporting
- Implement retry mechanisms
4. Security:
- Encrypt sensitive snapshot data
- Implement access controls for snapshot restoration
- Sanitize sensitive information from snapshots
- Secure snapshot storage
Conclusion
Snapshotting is a powerful technique for performance optimization, debugging, and system reliability in Java applications. Key benefits include:
- Performance Analysis: Identify bottlenecks and memory issues
- State Restoration: Recover application state after failures
- Debugging: Capture system state for post-mortem analysis
- Monitoring: Track system health and performance trends
Implementation Checklist:
- ✅ Define snapshot types and granularity
- ✅ Implement efficient serialization strategies
- ✅ Design incremental snapshot mechanisms
- ✅ Implement storage and compression
- ✅ Add monitoring and analytics
- ✅ Create restoration capabilities
- ✅ Implement security measures
- ✅ Add comprehensive testing
Snapshotting is particularly valuable for:
- High-performance applications requiring optimization
- Distributed systems needing state coordination
- Financial applications requiring audit trails
- Gaming applications with save/load functionality
- Enterprise systems requiring disaster recovery
By following this comprehensive guide, you can implement robust snapshotting capabilities that significantly enhance your Java application's performance, reliability, and maintainability.