我如何知道哪个版本的Java编译器被用来构建一个jar?我有一个jar文件,它可以构建在三个jdk中的任何一个中。我们需要确切知道是哪一个,这样我们才能证明兼容性。编译器版本是否嵌入到类文件或jar中?


当前回答

你可以在命令行上使用以下过程轻松完成:

如果你知道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文件 : myFile.jar

其他回答

基于现有的答案,我试图构建一些更方便的东西,应该在大多数操作系统上工作(这取决于cat, xxd和awk),你只需要把你的类文件名从你的jar中提取出来。

cat YOUR_FILE.class | xxd -s 7 -l1 | awk  '{print $2}' | sed -e 's/34/Java SE 8/' -e 's/35/Java SE 9/' -e 's/36/Java SE 10/' -e 's/37/Java SE 11/' -e 's/38/Java SE 12/' -e 's/39/Java SE 13/' -e 's/3a/Java SE 14/' -e 's/3b/Java SE 15/' -e 's/3c/Java SE 16/' -e 's/3d/Java SE 17/' -e 's/3e/Java SE 18/'

方便的映射到人类可读的版本适用于Java 8开始的主要版本。(否则你会得到主版本的普通十六进制版本号,参见1中的链接。对于一个定义)

这个小脚本是基于什么:

我使用类文件的内容,并使用xxd获取第7个字节。 根据定义,这是用于主要版本的,参见维基百科: https://en.wikipedia.org/wiki/Java_class_file 然后我只是用一个列表代替所有版本值开始的十六进制34 java 8和结束的十六进制3e java 18。

根据@David J. Liszewski的回答,我运行以下命令在Ubuntu上提取jar文件的清单:

# 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

因为我需要分析fat jar,所以我对jar文件中每个单独类的版本感兴趣。因此我采取了乔·利弗塞奇的方法 https://stackoverflow.com/a/27877215/1497139,并将其与David J. Liszewski的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
...

Bash脚本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 

罐子只是一个容器。它是一个文件归档文件(ā la tar或zip)。虽然jar可能在其META-INF层次结构中包含有趣的信息,但它没有义务指定其内容中的类的年份。为此,必须检查其中的类文件。

正如Peter Lawrey在对原始问题的评论中提到的,您不一定知道哪个JDK版本构建了给定的类文件,但您可以找到jar中包含的类文件的字节码类版本。

是的,这有点糟糕,但第一步是从罐子中提取一个或多个类。例如:

$ jar xf log4j-1.2.15.jar

在安装了Cygwin的Linux、Mac OS X或Windows上,file(1)命令知道类版本。

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

或者,使用JDK中的javap作为@jikes。Thunderbolt恰当地指出:

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

对于没有文件或grep的Windows环境

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

FWIW,我同意javap会告诉一个给定的类文件比最初的问题更多的信息。

总之,一个不同的类版本,例如:

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

下面的列表显示了类版本的主版本号和引入类主版本的JDK版本。

注意:类版本不一定标识用于编译类的JDK -它只标识可能生成该类的最早版本。

例如,类主版本52可以由Java 7之后的任何JDK生成。

45.3 = Java 1.1 46 = Java 1.2 47 = Java 1.3 48 = java1.4 49 = Java 5 50 = Java 6 51 = Java 7 52 = Java 8 53 = Java 9 54 = Java 10 55 = Java 11 56 = Java 12 57 = Java 13 58 = Java 14 59 = Java 15 60 = Java 16 61 = Java 17 62 = Java 18 63 = Java 19

每个类文件都嵌入了字节代码级别的版本号,JVM使用该版本号来查看它是否喜欢特定的字节代码块。Java 1.4是48,Java 1.5是49,Java 6是50。

有许多编译器可以在每个级别生成字节代码,javac使用“-target”选项来指示要生成哪个字节代码级别,Java 6 javac可以生成至少1.4、1.5和6级的字节代码。我不相信编译器插入任何可以识别编译器本身,这是我认为你要求的。Eclipse编译器也越来越多地被使用,因为它是一个单独的jar,只能与JRE一起运行。

在jar文件中通常有许多类,并且每个类都是独立的,因此您需要研究jar中的所有类,以确定内容的特征。