Quale versione di javac ha creato il mio vaso?


212

Come posso sapere quale versione del compilatore Java è stata utilizzata per creare un vaso? Ho un file jar e potrebbe essere stato creato in uno dei tre JDK. Dobbiamo sapere esattamente quale, in modo da poter certificare la compatibilità. La versione del compilatore è incorporata da qualche parte nei file di classe o nel vaso?


8
Puoi dire il versetto principale guardando il file manifest. Puoi dire la versione di destinazione guardando i file di classe stessi, tuttavia i JDK possono produrre file di classe per le versioni precedenti di java usando l'opzione -target, quindi guardare i primi byte potrebbe non essere accurato.
Peter Lawrey,

15
In MANIFEST.MF puoi trovare qualcosa di simileCreated-By: 1.7.0_13 (Oracle Corporation)
hko19

1
Sembra che Maven 3 faccia questo Created-By: Apache MaveneBuild-Jdk: 1.8.0_25
Neal Xiong,

Si prega di controllare questo: stackoverflow.com/questions/27065/...
Abheek Dutta

Risposte:


89

Non si può dire dal file JAR stesso, necessariamente.

Scarica un editor esadecimale e apri uno dei file di classe all'interno del JAR e osserva gli offset di byte da 4 a 7. Le informazioni sulla versione sono integrate.

http://en.wikipedia.org/wiki/Java_class_file

Nota: come menzionato nel commento qui sotto,

quei byte indicano quale versione è stata compilata per la classe, non quale versione l'ha compilata.


41
Per essere pedanti, quei byte ti dicono quale versione è stata compilata per la classe, non quale versione l'ha compilata. Java consente di compilare il codice in modo che siano compatibili con le versioni precedenti di Java. Tuttavia, questo vale solo per il codice e il formato byte. Ad esempio, compilerà felicemente il codice che fa riferimento alle librerie JDK 6 in un formato JDK 5. JDK 5 caricherà la classe, ma non può eseguirla poiché la libreria JDK 5 non ha il codice a cui fa riferimento JDK 6.
Will Hartung

6
Potremmo trovarlo nel file manifest come Creato da: 1.7.0_21-b11 (Oracle Corporation)
Krishna

8
L'altra risposta spiega come controllare facilmente tramite la riga di comando
Margarciaisaia,

313

A jarè semplicemente un contenitore. È un archivio di file tar. Mentre a jarpuò contenere informazioni interessanti contenute nella sua gerarchia META-INF , non ha l'obbligo di specificare la vendemmia delle classi all'interno dei suoi contenuti. Per questo, è necessario esaminare i classfile in essa contenuti.

Come ha detto Peter Lawrey nel commento alla domanda originale, non puoi necessariamente sapere quale versione di JDK ha creato un determinato classfile, ma puoi scoprire la versione della classe di codice byte del classfile contenuta in a jar.

Sì, questo fa schifo, ma il primo passo è estrarre una o più classi dal file jar. Per esempio:

$ jar xf log4j-1.2.15.jar

Su Linux, Mac OS X o Windows con Cygwin installato, il comando file (1) conosce la versione della classe.

$ file ./org/apache/log4j/Appender.class
./org/apache/log4j/Appender.class: compiled Java class data, version 45.3

O in alternativa, usando javapdal JDK come @ jikes.thunderbolt sottolinea giustamente:

$ javap -v ./org/apache/log4j/Appender.class | grep major
 major version: 45

E se sei relegato in un Windowsambiente senza fileogrep

> javap -v ./org/apache/log4j/Appender.class | findstr major
 major version: 45

FWIW, concorderò che javapdirà molto di più su un determinato classfile rispetto alla domanda originale.

Ad ogni modo, una versione di classe diversa, ad esempio:

$ file ~/bin/classes/P.class
/home/dave/bin/classes/P.class: compiled Java class data, version 50.0

Il numero maggiore della versione della classe corrisponde alle seguenti versioni JDK di Java:

  • 45.3 = Java 1.1
  • 46 = Java 1.2
  • 47 = Java 1.3
  • 48 = Java 1.4
  • 49 = Java 5
  • 50 = Java 6
  • 51 = Java 7
  • 52 = Java 8
  • 53 = Java 9

