Яка версія javac побудувала мою банку?


212

Як я можу сказати, яку версію компілятора Java використовували для складання баночки? У мене є jar файл, і він міг бути вбудований в будь-який з трьох JDK. Нам потрібно точно знати, який саме, щоб ми могли підтвердити сумісність. Чи вбудована версія компілятора десь у файлах класу чи банку?


8
Ви можете сказати про головний версон, переглянувши файл маніфесту. Ви можете вказати цільову версію, переглянувши самі файли класу, проте JDK можуть створювати файли класів для більш ранніх версій Java за допомогою параметра -target, тому перегляд перших байтів може бути неточним.
Пітер Лорі

15
У MANIFEST.MF ви можете знайти щось на зразокCreated-By: 1.7.0_13 (Oracle Corporation)
hko19

1
Схоже, Maven 3 робить це Created-By: Apache MavenіBuild-Jdk: 1.8.0_25
Ніл Ксьонг

Будь ласка , перевірте це: stackoverflow.com/questions/27065 / ...
Abheek Датта

Відповіді:


89

Ви не можете сказати з самого файлу JAR, обов'язково.

Завантажте шістнадцятковий редактор та відкрийте один із файлів класу всередині JAR та перегляньте зміщення байтів 4 - 7. Інформація про версію вбудована.

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

Примітка. Як зазначено в коментарі нижче,

ці байти говорять про те, для якої версії був складений клас FOR, а не з якої версії.


41
Щоб бути педантичними, ці байти підказують, для якої версії був складений клас FOR, а не з якої версії. Java дозволяє компілювати код таким чином, щоб вони були сумісні з більш ранніми версіями Java. Однак це стосується лише байт-коду та формату. Наприклад, він з радістю складе код, який посилається на бібліотеки JDK 6 у формат JDK 5. JDK 5 завантажить клас, але не може запустити його, оскільки у бібліотеці JDK 5 немає коду, на який посилається JDK 6.
Буде Хартунг

6
Ми можемо знайти його у файлі маніфесту як Created-By: 1.7.0_21-b11 (Oracle Corporation)
Крішна

8
Інша відповідь розповідає, як легко перевірити командний рядок
mgarciaisaia

313

А jar- просто контейнер. Це файловий архів ā la tar. Хоча jarможе бути цікава інформація, що міститься в її ієрархії META-INF , вона не зобов'язана вказувати вінтаж класів у її вмісті. Для цього треба вивчити classфайли в них.

Як згадував Пітер Лорі в коментарі до початкового запитання, ви не завжди можете знати, який випуск JDK створив даний classфайл, але ви можете дізнатися версію байтового коду classфайлу, що міститься в jar.

Так, це щось відстійно, але перший крок - витягнути один або кілька класів із jar. Наприклад:

$ jar xf log4j-1.2.15.jar

У Linux, Mac OS X або Windows із встановленим Cygwin команда file (1) знає версію класу.

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

Або в якості альтернативи, використовуючи javapвід JDK як @ jikes.thunderbolt, влучно вказує:

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

І якщо ви потрапили в Windowsоточення без жодного fileабоgrep

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

FWIW, я погоджуюся, що javapрозповім набагато більше про певний classфайл, ніж задано оригінальне запитання.

У будь-якому випадку, інша версія класу, наприклад:

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

Основне число версій класу відповідає наступним версіям 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

7
Моя версія fileне показали, але я був в змозі перевірити клас вручну з допомогою цієї команди: hexdump ~/bin/classes/P.class | head. Просто подивіться восьмий байт і перетворіть у десятковий.
Jarett Millard

2
«Файл» FYI, схоже, залежить від версії Java щодо того, чи відображатиметься також версія JDK. У CentOS / Oracle Linux і Java 6, складеному класом, я отримую "складені дані класу Java, версія 50.0 (Java 1.6)", але коли я запускаю їх у класі, компільованому з Java 7, я просто отримую загальну версію ", зібрані дані класу Java, версія 51.0 "
Дан Хейнс

1
JAR=something.jar ; unzip -p $JAR `unzip -l $JAR | grep '\.class$' | head -1` | file -
Рендалл Вітмен

3
@JarettMillard мій cygwin file file.class(5.22-1) спочатку не показував ціль класу java: "file.class: [architecture = 6909806] [architecture = 6845039]". Однак file -k file.classце зробив так: "file.class: [architecture = 6909806] [architecture = 6845039] зібрав дані класу Java, версія 50.0 (Java 1.6)". Схоже, fileотримав лише перший матч у своїх магічних файлах db без -k.
дим

1
Використання "javap -p <class> | grep major" не є надійним. Якщо у pom.xml джерело / ціль становить 1,7, javap завжди надасть вам "основну версію: 51", незалежно від того, для компіляції ви використовуєте JDK 1.7, JDK 1.8 або JDK 1.9.
користувач2569618

