Qual versão do javac construiu meu jar?

212

Como posso saber qual versão do compilador Java foi usada para criar um jar? Eu tenho um arquivo jar e poderia ter sido construído em qualquer um dos três JDKs. Precisamos saber exatamente qual deles, para que possamos certificar a compatibilidade. A versão do compilador está incorporada em algum lugar nos arquivos de classe ou jar?

Ron Romero
fonte
8
Você pode dizer ao verson principal observando o arquivo de manifesto. Você pode dizer a versão de destino observando os próprios arquivos de classe, no entanto, os JDKs podem produzir arquivos de classe para versões anteriores do java usando a opção -target, portanto, a visualização dos primeiros bytes pode não ser precisa.
22410 Peter Lawrey
15
No MANIFEST.MF você pode encontrar algo parecido comCreated-By: 1.7.0_13 (Oracle Corporation)
hko19 2/13
1
Looks como Maven 3 faz isso Created-By: Apache MaveneBuild-Jdk: 1.8.0_25
Neal Xiong
Por favor, verifique isto: stackoverflow.com/questions/27065/…
Abheek Dutta

Respostas:

89

Você não pode dizer pelo próprio arquivo JAR, necessariamente.

Faça o download de um editor hexadecimal e abra um dos arquivos de classe dentro do JAR e veja os desvios de bytes 4 a 7. As informações da versão estão embutidas.

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

Nota: Conforme mencionado no comentário abaixo,

esses bytes informam para qual versão a classe foi compilada FOR, não para qual versão a compilou.

Jonathon Faust
fonte
41
Para ser pedante, esses bytes informam para qual versão a classe foi compilada FOR, não para qual versão a compilou. Java permite que você compile código para que seja compatível com versões anteriores do Java. No entanto, isso se aplica apenas ao código e formato do byte. Compilará felizmente o código que referencia as bibliotecas JDK 6 em um formato JDK 5, por exemplo. O JDK 5 carregará a classe, mas não poderá executá-la, pois a biblioteca do JDK 5 não possui o código referenciado no JDK 6. #
Will Hartung
6
Nós podemos encontrá-lo no arquivo de manifesto como Created-By: 1.7.0_21-b11 (Oracle Corporation)
Krishna
8
A outra resposta diz como fácil verificação via linha de comando
mgarciaisaia
313

A jaré apenas um recipiente. É um arquivo de arquivo à la tar. Embora um jarpossa ter informações interessantes contidas em sua hierarquia META-INF , ele não tem obrigação de especificar o vintage das classes dentro de seu conteúdo. Para isso, é preciso examinar os classarquivos nele contidos.

Como Peter Lawrey mencionou no comentário à pergunta original, você não pode necessariamente saber qual versão do JDK construiu um determinado classarquivo, mas pode descobrir a versão da classe de código de bytes do classarquivo contido em a jar.

Sim, isso meio que é péssimo, mas o primeiro passo é extrair uma ou mais classes do jar. Por exemplo:

$ jar xf log4j-1.2.15.jar

No Linux, Mac OS X ou Windows com o Cygwin instalado, o comando file (1) conhece a versão da classe.

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

Ou, alternativamente, usar javapdo JDK como @ jikes.thunderbolt indica apropriadamente:

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

E se você for relegado a um Windowsambiente sem um fileou outrogrep

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

FWIW, vou concordar que javapdirá muito mais sobre um determinado classarquivo do que a pergunta original.

Enfim, uma versão de classe diferente, por exemplo:

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

O número principal da versão da classe corresponde às seguintes versões do 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
fonte
7
Minha versão de filenão mostrar isso, mas eu era capaz de verificar a classe manualmente com o comando: hexdump ~/bin/classes/P.class | head. Basta olhar para o oitavo byte e converter para decimal.
Jarett Millard
2
O 'arquivo' FYI parece depender da versão do Java para saber se também exibirá a versão do JDK. No CentOS / Oracle Linux e em uma classe compilada Java 6, recebo "dados compilados da classe Java, versão 50.0 (Java 1.6)", mas quando eu o executo em uma classe compilada com o Java 7, apenas obtenho a versão genérica "dados compilados da classe Java, versão 51.0 "
Dan Haynes
1
JAR=something.jar ; unzip -p $JAR `unzip -l $JAR | grep '\.class$' | head -1` | file -
Randall Whitman
3
@JarettMillard meu cygwin file file.class(5.22-1) não mostrou o destino da classe java inicialmente: "file.class: [architecture = 6909806] [architecture = 6845039]". No entanto file -k file.class, isso foi feito: "file.class: [architecture = 6909806] [architecture = 6845039] compilou dados da classe Java, versão 50.0 (Java 1.6)". Parece que filesó conseguiu a primeira partida em seus arquivos mágicos db sem -k.
dim
1
O uso de "javap -p <class> | grep major" não é confiável. Se um pom.xml tiver uma origem / destino 1.7, o javap sempre fornecerá a "versão principal: 51", independentemente de você usar o JDK 1.7, JDK 1.8 ou JDK 1.9 para compilar.
user2569618
54

