8

我有一个 Java Maven 项目,其中包含大约 800 个源文件(一些由 javacc/JTB 生成),使用 javac 编译需要 25 分钟。

当我将 pom.xml 更改为使用 Eclipse 编译器时,编译大约需要 30 秒。

关于为什么 javac (1.5) 运行如此缓慢的任何建议?(我不想永久切换到 Eclipse 编译器,因为 Maven 的插件似乎有点小错误。)

我有一个很容易重现问题的测试用例。以下代码在默认包中生成了一些源文件。如果您尝试使用 javac 编译 ImplementingClass.java,它似乎会暂停很长时间。

import java.io.File;
import java.io.FileNotFoundException;
import java.io.PrintStream;

public class CodeGenerator
{
    private final static String PATH = System.getProperty("java.io.tmpdir");
    private final static int NUM_TYPES = 1000;

    public static void main(String[] args) throws FileNotFoundException
    {
        PrintStream interfacePs = new PrintStream(PATH + File.separator + "Interface.java");
        PrintStream abstractClassPs = new PrintStream(PATH + File.separator + "AbstractClass.java");
        PrintStream implementingClassPs = new PrintStream(PATH + File.separator + "ImplementingClass.java");
        interfacePs.println("public interface Interface<T> {");
        abstractClassPs.println("public abstract class AbstractClass<T> implements Interface<T> {");
        implementingClassPs.println("public class ImplementingClass extends AbstractClass<Object> {");

        for (int i=0; i<NUM_TYPES; i++)
        {
            String nodeName = "Node" + i;
            PrintStream nodePs = new PrintStream(PATH + File.separator + nodeName + ".java");
            nodePs.printf("public class %s { }\n", nodeName);
            nodePs.close();
            interfacePs.printf("void visit(%s node, T obj);%n", nodeName);
            abstractClassPs.printf("public void visit(%s node, T obj) { System.out.println(obj.toString()); }%n", nodeName);
        }
        interfacePs.println("}");
        abstractClassPs.println("}");
        implementingClassPs.println("}");
        interfacePs.close();
        abstractClassPs.close();
        implementingClassPs.close();
    }
}
4

8 回答 8

7

Sun 通过电子邮件向我确认这是一个新错误(他们的错误数据库中的6827648)。

于 2009-04-08T12:03:57.063 回答
6

您在 JDK 1.6 中得到相同的行为,包括更新 14、构建 04,使用 G1 不会改变行为(尽管 G1 似乎工作得很好)。

使用 jvisualvm 监控 javac,重复的线程转储显示主线程花费大量时间

at com.sun.tools.javac.code.Types.isSubSignature(Types.java:1846)
at com.sun.tools.javac.code.Symbol$MethodSymbol.overrides(Symbol.java:1108)
at com.sun.tools.javac.code.Symbol$MethodSymbol.implementation(Symbol.java:1159)
at com.sun.tools.javac.comp.Check.checkCompatibleConcretes(Check.java:1239)
at com.sun.tools.javac.comp.Check.checkCompatibleSupertypes(Check.java:1567)
at com.sun.tools.javac.comp.Attr.attribClassBody(Attr.java:2674)
at com.sun.tools.javac.comp.Attr.attribClass(Attr.java:2628)
at com.sun.tools.javac.comp.Attr.attribClass(Attr.java:2564)
at com.sun.tools.javac.main.JavaCompiler.attribute(JavaCompiler.java:1036)
at com.sun.tools.javac.main.JavaCompiler.compile2(JavaCompiler.java:765)
at com.sun.tools.javac.main.JavaCompiler.compile(JavaCompiler.java:730)
at com.sun.tools.javac.main.Main.compile(Main.java:353)
at com.sun.tools.javac.main.Main.compile(Main.java:279)
at com.sun.tools.javac.main.Main.compile(Main.java:270)
at com.sun.tools.javac.Main.compile(Main.java:69)
at com.sun.tools.javac.Main.main(Main.java:54)

并搅动这些类的大量短暂实例:

com.sun.tools.javac.code.Types$Subst
com.sun.tools.javac.util.List
com.sun.tools.javac.code.Types$MethodType

我怀疑代码是通过com.sun.tools.javac.comp.Check.checkCompatibleConcretes将每种方法与其他所有方法进行比较 来搅动的

该方法的javadoc:

/** Check that a class does not inherit two concrete methods
 *  with the same signature.
 */

可能是 Eclipse 的编译器要么不执行该检查,要么不以相同的方式执行它。

于 2009-04-08T03:53:02.843 回答
5

可能是 javac 编译器在其堆限制(64MB 左右)附近运行。在这种情况下,它大部分时间都在垃圾收集器中。给编译器一大块内存,比如 256M 或 512M,看看它是否运行得更快。

于 2009-04-03T14:48:09.410 回答
2

您使用生成的源代码这一事实、速度上的巨大差异以及StackOverflowError可能表明您的一个(或多个)文件有一些javac解析器不同意的结构。

您能否尝试仅编译代码的子集,并查看是否有任何一个类/包会特别减慢进程(可能是生成的类/包之一)。

于 2009-03-27T08:44:27.553 回答
1

对于 Sun 编译器,您正在为要编译的每个文件启动一个完整的 JVM 进程。对于 Eclipse 编译器,它只是连接到一个守护进程。我建议将 fork 设置为 false,尽管它可能仍然没有那么快。

于 2009-03-26T17:19:47.497 回答
0

也许 Eclipse 构建只是编译修改过的源代码。如果你在清理之后在eclipse中编译它会发生什么?

于 2009-03-26T16:42:02.377 回答
0

我不知道 maven 如何调用编译器,但您提到的性能数字表明 javac 是在它自己的进程/VM 中执行的,正如另一个答案中已经建议的那样。由于为您编译的每个文件启动一个新进程/VM 的成本非常高,您需要确保将编译器配置为使用您可能已经拥有的 VM。我知道 ANT 提供了,但我自己没有使用过 maven。鉴于它很受欢迎,我怀疑它缺乏如此重要的功能。

于 2009-04-03T20:05:59.273 回答
0

我认为正在发生以下情况:Maven forks javac, JVM processes for different steps in its life-cycle: Maven Build Life-cycle

Eclipse 通常在后台(保存时)运行其编译,因此该步骤将被添加到编译阶段。如果存在大量依赖关系,这就是您失去吞吐量的地方。

此外(取决于 mvn 配置)每个测试方法都有自己的 JVM。由于测试通道是打包阶段的先决条件,因此您可能会在 JUnit 测试执行上浪费时间(特别是如果它们是运行缓慢的测试)。如果您的源代码树中有大量测试代码,这可能是罪魁祸首。

最有可能的是,您的班级执行了大量的文件 I/O,因此这是一个机会领域。看起来您的循环在每个文件发现事件中执行 1000 次,这意味着循环主体中有 800*1000 =800,000 个 PrintStream 创建。

于 2009-04-06T18:08:49.670 回答