The 64KB Java Language Problem - Lesson Learned from Using Rhino to Process JavaScript

September 1, 2008 – 10:13 am by coachwei | Category Tips, WebDev |

This entry documents a few tips related to using Rhino JavaScript Engine to process JavaScript code. If you are using Rhino, you probably won’t run into the issues covered in this post during development or even testing. However, you are fairly likely to run into these issues after your system goes live. It would easily result in days or even weeks of soul searching (speaking from my personal experience:-)). Part of the problem seems to be the lack of documentation from the web. The other part of the problem is that the problem is rather a Java language problem(very convoluted). - Java limits the maximum method size to be 64KB.

1. JVM Byte Code Size Limit Problem

On rare occasions, you will see exceptions like the followings from Rhino when processing JavaScript files:

Exception in thread “main” java.lang.IllegalArgumentException: out of range index

at org.mozilla.classfile.ClassFileWriter?.add(ClassFileWriter?.java:541) at org.mozilla.classfile.ClassFileWriter?.addLoadConstant(ClassFileWriter?.java:601) at org.mozilla.classfile.ClassFileWriter?.addPush(ClassFileWriter?.java:837) at org.mozilla.javascript.optimizer.BodyCodegen?.visitSpecialCall(Codegen.java:2571) at org.mozilla.javascript.optimizer.BodyCodegen?.generateExpression(Codegen.java:1763) at org.mozilla.javascript.optimizer.BodyCodegen?.visitSetProp(Codegen.java:3743) at org.mozilla.javascript.optimizer.BodyCodegen?.generateExpression(Codegen.java:2118) at org.mozilla.javascript.optimizer.BodyCodegen?.generateStatement(Codegen.java:1660) at org.mozilla.javascript.optimizer.BodyCodegen?.generateStatement(Codegen.java:1510) at org.mozilla.javascript.optimizer.BodyCodegen?.generateBodyCode(Codegen.java:1181) at org.mozilla.javascript.optimizer.Codegen.generateCode(Codegen.java:285) at org.mozilla.javascript.optimizer.Codegen.compileToClassFile(Codegen.java:157) at org.mozilla.javascript.optimizer.Codegen.compile(Codegen.java:67) at org.mozilla.javascript.Context.compileImpl(Context.java:2327) at org.mozilla.javascript.Context.compileString(Context.java:1323) at org.mozilla.javascript.Context.compileString(Context.java:1312) at org.mozilla.javascript.tools.shell.Main.loadScriptFromSource(Main.java:500) at org.mozilla.javascript.tools.shell.Main.processFileSecure(Main.java:439) at org.mozilla.javascript.tools.shell.Main.processFile(Main.java:406) at org.mozilla.javascript.tools.shell.Main.processSource(Main.java:397) at org.mozilla.javascript.tools.shell.Main.processFiles(Main.java:181) at org.mozilla.javascript.tools.shell.Main$IProxy.run(Main.java:102) at org.mozilla.javascript.Context.call(Context.java:540) at org.mozilla.javascript.ContextFactory?.call(ContextFactory?.java:447) at org.mozilla.javascript.tools.shell.Main.exec(Main.java:164) at org.mozilla.javascript.tools.shell.Main.main(Main.java:142)

Here are a few posts you can find when you do a web search that are related to this issue (though don’t seem to offer a solution):

  • http://markmail.org/message/ieuv5bnlddhufmdm#query:java.lang.IllegalArgumentException%3A%20out%20of%20range%20index+page:1+mid:ortmi3uehrzdrhcq+state:results
  • http://trac.dojotoolkit.org/ticket/2896

This can be extremely frustrating. For example, if you are a Dojo ShrinkSafe user, you know ShrinkSafe works because you have used it millions of times on many projects successfully. But on a certain situations, you will run into the above problem. When you search specifically for this ShrinkSafe problem, here is the best that I can find:

“While it’s not recommended that you develop 2MB JS files, it seems that it *does* happen, and it’s about the time that your files get this large that a project (naturally!) turns to the Dojo compressor. Unfortunantly, the default set of arguments doesn’t hold up to a file this big, and strange exceptions result. Here is a modified recipe for use with the kinds of files that really, *really* need the Dojo compressor:
java -Xmn100M -Xms500M -Xmx500M -jar custom_rhino.jar -opt -1 -c HUGE.js > smaller.js

This is not very helpful in my own experience. First of all, there are tons of reasons that Rhino needs to process big JavaScript files (for example, you want to use Rhino to analyze a bunch of JavaScript files concatenated together). Secondly, very little explanation is given about this command line “magic”. Thirdly, this command line “magic” seems to be misleading because the problem is not related to JVM memory limit (no need to do -Xms500M -Xmx500M in my experience).

