¿Qué versión de javac construyó mi jar?

212

¿Cómo puedo saber qué versión del compilador de Java se usó para construir un jar? Tengo un archivo jar, y podría haber sido construido en cualquiera de los tres JDK. Necesitamos saber exactamente cuál, para poder certificar la compatibilidad. ¿La versión del compilador está incrustada en algún lugar de los archivos de clase o jar?

Ron Romero
fuente
8
Puede decir la versión principal mirando el archivo de manifiesto. Puede ver la versión de destino mirando los propios archivos de clase, sin embargo, los JDK pueden producir archivos de clase para versiones anteriores de Java utilizando la opción -target, por lo que mirar los primeros bytes podría no ser exacto.
Peter Lawrey
15
En MANIFEST.MF puedes encontrar algo comoCreated-By: 1.7.0_13 (Oracle Corporation)
hko19
1
Parece que Maven 3 hace esto Created-By: Apache MavenyBuild-Jdk: 1.8.0_25
Neal Xiong
Verifique esto: stackoverflow.com/questions/27065/…
Abheek Dutta

Respuestas:

89

No se puede distinguir del archivo JAR en sí, necesariamente.

Descargue un editor hexadecimal y abra uno de los archivos de clase dentro del JAR y observe las compensaciones de bytes 4 a 7. La información de la versión está integrada.

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

Nota: Como se menciona en el comentario a continuación,

esos bytes le dicen para qué versión se compiló la clase, no qué versión la compiló.

Jonathon Faust
fuente
41
Para ser pedante, esos bytes le dicen para qué versión se compiló la clase, no qué versión la compiló. Java le permite compilar código para que sean compatibles con versiones anteriores de Java. Sin embargo, esto solo se aplica al código y formato de bytes. Felizmente compilará código que hace referencia a bibliotecas JDK 6 en un formato JDK 5, por ejemplo. El JDK 5 cargará la clase, pero no puede ejecutarla ya que la biblioteca JDK 5 no tiene el código referenciado desde JDK 6.
Will Hartung
66
Podríamos encontrarlo en el archivo de manifiesto como Creado por: 1.7.0_21-b11 (Oracle Corporation)
Krishna
8
La otra respuesta dice cómo verificar fácilmente a través de la línea de comando
mgarciaisaia
313

A jares simplemente un contenedor. Es un archivo de archivo a la tar. Si bien jarpuede tener información interesante dentro de su jerarquía META-INF , no tiene la obligación de especificar la antigüedad de las clases dentro de su contenido. Para eso, uno debe examinar los classarchivos allí.

Como Peter Lawrey mencionó en el comentario a la pregunta original, no necesariamente puede saber qué versión de JDK creó un classarchivo determinado , pero puede encontrar la versión de clase de código de byte del classarchivo contenido en a jar.

Sí, esto apesta, pero el primer paso es extraer una o más clases del jar. Por ejemplo:

$ jar xf log4j-1.2.15.jar

En Linux, Mac OS X o Windows con Cygwin instalado, el comando file (1) conoce la versión de la clase.

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

O, alternativamente, usar javapdesde el JDK como @ jikes.thunderbolt señala acertadamente:

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

Y si está relegado a un Windowsentorno sin fileogrep

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

FWIW, coincidiré en que javapcontará mucho más sobre un classarchivo dado que la pregunta original formulada.

De todos modos, una versión de clase diferente, por ejemplo:

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

El número principal de la versión de clase corresponde a las siguientes versiones de Java JDK:

  • 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
