3

I am getting the error "Could not find or load main class" even though the class is there. From this answer, I learned that the class can likely be found, but not loaded, due to missing dependencies.

Aside from manually decompiling the class, checking all its dependencies, and if those are on the classpath, and so on for each dependent class ad infinitum....is there any way to determine exactly which class is missing from the classpath therefore causing Java to be unable to load my main class?

That other question was a question of a missing parent/interface class. But I have manually checked that all ancestor classes are either on the specified classpath or in the JDK, as below.

$ cd ~/picketbox
$ java -cp picketbox-4.1.1.Final-redhat-1.jar org.picketbox.datasource.security.SecureIdentityLoginModule HelloWorld
Error: Could not find or load main class org.picketbox.datasource.security.SecureIdentityLoginModule
$ jar xvf picketbox-4.1.1.Final-redhat-1.jar > jarxvf.txt
$ cat jarxvf.txt | grep SecureIdentityLoginModule
 inflated: org/picketbox/datasource/security/SecureIdentityLoginModule.class
$ cd org/picketbox/datasource/security/
$ javap SecureIdentityLoginModule.class | grep main
  public static void main(java.lang.String[]) throws java.lang.Exception;
$ javap SecureIdentityLoginModule.class | grep extends
public class org.picketbox.datasource.security.SecureIdentityLoginModule extends org.picketbox.datasource.security.AbstractPasswordCredentialLoginModule {
$ cat ~/picketbox/jarxvf.txt | grep AbstractPasswordCredentialLoginModule
 inflated: org/picketbox/datasource/security/AbstractPasswordCredentialLoginModule.class
$ javap AbstractPasswordCredentialLoginModule.class | grep extends
public abstract class org.picketbox.datasource.security.AbstractPasswordCredentialLoginModule extends org.jboss.security.auth.spi.AbstractServerLoginModule {
$ cat ~/picketbox/jarxvf.txt | grep AbstractServerLoginModule
 inflated: org/jboss/security/auth/spi/AbstractServerLoginModule.class
$ cd ~/picketbox/org/jboss/security/auth/spi/
$ javap AbstractServerLoginModule.class | grep implements
public abstract class org.jboss.security.auth.spi.AbstractServerLoginModule implements javax.security.auth.spi.LoginModule {
$ cd ~/rtjar
$ cp /usr/java/jdk1.8.0_141/jre/lib/rt.jar ./
$ jar xvf rt.jar | grep spi/LoginModule
extracted: javax/security/auth/spi/LoginModule.class
$ cd javax/security/auth/spi/
$ javap LoginModule.class | grep interface
public interface javax.security.auth.spi.LoginModule {
$
Adam Burley
  • 5,551
  • 4
  • 51
  • 72
  • Did you try using the -XX:+TraceClassLoading option? – Claudio Corsi Dec 07 '17 at 23:09
  • @ClaudioCorsi I wasn't previously aware of that option. I tried it just now, but unfortunately it didn't help at all. It just shows Java loading a lot of JDK classes from `rt.jar` and still gives the same error message. I tried to attach the output as an edit to my question, but unfortunately it caused my question to exceed the character limit. But there is nothing useful in there, just a load of lines of the form `[Loaded x.y.z.ClassName from /.../rt.jar]` – Adam Burley Dec 07 '17 at 23:52
  • The other option is that you can implement a jvmti module and process the exceptionthrown events. You can then call the printStackTrace method of the exception. This should hopefully provide a clue as to which class is missing. – Claudio Corsi Dec 13 '17 at 13:22
  • @ClaudioCorsi sorry, I have no idea how to do that; I have never even heard of jvmti. Can you post your comment as an answer and go into some more detail? – Adam Burley Dec 13 '17 at 13:28

2 Answers2

1

Create a helper class:

public class Helper {
    public static void main(String[] args) {
        YourActualMainClass.main(args);
    }
}

and try to run this helper class instead of YourActualMainClass.

The key point is that neither Helper’s inheritance tree nor the signatures of its members depend on the problematic class, so loading and even initialization will succeed, given HotSpot’s lazy resolving strategy, so it will only attempt to load and resolve YourActualMainClass when trying to execute the Helper.main method. At this point, it will throw a detailed error telling you which class was actually missing.

This matches the behavior described in the linked answer that the use of a class leads to a specific error message when the inheritance of the using class does not depend on it.


Alternatively, you could try to run the application with Java 9, as its launcher will print the cause when the loading of the main class failed.

Holger
  • 285,553
  • 42
  • 434
  • 765
  • Unfortunately that didn't help (no pun intended). It just says "Could not find or load main class Helper". However I have since found that the JAR itself may be corrupted somehow (not sure how as it can still be extracted). If I extract the JAR and then re-compress it (i.e. `jar xvf`, `jar cvf`), it works. Furthermore, I had to do this re-build to even be able to compile `Helper.java`, otherwise `javac` reports "error in opening zip file" – Adam Burley Dec 08 '17 at 22:07
0

You can try to use JVMTI to display all exceptions that were thrown. Starting with Identifying exceptions through JVMTI. Just use the following updated agent.c source code.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <jni.h>
#include <jvmti.h>

#define CHECK_JVMTI_ERROR(x,call) \
    { if (x != JVMTI_ERROR_NONE) { fprintf (stderr, "Error during %s in %s:%d\n", #call, __FILE__, __LINE__); } }

/* Global static data */
static jvmtiEnv     *jvmti;

static void JNICALL cb_Exception (jvmtiEnv *jvmti_env, JNIEnv* jni_env,
    jthread thread, jmethodID method, jlocation location, jobject exception,
    jmethodID catch_method, jlocation catch_location)
{
    jclass exceptionClass = (*jni_env)->GetObjectClass(jni_env, exception);

    jmethodID methodId = (*jni_env)->GetMethodID(jni_env, exceptionClass,
                         "printStackTrace",
                         "()V");

    (*jni_env)->CallVoidMethod(jni_env, exception, methodId);
}

JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM *vm, char *options, void *reserved)
{
    jint                rc;
    jvmtiError          r;
    jvmtiCapabilities   capabilities;
    jvmtiEventCallbacks callbacks;

    /* Get JVMTI environment */
    rc = (*vm)->GetEnv(vm, (void **)&jvmti, JVMTI_VERSION);
    if (rc != JNI_OK)
    {
        fprintf (stderr, "Error!: Unable to create jvmtiEnv, rc=%d\n", rc);
        return -1;
    }

    /* Get/Add JVMTI capabilities */
    memset(&capabilities, 0, sizeof(capabilities));
    capabilities.can_generate_exception_events = 1;
    r = (*jvmti)->AddCapabilities(jvmti, &capabilities);
    CHECK_JVMTI_ERROR(r, AddCapabilities);

    /* Set callbacks and enable event notifications */
    memset(&callbacks, 0, sizeof(callbacks));
    callbacks.Exception               = &cb_Exception;
    r = (*jvmti)->SetEventCallbacks(jvmti, &callbacks, sizeof(callbacks));
    CHECK_JVMTI_ERROR(r, SetEventCallbacks);

    /* Exception events */
    r = (*jvmti)->SetEventNotificationMode(jvmti, JVMTI_ENABLE, 
      JVMTI_EVENT_EXCEPTION, NULL);
    CHECK_JVMTI_ERROR(r, SetEventNotificationMode);

    return 0;
}

JNIEXPORT void JNICALL Agent_OnUnload(JavaVM *vm)
{
}
Claudio Corsi
  • 329
  • 1
  • 4