线程,Arraylist对内存的影响 - Java

时间:2013-03-18 15:41:42

标签: java multithreading jvm out-of-memory

我的输入数据是一个列表中的50,000(或更多)电子邮件消息,并且dur中的多个收件人在to,cc和bcc中存在大量重复。因此,我需要从此列表中提取唯一的消息。

我必须比较Message的某些属性(From,To list和contains(String only))以确定是否存在相同的内容。

现在我将这50,000条消息分成50个小1000的消息列表,并在它的主题中运行每个小列表的重复。

所有线程将它的输出添加到一个列表中,最后我检查该线程中的重复。 当我这样做时,我的JVM达到1.25 GB内存。

因此,如果我尝试推送超过50,000条消息,我会收到内存不足错误。

我有一个名为removeDeduplicate(array of messages, blank list)的方法,它将消息数组和空列表作为输入,并在该空白列表中返回唯一消息。 这是我的代码:

public Message[] processForDeduplication(Message[] msgs) throws MessagingException, IOException, InterruptedException {
    final List<Message> output = new ArrayList<Message>();

    if(msgs.length < MAX_MSG){
        output.addAll(removeDeduplication(msgs, new ArrayList<Message>()));
    } else {
        List<Thread> threads = new ArrayList<Thread>();
        int index = 0, lastIndex = MAX_MSG;

        while(index < msgs.length){
            if(lastIndex >= msgs.length) {
                lastIndex = msgs.length;
            }
            final Message[] temp = Arrays.copyOfRange(msgs, index, lastIndex);
            Thread t = new Thread(new Runnable(){
                @Override
                public void run() {
                    try {
                        output.addAll(removeDeduplication(temp, new ArrayList<Message>()));
                    } catch (MessagingException ex) {
                        logger.error(EmailComparator.class.getName() +  ex);
                    } catch (IOException ex) {
                        logger.error(EmailComparator.class.getName() +  ex);
                    }
                }
             });
             t.start();
             threads.add(t);
            index = lastIndex;
            lastIndex = lastIndex + MAX_MSG;
        }
        for(Thread t: threads){
            while(t.isAlive()){
                Thread.sleep(100);
            }
        }
        threads = null;
    }
    List<Message> results = removeDeduplication(convertToArray(output), new ArrayList<Message>());
    return convertToArray(results);
}

我正在尝试微调我的代码以获得内存增强和性能。 现在需要大约12-15秒来完成50,000条记录,我希望这是5-6秒。

2 个答案:

答案 0 :(得分:1)

我不确定你的Message是什么,所以我认为它是javax.mail.Message。我创建了一个包装器对象,用于检查指定的消息是否相等。此对象将fromto数组缓存为Set s - 这允许更快的等于比较,因为Set具有O(1)包含方法。
包装器还会缓存hashCode,以便Set不必重新计算它。

public static class MessageWrapper {

    private final Message message;
    private final Set<Address> from;
    private final Set<Address> to;
    private final Object content;
    private final int hashCode;

    public MessageWrapper(Message message) throws MessagingException, IOException {
        this.message = message;
        this.from = new HashSet<Address>(Arrays.asList(message.getFrom()));
        this.to = new HashSet<Address>(Arrays.asList(message.getRecipients(Message.RecipientType.TO)));
        this.content = message.getContent();
        this.hashCode = calcHashCode();
    }

    public Message getMessage() {
        return message;
    }

    private int calcHashCode() {
        int hash = 7;
        hash = 37 * hash + (this.from != null ? this.from.hashCode() : 0);
        hash = 37 * hash + (this.to != null ? this.to.hashCode() : 0);
        hash = 37 * hash + (this.content != null ? this.content.hashCode() : 0);
        return hash;
    }

    @Override
    public int hashCode() {
        return hashCode;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == null) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        final MessageWrapper other = (MessageWrapper) obj;
        if (this.from != other.from && (this.from == null || !this.from.equals(other.from))) {
            return false;
        }
        if (this.to != other.to && (this.to == null || !this.to.equals(other.to))) {
            return false;
        }
        if (this.content != other.content && (this.content == null || !this.content.equals(other.content))) {
            return false;
        }
        return true;
    }
}

存储最昂贵的东西实际上是content - 您可能只想考虑存储content.hashCode然后进行比较;但这会让冲突发生。

现在您需要做的就是将所有Message放入MessageWrapper并将其放入HashSet - 这会自动删除equals() <的项目/ p>

public Message[] processForDeduplication(final Message[] messages) throws MessagingException, IOException {
    final Set<MessageWrapper> messageWrappers = new HashSet<MessageWrapper>(messages.length, 1.0f);
    for (final Message m : messages) {
        messageWrappers.add(new MessageWrapper(m));
    }
    final List<Message> ms = new ArrayList<Message>(messages.length);
    for (final MessageWrapper wrapper : messageWrappers) {
        ms.add(wrapper.getMessage());
    }
    return ms.toArray(new Message[messages.length]);
}

这有点乱,因为你必须在最后将事物转换回Message[]

显然,如果您的Message不是javax.mail.Message,则实施可能会有所不同。您甚至可以直接在相关课程上实施equalshashCode

答案 1 :(得分:0)

@Perception&amp; @大卫 我试过你们的建议,代码如下所示。 现在情况变得更糟,只有20,000我得到OutOfMemory例外。

public Message[] processForDeduplicationNew(Message[] msgs) throws MessagingException, IOException, InterruptedException {
    TreeSet<Message> output = new TreeSet<Message>(new Comparator<Message>(){

        @Override
        public int compare(Message msg1, Message msg2) {
            try {
                List<String> newRecipients = getRecipients(msg1.getAllRecipients());
                List<String> recipients = getRecipients(msg2.getAllRecipients());

                if (newRecipients.size() == recipients.size()) {
                    for (int i = 0; i < recipients.size(); i++) {
                        if (!StringUtils.equalsIgnoreCase(newRecipients.get(i), recipients.get(i))) {
                            return 1;
                        }
                    }
                } else {
                    return 1;
                }

                if (!StringUtils.equalsIgnoreCase(((InternetAddress) msg1.getFrom()[0]).getAddress(), ((InternetAddress) msg2.getFrom()[0]).getAddress())) {
                    return 1;
                }

                BodyPart newMsgBody= ((Multipart) msg1.getContent()).getBodyPart(0);
                BodyPart msgBody = ((Multipart) msg2.getContent()).getBodyPart(0);

                if (!StringUtils.equalsIgnoreCase((String) newMsgBody.getContent(), (String) msgBody.getContent())) {
                    return 1;
                }
            } catch (MessagingException ex) {
                java.util.logging.Logger.getLogger(EmailComparator.class.getName()).log(Level.SEVERE, null, ex);
            } catch (IOException ex) {
                java.util.logging.Logger.getLogger(EmailComparator.class.getName()).log(Level.SEVERE, null, ex);
            }
            return 0;
        }

    });

    for (Message message : msgs) {
        output.add(message);
    }

    return output.toArray(new Message[0]);
}

更多想法...... 感谢