David J. Liszewski
fuente
77
Mi versión de fileno demostró que, pero yo era capaz de comprobar la clase de forma manual con este comando: hexdump ~/bin/classes/P.class | head. Solo mire el octavo byte y conviértalo a decimal.
Jarett Millard
2
El 'archivo' de FYI parece depender de la versión de Java en cuanto a si también mostrará la versión JDK. En CentOS / Oracle Linux y una clase compilada de Java 6 obtengo "datos compilados de la clase Java, versión 50.0 (Java 1.6)", pero cuando lo ejecuto en una clase compilada con Java 7, obtengo la versión genérica "datos compilados de la clase Java, versión 51.0 "
Dan Haynes
1
JAR=something.jar ; unzip -p $JAR `unzip -l $JAR | grep '\.class$' | head -1` | file -
Randall Whitman
3
@JarettMillard mi cygwin file file.class(5.22-1) no mostró el objetivo de la clase java inicialmente: "file.class: [architecture = 6909806] [architecture = 6845039]". Sin embargo, file -k file.classhizo eso: "file.class: [architecture = 6909806] [architecture = 6845039] compiló datos de clase Java, versión 50.0 (Java 1.6)". Parece que filesolo obtuvo la primera coincidencia en sus archivos mágicos db sin-k .
dim
1
Usar "javap -p <clase> | grep major" no es confiable. Si un pom.xml tiene una fuente / destino de 1.7, javap siempre le dará "versión principal: 51" independientemente de si usa JDK 1.7, JDK 1.8 o JDK 1.9 para compilar.
user2569618
54

Aquí está la forma en que Java encuentra esta información.

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

Por ejemplo:
> javap -v Application | findstr major   major version: 51

jikes.thunderbolt
fuente
3
De todas las respuestas dadas, su respuesta es la más concisa y no implica escribir 10 líneas de código para conocer la información de la versión. +1 por eso
Thirumalai Parthasarathi
2
¿Para qué sirve el <class>parámetro?
Dims
1
@Dims El archivo ".class" del que está tratando de encontrar la versión. En su ejemplo, tiene un Application.classarchivo y resultó estar compilado para Java 7 ( major version: 51).
inanutshellus
1
Estos comandos entregan la versión JVM de destino, no la javacversión que compiló los archivos .class, que es lo que se solicitó.
Marqués de Lorne
15

No es necesario descomprimir el JAR (si se conoce o se busca uno de los nombres de clase, por ejemplo, usando 7zip), por lo que en Windows lo siguiente sería suficiente:

javap -cp log4j-core-2.5.jar -verbose org.apache.logging.log4j.core.Logger | findstr major
anre
fuente
1
Esto también funciona en Linux (excepto que usaría grep en lugar de findtr, por supuesto)
Michael Rusch
1
Estos comandos entregan la versión JVM de destino, no la javacversión que compiló los archivos .class, que es lo que se solicitó.
Marqués de Lorne
15

El compilador de Java ( javac) no crea frascos, traduce los archivos de Java en archivos de clase. La herramienta Jar ( jar) crea los frascos reales. Si no se especificó ningún manifiesto personalizado, el manifiesto predeterminado especificará qué versión del JDK se usó para crear el jar.

Jackrabbit
fuente
3
Si bien es cierto, esto no proporciona una respuesta a la pregunta.
zb226
2
@ zb226 yo sepa la versión del compilador utilizado para compilar las clases no se puede obtener a partir de los archivos de clase, sólo la versión de que las clases fueron compilados para . La información que proporcioné proporciona la única información (estándar) que está disponible sobre qué versión construyó el artefacto. Si eso no es lo que se pregunta, la pregunta debe redactarse nuevamente.
jackrabbit
OK, estaba mirando eso desde un punto de vista pragmático, pero ahora veo tu punto,
eliminé el voto negativo
9

Como necesitaba analizar frascos gordos, estaba interesado en la versión de cada clase individual en un archivo jar. Por lo tanto, tomé el enfoque de Joe Liversedge https://stackoverflow.com/a/27877215/1497139 y lo combiné con la tabla de versiones de números de clase de David J. Liszewski ' https://stackoverflow.com/a/3313839/1497139 para crear un script bash jarv para mostrar las versiones de todos los archivos de clase en un archivo jar.

