Skip to content

Commit

Permalink
Merge pull request #434 from tbak/refactorings/string_cache
Browse files Browse the repository at this point in the history
1.x Cache string values to reduce memory footprint.
  • Loading branch information
tbak committed Feb 25, 2015
2 parents 2f656f8 + b6335e6 commit 5ab86b9
Show file tree
Hide file tree
Showing 7 changed files with 160 additions and 20 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,11 @@ public Object unmarshal(HierarchicalStreamReader reader,
public static class ApplicationConverter implements Converter {

private static final String ELEM_NAME = "name";
private final StringCache cache;

public ApplicationConverter(StringCache cache) {
this.cache = cache;
}

/*
* (non-Javadoc)
Expand Down Expand Up @@ -214,7 +219,7 @@ public Object unmarshal(HierarchicalStreamReader reader,
String nodeName = reader.getNodeName();

if (ELEM_NAME.equals(nodeName)) {
app.setName(reader.getValue());
app.setName(cache.cachedValueOf(reader.getValue()));
} else if (NODE_INSTANCE.equals(nodeName)) {
app.addInstance((InstanceInfo) context.convertAnother(app,
InstanceInfo.class));
Expand Down Expand Up @@ -242,6 +247,12 @@ public static class InstanceInfoConverter implements Converter {
private static final String ELEM_IDENTIFYING_ATTR = "identifyingAttribute";
private static final String ATTR_ENABLED = "enabled";

private final StringCache cache;

public InstanceInfoConverter(StringCache cache) {
this.cache = cache;
}

/*
* (non-Javadoc)
*
Expand Down Expand Up @@ -366,13 +377,13 @@ public Object unmarshal(HierarchicalStreamReader reader,
String nodeName = reader.getNodeName();

if (ELEM_HOST.equals(nodeName)) {
builder.setHostName(reader.getValue());
builder.setHostName(cache.cachedValueOf(reader.getValue()));
} else if (ELEM_APP.equals(nodeName)) {
builder.setAppName(reader.getValue());
builder.setAppName(cache.cachedValueOf(reader.getValue()));
} else if (ELEM_IP.equals(nodeName)) {
builder.setIPAddr(reader.getValue());
} else if (ELEM_SID.equals(nodeName)) {
builder.setSID(reader.getValue());
builder.setSID(cache.cachedValueOf(reader.getValue()));
} else if (ELEM_IDENTIFYING_ATTR.equals(nodeName)) {
// nothing;
} else if (ELEM_STATUS.equals(nodeName)) {
Expand Down Expand Up @@ -421,6 +432,11 @@ public Object unmarshal(HierarchicalStreamReader reader,
public static class DataCenterInfoConverter implements Converter {

private static final String ELEM_NAME = "name";
private final StringCache cache;

public DataCenterInfoConverter(StringCache cache) {
this.cache = cache;
}

/*
* (non-Javadoc)
Expand Down Expand Up @@ -499,9 +515,13 @@ public Name getName() {
}
} else if (NODE_METADATA.equals(reader.getNodeName())) {
if (info.getName() == Name.Amazon) {
((AmazonInfo) info)
.setMetadata((Map<String, String>) context
.convertAnother(info, Map.class));
Map<String, String> metadataMap = (Map<String, String>) context
.convertAnother(info, Map.class);
Map<String, String> metadataMapInter = new HashMap<>(metadataMap.size());
for(Map.Entry<String, String> entry: metadataMap.entrySet()) {
metadataMapInter.put(cache.cachedValueOf(entry.getKey()), cache.cachedValueOf(entry.getValue()));
}
((AmazonInfo) info).setMetadata(metadataMapInter);
}
}

Expand Down Expand Up @@ -629,6 +649,12 @@ public Object unmarshal(HierarchicalStreamReader reader,
*/
public static class MetadataConverter implements Converter {

private final StringCache cache;

public MetadataConverter(StringCache cache) {
this.cache = cache;
}

/*
* (non-Javadoc)
*
Expand Down Expand Up @@ -693,7 +719,7 @@ private Map<String, String> unmarshalMap(
String value = reader.getValue();
reader.moveUp();

map.put(key, value);
map.put(cache.cachedValueOf(key), cache.cachedValueOf(value));
}
return map;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,26 +49,30 @@ protected NameCoder getNameCoder() {
return this.coder;
}
});
registerConverter(new Converters.ApplicationConverter());

StringCache cache = new StringCache();

registerConverter(new Converters.ApplicationConverter(cache));
registerConverter(new Converters.ApplicationsConverter());
registerConverter(new Converters.DataCenterInfoConverter());
registerConverter(new Converters.InstanceInfoConverter());
registerConverter(new Converters.DataCenterInfoConverter(cache));
registerConverter(new Converters.InstanceInfoConverter(cache));
registerConverter(new Converters.LeaseInfoConverter());
registerConverter(new Converters.MetadataConverter());
registerConverter(new Converters.MetadataConverter(cache));
setMode(XStream.NO_REFERENCES);
processAnnotations(new Class[] {InstanceInfo.class, Application.class, Applications.class});
processAnnotations(new Class[]{InstanceInfo.class, Application.class, Applications.class});
}

public static JsonXStream getInstance() {
return s_instance;
}

private static XmlFriendlyNameCoder initializeNameCoder(){
private static XmlFriendlyNameCoder initializeNameCoder() {
EurekaClientConfig clientConfig = DiscoveryManager
.getInstance().getEurekaClientConfig();
if (clientConfig == null) {
return new XmlFriendlyNameCoder();
}
return new XmlFriendlyNameCoder(clientConfig.getDollarReplacement(), clientConfig.getEscapeCharReplacement());
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package com.netflix.discovery.converters;

import java.lang.ref.WeakReference;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
* @author Tomasz Bak
*/
public class StringCache {

public static final int LENGTH_LIMIT = 38;

private final ReadWriteLock lock = new ReentrantReadWriteLock();
private final Map<String, WeakReference<String>> cache = new WeakHashMap<>();
private final int lengthLimit;

public StringCache() {
this(LENGTH_LIMIT);
}

public StringCache(int lengthLimit) {
this.lengthLimit = lengthLimit;
}

public String cachedValueOf(final String str) {
if (str != null && (lengthLimit < 0 || str.length() <= lengthLimit)) {
// Return value from cache if available
try {
lock.readLock().lock();
WeakReference<String> ref = cache.get(str);
if (ref != null) {
return ref.get();
}
} finally {
lock.readLock().unlock();
}

// Update cache with new content
try {
lock.writeLock().lock();
WeakReference<String> ref = cache.get(str);
if (ref != null) {
return ref.get();
}
cache.put(str, new WeakReference<String>(str));
} finally {
lock.writeLock().unlock();
}
return str;
}
return str;
}

public int size() {
try {
lock.readLock().lock();
return cache.size();
} finally {
lock.readLock().unlock();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,14 @@ public class XmlXStream extends XStream {
public XmlXStream() {
super(new DomDriver(null, initializeNameCoder()));

registerConverter(new Converters.ApplicationConverter());
StringCache cache = new StringCache();

registerConverter(new Converters.ApplicationConverter(cache));
registerConverter(new Converters.ApplicationsConverter());
registerConverter(new Converters.DataCenterInfoConverter());
registerConverter(new Converters.InstanceInfoConverter());
registerConverter(new Converters.DataCenterInfoConverter(cache));
registerConverter(new Converters.InstanceInfoConverter(cache));
registerConverter(new Converters.LeaseInfoConverter());
registerConverter(new Converters.MetadataConverter());
registerConverter(new Converters.MetadataConverter(cache));
setMode(XStream.NO_REFERENCES);
processAnnotations(new Class[] {InstanceInfo.class, Application.class,
Applications.class});
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.netflix.discovery.converters;

import org.junit.Test;

import static org.junit.Assert.assertTrue;

/**
* @author Tomasz Bak
*/
public class StringCacheTest {

public static final int CACHE_SIZE = 100000;

@Test
public void testVerifyStringsAreGarbageCollectedIfNotReferenced() throws Exception {
StringCache cache = new StringCache();
for (int i = 0; i < CACHE_SIZE; i++) {
cache.cachedValueOf("id#" + i);
}
gc();
// Testing GC behavior is unpredictable, so we set here low target level
// The tests run on desktop show that all strings are removed actually.
assertTrue(cache.size() < CACHE_SIZE * 0.1);
}

public static void gc() {
System.gc();
System.runFinalization();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// IGNORE
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;

import com.netflix.discovery.converters.StringCache;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand Down Expand Up @@ -77,6 +78,8 @@ public class EurekaBootStrap implements ServletContextListener {
private static final int EIP_BIND_SLEEP_TIME_MS = 1000;
private static final Timer timer = new Timer("Eureka-EIPBinder", true);

private final StringCache stringCache = new StringCache();

/**
* Initializes Eureka, including syncing up with other Eureka peers and publishing the registry.
*
Expand All @@ -89,10 +92,10 @@ public void contextInitialized(ServletContextEvent event) {

// For backward compatibility
JsonXStream.getInstance().registerConverter(
new V1AwareInstanceInfoConverter(),
new V1AwareInstanceInfoConverter(stringCache),
XStream.PRIORITY_VERY_HIGH);
XmlXStream.getInstance().registerConverter(
new V1AwareInstanceInfoConverter(),
new V1AwareInstanceInfoConverter(stringCache),
XStream.PRIORITY_VERY_HIGH);
InstanceInfo info = ApplicationInfoManager.getInstance().getInfo();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import com.netflix.appinfo.InstanceInfo;
import com.netflix.appinfo.InstanceInfo.InstanceStatus;
import com.netflix.discovery.converters.Converters.InstanceInfoConverter;
import com.netflix.discovery.converters.StringCache;

/**
* Support for {@link Version#V1}. {@link Version#V2} introduces a new status
Expand All @@ -29,6 +30,10 @@
*/
public class V1AwareInstanceInfoConverter extends InstanceInfoConverter {

public V1AwareInstanceInfoConverter(StringCache cache) {
super(cache);
}

@Override
public String getStatus(InstanceInfo info) {
Version version = CurrentRequestVersion.get();
Expand Down

0 comments on commit 5ab86b9

Please sign in to comment.