3

Environment:

  • Eclipse luna
  • maven 3.2.3
  • java 7

I've seen questions like this but what I don't understand is why eclipse can compile this file and we aren't able to do it through the shell.

I also know that this file it would have to be a properties file but it is what I found in a project.

Eclipse compiles de file and create the bytecode Messeges.class, but neither javac nor mvn could do that because the error: code too large.

This file Messages.java is an enum that has 3232 lines and is used as a properties to load al this in memory. It has about 3100 different elements.

Messages.java

public enum Messages {
  MENU_MANAGEMENT("Management menu"),
  ...3100..
  MENU_OTHER("Other");

  private String name;

  private Messages(String name) {
    this.name = name;
  }
  ...
}

Error

$javac Messages.java
Messages.java:11: error: code too large
        MENU_MANAGEMENT("Management menu"),
        ^
1 error

How or why is eclipse compiling this? I can not understand how eclipse can do that ¿?

Joe
  • 7,749
  • 19
  • 60
  • 110
  • The question is why the file does it compiles in eclipse, please read the complete question. I already said the property file is a better option. – Joe May 24 '18 at 06:48
  • 1
    You're right, sorry. For what I know about eclipse is, that it uses it's own compiler. So not `javac` directly. Which probably leads to that it can read/compile that larger file – Lino May 24 '18 at 06:50
  • 1
    Does the Eclipse compiler create valid bytecode? Can you run it? – howlger May 24 '18 at 06:55
  • yes, it is up and running in production! I tried to compile with javac, and mvn but none of them worked. – Joe May 24 '18 at 06:58
  • This means either the Eclipse compiler produces more compact bytecode or, more likely, javac incorrectly throws this error (see also [this fixed javac bug](https://bugs.java.com/bugdatabase/view_bug.do?bug_id=4309152)). If you get the same error with Java 10 and with the lates Java 11 Early Access build, report it to Oracle. – howlger May 24 '18 at 07:05
  • Might be this bug which has been fixed in Java 8: https://bugs.java.com/bugdatabase/view_bug.do?bug_id=8020132 – howlger May 24 '18 at 07:10
  • I'm not sure because it talks about jdk.nashorn that I think it is the javascript engine, – Joe May 24 '18 at 07:24
  • Yes, you're right, it's probably not that bug. Can you try it with javac of newer JDKs than Java 7? – howlger May 24 '18 at 07:42

3 Answers3

3

When the number of enum constants is higher than 2000, eclipse generates synthetic methods to work around the code size limit.

See https://bugs.eclipse.org/bugs/show_bug.cgi?id=331334

BTW: Switching to ecj as compiler in your maven build isn't a long term solution either: This solution only works before Java 9, because initializing a final field outside initialization methods is disallowed since https://bugs.java.com/view_bug.do?bug_id=JDK-8157181

Till Brychcy
  • 2,876
  • 1
  • 18
  • 28
1

Enums with many constants, especially if they use multiple constructor arguments tend to run into this error because the synthetic static initializer block gets too big (the code size is limited to about 64k per method).

In the long run the correct solution to this is to refactor your code to reduce the code size. Either by splitting the enum in multiple ones (possibly sharing a common interface to be able to continue using them in a consistent way) or by removing the argument (which will buy you some more constants, but you'll still run into this issue eventually).

The fact that the Eclipse compiler doesn't complain yet can be explained by slight differences in code generation. It simply doesn't hit the limit yet, but it will still hit it (try adding a few hundred more fields ...).

You could try using the Eclipse compiler in your Maven build, but that's just a temporary measure, as you'll still run into the same problem eventually.

See this question for a detailed description of the problem and suggested solutions.

Joachim Sauer
  • 302,674
  • 57
  • 556
  • 614
1

To put all already mentioned pieces together.

As @Joachim Sauer already said there is a limit for the byte code length of a method. see JLS 4.9.1.

The value of the code_length item must be less than 65536.

Assume a enum as

public enum FooEnum {
    NAME_1("1"),
    NAME_2("2"),
    ...
    NAME_2442("2442");
    private final String value;
    FooEnum(String enumValue) {
        this.value = enumValue;
    }
    public String getValue() {
        return value;
    }
}

Using JDK 8 the generated static initializer would be

static {};
  Code:
     0: new           #4                  // class FooEnum
     3: dup
     4: ldc           #8                  // String NAME_1
     6: iconst_0
     7: ldc           #9                  // String 1
     9: invokespecial #10                 // Method "<init>":(Ljava/lang/String;ILjava/lang/String;)V
    12: putstatic     #11                 // Field NAME_1:LFooEnum;
    ...
 65499: dup
 65500: sipush        2441
 65503: getstatic     #7334              // Field NAME_2442:LFooEnum;
 65506: aastore
 65507: putstatic     #1                 // Field $VALUES:[LFooEnum;
 65510: return

declaring enum NAME_2443 would create another 37 bytecodes which then would lead to the compilation error code to large.

As @Till Brychcy said the Eclipse compiler works around the size limit by generating additional methods, which are invoked in the static initializer.

The static initializer is generated as

static {};
  Code:
     0: invokestatic  #2455               // Method " enum constant initialization$2":()V
     3: invokestatic  #2458               // Method " enum constant initialization$3":()V
     6: sipush        2442
     9: anewarray     #1                  // class FooEnum
    12: dup
    13: iconst_0
    14: getstatic     #2461               // Field NAME_1:LFooEnum;
    17: aastore
    ...

the methods enum constant initialization$2 and enum constant initialization$3 initialize a enums NAME_1 till NAME_2000 respective NAME_2001 till NAME_2442.

To try out yourself create following two files

gen.sh

#!/bin/bash

mkdir -p src/main/java/
rm FooEnum.jdk FooEnum.ecj

(
echo "public enum FooEnum {"

number_of_enums=2442
i=1
while [ $i -lt $number_of_enums ]
do
  echo "    NAME_${i}(\"${i}\"),"
  i=$((i+1))
done
  echo "    NAME_${i}(\"${i}\");"

cat <<EOF
    private final String value;
    FooEnum(String value) {
        this.value = value;
    }
    public String getValue() {
        return value;
    }
}
EOF
) > src/main/java/FooEnum.java

mvn clean compile
javap -c -v -p target/classes/FooEnum.class > FooEnum.jdk

mvn clean compile -P ecj
javap -c -v -p target/classes/FooEnum.class > FooEnum.ecj

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>sub.optimal</groupId>
    <artifactId>ejc-demo</artifactId>
    <version>1.0</version>
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <!--<maven.compiler.source>1.8</maven.compiler.source>-->
        <!--<maven.compiler.target>1.8</maven.compiler.target>-->
    </properties>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.7.0</version>
            </plugin>
        </plugins>
    </build>
    <profiles>
        <profile>
            <id>ecj</id>
            <build>
                <plugins>
                    <plugin>
                        <artifactId>maven-compiler-plugin</artifactId>
                        <version>3.7.0</version>
                        <configuration>
                            <compilerId>eclipse</compilerId>
                        </configuration>
                        <dependencies>
                            <dependency>
                                <groupId>org.codehaus.plexus</groupId>
                                <artifactId>plexus-compiler-eclipse</artifactId>
                                <version>2.8.4</version>
                            </dependency>
                        </dependencies>
                    </plugin>
                </plugins>
            </build>
        </profile>
        <profile>
            <id>jdk9</id>
            <build>
                <plugins>
                    <plugin>
                        <groupId>org.apache.maven.plugins</groupId>
                        <artifactId>maven-compiler-plugin</artifactId>
                        <version>3.7.0</version>
                        <configuration>
                            <source>9</source>
                            <target>9</target>
                            <showWarnings>true</showWarnings>
                            <showDeprecation>true</showDeprecation>
                        </configuration>
                    </plugin>
                </plugins>
            </build>
        </profile>
    </profiles>
</project>

running the script gen.sh will generate the enum source file FooEnum.java and then compile it at first with your current JDK and second using the Eclipse compiler for Java. After each compilation the class is disassembled and the disassembled bytecode is stored in file FooEnum.jdk and FooEnum.ecj. For your further investigation.

SubOptimal
  • 22,518
  • 3
  • 53
  • 69