54

Ось спосіб Java знайти цю інформацію.

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

Наприклад:
> javap -v Application | findstr major   major version: 51


3
З усіх наведених відповідей ваша відповідь є найбільш стислим і не передбачає написання 10 рядків коду, щоб знати інформацію про версію. +1 для цього
Thirumalai Parthasarathi

2
Що таке <class>параметр?
Дімс

1
@Dims Файл ".class", який ви намагаєтесь знайти версію. У його прикладі у нього є Application.classфайл, який виявився складеним для Java 7 ( major version: 51).
inanutshellus

1
Ці команди доставляють цільову версію JVM, а не javacверсію, яка компілювала файли .class, про що і вимагали.
Маркіз Лорн

15

Не потрібно розпаковувати JAR (якщо одне з назв класів відоме або шукається, наприклад, використовуючи 7zip), тому для Windows достатньо буде наступного:

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

1
Це працює і на Linux (крім ви, звичайно, використовуєте grep замість findstr, звичайно)
Michael Rusch

1
Ці команди доставляють цільову версію JVM, а не javacверсію, яка компілювала файли .class, про що і вимагали.
Маркіз Лорн

15

Компілятор Java ( javac) не створює банки, він перетворює файли Java у файли класу. Інструмент Jar ( jar) створює фактичні банки. Якщо не вказано спеціальний маніфест, маніфест за замовчуванням визначатиме, яку версію JDK використовували для створення jar.


3
Це правда, але це не дає відповіді на питання.
zb226

2
@ Zb226 AFAIK версія компілятора використовується для компіляції класів не може бути отримана з файлів класів, тільки версія , що класи були складені для . Інформація, яку я дав, надає єдину (стандартну) інформацію про те, в якій версії був створений артефакт. Якщо це не те, що задають, питання слід переформулювати.
jackrabbit

Гаразд, я дивився на це з прагматичної точки зору, але зараз я бачу вашу думку, знищено питання :)
zb226

9

Оскільки мені потрібно було проаналізувати жирові банки, мене зацікавила версія кожного окремого класу в баночному файлі. Тому я взяв підхід Джо Ліверседж https://stackoverflow.com/a/27877215/1497139 і поєднав його з https://stackoverflow.com/a/3313839/1497139 таблицею версій класу Девіда Дж. Лішевського, щоб створити скрипт bash jarv, щоб показати версії всіх файлів класу у файлі jar.

використання

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

Приклад

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

#!/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
Чудовий сценарій! Мені вдалося перевірити версії java.
ARK

Дуже дякую за те, що поділилися сценарієм! Просто натяк з мого боку. head -1може використовуватися разом із сценарієм: зазвичай достатньо побачити версію першого класу у файлі jar.
Сергій Брунов

7

ви можете знайти версію компілятора Java з файлів .class за допомогою Hex Editor.

Крок 1: Витягніть файли .class з файлу jar за допомогою екстрактора

крок 2: відкрийте файл .class з шестигранним редактором (я використав блокнот для блокнота ++ шестигранного редактора. Цей плагін читає файл як двійковий і показує його в шістнадцятковій формі) Ви можете бачити нижче. введіть тут опис зображення

В індексах 6 і 7 наведено основний номер версії формату файлу класу, який використовується. https://en.wikipedia.org/wiki/Java_class_file

Java SE 11 = 55 (шістнадцятковий 0x37)

Java SE 10 = 54 (шістнадцятковий розмір 0x36)

Java SE 9 = 53 (шістнадцятковий 0x35)

Java SE 8 = 52 (шістнадцятковий розмір 0x34),

Java SE 7 = 51 (шістнадцятковий 0x33),

Java SE 6.0 = 50 (шістнадцятковий 0x32),

Java SE 5.0 = 49 (шістнадцятковий 0x31),

JDK 1,4 = 48 (шістнадцятковий 0x30),

JDK 1,3 = 47 (шістнадцятковий 0x2F),

JDK 1,2 = 46 (шістнадцятковий 0x2E),

JDK 1,1 = 45 (шістнадцятковий 0x2D).


4

Ви можете повідомити про двійкову версію Java, ознайомившись з першими 8 байтами (або скориставшись програмою, яка вміє).

Сам компілятор, наскільки мені відомо, не вставляє ідентифікаційного підпису. Я не можу помітити щось у форматі VM специфікації класу VM .


Ці команди доставляють цільову версію JVM, а не javacверсію, яка компілювала файли .class, про що і вимагали.
Маркіз Лорн

4

Код відправлений Оуен може сказати вам інформацію , зазначену в ряді інших відповідей тут:

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

Дивіться також це і цей сайт. Я швидко змінив код продукту Mind, щоб перевірити, для чого складена кожна моя залежність.