7
La mia versione di filenon ha dimostrato che, ma sono stato in grado di controllare manualmente la classe con questo comando: hexdump ~/bin/classes/P.class | head. Basta guardare l'ottavo byte e convertirlo in decimale.
Jarett Millard,

2
Il "file" di FYI sembra dipendere dalla versione di Java se visualizzerà anche la versione di JDK. Su CentOS / Oracle Linux e una classe compilata Java 6 ottengo "dati compilati della classe Java, versione 50.0 (Java 1.6)" ma quando lo eseguo su una classe compilata con Java 7 ottengo solo la versione generica "compilati i dati della classe Java, versione 51.0 "
Dan Haynes

1
JAR=something.jar ; unzip -p $JAR `unzip -l $JAR | grep '\.class$' | head -1` | file -
Randall Whitman,

3
@JarettMillard my cygwin file file.class(5.22-1) inizialmente non mostrava il target della classe java: "file.class: [architecture = 6909806] [architecture = 6845039]". Tuttavia ha file -k file.classfatto questo: "file.class: [architecture = 6909806] [architecture = 6845039] ha compilato i dati della classe Java, versione 50.0 (Java 1.6)". Sembra che abbia fileottenuto solo la prima partita nei suoi db di file magici senza -k.
dim

1
L'uso di "javap -p <class> | grep major" non è affidabile. Se un pom.xml ha un sorgente / target di 1.7, javap ti darà sempre "versione principale: 51" indipendentemente dal fatto che tu usi JDK 1.7, JDK 1.8 o JDK 1.9 per compilare.
user2569618

54

Ecco il modo di Java per trovare queste informazioni.

Windows: javap -v <class> | findstr major
Unix:javap -v <class> | grep major

Per esempio:
> javap -v Application | findstr major   major version: 51


3
di tutte le risposte fornite, la tua risposta è la più concisa e non comporta la scrittura di 10 righe di codice per conoscere le informazioni sulla versione. +1 per quello
Thirumalai Parthasarathi

2
A cosa serve il <class>parametro?
Dims

1
@Dims Il file ".class" di cui stai provando a trovare la versione. Nel suo esempio, ha un Application.classfile che si è rivelato essere compilato per Java 7 ( major version: 51).
inanutshellus,

1
Questi comandi forniscono la versione JVM di destinazione, non la javacversione che ha compilato i file .class, che è ciò che è stato richiesto.
Marchese di Lorne,

15

Non è necessario decomprimere il JAR (se uno dei nomi di classe è noto o viene cercato, ad esempio utilizzando 7zip), quindi su Windows sarebbe sufficiente:

javap -cp log4j-core-2.5.jar -verbose org.apache.logging.log4j.core.Logger | findstr major

1
Funziona anche su Linux (tranne ovviamente per usare grep invece di findstr)
Michael Rusch,

1
Questi comandi forniscono la versione JVM di destinazione, non la javacversione che ha compilato i file .class, che è ciò che è stato richiesto.
Marchese di Lorne,

15

Il compilatore Java ( javac) non crea barattoli, traduce i file Java in file di classe. Lo strumento Jar ( jar) crea i vasi reali. Se non è stato specificato alcun manifest personalizzato, il manifest predefinito specificherà quale versione di JDK è stata utilizzata per creare il jar.


3
Pur essendo vero, questo non fornisce una risposta alla domanda.
zb226,

2
@ zb226 AFAIK la versione del compilatore utilizzato per compilare le classi non sono disponibili presso i file di classe, solo la versione che le classi sono stati compilati per . Le informazioni che ho fornito forniscono le uniche (standard) informazioni disponibili su quale versione ha creato l'artefatto. In caso contrario, la domanda dovrebbe essere riformulata.
jackrabbit,

OK, lo stavo guardando da un punto di vista pragmatico, ma ora vedo il tuo punto, il downvote è stato rimosso :)
zb226

9

