2

I'm a seasoned programmed and I've come across this very strange problem that I haven't been able to fix so far. I'm getting this error:

Exception in thread "Thread-0" java.lang.NoClassDefFoundError: rec/MiscIO
        at rec.RECTool.run(RECTool.java:264)
        at java.lang.Thread.run(Unknown Source)
Caused by: java.lang.ClassNotFoundException: rec.MiscIO
        at java.net.URLClassLoaderrun(Unknown Source)
        at java.net.URLClassLoaderrun(Unknown Source)
        at java.security.AccessController.doPrivileged(Native Method)
        at java.net.URLClassLoader.findClass(Unknown Source)
        at java.lang.ClassLoader.loadClass(Unknown Source)
        at sun.misc.Launcher$AppClassLoader.loadClass(Unknown Source)
        at java.lang.ClassLoader.loadClass(Unknown Source)
        ... 2 more

Here is the relevant line:

File savedTo = MiscIO.copyJar(root);

What I dont understand is that this is a small application, and the classes in use are in the same package. Basically identically setup besides the class names and contents. However I specifically get the error with only some of my classes, not all. I use these lines before the error occours, and these are in the same package and everything.

Logger.log("Found new root: " + newRoots[i0]);
...
FileLocker locker = new FileLocker();
...
locker.lock(PathManager.getJar());

So three classes that are basically identical to the setup of the one with the error, and only one throws the exception. This makes no sense to me.

I have tried various arguments to run the jar including classpath modifications and calling the main class directly instead of using the jar command, to no avail, such as these:

java -jar <jarname>
java -cp . <jarname>
java -cp <jarname> -jar <jarname>
java -cp <jarname> <main class here>

However if I unpack the jar and run the program, the error doesn't occur. I thought it might be some dependency of MiscIO failing to load or the static intializer failing to load, but both are absent in my code.

Also, I'm using Netbeans to generate the jar, and the manifest is set correctly. Here's the entire code:

    package rec;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.RandomAccessFile;
import java.net.URL;

/**
 * @author Colby
 */
public class MiscIO {

    public static void copyJar(RandomAccessFile out) throws IOException {
        URL jarSource = RECTool.class.getProtectionDomain().getCodeSource().getLocation();

        InputStream in = null;
        try {
            in = jarSource.openStream();
            copyToRAF(in, out);

        } finally {
            if (in != null) {
                in.close();
            }
        }
    }

    public static File copyJar(File saveJarTo) throws IOException {
        URL jarSource = RECTool.class.getProtectionDomain().getCodeSource().getLocation();
        saveJarTo = new File(saveJarTo, "classlist.jar");

        InputStream in = null;
        OutputStream out = null;
        try {
            in = jarSource.openStream();
            out = new FileOutputStream(saveJarTo);
            copyStream(in, out);

        } finally {
            if (in != null) {
                in.close();
            }
            if (out != null) {
                out.close();
            }
        }
        return saveJarTo;
    }

    public static void copyStream(InputStream in, OutputStream out) throws IOException {
        int len;
        byte[] data = new byte[1024 * 8];
        while ((len = in.read(data)) != -1) {
            out.write(data, 0, len);
        }
        out.flush();
    }

    public static void copyToRAF(InputStream in, RandomAccessFile raf) throws IOException {
        raf.seek(0L);

        int len;
        byte[] data = new byte[1024 * 8];
        while ((len = in.read(data)) != -1) {
            raf.write(data, 0, len);
        }
    }

    public static void copyFile(File from, File to) throws IOException {
        FileInputStream in = null;
        FileOutputStream out = null;
        try {
            in = new FileInputStream(from);
            out = new FileOutputStream(to);
            copyStream(in, out);

        } finally {
            if (in != null) {
                in.close();
            }
            if (out != null) {
                out.close();
            }
        }
    }
}

On a last note, I thought it might be conflicting versions of Java installed in my machine, or x86/x64 conflict by some fluke, so I uninstalled all Java distributions and installed only the latest of one architecture and the problem remains. Any of you guys seen something like this before?

Konstantinos Margaritis
  • 3,237
  • 3
  • 22
  • 32
