我如何知道用来构建jar的Java编译器的版本?我有一个jar文件,它可以在三个JDK中的任何一个中构建。我们需要确切地知道哪一个,以便我们证明兼容性。编译器版本是否嵌入在类文件或jar中的某个位置?
Created-By: 1.7.0_13 (Oracle Corporation)
Created-By: Apache Maven
和Build-Jdk: 1.8.0_25
我如何知道用来构建jar的Java编译器的版本?我有一个jar文件,它可以在三个JDK中的任何一个中构建。我们需要确切地知道哪一个,以便我们证明兼容性。编译器版本是否嵌入在类文件或jar中的某个位置?
Created-By: 1.7.0_13 (Oracle Corporation)
Created-By: Apache Maven
和Build-Jdk: 1.8.0_25
Answers:
您不一定无法从JAR文件本身中分辨出来。
下载十六进制编辑器,然后在JAR中打开一个类文件,并查看字节偏移量4到7。内置了版本信息。
http://en.wikipedia.org/wiki/Java_class_file
注意:如以下评论中所述,
这些字节告诉您该类是针对哪个版本编译的,而不是哪个版本对其进行编译。
A jar
仅仅是一个容器。它是文件存档āla tar
。尽管a jar
的META-INF层次结构中可能包含有趣的信息,但它没有义务在其内容中指定类的年份。为此,必须检查class
其中的文件。
正如Peter Lawrey在对原始问题的评论中提到的那样,您不一定知道哪个JDK版本构建了给定class
文件,但是您可以找出class
包含在文件中的字节码类版本jar
。
是的,这有点糟,但是第一步是从中提取一个或多个类jar
。例如:
$ jar xf log4j-1.2.15.jar
在Linux,Mac OS X或装有Cygwin的 Windows上,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版本:
file
没有显示该内容,但是我可以使用以下命令手动检查该类:hexdump ~/bin/classes/P.class | head
。只需查看第八个字节并转换为十进制即可。
JAR=something.jar ; unzip -p $JAR `unzip -l $JAR | grep '\.class$' | head -1` | file -
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
在其magicfiles数据库中只有第一个匹配项而没有-k
。
这是Java查找此信息的方法。
Windows:javap -v <class> | findstr major
Unix:javap -v <class> | grep major
例如:
> javap -v Application | findstr major
major version: 51
<class>
参数?
Application.class
文件,事实证明该文件是为Java 7(major version: 51
)编译的。
javac
编译.class文件的版本。
无需解压缩JAR(如果其中一个类名已知或已被查找,例如使用7zip),那么在Windows上,以下内容就足够了:
javap -cp log4j-core-2.5.jar -verbose org.apache.logging.log4j.core.Logger | findstr major
javac
编译.class文件的版本。
Java编译器(javac
)不构建jar,而是将Java文件转换为类文件。Jar工具(jar
)创建实际的jar。如果未指定自定义清单,则默认清单将指定用于创建jar的JDK版本。
由于我需要分析胖罐子,因此我对jar文件中每个类的版本感兴趣。因此,我采用了Joe Liversedge的方法 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
head -1
可以与脚本结合使用:通常,足以在jar文件中看到第一类的版本。
您可以使用十六进制编辑器从.class文件中找到Java编译器版本。
步骤1:使用zip提取程序从jar文件中提取.class文件
步骤2:使用十六进制编辑器打开.class文件。(我使用过notepad ++十六进制编辑器插件。此插件将文件读取为二进制文件,并以十六进制显示)。
索引6和7给出了所使用的类文件格式的主要版本号。 https://zh.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(16x 0x33),
Java SE 6.0 = 50(16x 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)。
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
}
}
javac
编译.class文件的版本。
运行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.
编辑
正如jackrabbit指出的那样,您不能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在有关所包含类的清单中没有任何有用的信息。
javac
编译.class文件的版本。
每个类文件都为字节码级别嵌入了一个版本号,JVM使用该版本号来查看它是否喜欢特定的字节码块。Java 1.4为48,Java 1.5为49,Java 6为50。
存在许多可以在每个级别生成字节代码的编译器,javac使用“ -target”选项指示要生成哪个字节代码级别,而Java 6 javac可以生成至少1.4、1.5和6的字节代码。我没有相信编译器会插入任何可以识别编译器本身的东西,这正是我想您要的。Eclipse编译器也越来越多地被使用,因为它是一个只能与JRE一起运行的jar。
在jar文件中,通常有很多类,并且每个类都是独立的,因此您需要研究jar中的所有类以确保内容的特征。
继@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
unzip -p LiceneSearch.jar META-INF/MANIFEST.MF
javac
编译.class文件的版本。
很多时候,您可能会查看整个jar文件,或者查看war文件,这些文件除了自身之外还包含许多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可以加载该类,这可能就是您想开始的。
要扩展Jonathon Faust和McDowell的答案:如果您使用的是基于* nix的系统,则可以使用od
(最早的Unix程序1之一,该程序几乎在任何地方都可以使用)以.class
二进制级别查询文件:
od -An -j7 -N1 -t dC SomeClassFile.class
这将输出熟悉的整数值,例如50
for Java 5
,51
for Java 6
等。
javac
编译.class文件的版本。
我还编写了自己的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
#!/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文件来轻松实现此目的:
jar xf myFile.jar
我使用命令基于Davids的建议构建了一个bash脚本(在github上)file