【问题标题】:What version of javac built my jar?什么版本的 javac 构建了我的 jar?
【发布时间】:2011-03-19 19:37:01
【问题描述】:

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

【问题讨论】:

  • 您可以通过查看清单文件来判断主要版本。您可以通过查看类文件本身来判断目标版本,但是 JDK 可以使用 -target 选项为早期版本的 java 生成类文件,因此查看第一个字节可能不准确。
  • 在 MANIFEST.MF 你可以找到类似Created-By: 1.7.0_13 (Oracle Corporation)
  • 看起来 Maven 3 这样做了 Created-By: Apache MavenBuild-Jdk: 1.8.0_25

标签: java jar


【解决方案1】:

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

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

是的,这有点糟糕,但第一步是从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

对于没有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

类版本主号对应以下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
  • 54 = Java 10
  • 55 = Java 11
  • 56 = Java 12
  • 57 = Java 13
  • 58 = Java 14

【讨论】:

  • 我的file 版本没有显示,但我可以使用以下命令手动检查课程:hexdump ~/bin/classes/P.class | head。只需查看第八个字节并转换为十进制。
  • FYI 'file' 似乎取决于 Java 版本,它是否也会显示 JDK 版本。在 CentOS/Oracle Linux 和 Java 6 编译类上,我得到“编译的 Java 类数据,版本 50.0 (Java 1.6)”,但是当我在用 Java 7 编译的类上运行它时,我只得到通用版本“编译的 Java 类数据,版本 51.0"
  • JAR=something.jar ; unzip -p $JAR `unzip -l $JAR | grep '\.class$' | head -1` | file -
  • @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 在没有-k 的情况下只在其magicfiles db 中获得了第一个匹配项。
  • 使用“javap -p | grep major”是不可靠的。如果 pom.xml 的 source/target 为 1.7,无论您使用 JDK 1.7、JDK 1.8 还是 JDK 1.9 编译,javap 都会始终为您提供“主要版本:51”。
【解决方案2】:

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

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

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

注意:正如下面评论中提到的,

那些字节告诉你这个类编译的版本是什么,而不是 什么版本编译的。

【讨论】:

  • 为了迂腐,这些字节告诉您该类已编译的版本,而不是编译它的版本。 Java 允许您编译代码,以便它们与早期版本的 Java 兼容。但是,这只适用于字节码和格式。例如,它会愉快地将引用 JDK 6 库的代码编译成 JDK 5 格式。 JDK 5 将加载该类,但无法运行它,因为 JDK 5 库没有从 JDK 6 引用的代码。
  • 我们可以在清单文件中找到它作为 Created-By: 1.7.0_21-b11 (Oracle Corporation)
  • The other answer 告诉如何通过命令行轻松检查
【解决方案3】:

这是 Java 查找此信息的方法。

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

例如:
> javap -v Application | findstr major   major version: 51

【讨论】:

  • 在所有给出的答案中,你的答案是最简洁的,不涉及编写10行代码即可知道版本信息。为此 +1
  • <class> 参数是干什么用的?
  • @Dims 您尝试查找其版本的“.class”文件。在他的示例中,他有一个 Application.class 文件,结果证明它是为 Java 7 (major version: 51) 编译的。
  • 这些命令提供目标 JVM 版本,而不是编译 .class 文件的 javac 版本,这是所要求的。
【解决方案4】:

无需解压 JAR(如果其中一个类名已知或已查找,例如使用 7zip),因此在 Windows 上以下内容就足够了:

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

【讨论】:

  • 这也适用于 linux(当然你会使用 grep 而不是 findstr)
  • 这些命令提供目标 JVM 版本,而不是编译 .class 文件的 javac 版本,这是所要求的。
【解决方案5】:

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