Dato che avevo bisogno di analizzare i vasetti grassi, ero interessato alla versione di ogni singola classe in un file jar. Pertanto ho adottato l'approccio di Joe Liversedge https://stackoverflow.com/a/27877215/1497139 e l'ho combinato con la tabella delle versioni dei numeri di classe https://stackoverflow.com/a/3313839/1497139 di David J. Liszewski per creare uno script bash jarv per mostrare le versioni di tutti i file di classe in un file jar.

uso

usage: ./jarv jarfile
 -h|--help: show this usage

Esempio

jarv $Home/.m2/repository/log4j/log4j/1.2.17/log4j-1.2.17.jar

java 1.4 org.apache.log4j.Appender
java 1.4 org.apache.log4j.AppenderSkeleton
java 1.4 org.apache.log4j.AsyncAppender$DiscardSummary
java 1.4 org.apache.log4j.AsyncAppender$Dispatcher
...

Bash script jarv

#!/bin/bash
# WF 2018-07-12
# find out the class versions with in jar file
# see https://stackoverflow.com/questions/3313532/what-version-of-javac-built-my-jar

# uncomment do debug
# set -x

#ansi colors
#http://www.csc.uvic.ca/~sae/seng265/fall04/tips/s265s047-tips/bash-using-colors.html
blue='\033[0;34m'  
red='\033[0;31m'  
green='\033[0;32m' # '\e[1;32m' is too bright for white bg.
endColor='\033[0m'

#
# a colored message 
#   params:
#     1: l_color - the color of the message
#     2: l_msg - the message to display
#
color_msg() {
  local l_color="$1"
  local l_msg="$2"
  echo -e "${l_color}$l_msg${endColor}"
}

#
# error
#
#   show an error message and exit
#
#   params:
#     1: l_msg - the message to display
error() {
  local l_msg="$1"
  # use ansi red for error
  color_msg $red "Error: $l_msg" 1>&2
  exit 1
}

#
# show the usage
#
usage() {
  echo "usage: $0 jarfile"
  # -h|--help|usage|show this usage
  echo " -h|--help: show this usage"
  exit 1 
}

#
# showclassversions
#
showclassversions() {
  local l_jar="$1"
  jar -tf "$l_jar" | grep '.class' | while read classname
  do
    class=$(echo $classname | sed -e 's/\.class$//')
    class_version=$(javap -classpath "$l_jar" -verbose $class | grep 'major version' | cut -f2 -d ":" | cut -c2-)
    class_pretty=$(echo $class | sed -e 's#/#.#g')
    case $class_version in
      45.3) java_version="java 1.1";;
      46) java_version="java 1.2";;
      47) java_version="java 1.3";;
      48) java_version="java 1.4";;
      49) java_version="java5";;
      50) java_version="java6";;
      51) java_version="java7";;
      52) java_version="java8";;
      53) java_version="java9";;
      54) java_version="java10";;
      *) java_version="x${class_version}x";;
    esac
    echo $java_version $class_pretty
  done
}

# check the number of parameters
if [ $# -lt 1 ]
then
  usage
fi

# start of script
# check arguments
while test $# -gt 0
do
  case $1 in
    # -h|--help|usage|show this usage
    -h|--help) 
      usage
      exit 1
      ;;
    *)
     showclassversions "$1"
  esac
  shift
done 

1
Ottima sceneggiatura! Sono stato in grado di controllare le versioni java dei vasetti.
ARK

Grazie mille per aver condiviso la sceneggiatura! Solo un suggerimento da parte mia. head -1può essere usato insieme allo script: di solito, è sufficiente vedere la versione della prima classe nel file jar.
Sergey Brunov,

7

puoi trovare la versione del compilatore Java dai file .class usando un editor esadecimale.

Passaggio 1: estrai i file .class dal file jar usando un estrattore zip

passaggio 2: apri il file .class con un editor esadecimale (ho usato il plug-in editor hex hex di notepad ++. Questo plug-in legge il file come binario e lo mostra in esadecimale) Puoi vedere di seguito. inserisci qui la descrizione dell'immagine