uso

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

Ejemplo

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 
Wolfgang Fahl
fuente
1
Gran guión! Pude comprobar las versiones java de los tarros.
ARCA
¡Muchas gracias por compartir el guión! Solo una pista de mi lado. head -1se puede usar junto con el script: por lo general, es suficiente ver la versión de la primera clase en el archivo jar.
Sergey Brunov
7

Puede encontrar la versión del compilador de Java a partir de archivos .class utilizando un Editor Hex.

Paso 1: extraiga los archivos .class del archivo jar con un extractor zip

Paso 2: abra el archivo .class con un editor hexadecimal (he utilizado el complemento de editor hexadecimal Notepad ++. Este complemento lee el archivo como binario y lo muestra en hexadecimal). ingrese la descripción de la imagen aquí

Los índices 6 y 7 proporcionan el número de versión principal del formato de archivo de clase utilizado. https://en.wikipedia.org/wiki/Java_class_file

Java SE 11 = 55 (0x37 hex)

Java SE 10 = 54 (0x36 hexadecimal)

Java SE 9 = 53 (0x35 hexadecimal)

Java SE 8 = 52 (0x34 hexadecimal),

Java SE 7 = 51 (0x33 hexadecimal),

Java SE 6.0 = 50 (0x32 hexadecimal),

Java SE 5.0 = 49 (0x31 hexadecimal),

JDK 1.4 = 48 (0x30 hexadecimal),

JDK 1.3 = 47 (0x2F hexadecimal),

JDK 1.2 = 46 (0x2E hexadecimal),

JDK 1.1 = 45 (0x2D hexadecimal).

cahit beyaz
fuente
4

Puede ver la versión binaria de Java inspeccionando los primeros 8 bytes (o utilizando una aplicación que pueda).

El compilador en sí mismo, a mi leal saber y entender, no inserta ninguna firma de identificación. De todos modos, no puedo detectar tal cosa en el formato de clase de especificación de VM de archivo .

McDowell
fuente
Estos comandos entregan la versión JVM de destino, no la javacversión que compiló los archivos .class, que es lo que se solicitó.
Marqués de Lorne
4

El código publicado por Owen puede brindarle la información mencionada por varias de las otras respuestas aquí:

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
    }
}

Vea también este y este sitio. Terminé modificando el código de Mind Products rápidamente para verificar para qué se compiló cada una de mis dependencias.

JasonPlutext
fuente
Estos comandos entregan la versión JVM de destino, no la javacversión que compiló los archivos .class, que es lo que se solicitó.
Marqués de Lorne
3

Los desarrolladores y administradores que ejecutan Bash pueden encontrar útiles estas prácticas funciones:

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."
}

Puede pegarlos para usarlos una vez o agregarlos a ~/.bash_aliaseso ~/.bashrc. Los resultados se parecen a:

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

y

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

EDITAR Como señala jackrabbit , no puede confiar 100% en el manifiesto para decirle nada útil. Si lo fuera, entonces podría sacarlo en su shell UNIX favorito 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

Este .jar no tiene nada útil en el manifiesto sobre las clases contenidas.

Joe Liversedge
fuente
Estos comandos entregan la versión JVM de destino, no la javacversión que compiló los archivos .class, que es lo que se solicitó.
Marqués de Lorne
3

One liner (Linux)

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

Esto imprime el contenido del MANIFEST.MFarchivo en stdout (es de esperar que haya uno en su archivo jar :)

Dependiendo de qué construyó su paquete, encontrará la versión JDK en Created-Byo Build-Jdkclave.

Neal Xiong
fuente
3
Tenga en cuenta que no hay obligación de que estos campos estén presentes MANIFEST.MF, así como tampoco hay obligación de que los valores sean correctos (Sí, una vez me encontré con un .jarvalor falso).
zb226
Estos comandos entregan la versión JVM de destino, no la javacversión que compiló los archivos .class, que es lo que se solicitó.
Marqués de Lorne
2

