Java 收集和内存优化

Posted

技术标签:

【中文标题】Java 收集和内存优化【英文标题】:Java collection and memory optimization 【发布时间】:2012-08-12 00:18:31 【问题描述】:

我为自定义表编写了一个自定义索引,该表使用 500MB 的堆来存储 500k 个字符串。只有 10% 的字符串是唯一的;其余的都是重复的。每个字符串的长度为 4。

如何优化我的代码?我应该使用另一个集合吗?我尝试实现一个自定义字符串池来节省内存:

public class StringPool 

    private static WeakHashMap<String, String> map = new WeakHashMap<>();

    public static String getString(String str)  
        if (map.containsKey(str)) 
            return map.get(str);
         else 
            map.put(str, str);
            return map.get(str);
        
    


private void buildIndex() 
        if (monitorModel.getMessageIndex() == null) 
            // the index, every columns create an index
            ArrayList<HashMap<String, TreeSet<Integer>>> messageIndex = new ArrayList<>(filterableColumn.length);
            for (int i = filterableColumn.length; i >= 0; i--) 
                // key -> string,   value -> treeset, the row wich contains the key
                HashMap<String, TreeSet<Integer>> hash = new HashMap<>();
                messageIndex.add(hash);
            
            // create index for every column
            for (int i = monitorModel.getParser().getMyMessages().getMessages().size() - 1; i >= 0; --i) 
                TreeSet<Integer> tempList;

                for (int j = 0; j < filterableColumn.length; j++) 
                    String value  = StringPool.getString(getValueAt(i, j).toString());
                    if (!messageIndex.get(j).containsKey(value)) 
                        tempList = new TreeSet<>();
                        messageIndex.get(j).put(value, tempList);
                     else 
                        tempList = messageIndex.get(j).get(value);
                    

                    tempList.add(i);
                
            
            monitorModel.setMessageIndex(messageIndex);
        
    

【问题讨论】:

500,000 4 个字符串只有几十兆的内存,根本没有缓存。认为你找错地方了。 我同意 Affe 的观点,即不应超过几 MB,即使假设每 4 个字母字符串 50 字节(这是悲观的)也只会让您达到 25MB。 ArrayList>> -- 哇,这是一个结构! :) 使用这种数据结构会产生巨大的开销。这很可能是高内存消耗的原因,而不是字符串本身。我前段时间写过一篇关于 Java Collection 开销的博文:plumbr.eu/blog/fat-collections 谢谢,我搜索这种答案,我会看你的博客。 【参考方案1】:

您可能希望在分析器中检查您的内存堆。我的猜测是内存消耗主要不是在 String 存储中,而是在许多 TreeSet&lt;Integer&gt; 实例中。如果是这样,您可以通过使用原始数组(int[]short[]byte[],具体取决于您存储的整数值的实际大小)进行显着优化。或者您可以查看原始集合类型,例如 FastUtil 或 Trove 提供的那些。

如果您确实发现字符串存储存在问题,我会假设您希望将应用程序扩展到超过 500k 字符串,或者特别严格的内存限制要求您对短字符串进行重复数据删除。

正如 Dev 所说,String.intern() 将为您删除重复的字符串。然而,需要注意的是 - 在 Oracle 和 OpenJDK 虚拟机中,String.intern() 会将这些字符串存储在 VM 永久代中,以便将来不会对它们进行垃圾收集。如果:

    您存储的字符串在 VM 的整个生命周期内都不会更改(例如,如果您在启动时读取静态列表并在应用程序的整个生命周期中使用它)。 您需要存储的字符串可以轻松存储在 VM 永久代中(为类加载和 PermGen 的其他使用者提供足够的空间)。更新:见下文。

如果其中任何一个条件不成立,那么您构建自定义池可能是正确的。但我的建议是您考虑使用简单的HashMap 代替您当前使用的WeakHashMap。您可能不希望这些值在缓存中被垃圾回收,WeakHashMap 增加了另一个间接级别(以及相关的对象指针),进一步增加了内存消耗。

更新:有人告诉我 JDK 7 将实习字符串 (String.intern()) 存储在主堆中,而不是像早期的 JDK 那样存储在 perm-gen 中。如果您使用的是 JDK 7,这会降低 String.intern() 的风险。

【讨论】:

感谢您的回答 Aaron,我尝试使用原始集合,我尝试使用 String.intern(),但我的堆减少了更多内存。【参考方案2】:

无需提出自定义池。只需使用String.intern()

【讨论】:

感谢您的回答,我尝试了这个但它没有用,只减少堆-2MB。 这样可以省去创建自己的字符串池的麻烦。我确实相信@AaronD 的回答更准确,你的很多内存使用可能来自被实例化的嵌套数据结构的数量。

以上是关于Java 收集和内存优化的主要内容,如果未能解决你的问题,请参考以下文章

图解Java自动内存管理机制及JVM优化配置

性能优化之 JVM 高级特性

JVM 堆内存,参数优化

JVM故障问题排查心得「内存优化技术」Java虚拟机内存优化实战案例分析指南

Android内存优化篇

Android技术书3