哪个版本的javac构建了我的jar?

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

235920 次浏览

您不一定能从JAR文件本身判断出来。

下载一个十六进制编辑器,打开JAR中的一个类文件,查看字节偏移量4到7。版本信息是内置的。

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

注:如以下评论所述,

这些字节告诉你类被编译为什么版本,而不是

您可以通过检查前8个字节(或使用可以的应用程序)来告诉Java二进制版本。

据我所知,编译器本身并没有插入任何标识签名。无论如何,我不能在文件虚拟机规格类格式中发现这样的东西。

Java编译器(javac)不构建jar,它将Java文件转换为类文件。Jar工具(jar)创建实际的Jar。如果没有指定自定义清单,默认清单将指定使用哪个版本的JDK来创建jar。

每个类文件都嵌入了字节代码级别的版本号,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中的所有类,以确定内容的特征。

jar只是一个容器。它是一个文件存档,名为tarzip。虽然jar在其meta - inf层次结构中可能包含有趣的信息,但它没有义务在其内容中指定类的年份。为此,必须检查其中的class文件。

正如在原问题的注释中提到的彼得Lawrey,你不一定知道哪个JDK版本构建了给定的class文件,但你可以找到jar中包含的class文件的字节码类版本。

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

$ jar xf log4j-1.2.15.jar

在安装了Cygwin的Linux、Mac OS X或Windows上,文件(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

对于没有filegrepWindows环境

> 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

下面的列表显示了类版本的主版本号和引入类主版本的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

欧文发布的代码可以告诉你这里一些其他答案提到的信息:

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

参见 site。最后,我迅速修改了Mind Products代码,以检查我的每个依赖项是为了什么而编译的。

根据@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

在Windows上执行以下操作:

  1. 使用WinZip / Java JAR命令解压或解压缩JAR文件。
  2. 将其中一个类文件拖放到Eclipse Java项目中。
  3. 打开类文件。

现在Eclipse将显示准确的主版本和次版本。

下面是Java查找这些信息的方法。

< p >窗口:javap -v <class> | findstr major
Unix: javap -v <class> | grep major < / p >

例如:
<代码>比;javap -v Application | findstr major 大版本:51

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

<强>编辑 正如长耳大野兔指出的那样,你不能100%依赖清单来告诉你任何有用的东西。如果是,那么你可以在你最喜欢的UNIX shell中使用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在清单中没有任何关于所包含类的有用信息。

我基于david的建议使用file命令构建了一个小bash脚本(在github)

一行程序(Linux)

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

这将把MANIFEST.MF文件的内容打印到stdout(希望在你的jar文件中有一个:)

根据构建包的内容,你会在Created-ByBuild-Jdk键中找到JDK版本。

很多时候,您可能会查看整个jar文件,或者除了它们本身之外还包含许多jar文件的war文件。

因为我不想手动检查每个类,所以我写了一个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能够加载这些类,这可能是您想要开始的。

不需要解包JAR(如果其中一个类名已知或被查找,例如使用7zip),因此在Windows上,以下步骤就足够了:

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

为了扩展乔纳森浮士德的麦克道尔的的答案:如果你在一个基于*nix的系统上,你可以使用od(最早的Unix程序之一__abc4,它应该几乎在任何地方都可用)在二进制级别上查询.class文件:

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

这将输出熟悉的整数值,例如50表示Java 551表示Java 6,等等。

1引用自https://en.wikipedia.org/wiki/Od_ (Unix)

你检入jar的Manifest文件 例子:< / p > < p > Manifest-Version: 1.0 创建对象:1.6.0 (IBM Corporation)

. xml
因为我需要分析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

你可以使用十六进制编辑器从.class文件中找到Java编译器版本。

< p >步骤1: 使用zip解压器

从jar文件中提取.class文件

步骤2:用十六进制编辑器打开。class文件。(我已经使用notepad++十六进制编辑器插件。这个插件读取文件为二进制并显示为十六进制) 如下图所示。 enter image description here < / p >

索引6和7给出了所使用的类文件格式的主要版本号。 https://en.wikipedia.org/wiki/Java_class_file < / p >

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十六进制)。

我也写了自己的bash脚本来转储所有在命令行传递的jar所需的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

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

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

IntelliJ用户

  • 安装插件Archive Browser
  • 在Intellij中打开Jar通过从文件浏览器中拖拽jar文件,您可以简单地将jar添加到任何项目中。

  • 浏览jar文件找到一个类文件,IntelliJ的反编译器应该显示用于创建jar的Java版本。

IntelliJ显示java版本

基于现有的答案,我试图建立一个更方便的东西,应该在大多数操作系统上工作(这取决于catxxdawk),你只需要把你的类文件名从你的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 < / p >
  1. 然后我只是用一个列表代替所有版本值开始的十六进制34 java 8和结束的十六进制3e java 18。