Gli indici 6 e 7 forniscono il numero di versione principale del formato file di classe utilizzato. https://en.wikipedia.org/wiki/Java_class_file

Java SE 11 = 55 (0x37 hex)

Java SE 10 = 54 (0x36 hex)

Java SE 9 = 53 (0x35 hex)

Java SE 8 = 52 (0x34 hex),

Java SE 7 = 51 (0x33 hex),

Java SE 6.0 = 50 (0x32 hex),

Java SE 5.0 = 49 (0x31 hex),

JDK 1.4 = 48 (0x30 hex),

JDK 1.3 = 47 (esadecimale 0x2F),

JDK 1.2 = 46 (esadecimale 0x2E),

JDK 1.1 = 45 (esadecimale 0x2D).



4

Il codice pubblicato da Owen può dirti le informazioni menzionate da una serie di altre risposte qui:

public void simpleExample ()
{
    FileInputStream fis = new FileInputStream ("mytest.class");
    parseJavaClassFile ( fis );
}
protected void parseJavaClassFile ( InputStream classByteStream ) throws Exception
{
    DataInputStream dataInputStream = new DataInputStream ( classByteStream );
    magicNumber = dataInputStream.readInt();
    if ( magicNumber == 0xCAFEBABE )
    {
        int minorVer = dataInputStream.readUnsignedShort();
        int majorVer = dataInputStream.readUnsignedShort();
        // do something here with major & minor numbers
    }
}

Vedi anche questo e questo sito. Ho finito per modificare rapidamente il codice Mind Products per verificare per cosa era compilata ciascuna delle mie dipendenze.


Questi comandi forniscono la versione JVM di destinazione, non la javacversione che ha compilato i file .class, che è ciò che è stato richiesto.
Marchese di Lorne,

3

Gli sviluppatori e gli amministratori che eseguono Bash possono trovare utili queste funzioni utili:

jar_jdk_version() {
  [[ -n "$1" && -x "`command -v javap`" ]] && javap -classpath "$1" -verbose $(jar -tf "$1" | grep '.class' | head -n1 | sed -e 's/\.class$//') | grep 'major version' | sed -e 's/[^0-9]\{1,\}//'
}

print_jar_jdk_version() {
  local version
  version=$(jar_jdk_version "$1")
  case $version in 49) version=1.5;; 50) version=1.6;; 51) version=1.7;; 52) version=1.8;; esac
  [[ -n "$version" ]] && echo "`basename "$1"` contains classes compiled with JDK version $version."
}

Puoi incollarli per un utilizzo singolo o aggiungerli a ~/.bash_aliaseso ~/.bashrc. I risultati sembrano simili a:

$ jar_jdk_version poi-ooxml-3.5-FINAL.jar
49

e

$ print_jar_jdk_version poi-ooxml-3.5-FINAL.jar
poi-ooxml-3.5-FINAL.jar contains classes compiled with JDK version 1.5.

EDIT Come sottolinea jackrabbit , non puoi fare affidamento al 100% sul manifest per dirti qualcosa di utile. In tal caso, è possibile estrarlo nella shell UNIX preferita con unzip:

$ unzip -pa poi-ooxml-3.5-FINAL.jar META-INF/MANIFEST.MF
Manifest-Version: 1.0
Ant-Version: Apache Ant 1.7.1
Created-By: 11.3-b02 (Sun Microsystems Inc.)
Built-By: yegor
Specification-Title: Apache POI
Specification-Version: 3.5-FINAL-20090928
Specification-Vendor: Apache
Implementation-Title: Apache POI
Implementation-Version: 3.5-FINAL-20090928
Implementation-Vendor: Apache

Questo .jar non ha nulla di utile nel manifest sulle classi contenute.


Questi comandi forniscono la versione JVM di destinazione, non la javacversione che ha compilato i file .class, che è ciò che è stato richiesto.
Marchese di Lorne,

3

One liner (Linux)

unzip -p mylib.jar META-INF/MANIFEST.MF

Questo stampa il contenuto del MANIFEST.MFfile su stdout (speriamo che ce ne sia uno nel tuo file jar :)

