Use static, per-thread buffers for (de)serialization

Notes:
* TimedValue was turned to mutable object to leverage on re-suing
  objects and furthere reduce the memory footprint (see [1])
* serialization of 'created' and 'length' happens through bytes array
  ('out.write(buffer.array());') therefore deserialization has to be
  performed in the same way and per-thread read/write buffer (which is
  bytes array) was added

[1] Read interface documentation excerpt:
(...) should attempt to reuse the given using object, i. e. to read
the deserialized data into the given object. If it is possible, this
object then returned from this method back. If it is impossible for
any reason, a new object should be created and returned (...)

Change-Id: I89871476f8186d4f5d2bceef34276b4f5756b181
diff --git a/src/main/java/com/googlesource/gerrit/modules/cache/chroniclemap/TimedValue.java b/src/main/java/com/googlesource/gerrit/modules/cache/chroniclemap/TimedValue.java
index 6bb53bc..e6ccda2 100644
--- a/src/main/java/com/googlesource/gerrit/modules/cache/chroniclemap/TimedValue.java
+++ b/src/main/java/com/googlesource/gerrit/modules/cache/chroniclemap/TimedValue.java
@@ -16,9 +16,8 @@
 import com.google.common.base.Objects;
 
 public class TimedValue<V> {
-
-  private final V value;
-  private final long created;
+  private V value;
+  private long created;
 
   TimedValue(V value) {
     this.created = System.currentTimeMillis();
@@ -34,10 +33,18 @@
     return created;
   }
 
+  void setCreated(long created) {
+    this.created = created;
+  }
+
   public V getValue() {
     return value;
   }
 
+  void setValue(V value) {
+    this.value = value;
+  }
+
   @Override
   public boolean equals(Object o) {
     if (this == o) return true;
diff --git a/src/main/java/com/googlesource/gerrit/modules/cache/chroniclemap/TimedValueMarshaller.java b/src/main/java/com/googlesource/gerrit/modules/cache/chroniclemap/TimedValueMarshaller.java
index 13f12ec..d3dd713 100644
--- a/src/main/java/com/googlesource/gerrit/modules/cache/chroniclemap/TimedValueMarshaller.java
+++ b/src/main/java/com/googlesource/gerrit/modules/cache/chroniclemap/TimedValueMarshaller.java
@@ -27,6 +27,14 @@
         BytesReader<TimedValue<V>>,
         ReadResolvable<TimedValueMarshaller<V>> {
 
+  private static final ThreadLocal<byte[]> staticBuffer =
+      new ThreadLocal<byte[]>() {
+        @Override
+        protected byte[] initialValue() {
+          return new byte[Long.BYTES + Integer.BYTES];
+        }
+      };
+
   private final CacheSerializer<V> cacheSerializer;
 
   TimedValueMarshaller(MetricMaker metricMaker, String name) {
@@ -48,29 +56,23 @@
   @Override
   public TimedValue<V> read(Bytes in, TimedValue<V> using) {
     try (Timer0.Context timer = metrics.deserializeLatency.start()) {
-      long initialPosition = in.readPosition();
-
-      // Deserialize the creation timestamp (first 8 bytes)
-      byte[] serializedLong = new byte[Long.BYTES];
-      in.read(serializedLong, 0, Long.BYTES);
-      ByteBuffer buffer = ByteBuffer.wrap(serializedLong);
-      long created = buffer.getLong(0);
-      in.readPosition(initialPosition + Long.BYTES);
-
-      // Deserialize the length of the serialized value (second 8 bytes)
-      byte[] serializedInt = new byte[Integer.BYTES];
-      in.read(serializedInt, 0, Integer.BYTES);
-      ByteBuffer buffer2 = ByteBuffer.wrap(serializedInt);
-      int vLength = buffer2.getInt(0);
-      in.readPosition(initialPosition + Long.BYTES + Integer.BYTES);
+      byte[] bytesBuffer = staticBuffer.get();
+      in.read(bytesBuffer);
+      ByteBuffer buffer = ByteBuffer.wrap(bytesBuffer);
+      long created = buffer.getLong();
+      int vLength = buffer.getInt();
 
       // Deserialize object V (remaining bytes)
       byte[] serializedV = new byte[vLength];
-      in.read(serializedV, 0, vLength);
+      in.read(serializedV);
       V v = cacheSerializer.deserialize(serializedV);
 
-      using = new TimedValue<>(v, created);
-
+      if (using == null) {
+        using = new TimedValue<>(v, created);
+      } else {
+        using.setCreated(created);
+        using.setValue(v);
+      }
       return using;
     }
   }
@@ -84,20 +86,12 @@
       // Serialize as follows:
       // created | length of serialized V | serialized value V
       // 8 bytes |       4 bytes          | serialized_length bytes
-
-      int capacity = Long.BYTES + Integer.BYTES + serialized.length;
-      ByteBuffer buffer = ByteBuffer.allocate(capacity);
-
-      long timestamp = toWrite.getCreated();
-      buffer.putLong(0, timestamp);
-
-      buffer.position(Long.BYTES);
+      byte[] bytesBuffer = staticBuffer.get();
+      ByteBuffer buffer = ByteBuffer.wrap(bytesBuffer);
+      buffer.putLong(toWrite.getCreated());
       buffer.putInt(serialized.length);
-
-      buffer.position(Long.BYTES + Integer.BYTES);
-      buffer.put(serialized);
-
-      out.write(buffer.array());
+      out.write(bytesBuffer);
+      out.write(serialized);
     }
   }
 }