The source of the problem is not your code, not Rhino, but rather a Java language limit. See http://java.sun.com/docs/books/jvms/second_edition/html/ClassFile.doc.html, section 4.10:

The amount of code per non-native, non-abstract method is limited to 65536 bytes by the sizes…

Rhino implemented correctly by sticking to the Java language specification. In fact, you can see the following comments from <code>org.mozilla.classfile.ClassFileWriter<code>:

if (attrLength > 65536) {
// See http://java.sun.com/docs/books/jvms/second_edition/html/ClassFile.doc.html,
// section 4.10, “The amount of code per non-native, non-abstract
// method is limited to 65536 bytes…
throw new ClassFileFormatException(
“generated bytecode for method exceeds 64K limit.”);
}

The Dojo ShrinkSafe issue is fundamentally caused by this problem. Any other software that relies on Rhino will have this problem too.

2. So What Is the Solution?

The short answer is, well, there is no solution, if you rely on Rhino to compile JavaScript into Java code. Well, Java code has to subject to the 64KB Java language limit.

If you are in desperate need for a solution, you might try to “pre-process” your JavaScript code by breaking apart large JavaScript methods into a bunch of smaller methods, which may yield smaller than 64KB Java byte code for each compiled method. -Thought this approach looks really painful.

However, if you are not using Rhino to compile JavaScript to Java, there are easier ways to get around the problem. Rhino does not have to “compile JavaScript to Java code”. You can configure Rhino to run at an “Interpretive mode”. Given that there is Java byte code involved at all, this limit naturally does not apply. This is simple to do. It takes one line of code:

context.setOptimizationLevel(-1);

The above set the Rhino Context object to “interpreative” mode. See Rhino Optimization Level for detailed information.

3. Is it time for us to treat the 64KB JVM limit as a bug, rather that a Java language spec item?

In the world of JavaScript, well, it is not usual to have methods that far exceeds the 64KB limit. For example, it is very common these days for JavaScript libraries to wrap a lot of functions inside one parent function for the sake of encapsulation and scoping. For example, jQuery wraps all code inside one function block (function(){…})(). Other JavaScript libraries like Dojo use similar techniques too. In such situations, it is fairly easy to exceed the 64KB limit when compiled into Java byte code.

For dynamic languages, especially languages that developers tend to nest functions inside functions (”inner function”), the 64KB limit seems to be awfully low.

Of course, this problem is not unique to JavaScript developers. For example, JSP developers can easily run into this problem too. See http://forums.sun.com/thread.jspa?threadID=472929&messageID=2296445 for an example.

Interesting enough, bug 4262078 at bugs.sun.com, “Maximum method size is too small (64Kb)“, is exactly on this issue. See http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4262078. The bug was submitted in 1999. Nine years later, in 2008, we are still not sure whether this will be fixed.

  1. 4 Responses to “The 64KB Java Language Problem - Lesson Learned from Using Rhino to Process JavaScript”

  2. Isn’t a quite simple solution to split the method into method parts? You’ll need to do some static analysis to transfer the stack contents correctly, but it shouldn’t be too much of a problem.

    By Jevgeni Kabanov on Sep 2, 2008

  3. @Jevgeni Kabanov:

    Yes, you can certainly split the method into method parts to get away from this limit. However, there are many use cases that you don’t necessarily want to do splitting. For example: have you looked at JavaScript libraries like jQuery, Dojo, etc? It is fairly common these days to wrap the majority of the library inside “(function(){…})()”. There are good reasons to do so. Splitting here is not really a good idea.

    Another case would be JSP page problem. See http://forums.sun.com/thread.jspa?threadID=472929&messageID=2296445.

    Beyond the above, my question is “Is this time for us to re-think the 64KB JVM size limit”? Why 64KB?

    By Coach Wei on Sep 3, 2008

  4. I ran into this issue as well not because of real “code” but with JSON arrays holding rather large blocks of data.

    I’m inclined to say that while the 64KB limit is inconvenient here the right solution is not boost or eliminate the JVM/classfile format limit (whether that is justified for other reasons is a separate question).

    It sounds like the problem here is the rigid 1:1 mapping of function into a single method. This may make it harder to guarantee behavioral correctness, it is the job of the compiler to map the source language into the destination language or instruction set in a way that accounts for those kinds of limitations.

    The simplest fix on the Rhino side would be to automatically fall back to interpreted mode and emit a warning in this situation.

    By Dan Tull on Nov 8, 2008

  1. 1 Trackback(s)

  2. Sep 1, 2008: LinkingClever.info » Blog Archive » The 64KB Java Language Problem - Lesson Learnd from Using Rhino to Process JavaScript

Post a Comment