什么版本的javac构建了我的jar?


212

我如何知道用来构建jar的Java编译器的版本?我有一个jar文件,它可以在三个JDK中的任何一个中构建。我们需要确切地知道哪一个,以便我们证明兼容性。编译器版本是否嵌入在类文件或jar中的某个位置?


8
您可以通过查看清单文件来告知主要版本。您可以通过查看类文件本身来判断目标版本,但是JDK可以使用-target选项为Java的早期版本生成类文件,因此查看前一个字节可能并不准确。
彼得·劳里

15
在MANIFEST.MF中,您可以找到Created-By: 1.7.0_13 (Oracle Corporation)
hko19 2013年

1
喜欢的Maven 3的外观做到这一点Created-By: Apache MavenBuild-Jdk: 1.8.0_25
尼尔熊

Answers:


89

您不一定无法从JAR文件本身中分辨出来。

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

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

注意:如以下评论中所述,

这些字节告诉您该类是针对哪个版本编译的,而不是哪个版本对其进行编译。


41
令人讨厌的是,这些字节告诉您该类是为FOR编译的版本,而不是编译该版本的版本。Java允许您编译代码,使其与Java的早期版本兼容。但是,这仅适用于字节码和格式。例如,它将愉快地将引用JDK 6库的代码编译为JDK 5格式。在JDK 5将加载类,但随着JDK 5图书馆没有从JDK 6中引用的代码不能运行它
威尔哈同

6
我们可以在清单文件中找到它为Created-By:1.7.0_21-b11(Oracle公司)
克里希纳,

8
另一个答案告诉您如何通过命令行轻松检查
mgarciaisaia 2014年

313

A jar仅仅是一个容器。它是文件存档āla tar。尽管a jarMETA-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

或者,可以使用javapJDK中的@ jikes.thunderbolt恰当地指出:

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

并且如果您被降级到Windows没有任何环境filegrep

> 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“
Dan Haynes 2014年

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在其magicfiles数据库中只有第一个匹配项而没有-k
2015年

1
使用“ javap -p <class> | grep major”并不可靠。如果pom.xml的源/目标为1.7,则无论您使用JDK 1.7,JDK 1.8还是JDK 1.9进行编译,javap都会始终为您提供“主要版本:51”。
user2569618

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>参数?
DIMS

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)不构建jar,而是将Java文件转换为类文件。Jar工具(jar)创建实际的jar。如果未指定自定义清单,则默认清单将指定用于创建jar的JDK版本。


3
虽然是正确的,但这并不能为问题提供答案。
zb226

2
@ zb226 AFAIK的用于编译的类编译器的版本无法从类文件中,只有版本的类被编译得到。我提供的信息仅提供有关构建人工制品的版本的(标准)信息。如果不是要问的问题,则应改写该问题。
jackrabbit

好吧,我从务实的角度来看这件事,但是现在我明白了您的观点,删除了downvote :)
zb226

9

由于我需要分析胖罐子,因此我对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 

1
很棒的脚本!我能够检查jar的Java版本。
ARK

非常感谢您分享脚本!只是我身边的一个提示。head -1可以与脚本结合使用:通常,足以在jar文件中看到第一类的版本。
谢尔盖·布鲁诺夫

7

您可以使用十六进制编辑器从.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)。



4

Owen发布代码可以告诉您以下其他答案中提到的信息:

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 Products代码,以检查为每个依赖项编译的内容。


这些命令提供目标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.

编辑 正如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在有关所包含类的清单中没有任何有用的信息。


这些命令提供目标JVM版本,而不是要求的javac编译.class文件的版本。
罗恩侯爵

3

一线(Linux)

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

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

根据构建软件包的方式,您将在Created-ByBuild-Jdk键中找到JDK版本。


3
请注意,这些字段中没有义务存在MANIFEST.MF,也没有义务使值正确(是的,我曾经遇到过一个.jar伪造的值)。
zb226

这些命令提供目标JVM版本,而不是要求的javac编译.class文件的版本。
罗恩侯爵

2

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


1

继@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

2
或者,作为unzip -p LiceneSearch.jar META-INF/MANIFEST.MF
单线

这些命令提供目标JVM版本,而不是要求的javac编译.class文件的版本。
罗恩侯爵

1

很多时候,您可能会查看整个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可以加载该类,这可能就是您想开始的。


1

要扩展Jonathon FaustMcDowell的答案:如果您使用的是基于* nix的系统,则可以使用od(最早的Unix程序1之一,该程序几乎在任何地方都可以使用)以.class二进制级别查询文件:

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

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

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


这些命令提供目标JVM版本,而不是要求的javac编译.class文件的版本。
罗恩侯爵

1

我还编写了自己的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

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

您签入jar示例的清单文件:

清单版本:1.0创建者:1.6.0(IBM Corporation)


-1

在Windows上,请执行以下操作:

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

现在,Eclipse将显示确切的主要和次要版本。


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.