Ці команди доставляють цільову версію JVM, а не javacверсію, яка компілювала файли .class, про що і вимагали.
Маркіз Лорн

3

Розробники та адміністратори, на яких працює Bash, можуть вважати корисні такі функції зручності:

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

Ви можете вставити їх для одноразового використання або додати їх до ~/.bash_aliasesабо ~/.bashrc. Результати виглядають приблизно так:

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

і

$ 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 Як вказує jackrabbit , ви не можете на 100% розраховувати на маніфест, щоб сказати вам щось корисне. Якщо це було, то ви можете витягнути його в улюбленій оболонці UNIX за допомогою 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

Цей .jar не містить нічого корисного в маніфесті про вміщені класи.


Ці команди доставляють цільову версію JVM, а не javacверсію, яка компілювала файли .class, про що і вимагали.
Маркіз Лорн

3

Один вкладиш (Linux)

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

Це друкує вміст MANIFEST.MFфайлу до stdout (сподіваємось, він є у вашому файлі jar :)

Залежно від того, що побудувало ваш пакет, ви знайдете версію JDK Created-Byабо Build-Jdkключ.


3
Зауважте, що для цих полів немає ніяких зобов'язань MANIFEST.MF, а також немає обов'язків щодо правильності значень (Так, я колись стикався з тим, .jarде значення було помилковим).
zb226

Ці команди доставляють цільову версію JVM, а не javacверсію, яка компілювала файли .class, про що і вимагали.
Маркіз Лорн

2

У кожному файлі класу є вбудований номер версії для рівня байтового коду, який використовується JVM, щоб побачити, чи подобається він конкретному коду байтового коду чи ні. Це 48 для Java 1.4, 49 для Java 1.5 та 50 для Java 6.

Існує багато компіляторів, які можуть генерувати байт-код на кожному рівні, javac використовує параметр "-target", щоб вказати, який рівень байтового коду генерувати, а Java 6 javac може генерувати байт-код принаймні 1,4, 1,5 і 6. Я не вважаю, що компілятор вставляє все, що може ідентифікувати сам компілятор, і це те, що я думаю, що ви просите. Також все частіше використовується компілятор Eclipse, оскільки це єдина jar, яка може працювати лише з JRE.

У файлі jar зазвичай є багато класів, і кожен з них є незалежним, тому потрібно досліджувати всі класи в банці, щоб бути впевненим у характеристиках вмісту.


1

Слідкуючи за відповіддю @David J. Liszewski, я запустив такі команди, щоб витягнути маніфест файлу jar на 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
Або, як unzip -p LiceneSearch.jar META-INF/MANIFEST.MF
однолінійний

Ці команди доставляють цільову версію JVM, а не javacверсію, яка компілювала файли .class, про що і вимагали.
Маркіз Лорн

1

Ви багато разів можете переглядати цілі файли jar або військові файли, які крім себе містять багато файлів jar.

Оскільки я не хотів перевіряти кожен клас, я написав програму java для цього:

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

Хоча це не говорить про те, з чого був складений клас, він визначає, з чого JDK зможе завантажувати класи, що, мабуть, ви хотіли почати.


1

Щоб розширити відповіді Джонатана Фауста та МакДауелла : Якщо ви працюєте на системі * nix, ви можете скористатися od(однією з найдавніших програм Unix 1, яка повинна бути доступна практично скрізь) для запиту .classфайлу на бінарному рівні:

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

Це виведе знайомі цілі значення, наприклад, 50для Java 5, 51для Java 6тощо.

1 Цитата з https://en.wikipedia.org/wiki/Od_(Unix)


Ці команди доставляють цільову версію JVM, а не javacверсію, яка компілювала файли .class, про що і вимагали.
Маркіз Лорн

1

Я також написав власний скрипт bash, щоб скинути версію Java, необхідну всім банкам, що передаються в командному рядку ... Шахта трохи груба, але працює для мене ;-)

Приклад використання

$ 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

Ви можете легко зробити це в командному рядку, використовуючи наступний процес:

Якщо ви знаєте будь-яке ім'я класу в jar, ви можете використовувати таку команду:

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

приклад:

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

Вихід:

    major version: 51

Короткий довідник:

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: якщо ви не знаєте жодного з назв класу, ви можете легко зробити це за допомогою будь-якого з декомпіляторів jar або просто за допомогою наступної команди для вилучення файлу jar:

jar xf myFile.jar

0

Ви перевірте у файлі Manifest файл прикладу банку:

Версія маніфесту: 1.0 Створено: 1.6.0 (IBM Corporation)


-1

У Windows виконайте такі дії:

  1. Розпакуйте або витягніть файл JAR за допомогою команди WinZip / Java JAR.
  2. Перетягніть один із класових файлів у проект Java Eclipse.
  3. Відкрийте файл класу.

Тепер Eclipse покаже точну основну та другорядну версію.


Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.