A seconda della versione del pacchetto, troverai la versione Created-Byo la Build-Jdkchiave di JDK .


3
Si noti che non vi è alcun obbligo per la presenza di questi campi MANIFEST.MF, così come non vi è alcun obbligo che i valori siano corretti (Sì, una volta ho riscontrato un punto in .jarcui il valore era falso).
zb226,

Questi comandi forniscono la versione JVM di destinazione, non la javacversione che ha compilato i file .class, che è ciò che è stato richiesto.
Marchese di Lorne,

2

Ogni file di classe ha un numero di versione incorporato per il livello di codice byte che JVM usa per vedere se gli piace quel particolare pezzo di codice byte o meno. Questo è 48 per Java 1.4, 49 per Java 1.5 e 50 per Java 6.

Esistono molti compilatori che possono generare il codice byte ad ogni livello, javac utilizza l'opzione "-target" per indicare quale livello di codice byte generare e il Java 6 Java può generare il codice byte per almeno 1,4, 1,5 e 6. Non credo che il compilatore inserisca tutto ciò che può identificare il compilatore stesso che è quello che penso tu chieda. Anche il compilatore Eclipse viene sempre più utilizzato, in quanto è un singolo jar che può essere eseguito solo con JRE.

In un file jar di solito ci sono molte classi e ognuna di esse è indipendente, quindi è necessario indagare su tutte le classi nel vaso per essere certi delle caratteristiche del contenuto.


1

In seguito alla risposta di @David J. Liszewski, ho eseguito i seguenti comandi per estrarre il manifest del file jar su Ubuntu:

# Determine the manifest file name:
$ jar tf LuceneSearch.jar | grep -i manifest
META-INF/MANIFEST.MF

# Extract the file:
$ sudo jar xf LuceneSearch.jar META-INF/MANIFEST.MF

# Print the file's contents:
$ more META-INF/MANIFEST.MF
Manifest-Version: 1.0
Ant-Version: Apache Ant 1.8.2
Created-By: 1.7.0_25-b30 (Oracle Corporation)
Main-Class: org.wikimedia.lsearch.config.StartupManager

2
Oppure, come una fodera:unzip -p LiceneSearch.jar META-INF/MANIFEST.MF
Ogre Salmo33

Questi comandi forniscono la versione JVM di destinazione, non la javacversione che ha compilato i file .class, che è ciò che è stato richiesto.
Marchese di Lorne,

1

Molte volte, potresti guardare interi file jar o file di guerra che contengono molti file jar oltre a se stessi.

Poiché non volevo controllare manualmente ogni classe, ho scritto un programma Java per farlo:

https://github.com/Nthalk/WhatJDK

./whatjdk some.war
some.war:WEB-INF/lib/xml-apis-1.4.01.jar contains classes compatible with Java1.1
some.war contains classes compatible with Java1.6

Sebbene questo non dica con cosa è stata compilata la classe, determina ciò che JDK sarà in grado di CARICARE le classi, che è probabilmente quello con cui volevi iniziare.


1

Per espandere le risposte di Jonathon Faust e McDowell : Se utilizzi un sistema basato su * nix, puoi utilizzare od(uno dei primi programmi Unix 1 che dovrebbero essere disponibili praticamente ovunque) per eseguire una query sul .classfile a livello binario:

od -An -j7 -N1 -t dC SomeClassFile.class

Ciò genererà i valori interi familiari, ad esempio 50for Java 5, 51for Java 6e così via.

1 Citazione da https://en.wikipedia.org/wiki/Od_(Unix)


Questi comandi forniscono la versione JVM di destinazione, non la javacversione che ha compilato i file .class, che è ciò che è stato richiesto.
Marchese di Lorne,

1

Ho anche scritto il mio script bash per scaricare la versione Java richiesta da tutti i barattoli passati dalla riga di comando ... Il mio è un po 'approssimativo, ma funziona per me ;-)

esempio di utilizzo

