Java error code too large

Code conventions and standard software development wisdom dictate that methods should not be too long because they become difficult to fully comprehend, they lose readability when they get too long, they are difficult to appropriately unit test , and they are difficult to reuse .

Dustin Marx

About |

A software developer’s public collection of tips and tricks, real-world solutions, and industry commentary related to Java programming.

Code conventions and standard software development wisdom dictate that methods should not be too long because they become difficult to fully comprehend, they lose readability when they get too long, they are difficult to appropriately unit test, and they are difficult to reuse. Because most Java developers strive to write highly modular code with small, highly cohesive methods, the «code too large» error in Java is not seen very often. When this error is seen, it is often in generated code.

In this blog post, I intentionally force this «code too large» error to occur. Why in the world would one intentionally do this? In this case, it is because I always understand things better when I tinker with them rather than just reading about them and because doing so gives me a chance to demonstrate Groovy, the Java Compiler API (Java SE 6), and javap.

It turns out that the magic number for the «code too large» error is 65535 bytes (compiled byte code, not source code). Hand-writing a method large enough to lead to this size of a .class file would be tedious (and not worth the effort in my opinion). However, it is typically generated code that leads to this in the wild and so generation of code seems like the best approach to reproducing the problem. When I think of generic Java code generation, I think Groovy.

The Groovy script that soon follows generates a Java class that isn’t very exciting. However, the class will have its main function be of an approximate size based on how many conditions I tell the script to create. This allows me to quickly try generating Java classes with different main() method sizes to ascertain when the main() becomes too large.

After the script generates the Java class, it also uses the Java Compiler API to automatically compile the newly generated Java class for me. The resultant .class file is placed in the same directory as the source .java file. The script, creatively named generateJavaClass.groovy, is shown next.

generateJavaClass.groovy

#!/usr/bin/env groovy

import javax.tools.ToolProvider

println "You're running the script ${System.getProperty('script.name')}"
if (args.length < 2)
{
   println "Usage: javaClassGeneration packageName className baseDir #loops"
   System.exit(-1)
}

// No use of "def" makes the variable available to entire script including the
// defined methods ("global" variables)

packageName = args[0]
packagePieces = packageName.tokenize(".")  // Get directory names
def fileName = args[1].endsWith(".java") ? args[1] : args[1] + ".java"
def baseDirectory = args.length > 2 ? args[2] : System.getProperty("user.dir")
numberOfConditionals = args.length > 3 ? Integer.valueOf(args[3]) : 10

NEW_LINE = System.getProperty("line.separator")

// The setting up of the indentations shows off Groovy's easy feature for
// multiplying Strings and Groovy's tie of an overloaded * operator for Strings
// to the 'multiply' method.  In other words, the "multiply" and "*" used here
// are really the same thing.
SINGLE_INDENT = '   '
DOUBLE_INDENT = SINGLE_INDENT.multiply(2)
TRIPLE_INDENT = SINGLE_INDENT * 3

def outputDirectoryName = createDirectories(baseDirectory)
def generatedJavaFile = generateJavaClass(outputDirectoryName, fileName)
compileJavaClass(generatedJavaFile)


/**
 * Generate the Java class and write its source code to the output directory
 * provided and with the file name provided.  The generated class's name is
 * derived from the provided file name.
 *
 * @param outDirName Name of directory to which to write Java source.
 * @param fileName Name of file to be written to output directory (should include
 *    the .java extension).
 * @return Fully qualified file name of source file.
 */
def String generateJavaClass(outDirName, fileName)
{
   def className = fileName.substring(0,fileName.size()-5)
   outputFileName = outDirName.toString() + File.separator + fileName
   outputFile = new File(outputFileName)
   outputFile.write "package ${packageName};${NEW_LINE.multiply(2)}"  
   outputFile << "public class ${className}${NEW_LINE}"  
   outputFile << "{${NEW_LINE}"
   outputFile << "${SINGLE_INDENT}public static void main(final String[] arguments)"
   outputFile << "${NEW_LINE}${SINGLE_INDENT}{${NEW_LINE}"
   outputFile << DOUBLE_INDENT << 'final String someString = "Dustin";' << NEW_LINE
   outputFile << buildMainBody()
   outputFile << "${SINGLE_INDENT}}${NEW_LINE}"
   outputFile << "}"
   return outputFileName
}