Cada archivo de clase tiene un número de versión incrustado para el nivel de código de bytes que la JVM usa para ver si le gusta ese fragmento de código de bytes en particular o no. Esto es 48 para Java 1.4, 49 para Java 1.5 y 50 para Java 6.

Existen muchos compiladores que pueden generar código de bytes en cada nivel, javac usa la opción "-target" para indicar qué nivel de código de bytes generar, y Java 6 javac puede generar código de bytes para al menos 1.4, 1.5 y 6. No creo que el compilador inserta cualquier cosa que pueda identificar al compilador en sí, que es lo que creo que pides. Además, el compilador Eclipse se usa cada vez más, ya que es un solo jar que solo puede ejecutarse con JRE.

En un archivo jar generalmente hay muchas clases, y cada una de ellas es independiente, por lo que debe investigar todas las clases en el jar para asegurarse de las características de los contenidos.

Thorbjørn Ravn Andersen
fuente
1

Siguiendo la respuesta de @David J. Liszewski, ejecuté los siguientes comandos para extraer el manifiesto del archivo jar en 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
Alan
fuente
2
O, como una unzip -p LiceneSearch.jar META-INF/MANIFEST.MF
frase
Estos comandos entregan la versión JVM de destino, no la javacversión que compiló los archivos .class, que es lo que se solicitó.
Marqués de Lorne
1

Muchas veces, es posible que esté viendo archivos jar completos o archivos war que contienen muchos archivos jar además de ellos.

Debido a que no quería verificar manualmente cada clase, escribí un programa Java para hacer eso:

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

Si bien esto no dice con qué se compiló la clase, determina qué JDK podrá CARGAR las clases, que es probablemente con lo que quería comenzar.

Nthalk
fuente
1

Para ampliar las respuestas de Jonathon Faust y McDowell : Si está en un sistema basado en * nix, puede usar od(uno de los primeros programas Unix 1 que debería estar disponible prácticamente en todas partes) para consultar el .classarchivo a nivel binario:

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

Esta es la salida los valores enteros familiares, por ejemplo, 50para Java 5, 51por Java 6y así sucesivamente.

1 Cita de https://en.wikipedia.org/wiki/Od_(Unix)

zb226
fuente
Estos comandos entregan la versión JVM de destino, no la javacversión que compiló los archivos .class, que es lo que se solicitó.
Marqués de Lorne
1

También escribí mi propio script bash para volcar la versión de Java requerida por todos los frascos pasados ​​en la línea de comando ... El mío es un poco difícil, pero funciona para mí ;-)

ejemplo de uso

$ 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
Brad Parks
fuente
1

Puede hacer esto fácilmente en la línea de comandos utilizando el siguiente proceso:

Si conoce alguno de los nombres de clase en jar, puede usar el siguiente comando:

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

ejemplo:

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

Salida:

    major version: 51

Referencia rápida :

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 

PD: si no conoce ninguno de los nombres de clase, puede hacerlo fácilmente usando cualquiera de los descompiladores jar o simplemente usando el siguiente comando para extraer el archivo jar:

jar xf myFile.jar
Shweta
fuente
0

Verifica en el archivo de manifiesto del ejemplo jar:

Versión de manifiesto: 1.0 Creado por: 1.6.0 (IBM Corporation)

amoljdv06
fuente
-1

En Windows haga lo siguiente:

  1. Descomprima o extraiga el archivo JAR utilizando el comando WinZip / Java JAR.
  2. Arrastre y suelte uno de los archivos de clase en su proyecto Eclipse Java.
  3. Abre el archivo de clase.

Ahora Eclipse mostrará la versión mayor y menor exacta.

Kittu
fuente
-1

Construyo un pequeño script bash (en github) basado en la sugerencia de Davids usando el filecomando

barbudo
fuente