有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

java扫描器。findInLine()大量泄漏内存

我正在运行一个简单的扫描程序来解析一个字符串,但我发现如果调用的次数足够多,我就会从内存错误中解脱出来。此代码作为为字符串数组重复构建的对象构造函数的一部分调用:

编辑:这里是更多信息的构造器;除了关于扫描仪的try-catch之外,没有更多的事情发生

   public Header(String headerText) {
        char[] charArr;
        charArr = headerText.toCharArray();
        // Check that all characters are printable characters
        if (charArr.length > 0 && !commonMethods.isPrint(charArr)) {
            throw new IllegalArgumentException(headerText);
        }
        // Check for header suffix
        Scanner sc = new Scanner(headerText);
        MatchResult res;
        try {
            sc.findInLine("(\\D*[a-zA-Z]+)(\\d*)(\\D*)");
            res = sc.match();
        } finally {
            sc.close();
        }

        if (res.group(1) == null || res.group(1).isEmpty()) {
            throw new IllegalArgumentException("Missing header keyword found");     // Empty header to store
        } else {
            mnemonic = res.group(1).toLowerCase();                            // Store header
        }
        if (res.group(2) == null || res.group(2).isEmpty()) {
            suffix = -1;
        } else {
            try {
                suffix = Integer.parseInt(res.group(2));       // Store suffix if it exists
            }  catch (NumberFormatException e) {
                throw new NumberFormatException(headerText);
            }
        }
        if (res.group(3) == null || res.group(3).isEmpty()) {
            isQuery= false;
        } else {
            if (res.group(3).equals("?")) {
                isQuery = true;
            } else {
                throw new IllegalArgumentException(headerText);
            }
        }

        // If command was of the form *ABC, reject suffixes and prefixes
        if (mnemonic.contains("*") 
                && suffix != -1) {
            throw new IllegalArgumentException(headerText);
        }
    }

探查器内存快照显示扫描仪的读取(Char)方法。FindLine()在操作过程中被分配大量内存,就像我扫描几十万个字符串一样;几秒钟后,它已经分配了超过38MB的内存

enter image description here

我认为在构造函数中使用scanner()后调用close()会标记要由GC清除的旧对象,但不知何故,它会保留下来,并且read方法会在填充堆之前积累千兆字节的数据

谁能给我指出正确的方向吗


共 (6) 个答案

  1. # 1 楼答案

    正在填充内存的字符串是在findInLine()中创建的。因此,重复的Pattern创建不是问题

    在不知道其余代码的作用的情况下,我猜想从matcher中得到的一个组被保存在对象的一个字段中。然后,正如您在这里看到的,该字符串将在findInLine()中被分配,但它被保留的事实可能是由于您的代码

    编辑:

    这是你的问题:

    mnemonic = res.group(1).toLowerCase();
    

    您可能没有意识到的是,如果字符串中没有大写字母,则toLowerCase()返回this。另外,group(int)返回一个substring(),它将创建一个新字符串,该字符串由与完整字符串相同的char[]支持。因此,mnemonic实际上包含整行的char[]

    解决办法就是:

    mnemonic = new String(res.group(1).toLowerCase());
    
  2. # 2 楼答案

    我已经找到了问题的根源,它不是扫描器,而是包含在构造函数中进行扫描的对象的列表

    这个问题与保存对包含解析的对象的引用的列表的溢出有关,基本上每单位时间接收的字符串比可以处理的要多,并且列表不断增长,直到没有更多的RAM为止。将此列表限制为最大大小现在可以防止解析器过载内存;我将在解析器和数据源之间添加一些同步,以避免将来出现这种溢出

    感谢大家的建议,我已经对扫描仪进行了一些性能方面的更改,并感谢@RobI为我指出了jvisualvm,这使我能够追踪到持有参考资料的确切罪犯。内存转储未显示引用链接

  3. # 3 楼答案

    您尚未发布所有代码,但鉴于您正在重复扫描同一个正则表达式,因此事先编译静态Pattern并将其用于扫描程序的查找将更加有效:

    static Pattern p = Pattern.compile("(\\D*[a-zA-Z]+)(\\d*)(\\D*)");
    

    在构造器中:

    sc.findInLine(p);
    

    这可能是OOM问题的根源,也可能不是,但它肯定会使您的解析速度加快一点

    相关:java.util.regex - importance of Pattern.compile()?

    更新:在您发布了更多代码后,我发现了一些其他问题。如果您反复调用此构造函数,这意味着您可能事先标记或分解了输入。为什么要创建一个新的Scanner来解析每一行?它们很贵;如果可能的话,您应该使用相同的Scanner来解析整个文件。使用一个带有预编译的PatternScanner将比您现在所做的要快得多,这将为您正在解析的每一行创建一个新的Scanner和一个新的Pattern

  4. # 4 楼答案

    我认为您的代码片段不完整。我相信你是在循环调用scanner.findInLine()。无论如何,请尝试调用scanner.reset()。我希望这能解决你的问题

  5. # 5 楼答案

    JVM显然没有时间进行垃圾收集。可能是因为它重复使用相同的代码(构造函数)来创建同一类的多个实例。JVM可能不会对GC做任何事情,直到运行时堆栈发生变化——在本例中,这种情况不会发生。我曾经被警告过在构造函数中做“太多”,因为在调用其他方法时,一些内存管理行为并不完全相同

  6. # 6 楼答案

    您的问题是,您正在扫描数十万个字符串,并将模式作为字符串传入,因此循环的每一次迭代都有一个新的模式对象。您可以将图案从循环中拉出,如下所示:

        Pattern toMatch = Pattern.compile("(\\D*[a-zA-Z]+)(\\d*)(\\D*)")
    
        Scanner sc = new Scanner(headerText);
        MatchResult res;
    
        try {
            sc.findInLine(toMatch);
            res = sc.match();
        } finally {
            sc.close();
        }
    

    然后,您将只将对象引用传递给toMatch,而不是为每次尝试匹配创建一个新模式对象的开销。这会修补你的漏洞