/**
 * Compile the provided Java source code file name.
 *
 * @param fileName Name of Java file to be compiled.
 */
def void compileJavaClass(fileName)
{
   // Use the Java SE 6 Compiler API (JSR 199)
   // http://java.sun.com/mailers/techtips/corejava/2007/tt0307.html#1
   compiler = ToolProvider.getSystemJavaCompiler()
   
   // The use of nulls in the call to JavaCompiler.run indicate use of defaults
   // of System.in, System.out, and System.err. 
   int compilationResult = compiler.run(null, null, null, fileName)
   if (compilationResult == 0)
   {
      println "${fileName} compiled successfully"
 }
   else
   {
      println "${fileName} compilation failed"
   }
}


/**
 * Create directories to which generated files will be written.
 *
 * @param baseDir The base directory used in which subdirectories for Java
 *    source packages will be generated.
 */
def String createDirectories(baseDir)
{
   def outDirName = new StringBuilder(baseDir)
   for (pkgDir in packagePieces)
   {
      outDirName << File.separator << pkgDir
   }
   outputDirectory = new File(outDirName.toString())
   if (outputDirectory.exists() && outputDirectory.isDirectory())
   {
      println "Directory ${outDirName} already exists."
   }
   else
   {
      isDirectoryCreated = outputDirectory.mkdirs()  // Use mkdirs in case multiple
      println "Directory ${outputDirectoryName} ${isDirectoryCreated ? 'is' : 'not'} created."
   }
   return outDirName.toString()
}


/**
 * Generate the body of generated Java class source code's main function.
 */
def String buildMainBody()
{
   def str = new StringBuilder() << NEW_LINE
   str << DOUBLE_INDENT << "if (someString == null || someString.isEmpty())" << NEW_LINE
   str << DOUBLE_INDENT << "{" << NEW_LINE
   str << TRIPLE_INDENT << 'System.out.println("The String is null or empty.");'
   str << NEW_LINE << DOUBLE_INDENT << "}" << NEW_LINE
   for (i in 0..numberOfConditionals)
   {
      str << DOUBLE_INDENT << 'else if (someString.equals("a' << i << '"))' << NEW_LINE
      str << DOUBLE_INDENT << "{" << NEW_LINE
      str << TRIPLE_INDENT << 'System.out.println("You found me!");' << NEW_LINE
      str << DOUBLE_INDENT << "}" << NEW_LINE
   }
   str << DOUBLE_INDENT << "else" << NEW_LINE
   str << DOUBLE_INDENT << "{" << NEW_LINE
   str << TRIPLE_INDENT << 'System.out.println("No matching string found.");'
   str << DOUBLE_INDENT << NEW_LINE << DOUBLE_INDENT << "}" << NEW_LINE
   return str
}

Because this script is intended primarily for generating Java code to learn more about the «code too large» error and to demonstrate a few things, I did not make it nearly as fancy as it could be. For one thing, I did not use Groovy’s built-in Apache CLI support for handling command-line arguments as I have demonstrated in previous blog posts on using Groovy to check seventh grade homework.

Even though the script above does not apply Groovy’s full potential, it still manages to demonstrate some Groovy niceties. I tried to add comments in the script describing some of these. These include features such as Groovy GDK’s String.tokenize method and other useful Groovy String extensions.

