25

A .class file is a rather well documented format that defines sections and size, and therefore maximum sizes as well.

For instance, a .class file contains a magic number (4 bytes), a version (4 bytes), the constant pool (variable size), etc. But sizes can be defined on several levels: you can have 65535 methods and each is limited to 65535 bytes.

What are the other limits? And, if you would make the largest .class file possible, what size would it be?

If needed, limit answers to Java. Meaning that if Scala or Clojure (or...) change some limits, disregard those values.

Olivier Grégoire
  • 28,397
  • 21
  • 84
  • 121
  • http://stackoverflow.com/questions/5497495/maximum-size-of-java-class-exception-table and http://stackoverflow.com/questions/107855/maximum-lines-of-code-permitted-in-a-java-class helps ? – Suresh Atta Feb 17 '17 at 10:06
  • @sᴜʀᴇsʜᴀᴛᴛᴀ No: first one only show methods size (which I referenced in my question). The second one is about a Java file size (which can be infinite since spaces are just discarded). This question is about the maximum size of a `.class` file. – Olivier Grégoire Feb 17 '17 at 10:09
  • 2
    Just for the record: the maximum size shouldn't matter in practical reality. If you end up with class files that exceed this limit, this means that your Java source must be **huge**. So huge that it is **several orders of magnitude** too large for being considered reasonable. Sure, an interesting theoretical question, but when your daily work is affected by the answer, then chances are: you are doing something *very* wrong ;-) – GhostCat Feb 17 '17 at 10:09
  • @GhostCat Yes, it's a theoretical question, not a practical one. Practically, I'd be more interested in how many files can a file system handle since I usually cut my code in short files rather than big ones ;) – Olivier Grégoire Feb 17 '17 at 10:11
  • 2
    The limits aren't only theoretical. When dealing with generated source or generated bytecode, the limits do matter. As for amount of files, modern file systems are perfectly capable of handling files that are both huge and/or numerous, so that's nothing to worry about. – Kayaman Feb 17 '17 at 10:15
  • Possible repeate of [this](http://stackoverflow.com/questions/17422480/maximum-size-of-a-method-in-java-7-and-8) question – zeeshan Feb 17 '17 at 11:10

3 Answers3

23

The JVM specification doesn’t mandate a limit for class files and since class files are extensible containers, supporting arbitrary custom attributes, you can even max it out as much as you wish.

Each attribute has a size field of the u4 type, thus, could specify a number of up to 2³²-1 (4GiB). Since, in practice, the JRE API (ClassLoader methods, Instrumentation API and Unsafe) all consistently use either byte[] or ByteBuffer to describe class files, it is impossible to create a runtime class of a class file having more than 2³¹-1 bytes (2GiB).

In other words, even a single custom attribute could have a size that exceeds the size of actually loadable classes. But a class can have 65535 attributes, plus 65535 fields, each of them having 65535 attributes of its own and plus 65535 methods, each of them having up to 65535 attribute as well.

If you do the math, you will come to the conclusion that the theoretical maximum of a still well formed class file may exceed any real storage space (more than 2⁶⁵ bytes).

Holger
  • 243,335
  • 30
  • 362
  • 661
  • I wasn't aware of the extensibilty of the format with its attribute mechanism and I thought that everything was accounted for in the `.class` format. My bad. That's pretty much the final answer anyone can give. I'll mark your answer as the accepted one in the following days to allow other insightful answers to come. – Olivier Grégoire Feb 17 '17 at 14:52
  • 2
    The conclusions are absolutely correct, and I've managed to write a [program](https://gist.github.com/apangin/7c8cfb671c8683a751fa3fdae0bbb38f) that creates a valid class file of 2,147,483,647 bytes which could be successfully loaded into HotSpot JVM, but not a single byte more. However, the reason is not JRE API, since bootstrap classloader does not use either `byte[]` or `ByteBuffer` or `Unsafe`. But still, HotSpot `classFileParser` also relies on `int` variables to work with class file stream. – apangin Feb 17 '17 at 15:31
  • 1
    @apangin: your main class is normally loaded via application class loader, which is a specialized `URLClassLoader`, thus bound to the well known API. I wouldn’t be surprised, if the bootstrap loader implementation is subject to the same kind of limitation, but in real life scenarios, the bootstrap loader reads the shared class data (.jsa) archive rather than class files. – Holger Feb 17 '17 at 15:41
  • I agree on the main point of the answer, I was just saying that looking at JRE API is not enough to conclude that it's impossible to load a larger class file in a real JVM. Even in JDK 9 application class loader is no longer an instance of `URLClassLoader`, so it could behave differently (though it actually does not). – apangin Feb 17 '17 at 15:53
  • 1
    @apangin: well, relying on the bootstrap loader would violate the definition of a well-formed Java program. Anyway, what matters are practical considerations, e.g. if I implement a class transformer, it doesn’t matter whether the JVM knows a way to load humongous class files, as the Instrumentation API requires it to create a representation that fits into a byte array. Likewise, if I generate a class file to be instantiated at runtime, I have to face the fact that there is no JRE method accepting bigger files (there is no Java interface to the bootstrap loader), etc. – Holger Feb 17 '17 at 16:46
  • [`AddToBootstrapClassLoaderSearch`](http://docs.oracle.com/javase/8/docs/platform/jvmti/jvmti.html#AddToBootstrapClassLoaderSearch) and its [Java counterpart](http://docs.oracle.com/javase/8/docs/api/java/lang/instrument/Instrumentation.html#appendToBootstrapClassLoaderSearch-java.util.jar.JarFile-) is a legal public API to the bootstrap class loader :) – apangin Feb 17 '17 at 17:02
  • 1
    @apangin: JVMTI is not a *Java* API and it’s not mandatory for a JVM to provide this API. The Instrumentation API is a Java API, but still not available to arbitrary Java applications, but only to Java Agents, which again are not a mandatory feature that a clean Java program can rely on. – Holger Feb 17 '17 at 17:06
22

It's quite easy to make huge StackMapTable using nested finally blocks as javac unwisely generates separate variables for each nesting level. This allows to produce several megabytes from very simple method like this:

class A {{
  int a;
  try {a=0;} finally {
  try {a=0;} finally {
  try {a=0;} finally {
  try {a=0;} finally {
  try {a=0;} finally {
  try {a=0;} finally {
  try {a=0;} finally {
  try {a=0;} finally {
  try {a=0;} finally {
  try {a=0;} finally {
  try {a=0;} finally {
  try {a=0;} finally {
  a=0;
  }}}}}}}}}}}}
}}

Adding more nesting level is not possible as you will exceed code size for single method. You can also duplicate this using the fact that instance initializer is copied to every constructor:

class A {{
  int a;
  try {a=0;} finally {
  try {a=0;} finally {
  try {a=0;} finally {
  try {a=0;} finally {
  try {a=0;} finally {
  try {a=0;} finally {
  try {a=0;} finally {
  try {a=0;} finally {
  try {a=0;} finally {
  try {a=0;} finally {
  try {a=0;} finally {
  try {a=0;} finally {
  a=0;
  }}}}}}}}}}}}
}
A() { }
A(int a) { }
A(char a) { }
A(double a) { }
A(float a) { }
A(long a) { }
A(short a) { }
A(boolean a) { }
A(String a) { }
A(Integer a) { }
A(Float a) { }
A(Short a) { }
A(Long a) { }
A(Double a) { }
A(Boolean a) { }
A(Character a) { }

}

This simple java file when compiled with Java 8 javac produces 105,236,439 bytes .class-file. You can also add more constructors, though there's a risk that javac will fail with OutOfMemoryError (use javac -J-Xmx4G to overcome this).

Tagir Valeev
  • 87,515
  • 18
  • 194
  • 305
  • 1
    Nice one. Now, lets combine it with try-with-resource… – Holger Feb 17 '17 at 16:52
  • 2
    This is amazing. :) – Christopher Schultz Mar 08 '18 at 00:32
  • It's worth noting that this behavior only applies to the standard compiler following Java 1.6, wherein finally blocks are compiled to inlined bytecode instead of using the `JSR` instruction. See [this question answer](https://stackoverflow.com/a/29062816/3521691) for more. – RlonRyan Apr 02 '19 at 00:42
3

The theoretical, semi-realistic limit for a class with methods is most likely bound by the constant pool. You can only have 64K across all methods. java.awt.Component has 2863 constants and 83548 bytes. A class which had the same ratio of bytes/constant would run out of constant pool at 1.9 MB. By comparison a class like com.sun.corba.se.impl.logging.ORBUtilSystemException would run out around 3.1 MB.

For a large class, you are likely to run out of constant in the constant pool around 2 - 3 MB.

By contract sun.awt.motif.X11GB18030_1$Encoder is full of large constant Strings and it is 122KB with just 68 constants. This class doesn't have any methods.

For experimentation, my compile blows up with too many constants at around 21800 constants.

public static void main(String[] args) throws FileNotFoundException {
    try (PrintWriter out = new PrintWriter("src/main/java/Constants.java")) {
        out.println("class Constants {");
        for (int i = 0; i < 21800; i++) {
            StringBuilder sb = new StringBuilder();
            while (sb.length() < 100)
                sb.append(i).append(" ");
            out.println("private static final String c" + i + " = \"" + sb + "\";");
        }
        out.println("}");
    }
}

Also it appears that the compiled loads the text into a ByteBuffer. This means the source can't be 1 GB or the compiler gets this error. My guess is that the chars in bytes has overflown to a negative number.

java.lang.IllegalArgumentException
    at java.nio.ByteBuffer.allocate(ByteBuffer.java:334)
    at com.sun.tools.javac.util.BaseFileManager$ByteBufferCache.get(BaseFileManager.java:325)
Peter Lawrey
  • 498,481
  • 72
  • 700
  • 1,075
  • What about methods that contain 64KiB worth of `a=a+a;`? There are barely any constants, but a lot of statements. Your answer is several orders of magnitude lower of what I'd actually expect (at least 4 GiB+). You speak about (semi-)realistic limits, I don't recall having put any such constraint in my question. – Olivier Grégoire Feb 17 '17 at 12:55
  • @OlivierGrégoire You can create long byte[] in a method to fill it up with code without constants. This thing is it wouldn't have a practical value. – Peter Lawrey Feb 17 '17 at 13:03
  • 1
    I know, and while I value your answer as a practical one, I don't think it answer the theorical part. The question is about the `.class` file, not `.java` files that get compiled into `.class` files. – Olivier Grégoire Feb 17 '17 at 13:07
  • 1
    @OlivierGrégoire with experimentation it appears the compiler can't handle a source file of 1 GB or more. I suspect this could be fixed though I doubt Oracle will consider it worth doing so. – Peter Lawrey Feb 17 '17 at 13:24
  • 1
    I just checked [the source code](http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/8u40-b25/java/nio/ByteBuffer.java#332). As expected, the limit is caused by an `int` overflow, hence, the limit is `2GiB`, not `1GiB`. – Holger Feb 17 '17 at 13:33
  • @Holger 2 GiB after converting the bytes of the source into chars. After converting the text back into UTF8 strings this is 1 GB approx of `.java` file. Using the three byte characters this could mean ~3 GB. – Peter Lawrey Feb 17 '17 at 13:46
  • @Holger i.e. 1 GB source file using ASCII, turns into 2 GB in memory using chars. – Peter Lawrey Feb 17 '17 at 13:50
  • 1
    The stack trace clearly points to `ByteBuffer`, trying to allocate a number of bytes. From this place, I can not deduce a relationship to `char`s. But the question was about class files rather than source files anyway… – Holger Feb 17 '17 at 13:51