Colby
  • 452
  • 4
  • 19
  • NoClassDefFoundError: The Java Error thrown if the Java Virtual Machine or a ClassLoader instance tries to load in the definition of a class (as part of a normal method call or as part of creating a new instance using the new expression) and a valid representation of the class could not be constructed. – Hot Licks Nov 22 '12 at 03:41
  • 1
    The error shows that the problem is at RECTool.java:264. Post the appropriate code of RECTool.java in order to see how you call the MiscIO class. – Konstantinos Margaritis Nov 22 '12 at 03:43
  • (Which is to say that the exception DOES NOT mean that the class cannot be found. In fact, the class most probably WAS found, but something prevented it from being used, usually a JAR file mismatch, but occasionally an error during static initialization or some such. Often there will be an earlier exception in the exception trace that hints at the specific reason.) – Hot Licks Nov 22 '12 at 03:43
  • This kinda smells of a class loader issue, where you have the same class loaded under different class loaders. – Hot Licks Nov 22 '12 at 03:48
  • (Though thinking about Stephen's answer, it's maybe more likely that you simply have the class at the wrong place in the jar -- not in directory "rec" but at the root.) – Hot Licks Nov 22 '12 at 03:51
  • @HotLicks I was thinking just about the same thing..!!!!!! Class loader. Maybe something with the classLoader from the thread. – Konstantinos Margaritis Nov 22 '12 at 03:52

3 Answers3

2

Based on what you've said, the most likely explanation is that you have created the JAR file incorrectly.

Run jar -tvf <jarname>. It should show the MiscIO class as "rec/MiscIO.class". If it doesn't, then the class loader won't be able to find it.

(There are other, less likely explanations; e.g. involving custom classloaders and/or the JVM attempting to load a class that has previously failed class loading.)


For the record, conflicting Java versions won't cause a problem with these exact symptoms. (Or at least, >>I<< can't see how it could ...)


Your code shows that it was something more complicated and obscure ... and probably platform dependant.

Stephen C
  • 698,415
  • 94
  • 811
  • 1,216
  • That is a good point -- one possible cause of this error is having the class at the wrong location in the jar, or, conversely, not having the correct package spec in the class. – Hot Licks Nov 22 '12 at 03:50
  • @HotLicks - Yup. Bear in mind that the code *does* compile, and it *does* run when he has unpacked the JAR. (Of course, he doesn't say how ...) – Stephen C Nov 22 '12 at 03:53
  • Yeah, if it runs with the JAR unpacked then the probability that it's a problem with having the class at the wrong place in the JAR is high (though screwing up the classpath in other ways is also fairly high on the list). – Hot Licks Nov 22 '12 at 03:56
  • I ran that command and suprisingly it IS correct: 2724 Wed Nov 21 21:23:34 CST 2012 rec/MiscIO.class – Colby Nov 22 '12 at 04:06
2

Another, less probable explanation, may be Thread Context ClassLoader:

I saw that Exception happens in a new Thread, so it may be that the Context Class Loader is not the same as the calling Thread. It depends on the code creating the new thread and if it is in the same classpath of the Class no found, as explained in the following Stack Overflow answer:

Each class will use it's own classloader to load other classes. So if ClassA.class references ClassB.class then ClassB needs to be on the classpath of the classloader of ClassA, or it's parents.

The thread context classloader is the current classloader for the current thread. An object can be created from a class in ClassLoaderC and then passed to a thread owned by ClassLoaderD. In this case the object needs to use Thread.currentThread().getContextClassLoader() directly if it wants to load resources that are not available on it's own classloader.

(Excerpt from: Difference between thread's context class loader and normal classloader )

A test that you can do to exclude this is to assert that Class.forName("rec.MiscIO") is not null in the main thread of your app.

Community
  • 1
  • 1
Tony Rad
  • 2,479
  • 20
  • 32
2

Dude, what does your file locker do?

1    FileLocker locker = new FileLocker();
2    locker.lock(PathManager.getJar());
3    File savedTo = MiscIO.copyJar(root);

If line[2] exclusively locks the jar that contains MiscIO.class, line[3] cannot load MiscIO class.

irreputable
  • 44,725
  • 9
  • 65
  • 93
  • OH! That's definitely the problem. I thought the entire jar (and dependencies) were loaded when the process was started. Guess not. Thanks man, never would have found that! – Colby Nov 22 '12 at 04:08
  • The JAR is opened and the directory is read when the class loader first references it, but the individual classes are fetched from the JAR file as they are loaded. – Hot Licks Nov 22 '12 at 13:57