Aqui está o caminho do Java para encontrar essas informações.

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

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

jikes.thunderbolt
fonte
3
de todas as respostas dadas, sua resposta é a mais concisa e não envolve escrever 10 linhas de código para conhecer as informações da versão. +1 para isso
Thirumalai Parthasarathi
2
Para que serve o <class>parâmetro?
Dims
1
@Dims O arquivo ".class" do qual você está tentando encontrar a versão. No exemplo dele, ele tem um Application.classarquivo e acabou sendo compilado para o Java 7 ( major version: 51).
23716 inanutshellus
1
Esses comandos entregam a versão da JVM de destino, não a javacversão que compilou os arquivos .class, que foi solicitado.
Marquês de Lorne
15

Não é necessário descompactar o JAR (se um dos nomes de classe for conhecido ou consultado, por exemplo, usando 7zip), portanto, no Windows, o seguinte seria suficiente:

javap -cp log4j-core-2.5.jar -verbose org.apache.logging.log4j.core.Logger | findstr major
anre
fonte
1
Isso também funciona no linux (exceto que você usaria grep em vez de findstr, é claro) #
506 Michael Rusch
1
Esses comandos entregam a versão de destino da JVM, não a javacversão que compilou os arquivos .class, que foi solicitado.
Marquês de Lorne
15

O compilador Java ( javac) não cria jars, converte arquivos Java em arquivos de classe. A ferramenta Jar ( jar) cria os frascos reais. Se nenhum manifesto customizado foi especificado, o manifesto padrão especificará qual versão do JDK foi usada para criar o jar.

jackrabbit
fonte
3
Embora seja verdade, isso não fornece uma resposta para a pergunta.
Zb226
2
@ zb226 AFAIK a versão do compilador usado para compilar as classes não pode ser obtida a partir dos arquivos de classe, apenas a versão de que as classes foram compiladas para . As informações que forneci fornecem as únicas informações (padrão) disponíveis sobre qual versão construiu o artefato. Se não é isso que está sendo solicitado, a pergunta deve ser reformulada.
perfil
OK, eu estava olhando para que a partir de um ponto de vista pragmático, mas eu vejo o seu ponto agora, downvote removido :)
zb226
9

Como eu precisava analisar os frascos gordos, estava interessado na versão de cada classe individual em um arquivo jar. Portanto, adotei a abordagem de Joe Liversedge https://stackoverflow.com/a/27877215/1497139 e a combinei com a tabela de versão do número da classe https://stackoverflow.com/a/3313839/1497139 da classe de J. J. Liszewski para criar um script bash jarv para mostrar as versões de todos os arquivos de classe em um arquivo jar.

uso

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

Exemplo

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

Jarv do script Bash

#!/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
fonte
1
Ótimo roteiro! Consegui verificar as versões java dos frascos.
ARK
Muito obrigado por compartilhar o script! Apenas uma dica do meu lado. head -1pode ser usado em conjunto com o script: geralmente, é suficiente ver a versão da primeira classe no arquivo jar.
Sergey Brunov
7

você pode encontrar a versão do compilador Java nos arquivos .class usando um Editor Hex.

Etapa 1: extrair arquivos .class do arquivo jar usando um extrator zip

passo 2: abra o arquivo .class com um editor hexadecimal. (Eu usei o plug-in do editor hexadecimal do notepad ++. Este plug-in lê o arquivo como binário e mostra-o em hexadecimal) Você pode ver abaixo. insira a descrição da imagem aqui

Os índices 6 e 7 fornecem o número da versão principal do formato de arquivo de classe que está sendo usado. 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 (0x2F hex),

JDK 1.2 = 46 (0x2E hex),

JDK 1.1 = 45 (0x2D hex).

cahit beyaz
fonte
4

Você pode informar a versão binária do Java inspecionando os primeiros 8 bytes (ou usando um aplicativo que pode).

O próprio compilador não insere, de acordo com o meu conhecimento, nenhuma assinatura de identificação. Não consigo identificar isso no formato de classe de especificação de arquivo VM .

McDowell
fonte
Esses comandos entregam a versão de destino da JVM, não a javacversão que compilou os arquivos .class, que foi solicitado.
Marquês de Lorne
4

O código postado por Owen pode fornecer as informações mencionadas por várias das outras respostas aqui:

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

Veja também este e este site. Acabei modificando o código Mind Products rapidamente para verificar para que cada uma das minhas dependências foi compilada.

JasonPlutext
fonte
Esses comandos entregam a versão de destino da JVM, não a javacversão que compilou os arquivos .class, que foi solicitado.
Marquês de Lorne
3

Os desenvolvedores e administradores que executam o Bash podem achar úteis estas funções de conveniência:

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

