diff --git a/apps/routerconsole/java/src/net/i2p/router/web/helpers/JobQueueHelper.java b/apps/routerconsole/java/src/net/i2p/router/web/helpers/JobQueueHelper.java
index b5a67fcac1c6cf426b2f78fb79581544d1340743..185fdba80be6c8e1ae41b4066051c42c3a4b2cf2 100644
--- a/apps/routerconsole/java/src/net/i2p/router/web/helpers/JobQueueHelper.java
+++ b/apps/routerconsole/java/src/net/i2p/router/web/helpers/JobQueueHelper.java
@@ -14,7 +14,7 @@ import net.i2p.data.DataHelper;
 import net.i2p.router.Job;
 import net.i2p.router.JobStats;
 import net.i2p.router.web.HelperBase;
-import net.i2p.util.ObjectCounter;
+import net.i2p.util.ObjectCounterUnsafe;
 
 public class JobQueueHelper extends HelperBase {
     
@@ -79,7 +79,7 @@ public class JobQueueHelper extends HelperBase {
         buf.append("<h3 id=\"readyjobs\">")
            .append(_t("Ready/waiting jobs")).append(": ").append(readyJobs.size())
            .append("</h3><ol>");
-        ObjectCounter<String> counter = new ObjectCounter<String>();
+        ObjectCounterUnsafe<String> counter = new ObjectCounterUnsafe<String>();
         for (int i = 0; i < readyJobs.size(); i++) {
             Job j = readyJobs.get(i);
             counter.increment(j.getName());
@@ -129,7 +129,7 @@ public class JobQueueHelper extends HelperBase {
     }
     
     /** @since 0.9.5 */
-    private void getJobCounts(StringBuilder buf, ObjectCounter<String> counter) {
+    private void getJobCounts(StringBuilder buf, ObjectCounterUnsafe<String> counter) {
         List<String> names = new ArrayList<String>(counter.objects());
         if (names.size() < 4)
             return;
@@ -232,10 +232,10 @@ public class JobQueueHelper extends HelperBase {
 
     /** @since 0.9.5 */
     private static class JobCountComparator implements Comparator<String>, Serializable {
-         private final ObjectCounter<String> _counter;
+         private final ObjectCounterUnsafe<String> _counter;
          private final Collator coll = Collator.getInstance();
 
-         public JobCountComparator(ObjectCounter<String> counter) {
+         public JobCountComparator(ObjectCounterUnsafe<String> counter) {
              _counter = counter;
          }
 
diff --git a/apps/routerconsole/java/src/net/i2p/router/web/helpers/NetDbRenderer.java b/apps/routerconsole/java/src/net/i2p/router/web/helpers/NetDbRenderer.java
index 11b2faf03ac394c0315add737f38d1c041f9efed..751c6af59a9558f54dac8d477a754bf817f9136c 100644
--- a/apps/routerconsole/java/src/net/i2p/router/web/helpers/NetDbRenderer.java
+++ b/apps/routerconsole/java/src/net/i2p/router/web/helpers/NetDbRenderer.java
@@ -52,7 +52,7 @@ import net.i2p.router.web.WebAppStarter;
 import net.i2p.util.Addresses;
 import net.i2p.util.ConvertToHash;
 import net.i2p.util.Log;
-import net.i2p.util.ObjectCounter;
+import net.i2p.util.ObjectCounterUnsafe;
 import net.i2p.util.Translate;
 import net.i2p.util.VersionComparator;
 
@@ -935,8 +935,8 @@ class NetDbRenderer {
             buf.setLength(0);
         }
 
-        ObjectCounter<String> versions = new ObjectCounter<String>();
-        ObjectCounter<String> countries = new ObjectCounter<String>();
+        ObjectCounterUnsafe<String> versions = new ObjectCounterUnsafe<String>();
+        ObjectCounterUnsafe<String> countries = new ObjectCounterUnsafe<String>();
         int[] transportCount = new int[TNAMES.length];
 
         int skipped = 0;
@@ -1137,10 +1137,10 @@ class NetDbRenderer {
      */
     private class CountryCountComparator implements Comparator<String> {
          private static final long serialVersionUID = 1L;
-         private final ObjectCounter<String> counts;
+         private final ObjectCounterUnsafe<String> counts;
          private final Collator coll;
 
-         public CountryCountComparator(ObjectCounter<String> counts) {
+         public CountryCountComparator(ObjectCounterUnsafe<String> counts) {
              super();
              this.counts = counts;
              coll = Collator.getInstance(new Locale(Messages.getLanguage(_context)));
diff --git a/apps/routerconsole/java/src/net/i2p/router/web/helpers/SybilRenderer.java b/apps/routerconsole/java/src/net/i2p/router/web/helpers/SybilRenderer.java
index d48a415f9d853148573ab815cbf3479cb02fe1ae..3461d95e82dedce30c988e2c4dad2aecc7f12087 100644
--- a/apps/routerconsole/java/src/net/i2p/router/web/helpers/SybilRenderer.java
+++ b/apps/routerconsole/java/src/net/i2p/router/web/helpers/SybilRenderer.java
@@ -46,7 +46,6 @@ import net.i2p.stat.RateAverages;
 import net.i2p.stat.RateStat;
 import net.i2p.util.ConvertToHash;
 import net.i2p.util.Log;
-import net.i2p.util.ObjectCounter;
 import net.i2p.util.SystemVersion;
 import net.i2p.util.Translate;
 import net.i2p.util.VersionComparator;
diff --git a/core/java/src/net/i2p/util/ObjectCounterUnsafe.java b/core/java/src/net/i2p/util/ObjectCounterUnsafe.java
new file mode 100644
index 0000000000000000000000000000000000000000..3e2f532cdb00306b875ccd704456fbb52571550e
--- /dev/null
+++ b/core/java/src/net/i2p/util/ObjectCounterUnsafe.java
@@ -0,0 +1,107 @@
+package net.i2p.util;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ *  Count things.
+ *  NOT thread safe, mostly for UI and Sybil.
+ *  Dropin replacement for ObjectCounter.
+ *  Much less object churn than ObjectCounter.
+ *  Also provides add() and sortedObjects()
+ *
+ *  @since 0.9.58
+ */
+public class ObjectCounterUnsafe<K> {
+    private final HashMap<K, Int> map = new HashMap<K, Int>();
+
+    /**
+     *  Add one.
+     *  @return count after increment
+     */
+    public int increment(K h) {
+        Int i = map.get(h);
+        if (i != null) {
+            return ++(i.c);
+        }
+        map.put(h, new Int(1));
+        return 1;
+    }
+
+    /**
+     *  Add a value
+     *  @return count after adding
+     */
+    public int add(K h, int val) {
+        Int i = map.get(h);
+        if (i != null) {
+            i.c += val;
+            return i.c;
+        }
+        map.put(h, new Int(val));
+        return val;
+    }
+
+    /**
+     *  @return current count
+     */
+    public int count(K h) {
+        Int i = map.get(h);
+        if (i != null)
+            return i.c;
+        return 0;
+    }
+
+    /**
+     *  @return set of objects with counts &gt; 0
+     */
+    public Set<K> objects() {
+        return map.keySet();
+    }
+
+    /**
+     *  @return list of objects reverse sorted by count, highest to lowest
+     */
+    public List<K> sortedObjects() {
+        List<K> rv = new ArrayList<K>(map.keySet());
+        Collections.sort(rv, new ObjComparator());
+        return rv;
+    }
+
+    /**
+     *  Start over. Reset the count for all keys to zero.
+     */
+    public void clear() {
+        map.clear();
+    }
+
+    /**
+     *  Reset the count for this key to zero
+     */
+    public void clear(K h) {
+        map.remove(h);
+    }
+
+    /**
+     *  Modifiable integer
+     */
+    private static class Int {
+        int c;
+        public Int(int i) { c = i; }
+    }
+
+    /**
+     *  reverse sort
+     */
+    private class ObjComparator implements Comparator<K> {
+        public int compare(K l, K r) {
+            return (map.get(r).c - map.get(l).c);
+        }
+    }
+}
+
diff --git a/router/java/src/net/i2p/router/sybil/Analysis.java b/router/java/src/net/i2p/router/sybil/Analysis.java
index 074eae207f57760b23455b33f1dd0150d4d23d6c..d66c4386b204609e088fda2f513aa74f489188a3 100644
--- a/router/java/src/net/i2p/router/sybil/Analysis.java
+++ b/router/java/src/net/i2p/router/sybil/Analysis.java
@@ -41,7 +41,7 @@ import net.i2p.stat.RateAverages;
 import net.i2p.stat.RateStat;
 import net.i2p.util.Addresses;
 import net.i2p.util.Log;
-import net.i2p.util.ObjectCounter;
+import net.i2p.util.ObjectCounterUnsafe;
 import net.i2p.util.SystemVersion;
 import net.i2p.util.Translate;
 
@@ -683,7 +683,7 @@ public class Analysis extends JobImpl implements RouterApp {
      *  @since 0.9.38 split out from renderIPGroups32()
      */
     public Map<Integer, List<RouterInfo>> calculateIPGroups32(List<RouterInfo> ris, Map<Hash, Points> points) {
-        ObjectCounter<Integer> oc = new ObjectCounter<Integer>();
+        ObjectCounterUnsafe<Integer> oc = new ObjectCounterUnsafe<Integer>();
         for (RouterInfo info : ris) {
             byte[] ip = getIP(info);
             if (ip == null)
@@ -732,7 +732,7 @@ public class Analysis extends JobImpl implements RouterApp {
      *  @since 0.9.38 split out from renderIPGroups24()
      */
     public Map<Integer, List<RouterInfo>> calculateIPGroups24(List<RouterInfo> ris, Map<Hash, Points> points) {
-        ObjectCounter<Integer> oc = new ObjectCounter<Integer>();
+        ObjectCounterUnsafe<Integer> oc = new ObjectCounterUnsafe<Integer>();
         for (RouterInfo info : ris) {
             byte[] ip = getIP(info);
             if (ip == null)
@@ -785,7 +785,7 @@ public class Analysis extends JobImpl implements RouterApp {
      *  @since 0.9.38 split out from renderIPGroups16()
      */
     public Map<Integer, List<RouterInfo>> calculateIPGroups16(List<RouterInfo> ris, Map<Hash, Points> points) {
-        ObjectCounter<Integer> oc = new ObjectCounter<Integer>();
+        ObjectCounterUnsafe<Integer> oc = new ObjectCounterUnsafe<Integer>();
         for (RouterInfo info : ris) {
             byte[] ip = getIP(info);
             if (ip == null)
@@ -828,7 +828,7 @@ public class Analysis extends JobImpl implements RouterApp {
      *  @since 0.9.57
      */
     public Map<Long, List<RouterInfo>> calculateIPGroups64(List<RouterInfo> ris, Map<Hash, Points> points) {
-        ObjectCounter<Long> oc = new ObjectCounter<Long>();
+        ObjectCounterUnsafe<Long> oc = new ObjectCounterUnsafe<Long>();
         for (RouterInfo info : ris) {
             byte[] ip = getIPv6(info);
             if (ip == null)
@@ -893,7 +893,7 @@ public class Analysis extends JobImpl implements RouterApp {
      *  @since 0.9.57
      */
     public Map<Long, List<RouterInfo>> calculateIPGroups48(List<RouterInfo> ris, Map<Hash, Points> points) {
-        ObjectCounter<Long> oc = new ObjectCounter<Long>();
+        ObjectCounterUnsafe<Long> oc = new ObjectCounterUnsafe<Long>();
         for (RouterInfo info : ris) {
             byte[] ip = getIPv6(info);
             if (ip == null)
diff --git a/router/java/src/net/i2p/router/tunnel/pool/TunnelPoolManager.java b/router/java/src/net/i2p/router/tunnel/pool/TunnelPoolManager.java
index 27460c6a824e3137205b214193312c8fd82866c5..e6a13f91a9ee7d0fedce81443e48be415b18be8e 100644
--- a/router/java/src/net/i2p/router/tunnel/pool/TunnelPoolManager.java
+++ b/router/java/src/net/i2p/router/tunnel/pool/TunnelPoolManager.java
@@ -24,7 +24,7 @@ import net.i2p.router.TunnelPoolSettings;
 import net.i2p.router.tunnel.TunnelDispatcher;
 import net.i2p.util.I2PThread;
 import net.i2p.util.Log;
-import net.i2p.util.ObjectCounter;
+import net.i2p.util.ObjectCounterUnsafe;
 import net.i2p.util.SimpleTimer;
 
 /**
@@ -645,7 +645,7 @@ public class TunnelPoolManager implements TunnelManagerFacade {
     }
 
     /** @return total number of non-fallback expl. + client tunnels */
-    private int countTunnelsPerPeer(ObjectCounter<Hash> lc) {
+    private int countTunnelsPerPeer(ObjectCounterUnsafe<Hash> lc) {
         List<TunnelPool> pools = new ArrayList<TunnelPool>();
         listPools(pools);
         int tunnelCount = 0;
@@ -681,7 +681,7 @@ public class TunnelPoolManager implements TunnelManagerFacade {
      *  @return Set of peers that should not be allowed in another tunnel
      */
     public Set<Hash> selectPeersInTooManyTunnels() {
-        ObjectCounter<Hash> lc = new ObjectCounter<Hash>();
+        ObjectCounterUnsafe<Hash> lc = new ObjectCounterUnsafe<Hash>();
         int tunnelCount = countTunnelsPerPeer(lc);
         Set<Hash> rv = new HashSet<Hash>();
         if (tunnelCount >= 4 && _context.router().getUptime() > 10*60*1000) {