I’m getting Java code generated for me from an application.
I took the JRE and extracted all the files into a new directory.
There is a META-INF folder that has a MANIFEST.MF without any main method.
Witin this JRE is the class of the code I’m interested in however when I CMD the following…
java Steve.class
I get this error…
Could not load for find Main Class Steve.class.
I’m assuming that somewhere in all these class files there is a Main class but how do I search all these documents to find it? Is there an application?
Thanks!
asked Jan 25, 2012 at 8:31
1
You don’t need the .class
suffix when invoking a Java program. Do it like this:
java Steve
To work out which class has a main
method, you can use javap
(Java Class File Disassembler) on each class file. For example:
$ javap Foo
Compiled from "Foo.java"
public class Foo extends java.lang.Object{
public Foo();
public static void main(java.lang.String[]);
}
answered Jan 25, 2012 at 8:34
dogbanedogbane
265k75 gold badges395 silver badges413 bronze badges
First: every class that exposes this method signature:
public static void main(String[] args) { }
could be a main class launchable from the JVM and eligible to be put in the manifest to enable shell execution.
Second: when you launch a class in a JRE you must specify the fully qualified name of the class; for example if Steve.class file is in a tree structure such as com/mycompany/app, starting from the root of your application where the MANIFEST directory is, you should launch it, from the root directory, typing:
java com.mycompany.app.Steve
So if Steve exposes a main method and if you can correctly point to it from the root, you can launch it.
answered Jan 25, 2012 at 8:39
Andrea ColleoniAndrea Colleoni
5,8813 gold badges30 silver badges49 bronze badges
3
Программирование на Java тесно связано с понятием, так называемого, главного класса. В чём его особенность, и какую роль этот класс играет в создании и работе приложения.
Главный класс приложения – это класс, метод main которого используется в качестве точки входа в программу.
В приложении на языке Java любой класс может содержать в себе метод main. Но при его сборке и последующем запуске используется только метод main главного класса. Методы main остальных классов игнорируются.
При отсутствии главного класса сборка и, тем более, работа приложения не возможны.
Современные среды программирования (IDE) для Java, как правило, создают главный класс автоматически. Если такой возможности нет или автоматически созданный класс по каким-либо причинам не устраивает разработчика в качестве главного, необходимо самостоятельно создать другой класс, реализовать в нём метод main и задать этот класс в качестве главного.
Последнее можно сделать либо в манифесте приложения (файл manifest.mf) добавив в него следующую строку.
Main—Class: myJavaApplication.MainClass |
Главный класс задаётся обязательно с указанием полного имени пакета.
Либо в графическом интерфейсе IDE. Ниже показан пример для NetBeans.
Однажды созданный главный класс вовсе не обязательно должен оставаться таковым. В случае необходимости вполне можно создать новый. Процесс его создания и указания в качестве главного ничем не отличается от описанного выше.
Приведём простейший пример. Приложение имеет графический интерфейс на основе библиотеки swing. IDE создают в качестве главного обычный не визуальный класс. Поэтому при наличии в программе графического интерфейса в методе main необходимо прописывать код его инициализации и вывода на экран.
MyJFrameForm jf = new MyJFrameForm(); jf.setDefaultCloseOperation(jf.EXIT_ON_CLOSE); jf.setVisible(true); |
Если же использовать в качестве главного класс myJFrameForm, то вся инициализация сводится фактически к следующему:
new MyJFrameForm().setVisible(true); |
Это всего лишь частный случай того, как можно оптимизировать приложение просто с помощью умелого манипулирования с главными классами. Однако, чтобы вместо улучшения не получить худшее чем было изначально при работе с ними стоит придерживаться некоторых простых рекомендаций.
1.Не перегружайте главный класс
Если приложение достаточно сложное, используйте декомпозицию. Выделяйте сложные алгоритмы в отдельные классы.
В идеале, если речь не идёт о простейших программах (неважно консольных или с графическим интерфейсом), это должен быть всё-таки отдельный не визуальный класс, который содержит только один единственный метод main с инициализацией только основных компонентов программы.
2.Давайте главному классу понятное имя
Лучше всего назвать главный класс на основе названия самого приложения или MainClass.
3.Старайтесь проектировать приложение таким образом, чтобы свести к минимуму все возможные манипуляции с главным классом, а ещё лучше полностью их избежать
Главный класс по своей сути это фундамент Вашего приложения. Старайтесь делать так, чтобы все детальные алгоритмы его работы можно было изменять независимо от него.
Важно отметить, что серьёзная работа над главным классом приложения в реальных проектах явление достаточно редкое, так как это традиционно относят, скорее, к «тонкостям» языка и архитектуры. Но, зачастую именно вследствие игнорирования таких «тонкостей» и возникают многие «непонятные» и трудно локализуемые проблемы.
Время на прочтение
12 мин
Количество просмотров 18K
Без сомнения каждый, кто в своем резюме указывает опыт разработки на Java, хоть раз в жизни писал строки
public static void main(String[] args)
компилировал их и запускал на выполнение командой наподобие java HelloWorld
.
Но многие ли знают, что происходит внутри JVM от момента выполнения этой команды до того как управление передается методу main, как Java находит и загружает необходимые пользователю классы? Возникшая однажды производственная задача заставила автора разобраться в этом вопросе. Результаты изысканий под катом. Сразу стоит оговориться, что статья не претендует на полноту охвата всех существующих JVM, тестирование проводилось только на Sun HotSpot JVM.
Постановка задачи
В один прекрасный день заказчику потребовалось выяснить, какие классы использует его приложение. Приложение было уже хорошо знакомо автору и представляло собой гремучую смесь из кода различной расовой принадлежности, реализующего (к чести разработчиков системы, по большей части грамотно и к месту) механизмы наследования, позднего связывания и динамической компиляции. Поэтому информация о действительно используемых классах могла существенно помочь в рефакторинге приложения.
Задача поставлена следующим образом: в процессе работы приложения должен формироваться файл, содержащий имена всех классов, непосредственно использованных приложением. Оно, к слову, состоит из двух основных частей: сервера приложений, на котором размещен веб-интерфейс приложения, и сервера обработки (отдельный сервер, на котором различные периодические задачи запускаются с помощью скриптов Ant). Разумеется, информацию о классах необходимо собирать с обеих частей приложения.
Приступим к поиску решения поставленной задачи и заодно разберемся с механизмами загрузки классов в Java.
Переопределение системного загрузчика классов
Первым направлением, которое пришло в голову при решении данной задачи, было воспользоваться возможностями расширения механизма загрузки классов в Java. На данную тему написано достаточно много статей, том числе и на русском языке (ссылки в конце статьи).
Суть данного механизма в следующем:
- наследники абстрактного класса
java.lang.ClassLoader
используются для непосредственной загрузки классов, о чем красноречиво свидетельствует сигнатура методаClass loadClass(String name)
. Данный метод должен найти массив байт, являющийся байт-кодом искомого класса и передать его методуprotected Class defineClass(String name, byte[] b, int off, int len)
, который превратит его в экземпляр классаjava.lang.Class
. Таким образом, реализуя свои загрузчики, разработчики могут загружать из любого места, откуда можно получить массив байт; - разработчиками фреймворка декларируется хитрый механизм иерархии и наследования загрузчиков. При этом наследование здесь следует понимать не в терминах наследования классов в ООП, а как отдельную иерархию, организованную с помощью метода
getParent
классаClassLoader
. При старте JVM создается вершина этой иерархии из трех основных загрузчиков: базового (Bootstrap Classloader, отвечает за загрузку базовых классов фреймворка), загрузчика расширений (Extension Classloader, отвечает за загрузку классов из lib/ext) и системного загрузчика (System Classloader, отвечает за загрузку пользовательских классов). Далее разработчики вольны продолжать эту иерархию от системного загрузчика и ниже. По умолчанию в HotSpot JVM в качестве системного загрузчика используется классsun.misc.Launcher$AppClassLoader
однако его можно легко переопределить с помощью системного свойстваjava.system.class.loader
ключа командной строкиjava -Djava.system.class.loader=имя.класса.загрузчика
; - декларируется правило делегирования загрузки: любой загрузчик, прежде чем пытаться загрузить любой класс, сначала должен обратиться к своему родителю, и только если тот не смог загрузить искомый класс, попытаться загрузить его сам. К сожалению, красота и удобство данного правила компенсируются необязательностью его исполнения. С последствиями неисполнения этого правила автору еще предстоит столкнуться.
Однако на данном этапе уже появилась первая концепция решения поставленной задачи:
- Реализовать собственный загрузчик классов, заменяющий системный, который при вызове метода loadClass будет просто записывать имя класса в файл, и передавать запрос на загрузку класса настоящему системному загрузчику. При условии соблюдения описанного выше правила делегирования это должно позволить отловить все загружаемые пользовательские классы, даже если они загружаются другими загрузчиками;
- Заставить все JVM, запускаемые на машине использовать данный загрузчик классов как системный.
Для реализации второго пункта необходимо решить следующие задачи:
- сделать класс видимым для всех запускаемых JVM. Включать класс во все classpath множества компонентов приложения неудобно, трудоемко и нерационально с точки зрения расширения системы. Тем более что существует более красивое решение — поместить класс загрузчика в папку lib/ext JRE. Классы в этой папке становятся доступны автоматически без внесения их в classpath (как отмечалось выше, они загружаются загрузчиком расширений при старте JVM);
- задать для всех JVM системное свойство
java.system.class.loader
— из командной строки это можно сделать так:java -Djava.system.class.loader=имя.класса.загрузчика HelloWolrd
- непосредственно заставить все JVM запускаться с необходимым параметром
-Djava.system.class.loader
. Как оказалось, для этого тоже существует изящное решение — нужно использовать специальную переменную окружения, значение которой автоматически добавляется к параметрам запуска любой JVM. В процессе поиска было найдено две переменные, которые могли бы отвечать за данную возможность: JAVA_OPTS и JAVA_TOOL_OPTIONS. Однако ни одна из статей не давала четкого ответа на вопрос в чем же отличие этих двух переменных? Ответ на данный вопрос было решено найти опытным путем. В ходе эксперимента было установлено, что по настоящему «волшебной» является переменная JAVA_TOOL_OPTIONS, значение которой автоматически добавляется к параметрам запуска любой запускаемой HotSpot JVM. А JAVA_OPTS — это результат негласного соглашения разработчиков различных Java приложений. Данную переменную в явном виде используют многие скрипты (например, startup.sh/startup.bat для запуска Apache Tomcat), однако никто не гарантирует, что данную переменную будут использовать все разработчики скриптов.
Итак, дело сделано, загрузчик скомпилирован и помещен в lib/ext, значение переменной окружения JAVA_TOOL_OPTIONS
задано, запускаем приложение, работаем, открываем лог и видим… скудный список из десятка классов, включая системные и еще несколько сторонних классов. Вот тут-то и пришлось вспомнить о необязательности выполнения правила делегирования загрузки, а так же заглянуть в исходный код Apache Ant и Tomcat. Как оказалось, в этих приложениях используются собственные загрузчики классов. Это, с одной стороны, отчасти и позволило им обрести свой мощный функционал. Однако по тем или иным причинам разработчики этих продуктов решили не придерживаться рекомендованного правила делегирования загрузки и написанные ими загрузчики далеко не всегда обращаются к своим родителям, перед тем как загрузить очередной класс. Именно поэтому наш системный загрузчик почти ничего не знает о классах, загружаемых Tomcat-ом и Ant-ом.
Таким образом, описанный способ не позволяет отловить все требуемые классы, особенно учитывая разнообразие используемых серверов приложений — кто знает, как отнеслись к правилу делегирования загрузки разработчики используемого заказчиком сервера приложений.
Попытка номер два. Применяем инструментацию классов
Порой для решения задачи не достаточно одних знаний или умений. Иногда для достижения цели необходима интуиция и чуточку везения. Сейчас автор уже и не вспомнит, в ответ на какой поисковый запрос о загрузчиках классов, поисковый гигант выдал ссылку на статью о механизме инструментации классов. Как оказалось, данный инструмент предназначен для изменения байт кода Java классов во время их загрузки (к примеру, JProfiler с помощью данного механизма встраивается в классы для замеров производительности). Стоп, что значит во время их загрузки? То есть данный механизм знает о каждом загруженном классе? Да знает, и, как оказалось, даже лучше чем загрузчики классов — метод byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer)
интерфейса ClassFileTransformer
вызывается у реализующего его класса-трансформатора при загрузке любого класса. Этот метод и оказался тем бутылочным горлышком, через которое проходит любой загружаемый класс, за исключением совсем небольшого количества системных.
Теперь задача сводится к следующему:
- Написать свой класс-трансформатор, реализующий метод
ClassFileTransformer.transform
, который, правда, не будет осуществлять никакой трансформации, а будет всего лишь записывать имя загруженного класса в файл. - И снова нужно сделать так, чтобы написанный нами класс подключался к любому запускаемому Java приложению.
Исходный код класса-трансформатора представлен ниже:
package com.test;
import java.io.File;
import java.lang.instrument.Instrumentation;
import java.lang.instrument.ClassFileTransformer;
import java.security.ProtectionDomain;
import java.lang.instrument.IllegalClassFormatException;
public class LoggingClassFileTransformer implements ClassFileTransformer {
public static void premain(String agentArguments, Instrumentation instrumentation) {
instrumentation.addTransformer(new LoggingClassFileTransformer());
}
/**
* Данный метод вызывается для любого загруженного класса
* @param className имя загружаемого класса для записи в лог
* @return неизмененный classfileBuffer содержащий байт-код класса
*/
public byte[] transform(ClassLoader loader,
String className,
Class<?> classBeingRedefined,
ProtectionDomain protectionDomain,
byte[] classfileBuffer)
throws IllegalClassFormatException
{
log(className);
return classfileBuffer;
}
// сохраняем лог в папку lib/ext
private static final File logFile = new File(System.getProperty("java.ext.dirs").split(File.pathSeparator)[0]+"/LoggingClassFileTransformer.log");
public static synchronized void log(String text)
{
// запись в файл
// ...
}
}
Здесь необходимо пояснить механизм использования классов-трансформаторов. Чтобы подключить такой класс к приложению нам понадобиться так называемый premain класс, т.е. класс, содержащий метод public static void premain(String paramString, Instrumentation paramInstrumentation)
. Из названия метода понятно, что он вызывается до вызова метода main. В этот момент можно подключить к приложению классы-трансформаторы с помощью метода addTransformer
интерфейса java.lang.instrument.Instrumentation
. Таким образом, приведенный выше класс одновременно является и классом-трансформатором и premain-классом. Чтобы данный класс можно было использовать, его необходимо поместить в JAR файл, манифест которого (файл META-INF/MANIFEST.MF) содержит параметр Premain-Class, указывающий на полное имя premain-класса, в нашем случае Premain-Class: com.test.LoggingClassFileTransformer
. Затем необходимо указать полный путь к данному архиву с помощью параметра -javaagent
при запуске JVM. Тут нам на помощь снова приходит переменная JAVA_TOOL_OPTIONS.
Итак, класс написан, скомпилирован, упакован вместе с манифестом в JAR, переменная окружения JAVA_TOOL_OPTIONS=-javaagent:"путь к LoggingClassFileTransformer.jar"
задана, приложение запущено, лог собран, PROFIT!
upd3.
Хабраюзер grossws предложил еще один способ, использующий инструментацию — AspectJ: «Актуален, если надо дешево инструментировать все классы в своей части приложений не затрагивая окружение, но это немного другая задача».
upd.
Путь третий. Простой как лом
Спасибо хабраюзерам spiff и apangin которые в личке напомнили про еще один способ, который был мною испробован, но был незаслуженно забыт, т.к. в конечном итоге не подошел.
Данный способ основан на запуске JVM с параметрами -verbose:class
или --XX:+TraceClassLoading
. При использовании любого из этих параметров в стандартный поток вывода JVM сыпятся сообщения вида [Loaded java.util.Date from shared objects file]
. Однако у данного способа, несмотря на его простоту, есть один существенный недостаток — сложно контролировать формат выводимого сообщения, а так же направление вывода. А рассматриваемое приложение и без того выводит достаточно отладочной информации в stdout, и возможность отфильтровать из этого потока нужные сообщения и перенаправить их в отдельный файл для всех экземпляров JVM запускаемых на сервере представляется весьма проблематичным.
upd2.
Хабраюзером apangin данный способ был допилен до следующего варианта запуска JVM:
java -XX:+TraceClassLoading -XX:+UnlockDiagnosticVMOptions -XX:+LogVMOutput -XX:LogFile=java_*.log -XX:-DisplayVMOutput HelloWorld
Вместо * автоматически подставится pid.
После запуска такой команды сформируется файл с именем, например, java_580.log, примерно следующего содержания:
<?xml version='1.0' encoding='UTF-8'?>
<hotspot_log version='160 1' process='580' time_ms='1334248301214'>
<vm_version>
<name>
Java HotSpot(TM) Client VM
</name>
<release>
20.6-b01
</release>
<info>
Java HotSpot(TM) Client VM (20.6-b01) for windows-x86 JRE (1.6.0_31-b05), built on Feb 3 2012 18:44:09 by "java_re" with MS VC++ 7.1 (VS2003)
</info>
</vm_version>
<vm_arguments>
<args>
-XX:+TraceClassLoading -XX:+UnlockDiagnosticVMOptions -XX:+LogVMOutput -XX:LogFile=java_*.log -XX:-DisplayVMOutput
</args>
<command>
test
</command>
<launcher>
SUN_STANDARD
</launcher>
<properties>
java.vm.specification.name=Java Virtual Machine Specification
java.vm.version=20.6-b01
java.vm.name=Java HotSpot(TM) Client VM
java.vm.info=mixed mode, sharing
java.ext.dirs=C:Program FilesJavajre6libext;C:WINDOWSSunJavalibext
java.endorsed.dirs=C:Program FilesJavajre6libendorsed
sun.boot.library.path=C:Program FilesJavajre6bin
java.library.path=C:WINDOWSsystem32;C:WINDOWSSunJavabin;C:WINDOWSsystem32;C:WINDOWS;C:Program FilesPC Connectivity Solution;C:Program FilesRockwell SoftwareRSCommon;C:WINDOWSsystem32;C:WINDOWS;C:WINDOWSSystem32Wbem;C:Program FilesJavajdk1.6.0_06bin;c:hibernate;C:WINDOWSsystem32WindowsPowerShellv1.0;C:Program FilesTortoiseSVNbin;C:Program FilesNmap;;C:PROGRA~1COMMON~1MUVEET~130625;C:PROGRA~1COMMON~1MUVEET~130625;.
java.home=C:Program FilesJavajre6
java.class.path=.
sun.boot.class.path=C:Program FilesJavajre6libresources.jar;C:Program FilesJavajre6librt.jar;C:Program FilesJavajre6libsunrsasign.jar;C:Program FilesJavajre6libjsse.jar;C:Program FilesJavajre6libjce.jar;C:Program FilesJavajre6libcharsets.jar;C:Program FilesJavajre6libmodulesjdk.boot.jar;C:Program FilesJavajre6classes
java.vm.specification.vendor=Sun Microsystems Inc.
java.vm.specification.version=1.0
java.vm.vendor=Sun Microsystems Inc.
sun.java.command=test
sun.java.launcher=SUN_STANDARD
</properties>
</vm_arguments>
<tty>
<writer thread='1504'/>
[Loaded java.lang.Object from shared objects file]
...
<writer thread='3092'/>
<destroy_vm stamp='0.281'/>
<tty_done stamp='0.283'/>
</tty>
<hotspot_log_done stamp='0.283'/>
</hotspot_log>
В стандартный поток вывода при этом ничего нового не пишется, благодаря опции -XX:-DisplayVMOutput
.
Главным достоинством данного способа является его простота. Не факт, что такой формат файла подошел бы заказчику, но это уже вопрос дискуссионный и он не входит в рамки данной статьи.
Заключение
Итак, какие выводы можно сделать после окончания работы над проектом:
- механизм загрузки классов в Java — интересная и весьма полезная возможность фреймворка, которая может пригодиться для решения определенного круга задач. Однако стоит помнить, что основная задача, которую можно решить с помощью данного механизма это именно найти и загрузить класс из места, откуда его не могут загрузить другие. Для сбора информации о загруженных классах данный механизм может быть малопригоден;
- инструментация классов в Java — другой мощный механизм по работе с классами, и как раз таки его задача — произвольная работа с классами как с подопытными кроликами. Нужно ли вам получить информацию о времени работы метода или просто узнать имя только что загруженного класса — данный механизм придет на помощь;
- Java в целом — открытая и способствующая творчеству платформа. Приложения с открытым исходным кодом на этом (как и на любом другом языке) — полезны не только своей функциональностью, но и ценны идеями, которые можно почерпнуть, изучая на их исходный код. Да и отсутствие исходного кода зачастую не является проблемой для приложений на Java. Существует много средств, позволяющих отобразить исходный код практически любого скомпилированного Java класса. С их помощью вы можете с легкостью проанализировать код практически любого класса и метода, даже из ядра фреймворка (например, из файла rt.jar), разумеется, за исключением нативных методов. В работе над этим и многими другими проектами, автору пригодилась бесплатная утилита Java Decompiler, позволяющая увидеть исходный код практически любого скомпилированного класса Java. Особенно завораживает возможность увидеть ту часть исходного кода ядра Java, которая сама написана на Java, если, например, открыть файл rt.jar лежащий в папке lib JRE.
Список использованных источников
Статьи о загрузчиках классов в Java:
- habrahabr.ru/post/103830
- voituk.kiev.ua/2008/01/14/java-plugins
- blogs.oracle.com/vmrobot/entry/%D0%BE%D1%81%D0%BD%D0%BE%D0%B2%D1%8B_%D0%B4%D0%B8%D0%BD%D0%B0%D0%BC%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%BE%D0%B9_%D0%B7%D0%B0%D0%B3%D1%80%D1%83%D0%B7%D0%BA%D0%B8_%D0%BA%D0%BB%D0%B0%D1%81%D1%81%D0%BE%D0%B2_%D0%B2
Другие полезные ссылки:
- stackoverflow.com/questions/3933300/difference-between-java-opts-and-java-tool-options — вопрос о переменных JAVA_OPTS и JAVA_TOOL_OPTIONS;
- docs.oracle.com/javase/1.4.2/docs/api/java/lang/ClassLoader.html#getSystemClassLoader — как переопределить системный загрузчик;
- www.sql.ru/forum/actualthread.aspx?tid=858652 — именно данная тема натолкнула на идею об использовании Instrumentation вместо загрузчиков классов;
- www.exampledepot.com/egs/java.lang/PropCmdLine.html — как установить значение системного свойства Java из командной строки;
- docs.oracle.com/javase/1.4.2/docs/guide/extensions/spec.html — как получить путь к папке lib/ext;
- www.javalobby.org/java/forums/t19309.html — в статье приведен пример простейшего класса-трансформатора;
- today.java.net/pub/a/today/2008/04/24/add-logging-at-class-load-time-with-instrumentation.html — в статье решается похожая задача по принудительному ведению логов во всех загружаемых классах;
- java.decompiler.free.fr — домашняя страница проекта Java Decompiler.
- www.oracle.com/technetwork/java/javase/tech/vmoptions-jsp-140102.html — описание некоторых параметров запуска HotSpot JVM
- q-redux.blogspot.com/2011/01/inspecting-hotspot-jvm-options.html — еще одна статья про редко используемые параметры запуска HotSpot JVM
- habrahabr.ru/post/140133 — статья про особенности загрузчиков классов в некоторых серверах приложений
The java <class-name>
command syntax
First of all, you need to understand the correct way to launch a program using the java
(or javaw
) command.
The normal syntax1 is this:
java [ <options> ] <class-name> [<arg> ...]
where <option>
is a command line option (starting with a «-» character), <class-name>
is a fully qualified Java class name, and <arg>
is an arbitrary command line argument that gets passed to your application.
1 — There are some other syntaxes which are described near the end of this answer.
The fully qualified name (FQN) for the class is conventionally written as you would in Java source code; e.g.
packagename.packagename2.packagename3.ClassName
However some versions of the java
command allow you to use slashes instead of periods; e.g.
packagename/packagename2/packagename3/ClassName
which (confusingly) looks like a file pathname, but isn’t one. Note that the term fully qualified name is standard Java terminology … not something I just made up to confuse you
Here is an example of what a java
command should look like:
java -Xmx100m com.acme.example.ListUsers fred joe bert
The above is going to cause the java
command to do the following:
- Search for the compiled version of the
com.acme.example.ListUsers
class. - Load the class.
- Check that the class has a
main
method with signature, return type and modifiers given bypublic static void main(String[])
. (Note, the method argument’s name is NOT part of the signature.) - Call that method passing it the command line arguments («fred», «joe», «bert») as a
String[]
.
Reasons why Java cannot find the class
When you get the message «Could not find or load main class …», that means that the first step has failed. The java
command was not able to find the class. And indeed, the «…» in the message will be the fully qualified class name that java
is looking for.
So why might it be unable to find the class?
Reason #1 — you made a mistake with the classname argument
The first likely cause is that you may have provided the wrong class name. (Or … the right class name, but in the wrong form.) Considering the example above, here are a variety of wrong ways to specify the class name:
-
Example #1 — a simple class name:
java ListUser
When the class is declared in a package such as
com.acme.example
, then you must use the full classname including the package name in thejava
command; e.g.java com.acme.example.ListUser
-
Example #2 — a filename or pathname rather than a class name:
java ListUser.class java com/acme/example/ListUser.class
-
Example #3 — a class name with the casing incorrect:
java com.acme.example.listuser
-
Example #4 — a typo
java com.acme.example.mistuser
-
Example #5 — a source filename (except for Java 11 or later; see below)
java ListUser.java
-
Example #6 — you forgot the class name entirely
java lots of arguments
Reason #2 — the application’s classpath is incorrectly specified
The second likely cause is that the class name is correct, but that the java
command cannot find the class. To understand this, you need to understand the concept of the «classpath». This is explained well by the Oracle documentation:
- The
java
command documentation - Setting the Classpath.
- The Java Tutorial — PATH and CLASSPATH
So … if you have specified the class name correctly, the next thing to check is that you have specified the classpath correctly:
- Read the three documents linked above. (Yes … READ them! It is important that a Java programmer understands at least the basics of how the Java classpath mechanisms works.)
- Look at command line and / or the CLASSPATH environment variable that is in effect when you run the
java
command. Check that the directory names and JAR file names are correct. - If there are relative pathnames in the classpath, check that they resolve correctly … from the current directory that is in effect when you run the
java
command. - Check that the class (mentioned in the error message) can be located on the effective classpath.
- Note that the classpath syntax is different for Windows versus Linux and Mac OS. (The classpath separator is
;
on Windows and:
on the others. If you use the wrong separator for your platform, you won’t get an explicit error message. Instead, you will get a nonexistent file or directory on the path that will be silently ignored.)
Reason #2a — the wrong directory is on the classpath
When you put a directory on the classpath, it notionally corresponds to the root of the qualified name space. Classes are located in the directory structure beneath that root, by mapping the fully qualified name to a pathname. So for example, if «/usr/local/acme/classes» is on the class path, then when the JVM looks for a class called com.acme.example.Foon
, it will look for a «.class» file with this pathname:
/usr/local/acme/classes/com/acme/example/Foon.class
If you had put «/usr/local/acme/classes/com/acme/example» on the classpath, then the JVM wouldn’t be able to find the class.
Reason #2b — the subdirectory path doesn’t match the FQN
If your classes FQN is com.acme.example.Foon
, then the JVM is going to look for «Foon.class» in the directory «com/acme/example»:
-
If your directory structure doesn’t match the package naming as per the pattern above, the JVM won’t find your class.
-
If you attempt rename a class by moving it, that will fail as well … but the exception stacktrace will be different. It is liable to say something like this:
Caused by: java.lang.NoClassDefFoundError: <path> (wrong name: <name>)
because the FQN in the class file doesn’t match what the class loader is expecting to find.
To give a concrete example, supposing that:
- you want to run
com.acme.example.Foon
class, - the full file path is
/usr/local/acme/classes/com/acme/example/Foon.class
, - your current working directory is
/usr/local/acme/classes/com/acme/example/
,
then:
# wrong, FQN is needed
java Foon
# wrong, there is no `com/acme/example` folder in the current working directory
java com.acme.example.Foon
# wrong, similar to above
java -classpath . com.acme.example.Foon
# fine; relative classpath set
java -classpath ../../.. com.acme.example.Foon
# fine; absolute classpath set
java -classpath /usr/local/acme/classes com.acme.example.Foon
Notes:
- The
-classpath
option can be shortened to-cp
in most Java releases. Check the respective manual entries forjava
,javac
and so on. - Think carefully when choosing between absolute and relative pathnames in classpaths. Remember that a relative pathname may «break» if the current directory changes.
Reason #2c — dependencies missing from the classpath
The classpath needs to include all of the other (non-system) classes that your application depends on. (The system classes are located automatically, and you rarely need to concern yourself with this.) For the main class to load correctly, the JVM needs to find:
- the class itself.
- all classes and interfaces in the superclass hierarchy (e.g. see Java class is present in classpath but startup fails with Error: Could not find or load main class)
- all classes and interfaces that are referred to by means of variable or variable declarations, or method call or field access expressions.
(Note: the JLS and JVM specifications allow some scope for a JVM to load classes «lazily», and this can affect when a classloader exception is thrown.)
Reason #3 — the class has been declared in the wrong package
It occasionally happens that someone puts a source code file into the
the wrong folder in their source code tree, or they leave out the package
declaration. If you do this in an IDE, the IDE’s compiler will tell you about this immediately. Similarly if you use a decent Java build tool, the tool will run javac
in a way that will detect the problem. However, if you build your Java code by hand, you can do it in such a way that the compiler doesn’t notice the problem, and the resulting «.class» file is not in the place that you expect it to be.
Still can’t find the problem?
There lots of things to check, and it is easy to miss something. Try adding the -Xdiag
option to the java
command line (as the first thing after java
). It will output various things about class loading, and this may offer you clues as to what the real problem is.
Also, consider possible problems caused by copying and pasting invisible or non-ASCII characters from websites, documents and so on. And consider «homoglyphs», where two letters or symbols look the same … but aren’t.
You may run into this problem if you have invalid or incorrect signatures in META-INF/*.SF
. You can try opening up the .jar in your favorite ZIP editor, and removing files from META-INF
until all you have is your MANIFEST.MF
. However this is NOT RECOMMENDED in general. (The invalid signature may be the result of someone having injected malware into the original signed JAR file. If you erase the invalid signature, you are in infecting your application with the malware!) The recommended approach is to get hold of JAR files with valid signatures, or rebuild them from the (authentic) original source code.
Finally, you can apparently run into this problem if there is a syntax error in the MANIFEST.MF
file (see https://stackoverflow.com/a/67145190/139985).
Alternative syntaxes for java
There are three alternative syntaxes for the launching Java programs using the java command
.
-
The syntax used for launching an «executable» JAR file is as follows:
java [ <options> ] -jar <jar-file-name> [<arg> ...]
e.g.
java -Xmx100m -jar /usr/local/acme-example/listuser.jar fred
The name of the entry-point class (i.e.
com.acme.example.ListUser
) and the classpath are specified in the MANIFEST of the JAR file. -
The syntax for launching an application from a module (Java 9 and later) is as follows:
java [ <options> ] --module <module>[/<mainclass>] [<arg> ...]
The name of the entrypoint class is either defined by the
<module>
itself, or is given by the optional<mainclass>
. -
From Java 11 onwards, you can use the
java
command to compile and run a single source code file using the following syntax:java [ <options> ] <sourcefile> [<arg> ...]
where
<sourcefile>
is (typically) a file with the suffix «.java».
For more details, please refer to the official documentation for the java
command for the Java release that you are using.
IDEs
A typical Java IDE has support for running Java applications in the IDE JVM itself or in a child JVM. These are generally immune from this particular exception, because the IDE uses its own mechanisms to construct the runtime classpath, identify the main class and create the java
command line.
However it is still possible for this exception to occur, if you do things behind the back of the IDE. For example, if you have previously set up an Application Launcher for your Java app in Eclipse, and you then moved the JAR file containing the «main» class to a different place in the file system without telling Eclipse, Eclipse would unwittingly launch the JVM with an incorrect classpath.
In short, if you get this problem in an IDE, check for things like stale IDE state, broken project references or broken launcher configurations.
It is also possible for an IDE to simply get confused. IDE’s are hugely complicated pieces of software comprising many interacting parts. Many of these parts adopt various caching strategies in order to make the IDE as a whole responsive. These can sometimes go wrong, and one possible symptom is problems when launching applications. If you suspect this could be happening, it is worth trying other things like restarting your IDE, rebuilding the project and so on.
Other References
- From the Oracle Java Tutorials — Common Problems (and Their Solutions)
Ошибка « Не удалось найти или загрузить основной класс » возникает при использовании java-команды в командной строке для запуска Java-программы путем указания имени класса в терминале. Причина, по которой это происходит, в основном связана с ошибкой программирования пользователя при объявлении класса.
Как упоминалось ранее, эта ошибка в основном не связана с системой, и пользователь допускает ошибку в нескольких сценариях, как показано ниже. Прежде чем двигаться дальше, мы предполагаем, что у вас есть базовые знания о языке программирования Java и о том, как он работает.
Что вызывает ошибку «Не удалось найти или загрузить основной класс» в Java?
Сообщение «Не удалось найти или загрузить основной класс» означает, что первый шаг механизма Java, извлекающего класс для выполнения, не удался. Команда Java не смогло найти класс в нужном каталоге.
В некоторых случаях вам необходимо добавить правильный путь к файлу и указать терминалу Java в правильном месте. Поскольку вы выполняете команду из терминала командной строки, компьютер не знает, где находится класс или где он находится. В целевой среде IDE это не проблема, поскольку среда IDE сохраняет указатель, указывающий на текущий рабочий каталог.
Что такое синтаксис java?
Прежде чем мы начнем устранять неполадки, по которым терминал возвращает нам ошибку при попытке выполнения, сначала нам нужно взглянуть на синтаксис команды. Если вы не используете правильный синтаксис, вы неизбежно столкнетесь с этой ошибкой.
Обычный синтаксис команды выглядит примерно так:
Ява [ ... ] [ ...]
Это параметр командной строки, это полное имя класса Java и аргумент командной строки, который передается вашему приложению при компиляции всего пакета.
Пример допустимой команды:
java -Xmx100m com.acme.example.ListAppuals Кевин Стрелок Барт
Приведенная выше команда заставит java-команду выполнить следующие операции:
- Он будет искать скомпилированную версию класса com.acme.example.ListAppuals .
- После поиска он загрузит класс.
- Затем, когда класс загружен, в классе будет производиться поиск «основного» метода с действительной подписью, модификаторами и типом возвращаемого значения. Пример основного класса будет примерно таким:
public static void main (String [])
- Метод будет вызываться с аргументами kevin, arrow и bart как string [].
Как исправить ошибку «Не удалось найти или загрузить основной класс»
Решение 1. Проверка аргумента имени класса
Самая распространенная ошибка пользователей заключается в том, что они указывают неправильное имя класса в качестве аргумента (или правильное имя класса имеет неправильную форму). Поскольку мы объявляем параметры в командной строке, весьма вероятно, что вы передадите аргумент имени класса в неправильной форме. Здесь мы перечислим все возможные сценарии, при которых можно ошибиться.
- Написание простого имени класса . Если вы объявляете класс в пакете, таком как com.acme.example, вы должны использовать полное имя класса, включая пакет, в команде Java.
java com.acme.example.ListAppuals
вместо того
java ListAppuals
- Вы должны объявить имя класса вместо объявления имени файла или пути. Java не получает класс, если вы объявляете для него путь / имя файла. Неправильные записи включают следующее:
java ListAppuals.class java com / acme / example / ListAppuals.class
- Следует учитывать корпус . Команды Java чувствительны к регистру, и если вы ошиблись хотя бы одной буквой, вы не сможете загрузить основной класс. Примеры неправильных ошибок :
java com.acme.example.listappuals
- Вы не должны объявлять имя исходного файла . Как упоминалось ранее, вам нужно только объявить класс в правильном формате полного имени класса. Пример ошибки:
java ListAppuals.java
- Эта ошибка также возникнет, если вы допустите опечатку или забудете полностью написать имя класса .
Если вы допустили какие-либо небрежные ошибки при объявлении имени класса, обязательно исправьте их, а затем попробуйте запустить программу.
Решение 2. Проверка пути к классам
Если вы правильно объявили имя класса, но по-прежнему отображается ошибка, скорее всего, команда java не смогла найти указанное имя класса по пути. Путь к классам — это путь, по которому среда выполнения Java ищет файлы ресурсов и классов. Вы можете легко установить путь к классам, используя две разные команды, как показано ниже:
C:> sdkTool -classpath classpath1; classpath2 ... C:> установить CLASSPATH = classpath1; classpath2 ...
Чтобы получить больше информации о пути к классам, вам следует ознакомиться со следующими документами.
Документация по командам Java
Установка пути к классам
Решение 3. Проверка каталога
Когда вы объявляете каталог как путь к классам, он всегда будет соответствовать корню пространства имен. Например, если «/ usr / local / acme / classes» находится в пути к классам, то Java будет искать класс «com.acme.example.Appuals». Он будет искать класс со следующим путем:
/usr/local/acme/classes/com/acme/example/Appuals.class
По сути, если вы укажете следующий адрес в пути к классам, Java не сможет найти класс:
/ USR / местные / acme / классы / ком / acme / пример
Вы также должны проверить свой подкаталог и посмотреть, соответствует ли он FQN. Если FQN вашего класса — «com.acme.example.Appuals», то Java будет искать «Appuals.class» в каталоге «com / acme / example».
Чтобы дать вам пример, давайте предположим следующий сценарий:
- Класс, который вы хотите запустить: com.acme.example.Appuals
- Полный путь к файлу является: /usr/local/acme/classes/com/acme/example/Appuals.class
- Текущий рабочий каталог является: / USR / местные / Acme / классы / ком / Acme / пример /
Тогда будут иметь место следующие сценарии:
# неверно, требуется FQN java Appuals # неверно, в текущем рабочем каталоге нет папки com / acme / example. java com.acme.example.Appuals # неверно, аналогично сценарию выше java -classpath. com.acme.example.Appuals # OK; устанавливается относительный путь к классам java -classpath ../../ .. com.acme.example.Appuals # OK; установлен абсолютный путь к классам java -classpath / usr / local / acme / classes com.acme.example.Appuals
Примечание. Путь к классам должен также включать все другие классы (несистемные), которые нужны вашим приложениям.
Решение 4.Проверка пакета класса
Если все вышеперечисленные решения верны в вашем случае, вам необходимо убедиться, что ваш исходный код помещен в правильную папку. Кроме того, вы правильно заявили о пакете . Если вы запустите свой код с IDE, он, вероятно, проинформирует вас о проблеме. Однако в нашем случае, поскольку мы запускаем его в командной строке, ошибка останется незамеченной, и вы получите обсуждаемую ошибку класса.