$ jar_dump_version_of_jvm_required.sh *.jar
JVM VERSION REQUIRED: 46.0, /private/tmp/jars/WEB-INF/lib/json-simple-1.1.jar
JVM VERSION REQUIRED: 49.0, /private/tmp/jars/WEB-INF/lib/json-smart-1.1.1.jar
JVM VERSION REQUIRED: 50.0, /private/tmp/jars/WEB-INF/lib/jsontoken-1.0.jar
JVM VERSION REQUIRED: 50.0, /private/tmp/jars/WEB-INF/lib/jsr166y-1.7.0.jar

jar_dump_version_of_jvm_required.sh

#!/bin/bash

DIR=$(PWD)
function show_help()
{
  ME=$(basename $0)
  IT=$(cat <<EOF

  Dumps the version of the JVM required to run the classes in a jar file

  usage: $ME JAR_FILE

  e.g. 

  $ME myFile.jar    ->  VERSION: 50.0     myFile.jar

  Java versions are:
  54 = Java 10
  53 = Java 9
  52 = Java 8
  51 = Java 7
  50 = Java 6
  49 = Java 5
  48 = Java 1.4
  47 = Java 1.3
  46 = Java 1.2
  45.3 = Java 1.1

EOF
  )
  echo "$IT"
  exit
}

if [ "$1" == "help" ]
then
  show_help
fi
if [ -z "$1" ]
then
  show_help
fi

function unzipJarToTmp()
{
  JAR=$1
  CLASS_FILE=$(jar -tf "$JAR" | grep \.class$ | grep -v '\$' | head -n1 | awk '{print $NF}')
  OUT_FILE="$CLASS_FILE"
  #echo "J=$JAR C=$CLASS_FILE O=$OUT_FILE"
  jar xf "$JAR" "$CLASS_FILE"

  MAJOR=$(javap -v "$OUT_FILE" 2>&1 | grep major | awk -F' ' '{print $3'})
  MINOR=$(javap -v "$OUT_FILE" 2>&1 | grep minor | awk -F' ' '{print $3'})
  if [ -z "$MAJOR" ]
  then
    echo "JVM VERSION REQUIRED: NA as no classes in $JAR"
  else
    echo "JVM VERSION REQUIRED: $MAJOR.$MINOR, $JAR"
  fi
}

# loop over cmd line args
for JAR in "$@"
do
  cd "$DIR"
  JAR_UID=$(basename "$JAR" | sed s/.jar//g)
  TMPDIR=/tmp/jar_dump/$JAR_UID/
  mkdir -p "$TMPDIR"
  JAR_ABS_PATH=$(realpath $JAR)

  cd "$TMPDIR"

  #echo "$JAR_ABS_PATH"
  unzipJarToTmp "$JAR_ABS_PATH"
  #sleep 2
done

1

Puoi farlo facilmente dalla riga di comando usando il seguente processo:

Se conosci qualcuno del nome della classe in jar, puoi usare il seguente comando:

javap -cp jarname.jar -verbose packagename.classname | findstr major

esempio :

    C:\pathwherejarlocated> javap -cp jackson-databind-2.8.6.jar -verbose com.fasterxml.jackson.databind.JsonMappingException | findstr major

Produzione :

    major version: 51

Riferimento rapido :

JDK 1.0  major version 45 
DK 1.1  major version 45 
JDK 1.2  major version 46 
JDK 1.3  major version 47 
JDK 1.4  major version 48 
JDK 1.5  major version 49 
JDK 1.6  major version 50 
JDK 1.7  major version 51 
JDK 1.8  major version 52 
JDK 1.9  major version 53 

PS: se non conosci nessuno dei nomi delle classi, puoi farlo facilmente usando uno dei decompilatori jar o semplicemente usando il seguente comando per estrarre il file jar:

jar xf myFile.jar

0

Si controlla nel file manifest dell'esempio jar:

Versione manifest: 1.0 Creato da: 1.6.0 (IBM Corporation)


-1

Su Windows, procedi come segue:

  1. Decomprimi o estrai il file JAR usando il comando JAR WinZip / Java.
  2. Trascina e rilascia uno dei file di classe nel tuo progetto Eclipse Java.
  3. Apri il file di classe.

Ora Eclipse mostrerà l'esatta versione principale e secondaria.


Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.