Você pode colá-los para uso único ou adicioná-los a ~/.bash_aliasesou ~/.bashrc. Os resultados são parecidos com:

$ 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.

EDITAR Como o jackrabbit aponta, você não pode 100% confiar no manifesto para lhe dizer algo útil. Se fosse, você poderia retirá-lo em seu shell UNIX favorito com 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 não tem nada útil no manifesto sobre as classes contidas.

Joe Liversedge
fonte
Esses comandos entregam a versão de destino da JVM, não a javacversão que compilou os arquivos .class, que foi solicitado.
Marquês de Lorne
3

Um forro (Linux)

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

Isso imprime o conteúdo do MANIFEST.MFarquivo no stdout (espero que exista um no seu arquivo jar :)

Dependendo do que construiu seu pacote, você encontrará a versão Created-Byou Build-Jdkchave do JDK .

Neal Xiong
fonte
3
Observe que não há obrigação de esses campos estarem presentes MANIFEST.MF, assim como não há obrigação de os valores estarem corretos (Sim, uma vez encontrei uma .jaronde o valor era falso).
Zb226 13/10/19
Esses comandos entregam a versão de destino da JVM, não a javacversão que compilou os arquivos .class, que foi solicitado.
Marquês de Lorne
2

Cada arquivo de classe possui um número de versão incorporado ao nível do código de bytes que a JVM usa para verificar se gosta ou não desse pedaço de código de bytes específico. Isso é 48 para Java 1.4, 49 para Java 1.5 e 50 para Java 6.

Existem muitos compiladores que podem gerar código de byte em cada nível, o javac usa a opção "-target" para indicar qual nível de código de byte gerar e o javac Java 6 pode gerar código de byte para pelo menos 1.4, 1.5 e 6. Eu não acredito que o compilador insere qualquer coisa que possa identificar o próprio compilador, o que eu acho que você pede. Além disso, o compilador Eclipse está sendo cada vez mais utilizado, pois é um jar único que pode ser executado apenas com o JRE.

Em um arquivo jar geralmente há muitas classes, e cada uma delas é independente; portanto, você precisa investigar todas as classes na jar para ter certeza sobre as características do conteúdo.

Thorbjørn Ravn Andersen
fonte
1

Seguindo a resposta de @David J. Liszewski, executei os seguintes comandos para extrair o manifesto do arquivo jar no 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
fonte
2
Ou, como uma unzip -p LiceneSearch.jar META-INF/MANIFEST.MF
frase
Esses comandos entregam a versão de destino da JVM, não a javacversão que compilou os arquivos .class, que foi solicitado.
Marquês de Lorne
1

Muitas vezes, você pode estar vendo arquivos jar inteiros ou arquivos war que contêm muitos arquivos jar além deles mesmos.

Como não queria verificar manualmente cada classe, escrevi um programa java para fazer isso:

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

Embora isso não diga com o que a classe foi compilada, ela determina com que JDKs serão capazes de CARREGAR as classes, o que provavelmente é o que você deseja começar.

Nthalk
fonte
1

Para expandir as respostas de Jonathon Faust e McDowell : Se você usa odum sistema baseado em * nix, pode usar (um dos primeiros programas Unix 1, que deve estar disponível em praticamente qualquer lugar) para consultar o .classarquivo em nível binário:

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

Esta saída vontade os valores inteiros conhecidos, por exemplo, 50para Java 5, 51por Java 6e assim por diante.

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

zb226
fonte
Esses comandos entregam a versão de destino da JVM, não a javacversão que compilou os arquivos .class, que foi solicitado.
Marquês de Lorne
1

Também escrevi meu próprio script bash para despejar a versão Java exigida por todos os frascos passados ​​na linha de comando ... O meu é um pouco difícil, mas funciona para mim ;-)

exemplo 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
fonte
1

Você pode fazer isso facilmente na linha de comando usando o seguinte processo:

Se você conhece algum nome de classe no jar, pode usar o seguinte comando:

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

exemplo:

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

Resultado :

    major version: 51

Referência 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 

PS: se você não souber o nome da classe, poderá fazer isso facilmente usando qualquer um dos descompiladores jar ou simplesmente usando o seguinte comando para extrair o arquivo jar:

jar xf myFile.jar
Shweta
fonte
0

Você faz check-in no arquivo Manifest do exemplo do jar:

Versão do manifesto: 1.0 Criado por: 1.6.0 (IBM Corporation)

amoljdv06
fonte
-1

No Windows, faça o seguinte:

  1. Descompacte ou extraia o arquivo JAR usando o comando WinZip / Java JAR.
  2. Arraste e solte um dos arquivos de classe em seu projeto Java Eclipse.
  3. Abra o arquivo da turma.

Agora, o Eclipse mostrará a versão principal e secundária exata.

Kittu
fonte
-1

Eu construo um pequeno script bash (no github) com base na sugestão de Davids usando o filecomando

beardedN5rd
fonte