When I run this script from the directory C:javaexamplesgroovyExamplesjavaClassGeneration with the arguments «dustin.examples» (package structure), «BigClass» (name of generated Java class), «.» (current directory is based directory for generated code, and «5» (number of conditionals to be in generated code), the script’s output is shown here and in the following screen snapshot:

You're running the script C:javaexamplesgroovyExamplesjavaClassGenerationgenerateJavaClass.groovy
Directory .dustinexamples already exists.
.dustinexamplesBigClass.java compiled successfully

This output tells us that the generated Java class with five conditionals compiled successfully. To get a taste of what this generated Java class looks like, we’ll look at this newly generated version with only five conditionals.

BigClass.java (generated with 5 conditionals)

package dustin.examples;

public class BigClass
{
   public static void main(final String[] arguments)
   {
      final String someString = "Dustin";

      if (someString == null || someString.isEmpty())
      {
         System.out.println("The String is null or empty.");
      }
      else if (someString.equals("a0"))
      {
         System.out.println("You found me!");
      }
      else if (someString.equals("a1"))
      {
         System.out.println("You found me!");
      }
      else if (someString.equals("a2"))
      {
         System.out.println("You found me!");
      }
      else if (someString.equals("a3"))
      {
         System.out.println("You found me!");
      }
      else if (someString.equals("a4"))
      {
         System.out.println("You found me!");
      }
      else if (someString.equals("a5"))
      {
         System.out.println("You found me!");
      }
      else
      {
         System.out.println("No matching string found.");      
      }
   }
}

The above code includes two default conditionals every time regardless of how many conditionals are selected when the class generation script is run. In between the check for null/empty String and the else clause if no other else if has been satisfied are the number of else if statements specified when the class generation script was run. In this case, 5 was that number and so there are five else if conditionals between the two default conditionals on either end. As this demonstrates, it will be easy to scale up the number of conditionals until the Java compiler just won’t take it anymore.

I now try the Groovy script for generating the Java class again, but this time go all out and select 5000 as the number of desired conditionals. As the output shown below and in the following screen snapshot indicate, Groovy has no trouble generating the text file representing the Java class with this many conditionals in its main() function, but the Java compiler doesn’t like it one bit.

You're running the script C:javaexamplesgroovyExamplesjavaClassGenerationgenerateJavaClass.groovy
Directory .dustinexamples already exists.
.dustinexamplesBigClass.java:5: code too large
   public static void main(final String[] arguments)
                      ^
1 error
.dustinexamplesBigClass.java compilation failed

Obviously, the attempt to compile the generated class with a 5000+2 conditional main was too much. Through a little iterative trial-and-error, I was able to determine that 2265 conditionals (beyond the two defaults) was the maximum compilable number for my main() function and 2266 would break it. This is demonstrated in the next screen snapshot.

Knowing our limits better, we can now «look» at the byte code using the javap tool provided with Sun’s JDK to analyze the corresponding class file. Because there was a compiler error when we tried to compile the code with 2266 additional conditionals, we must run javap against the BigClass.class file generated with 2265 additional conditionals. The output of running javap with the -c option for this large class is too large (~1 MB) to bludgeon readers with here. However, I include key snippets from its output below.

Today while browsing through the internet, I found a very strange thing. I am sure it will be a very new and unknown fact for most of the java programmers, even the well experienced ones. The reason is because while working on a project, problem of this type does not occur very often. Now what kind of problem am I talking about? Well, most of you must have had a slight idea by looking at the title of the post. It says something when we have written a very large code and the compiler produces the error. Here is the snapshot of what I tried myself after reading this thing on internet:

C:Documents and Settingschirag.jainDesktop>javac LargeCode.java
LargeCode.java:3: code too large
void largeMethod()
1 error

So does the java compiler enforces any limitation on the size of the code? The answer is yes, and that boundary is 65536 Bytes. This limitation is not on the size of whole file, but on a single method.
Now lets us delve a bit in the details. When we compile a java source file(.java file), compiler produces the byte code in .class file. When the size of the byte code of a single method crosses 65536 bytes, the compiler is not able to compile that method, and gives “code too large” error for that particular method.
Now here is one thing to notice. The overall size of your class file can grow more than 65536 bytes, but the byte code for a single method should not be more than this. Notice that here I am getting this error for a method named largeMethod(), not for the whole file.
Now for the folks who want to try this by themselves. First thing is how would you generate such a large amount of code. Although there are some code generation tools like Groovy, but these are for large projects. To try it by yourselves, you can do what I did. Here is my code:

import java.io.*;

class WriteFile
{
public static void main(String args[])
{
BufferedWriter bw=null;
try{
File f= new File("LargeCode.java");
FileWriter fr= new FileWriter(f);
bw= new BufferedWriter(fr);
String s= "System.out.println("hello");";
for(int i=0;i<10000;i++)
{
bw.write(s);
}

bw.close();
}
catch(Exception e) { }
}
}

Here I have generated a new file using java IO API. It writes the statement
System.out.println("hello");
10,000 times in a separate file. Now you can add other things (class name, method name) to compile the program. If you write the whole code in a single method and compile it, you will get the error.

class file, compile time error


This entry was posted on January 19, 2010, 11:47 AM and is filed under Java, Java Compiler. You can follow any responses to this entry through RSS 2.0.

You can leave a response, or trackback from your own site.

Code conventions and standard software development wisdom dictate that methods should not be too long because they become difficult to fully comprehend, they lose readability when they get too long, they are difficult to appropriately unit test, and they are difficult to reuse. Because most Java developers strive to write highly modular code with small, highly cohesive methods, the «code too large» error in Java is not seen very often. When this error is seen, it is often in generated code.

In this blog post, I intentionally force this «code too large» error to occur. Why in the world would one intentionally do this? In this case, it is because I always understand things better when I tinker with them rather than just reading about them and because doing so gives me a chance to demonstrate Groovy, the Java Compiler API (Java SE 6), and javap.

It turns out that the magic number for the «code too large» error is 65535 bytes (compiled byte code, not source code). Hand-writing a method large enough to lead to this size of a .class file would be tedious (and not worth the effort in my opinion). However, it is typically generated code that leads to this in the wild and so generation of code seems like the best approach to reproducing the problem. When I think of generic Java code generation, I think Groovy.

The Groovy script that soon follows generates a Java class that isn’t very exciting. However, the class will have its main function be of an approximate size based on how many conditions I tell the script to create. This allows me to quickly try generating Java classes with different main() method sizes to ascertain when the main() becomes too large.

After the script generates the Java class, it also uses the Java Compiler API to automatically compile the newly generated Java class for me. The resultant .class file is placed in the same directory as the source .java file. The script, creatively named generateJavaClass.groovy, is shown next.

generateJavaClass.groovy


#!/usr/bin/env groovy

import javax.tools.ToolProvider

println "You're running the script ${System.getProperty('script.name')}"
if (args.length < 2)
{
println "Usage: javaClassGeneration packageName className baseDir #loops"
System.exit(-1)
}

// No use of "def" makes the variable available to entire script including the
// defined methods ("global" variables)

packageName = args[0]
packagePieces = packageName.tokenize(".") // Get directory names
def fileName = args[1].endsWith(".java") ? args[1] : args[1] + ".java"
def baseDirectory = args.length > 2 ? args[2] : System.getProperty("user.dir")
numberOfConditionals = args.length > 3 ? Integer.valueOf(args[3]) : 10

NEW_LINE = System.getProperty("line.separator")

// The setting up of the indentations shows off Groovy's easy feature for
// multiplying Strings and Groovy's tie of an overloaded * operator for Strings
// to the 'multiply' method. In other words, the "multiply" and "*" used here
// are really the same thing.
SINGLE_INDENT = ' '
DOUBLE_INDENT = SINGLE_INDENT.multiply(2)
TRIPLE_INDENT = SINGLE_INDENT * 3

def outputDirectoryName = createDirectories(baseDirectory)
def generatedJavaFile = generateJavaClass(outputDirectoryName, fileName)
compileJavaClass(generatedJavaFile)

/**
* Generate the Java class and write its source code to the output directory
* provided and with the file name provided. The generated class's name is
* derived from the provided file name.
*
* @param outDirName Name of directory to which to write Java source.
* @param fileName Name of file to be written to output directory (should include
* the .java extension).
* @return Fully qualified file name of source file.
*/
def String generateJavaClass(outDirName, fileName)
{
def className = fileName.substring(0,fileName.size()-5)
outputFileName = outDirName.toString() + File.separator + fileName
outputFile = new File(outputFileName)
outputFile.write "package ${packageName};${NEW_LINE.multiply(2)}"
outputFile << "public class ${className}${NEW_LINE}"
outputFile << "{${NEW_LINE}"
outputFile << "${SINGLE_INDENT}public static void main(final String[] arguments)"
outputFile << "${NEW_LINE}${SINGLE_INDENT}{${NEW_LINE}"
outputFile << DOUBLE_INDENT << 'final String someString = "Dustin";' << NEW_LINE
outputFile << buildMainBody()
outputFile << "${SINGLE_INDENT}}${NEW_LINE}"
outputFile << "}"
return outputFileName
}

/**
* Compile the provided Java source code file name.
*
* @param fileName Name of Java file to be compiled.
*/
def void compileJavaClass(fileName)
{
// Use the Java SE 6 Compiler API (JSR 199)
// http://java.sun.com/mailers/techtips/corejava/2007/tt0307.html#1
compiler = ToolProvider.getSystemJavaCompiler()

// The use of nulls in the call to JavaCompiler.run indicate use of defaults
// of System.in, System.out, and System.err.
int compilationResult = compiler.run(null, null, null, fileName)
if (compilationResult == 0)
{
println "${fileName} compiled successfully"
}
else
{
println "${fileName} compilation failed"
}
}

/**
* Create directories to which generated files will be written.
*
* @param baseDir The base directory used in which subdirectories for Java
* source packages will be generated.
*/
def String createDirectories(baseDir)
{
def outDirName = new StringBuilder(baseDir)
for (pkgDir in packagePieces)
{
outDirName << File.separator << pkgDir
}
outputDirectory = new File(outDirName.toString())
if (outputDirectory.exists() && outputDirectory.isDirectory())
{
println "Directory ${outDirName} already exists."
}
else
{
isDirectoryCreated = outputDirectory.mkdirs() // Use mkdirs in case multiple
println "Directory ${outputDirectoryName} ${isDirectoryCreated ? 'is' : 'not'} created."
}
return outDirName.toString()
}

/**
* Generate the body of generated Java class source code's main function.
*/
def String buildMainBody()
{
def str = new StringBuilder() << NEW_LINE
str << DOUBLE_INDENT << "if (someString == null || someString.isEmpty())" << NEW_LINE
str << DOUBLE_INDENT << "{" << NEW_LINE
str << TRIPLE_INDENT << 'System.out.println("The String is null or empty.");'
str << NEW_LINE << DOUBLE_INDENT << "}" << NEW_LINE
for (i in 0..numberOfConditionals)
{
str << DOUBLE_INDENT << 'else if (someString.equals("a' << i << '"))' << NEW_LINE
str << DOUBLE_INDENT << "{" << NEW_LINE
str << TRIPLE_INDENT << 'System.out.println("You found me!");' << NEW_LINE
str << DOUBLE_INDENT << "}" << NEW_LINE
}
str << DOUBLE_INDENT << "else" << NEW_LINE
str << DOUBLE_INDENT << "{" << NEW_LINE
str << TRIPLE_INDENT << 'System.out.println("No matching string found.");'
str << DOUBLE_INDENT << NEW_LINE << DOUBLE_INDENT << "}" << NEW_LINE
return str
}

Because this script is intended primarily for generating Java code to learn more about the «code too large» error and to demonstrate a few things, I did not make it nearly as fancy as it could be. For one thing, I did not use Groovy’s built-in Apache CLI support for handling command-line arguments as I have demonstrated in previous blog posts on using Groovy to check seventh grade homework.

Even though the script above does not apply Groovy’s full potential, it still manages to demonstrate some Groovy niceties. I tried to add comments in the script describing some of these. These include features such as Groovy GDK’s String.tokenize method and other useful Groovy String extensions.

When I run this script from the directory C:javaexamplesgroovyExamplesjavaClassGeneration with the arguments «dustin.examples» (package structure), «BigClass» (name of generated Java class), «.» (current directory is based directory for generated code, and «5» (number of conditionals to be in generated code), the script’s output is shown here and in the following screen snapshot:


You're running the script C:javaexamplesgroovyExamplesjavaClassGenerationgenerateJavaClass.groovy
Directory .dustinexamples already exists.
.dustinexamplesBigClass.java compiled successfully

This output tells us that the generated Java class with five conditionals compiled successfully. To get a taste of what this generated Java class looks like, we’ll look at this newly generated version with only five conditionals.

BigClass.java (generated with 5 conditionals)


package dustin.examples;

public class BigClass
{
public static void main(final String[] arguments)
{
final String someString = "Dustin";

if (someString == null || someString.isEmpty())
{
System.out.println("The String is null or empty.");
}
else if (someString.equals("a0"))
{
System.out.println("You found me!");
}
else if (someString.equals("a1"))
{
System.out.println("You found me!");
}
else if (someString.equals("a2"))
{
System.out.println("You found me!");
}
else if (someString.equals("a3"))
{
System.out.println("You found me!");
}
else if (someString.equals("a4"))
{
System.out.println("You found me!");
}
else if (someString.equals("a5"))
{
System.out.println("You found me!");
}
else
{
System.out.println("No matching string found.");
}
}
}

The above code includes two default conditionals every time regardless of how many conditionals are selected when the class generation script is run. In between the check for null/empty String and the else clause if no other else if has been satisfied are the number of else if statements specified when the class generation script was run. In this case, 5 was that number and so there are five else if conditionals between the two default conditionals on either end. As this demonstrates, it will be easy to scale up the number of conditionals until the Java compiler just won’t take it anymore.

I now try the Groovy script for generating the Java class again, but this time go all out and select 5000 as the number of desired conditionals. As the output shown below and in the following screen snapshot indicate, Groovy has no trouble generating the text file representing the Java class with this many conditionals in its main() function, but the Java compiler doesn’t like it one bit.


You're running the script C:javaexamplesgroovyExamplesjavaClassGenerationgenerateJavaClass.groovy
Directory .dustinexamples already exists.
.dustinexamplesBigClass.java:5: code too large
public static void main(final String[] arguments)
^
1 error
.dustinexamplesBigClass.java compilation failed

Obviously, the attempt to compile the generated class with a 5000+2 conditional main was too much. Through a little iterative trial-and-error, I was able to determine that 2265 conditionals (beyond the two defaults) was the maximum compilable number for my main() function and 2266 would break it. This is demonstrated in the next screen snapshot.

Knowing our limits better, we can now «look» at the byte code using the javap tool provided with Sun’s JDK to analyze the corresponding class file. Because there was a compiler error when we tried to compile the code with 2266 additional conditionals, we must run javap against the BigClass.class file generated with 2265 additional conditionals. The output of running javap with the -c option for this large class is too large (~1 MB) to bludgeon readers with here. However, I include key snippets from its output below.


Compiled from "BigClass.java"
public class dustin.examples.BigClass extends java.lang.Object{
public dustin.examples.BigClass();
Code:
0: aload_0
1: invokespecial #1; //Method java/lang/Object."<init>":()V
4: return

public static void main(java.lang.String[]);
Code:
0: ldc #2; //String Dustin
2: ifnonnull 10
5: goto_w 23
10: ldc #2; //String Dustin
12: invokevirtual #3; //Method java/lang/String.isEmpty:()Z
15: ifne 23
18: goto_w 36
23: getstatic #4; //Field java/lang/System.out:Ljava/io/PrintStream;
26: ldc #5; //String The String is null or empty.
28: invokevirtual #6; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
31: goto_w 65512
36: ldc #2; //String Dustin
38: ldc #7; //String a0
40: invokevirtual #8; //Method java/lang/String.equals:(Ljava/lang/Object;)Z
43: ifne 51
46: goto_w 64
51: getstatic #4; //Field java/lang/System.out:Ljava/io/PrintStream;
54: ldc #9; //String You found me!
56: invokevirtual #6; //Method java/io/PrintStream.println:

. . .

. . .

. . .

65411: goto_w 65512
65416: ldc #2; //String Dustin
65418: ldc_w #2272; //String a2263
65421: invokevirtual #8; //Method java/lang/String.equals:(Ljava/lang/Object;)Z
65424: ifne 65432
65427: goto_w 65445
65432: getstatic #4; //Field java/lang/System.out:Ljava/io/PrintStream;
65435: ldc #9; //String You found me!
65437: invokevirtual #6; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
65440: goto_w 65512
65445: ldc #2; //String Dustin
65447: ldc_w #2273; //String a2264
65450: invokevirtual #8; //Method java/lang/String.equals:(Ljava/lang/Object;)Z
65453: ifne 65461
65456: goto_w 65474
65461: getstatic #4; //Field java/lang/System.out:Ljava/io/PrintStream;
65464: ldc #9; //String You found me!
65466: invokevirtual #6; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
65469: goto_w 65512
65474: ldc #2; //String Dustin
65476: ldc_w #2274; //String a2265
65479: invokevirtual #8; //Method java/lang/String.equals:(Ljava/lang/Object;)Z
65482: ifne 65490
65485: goto_w 65503
65490: getstatic #4; //Field java/lang/System.out:Ljava/io/PrintStream;
65493: ldc #9; //String You found me!
65495: invokevirtual #6; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
65498: goto_w 65512
65503: getstatic #4; //Field java/lang/System.out:Ljava/io/PrintStream;
65506: ldc_w #2275; //String No matching string found.
65509: invokevirtual #6; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
65512: return
}

From the snippets of javap output shown above, we see that the highest Code offset (65512) for this function pushing the limits of the method size was getting awfully close to the magic 65535 bytes (216-1 or Short.MAX_VALUE - Short.MIN_VALUE).

Conclusion

Most Java developers don’t see the «code too large» problem very often because they write methods and classes that are reasonable in size (or at least more reasonable than the limits allow). However, generated code can much more easily exceed these «limitations.» So of what value is intentionally reproducing this error? Well, the next time someone tries to convince you that bigger is better, you can refer that person to this post.

Other Resources

⇒ Java Language Specification, Third Edition

⇒ Class File Format

⇒ Code Too Large for Try Statement?

⇒ Code Too Long

⇒ Is There Any Number of Lines Limit in a Java Class?

«It’s too big! It doesn’t fit!»

The above does not refer to any particular pornographic feature film, but rather, to a longstanding problem in JavaCC: if you write a very big, complex lexical grammar, the generated XXXTokenManager would fail to compile, with the compiler reporting the error: «Code too large».

Well, this has now been fixed. You know, it is actually quite shocking how trivial a problem this really is. First of all, this is something that is probably very typical of generated code and one just about never runs into this in regular hand-written code. This whole thing originates in the fact that a single method in Java can be at most 64K — in bytecode, presumably.

This happens in generated code with some frequency because, if you have a number of static initializations in your code, they are all compiled into a single initialization method.

So, let’s say you have a series of initializations, like:

 static int[] foo = {1, 8, 9,... and a thousand more ints}
 static int[] bar = {...another thousand ints...}
 static int[] baz = {... some more data...}
 ... some more such initializations ...

A class that acts largely as a holder for data like this will easily run into the 64K limit because all of the above initialization gets compiled into a single method. The solution, of course, is the same as it would be if you really did write by hand some enormous method that hit this limit.

You need to break it into multiple pieces.

Thus, the above needs to be written something like:

 static int[] foo, bar, baz;

 static private void populate_foo() {
     foo = new int[FOO_SIZE];
     foo[0] = 1;
     foo[1] = 8;
     foo[2] = 9;
     ... etc...
 }

 static private void populate_bar() {
     bar = new int[BAR_SIZE];
     bar[0] =...
     ....etc...
 }

 static private void populate_baz() {
     ...etc...
 }

And then elsewhere you have:

 static {
     populate_foo();
     populate_bar();
     populate_baz();
     .... etc...
 }

And the truth of the matter is that it is not much harder generate the above code than the other code. JavaCC21 uses FreeMarker templates to generate the Java code and you could have a macro that looks like:

 [#macro DeclareArray name, data]
   int[] ${name};
   static private void ${name}_populate() {
     int[] ${name} = new int[${data?size}];
     [#list data as element]
       name[${element_index}] = ${element};
     [/#list]
   }
  [/#macro]

The above macro generates code that declares the variable and then it defines a separate method to populate it with the initialization data.

And elsewhere, assuming you had a hash of the variable names to the array data, you would have:

 [#list map?keys as key]
     [@DeclareArray key, map[key]/]
 [/#list]

 static {
  [#list map?keys as key]
      ${key}_populate();
  [/#list]
 } 

Well, come to think of it, you can see the actual code here. It’s not very different from what I outline above.

Granted, the above is particularly clean because of the use of a templating tool to generate the code, but even with the approach of the legacy codebase, using out.println statements all over the place, it is surely not so very hard to generate the code that avoids the «Code too large» problem. It’s one of the issues in the legacy tool that would be quite easy to fix, probably not a full day’s work. (The other biggie is the lack of an INCLUDE directive.)

As a final point, the above example assumes that no individual array is so big as to hit the «Code too large» limitation on its own. And that does seem to be the case, in practice, with JavaCC. Fairly obviously, if any single array was big enough, on its own, to hit this limitation, you would need multiple XXX_populate() methods. So, if your foo[] array had, let’s say 20,000 elements, you could generate:

  static private void foo_populate1() {...}
  static private void foo_populate2() {...}
  ...
  static private void foo_populate20() {...}

I haven’t (yet) dealt with that possibility in the JavaCC21 codebase, since I don’t think it ever is necessary. But if it turns out to be necessary to handle this case, a single array that is so big that it needs multiple initializers, then I’ll handle that quite quickly, of course. For the moment, I decided to apply the principle of YNGNI (You’re not gonna need it!)

Concluding Comments

It occurs to me that this fix to this dreaded «Code too large» problem, along with the support for full 32-bit Unicode, surely constitutes a major milestone in JavaCC21 development. You see, I believe that, with these last two fixes:

Every longstanding issue in the legacy JavaCC tool has now been addressed.

I do not mean to say by that, that development is now complete. It does mean, however, that, from this point onwards, any major development is oriented towards adding new functionality — not towards addressing issues that were inherited from the legacy codebase, since those have now been addressed.

The two main directions of development will be:

— rounding out and polishing the fault-tolerant parsing machinery
— moving towards supporting the generation of parsers in other programming languages

At the time of this writing, JavaCC21 is still not a 100% rewrite of the legacy codebase. I reckon that it is certainly more than 90% rewritten, and in fact, what bits and pieces remain from the original codebase are basically vestigial. I anticipate that, within a couple of months, I will be able to say that this is a 100% rewrite. And I intend to announce that milestone, since that will also be a very happy day indeed!

Post Views: 2,742

Symptoms

Compilation of Groovy code instrumented by Clover fails with an error like:

| Error Compilation error compiling [integration] tests: startup failed:
General error during class generation: Method code too large!

java.lang.RuntimeException: Method code too large!
	at groovyjarjarasm.asm.MethodWriter.a(Unknown Source)
	at groovyjarjarasm.asm.ClassWriter.toByteArray(Unknown Source)
	at org.codehaus.groovy.control.CompilationUnit$16.call(CompilationUnit.java:807)
	at org.codehaus.groovy.control.CompilationUnit.applyToPrimaryClassNodes(CompilationUnit.java:1047)
	at org.codehaus.groovy.control.CompilationUnit.doPhaseOperation(CompilationUnit.java:583)
	at org.codehaus.groovy.control.CompilationUnit.processPhaseOperations(CompilationUnit.java:561)
	at org.codehaus.groovy.control.CompilationUnit.compile(CompilationUnit.java:538)
	at org.codehaus.groovy.control.CompilationUnit.compile(CompilationUnit.java:517)
    ...

Cause

There is a 64kB limit of byte code of a single method. In some cases this limit may be hit, for instance when many AST transformation plugins are used.

A good example is a test method written in a Spock framework, having dozens of «when: / then:» blocks, which is first transformed by the Spock and next instrumented by Clover.

Resolution

1) Identify the large method in your code causing a problem (this may be hard as there’s no information from a groovyc, even with —verbose or —verboseCompile). Reduce size of this method, e.g. by splitting it.

or

2) Identify which plugins used in your build perform heavy AST transformations. Check what AST annotations you’re using in your code. Try disabling or reconfiguring these plugins or removing annotations.

or

3) Reduce instrumentation level performed by Clover from «statement» to «method». This will result in smaller overhead generated by Clover at the cost of the coverage recording accuracy — only method entries will be recorded.

Ant

<clover-setup instrumentationLevel="method"/> 

See: Clover-for-Ant / clover-setup

Maven

<plugin>
  <groupId>com.atlassian.maven.plugins</groupId>
  <artifactId>clover-maven-plugin</artifactId> <!-- artifact was called maven-clover2-plugin before 4.1.1 -->
  <configuration>
    <instrumentation>method</instrumentation>
  </configuration>
</plugin> 

See: Clover-for-Maven / setup mojo

Grails

// grails-app/conf/BuildConfig.groovy:

clover { 
    setuptask = { ant, binding, plugin ->
        ant.'clover-setup'(instrumentationLevel: "method", 
                           initstring: "${binding.projectWorkDir}/target/clover/clover.db") {
            
        }
    }
}

See: Clover-for-Grails / Advanced setup configuration

A single method in a Java class may be at most 64KB of bytecode.

But you should clean this up!

Use .properties file to store this data, and load it via java.util.Properties

You can do this by placing the .properties file on your classpath, and use:

Properties properties = new Properties();InputStream inputStream = getClass().getResourceAsStream("yourfile.properties");properties.load(inputStream);

There is a 64K byte-code size limit on a method

Having said that, I have to agree w/Richard; why do you need a method that large? Given the example in the OP, a properties file should suffice … or even a database if required.


According to the Java Virtual Machine specification, the code of a method must not be bigger than 65536 bytes:

The value of the code_length item gives the number of bytes in the code array for this method.

The value of code_length must be greater than zero (as the code array must not be empty) and less than 65536.

code_length defines the size of the code[] attribute which contains the actual bytecode of a method:

The code array gives the actual bytes of Java Virtual Machine code that implement the method.


Понравилась статья? Поделить с друзьями:
  • Jam 0511 ошибка kyocera
  • Java error code 143
  • Jam 0501 ошибка kyocera
  • Java error cannot find symbol ioexception
  • Java error cannot access class