【讨论】:

  • 虽然是真的,但这并不能提供问题的答案。
  • @zb226 AFAIK 无法从类文件中获取用于编译类的编译器版本,只能从类文件中获取编译类的版本for。我提供的信息提供了关于构建工件的版本的唯一(标准)信息。如果这不是所问的,则应重新措辞。
  • 好的,我从实用的角度来看这个,但我现在明白你的意思了,downvote 已删除:)
【解决方案6】:

因为我需要分析胖 jar,所以我对 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 

【讨论】:

  • 好剧本!我能够检查 jar 的 java 版本。
  • 非常感谢您分享脚本!只是我身边的一个提示。 head -1可以和脚本配合使用:一般情况下,看jar文件中第一个类的版本就够了。
【解决方案7】:

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

第 1 步: 使用 zip 提取器从 jar 文件中提取 .class 文件

第 2 步:使用十六进制编辑器打开 .class 文件。(我使用了 notepad++ 十六进制编辑器插件。该插件将文件读取为二进制文件并以十六进制显示) 你可以在下面看到。

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

【讨论】:

    【解决方案8】:

    您可以通过inspecting the first 8 bytes(或使用app)来判断Java 二进制版本。

    据我所知,编译器本身不会插入任何识别签名。反正我在文件VM spec class format 中找不到这样的东西。

    【讨论】:

    • 这些命令提供目标 JVM 版本,而不是编译 .class 文件的 javac 版本,这是所要求的。
    【解决方案9】:

    code posted by 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
        }
    }
    

    另请参阅 thisthis 网站。我最终快速修改了 Mind Products 代码,以检查我的每个依赖项是针对什么编译的。

    【讨论】:

    • 这些命令提供目标 JVM 版本,而不是编译 .class 文件的 javac 版本,这是所要求的。
    【解决方案10】:

    运行 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% 依赖清单来告诉您任何有用的信息。如果是,那么您可以使用unzip 在您最喜欢的 UNIX shell 中将其拉出:

    $ 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 版本,而不是编译 .class 文件的 javac 版本,这是所要求的。
    【解决方案11】:

    一个班轮(Linux)

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

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

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

    【讨论】:

    • 请注意,这些字段没有义务出现在 MANIFEST.MF 中,也没有义务要求值正确(是的,我曾经遇到过 .jar值是假的)。
    • 这些命令提供目标 JVM 版本,而不是编译 .class 文件的 javac 版本,这是所要求的。
    【解决方案12】:

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

    【讨论】:

      【解决方案13】:

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

      【讨论】:

        【解决方案14】:

        您可以使用以下过程在命令行上轻松执行此操作:

        如果你知道 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
        

        【讨论】:

          【解决方案15】:

          IntelliJ 用户

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

          • 浏览 jar 文件以定位类文件,IntelliJ 的反编译器应显示用于创建 jar 的 Java 版本。

          【讨论】:

            【解决方案16】:

            根据@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
            • 这些命令提供目标 JVM 版本,而不是编译 .class 文件的 javac 版本,这是所要求的。
            【解决方案17】:

            扩展 Jonathon Faust'sMcDowell's几乎在任何地方都可用)以在二进制级别上查询 .class 文件:

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

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

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

            【讨论】:

            • 这些命令提供目标 JVM 版本,而不是编译 .class 文件的 javac 版本,这是所要求的。
            【解决方案18】:

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

            【讨论】:

              【解决方案19】:

              你签入 jar 的 Manifest 文件 示例:

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

              【讨论】:

                【解决方案20】:

                在 Windows 上执行以下操作:

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

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

                【讨论】:

                  【解决方案21】:

                  我根据 Davids 的建议使用 file 命令构建了一个小 bash 脚本 (on github)

                  【讨论】:

                    猜你喜欢
                    • 1970-01-01
                    • 2014-03-08
                    • 1970-01-01
                    • 2010-09-14
                    • 1970-01-01
                    • 2016-11-27
                    • 2016-02-26
                    • 2012-09-30
                    • 1970-01-01
                    相关资源
                    最近更新 更多