读取和显示Java .class版本的工具

128

你们中是否有人知道一种工具,可以搜索.class文件并显示它们的编译版本?

我知道你可以在十六进制编辑器中逐个查看它们,但我有很多要检查的class文件(我的巨型应用程序中有某些东西以某种原因编译为Java6)。


1
更受欢迎的重复问题 https://dev59.com/KXNA5IYBdhLWcg3wF5uO 在答案中提到了一些未在此处提及的方便工具。 - Vadzim
11个回答

153

使用随JDK一起提供的javap工具。使用-verbose选项可打印类文件的版本号。

> javap -verbose MyClass
Compiled from "MyClass.java"
public class MyClass
  SourceFile: "MyClass.java"
  minor version: 0
  major version: 46
...

仅显示版本:

WINDOWS> javap -verbose MyClass | find "version"
LINUX  > javap -verbose MyClass | grep version

5
这段内容是 JDK/JavaSE 的版本号对应表,其中“Version major.minor”表示版本号,“JDK/JavaSE”表示对应的 JDK 或 JavaSE 版本。具体对应关系如下:45.3 对应 JDK1.1,46.0 对应 JDK1.2,47.0 对应 JDK1.3,48.0 对应 JDK1.4,49.0 对应 JavaSE5(1.5),50.0 对应 JavaSE6(1.6),51.0 对应 JavaSE7(1.7),52.0 对应 JavaSE8(1.8),53.0 对应 JavaSE9,54.0 对应 JavaSE10,55.0 对应 JavaSE11,56.0 对应 JavaSE12,57.0 对应 JavaSE13,58.0 对应 JavaSE14。 - nephewtom
od比javap快三个数量级,因此在脚本编写时请使用od。https://dev59.com/cHVD5IYBdhLWcg3wTJrF#25201428 - user1133275

50

很容易读取类文件签名并获取这些值而无需第三方API。您只需要读取前8个字节即可。

ClassFile {
    u4 magic;
    u2 minor_version;
    u2 major_version;

对于类文件版本51.0(Java 7),开头的字节为:
CA FE BA BE 00 00 00 33

...其中0xCAFEBABE是魔数,0x0000是小版本号,0x0033是大版本号。

import java.io.*;

public class Demo {
  public static void main(String[] args) throws IOException {
    ClassLoader loader = Demo.class.getClassLoader();
    try (InputStream in = loader.getResourceAsStream("Demo.class");
        DataInputStream data = new DataInputStream(in)) {
      if (0xCAFEBABE != data.readInt()) {
        throw new IOException("invalid header");
      }
      int minor = data.readUnsignedShort();
      int major = data.readUnsignedShort();
      System.out.println(major + "." + minor);
    }
  }
}

浏览目录(文件)和归档文件(JarFile)以查找类文件是微不足道的。

Oracle公司的Joe Darcy博客列出了Java 7之前的类版本与JDK版本的映射关系

Target   Major.minor Hex
1.1      45.3        0x2D
1.2      46.0        0x2E
1.3      47.0        0x2F
1.4      48.0        0x30
5 (1.5)  49.0        0x31
6 (1.6)  50.0        0x32
7 (1.7)  51.0        0x33
8 (1.8)  52.0        0x34
9        53.0        0x35

还要记住,只有在启动Java时启用了assert,它才会运行,因此如果您没有使用IllegalArgumentException(例如),则可能会读取垃圾文件。 - jontejj

24

在类Unix操作系统上,运行命令 file /path/to/Thing.class 可以查看文件类型和版本信息。以下是输出结果示例:

已编译的Java类数据,版本号为49.0


请提供原文作为参考。 - phunehehe
这比其他解决方案要简单得多。 - mmuller

9
如果您使用的是Unix系统,您可以直接执行以下操作:
find /target-folder -name \*.class | xargs file | grep "version 50\.0"

我的文件版本显示为“编译的Java类数据,版本50.0”,适用于Java6类。


在 macOS(至少是10.12.6版本),输出结果会更加有用:`file *.class`产生的结果是:`ClassName.class: 编译后的Java类数据,版本为50.0(Java 1.6)` - Gary

6

又一个Java版本检查

od -t d -j 7 -N 1 ApplicationContextProvider.class | head -1 | awk '{print "Java", $2 - 44}'

5
在Eclipse中,如果您没有附加源代码,请注意在附加源代码按钮后的第一行。
// 编译自CDestinoLog.java(版本1.5:49.0,超级位) enter image description here

4

将第8个字节转换为十进制:

类Unix系统: hexdump -s 7 -n 1 -e '"%d"' Main.class

Windows系统: busybox.exe hexdump -s 7 -n 1 -e '"%d"' Main.class

输出示例:

55

说明:

  • -s 7 偏移量为7
  • -n 1 限制为1
  • -e '"%d"' 转换为十进制输出

版本映射:

JDK 1.1 = 45 (0x2D hex)
JDK 1.2 = 46 (0x2E hex)
JDK 1.3 = 47 (0x2F hex)
JDK 1.4 = 48 (0x30 hex)
Java SE 5.0 = 49 (0x31 hex)
Java SE 6.0 = 50 (0x32 hex)
Java SE 7 = 51 (0x33 hex)
Java SE 8 = 52 (0x34 hex)
Java SE 9 = 53 (0x35 hex)
Java SE 10 = 54 (0x36 hex)
Java SE 11 = 55 (0x37 hex)
Java SE 12 = 56 (0x38 hex)
Java SE 13 = 57 (0x39 hex)
Java SE 14 = 58 (0x3A hex)
Java SE 15 = 59 (0x3B hex)
Java SE 16 = 60 (0x3C hex)
Java SE 17 = 61 (0x3D hex)

3
也许这对某些人有帮助。看起来有更简单的方法来获取用于编译/构建 .class 文件的Java版本。该方法对于应用程序/类自我检查JAVA版本非常有用。
我已经浏览了JDK库并找到了这个有用的常量:com.sun.deploy.config.BuiltInProperties.CURRENT_VERSION。我不知道它从什么时候开始在JAVA JDK中出现。
尝试使用几个版本常量的此代码,我得到以下结果:
System.out.println("JAVA DEV       ver.: " + com.sun.deploy.config.BuiltInProperties.CURRENT_VERSION);
System.out.println("JAVA RUN     v. X.Y: " + System.getProperty("java.specification.version") );
System.out.println("JAVA RUN v. W.X.Y.Z: " + com.sun.deploy.config.Config.getJavaVersion() ); //_javaVersionProperty
System.out.println("JAVA RUN  full ver.: " + System.getProperty("java.runtime.version")  + " (may return unknown)" );
System.out.println("JAVA RUN       type: " + com.sun.deploy.config.Config.getJavaRuntimeNameProperty() );

输出:

JAVA DEV       ver.: 1.8.0_77
JAVA RUN     v. X.Y: 1.8
JAVA RUN v. W.X.Y.Z: 1.8.0_91
JAVA RUN  full ver.: 1.8.0_91-b14 (may return unknown)
JAVA RUN       type: Java(TM) SE Runtime Environment

在类字节码中,实际上存储了常量 - 参见Main.call中标记为红色的部分- 常量存储在.class字节码中

常量用于检查JAVA版本是否过时(请参见Java如何检查是否过时)......


2
一种基于Java的解决方案,使用 魔数 版本。程序本身使用它来检测其字节码版本。
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.codec.DecoderException;
import org.apache.commons.codec.binary.Hex;
import org.apache.commons.io.IOUtils;
public class Main {
    public static void main(String[] args) throws DecoderException, IOException {
        Class clazz = Main.class;
        Map<String,String> versionMapping = new HashMap();
        versionMapping.put("002D","1.1");
        versionMapping.put("002E","1.2");
        versionMapping.put("002F","1.3");
        versionMapping.put("0030","1.4");
        versionMapping.put("0031","5.0");
        versionMapping.put("0032","6.0");
        versionMapping.put("0033","7");
        versionMapping.put("0034","8");
        versionMapping.put("0035","9");
        versionMapping.put("0036","10");
        versionMapping.put("0037","11");
        versionMapping.put("0038","12");
        versionMapping.put("0039","13");
        versionMapping.put("003A","14");

        InputStream stream = clazz.getClassLoader()
            .getResourceAsStream(clazz.getName().replace(".", "/") + ".class");
        byte[] classBytes = IOUtils.toByteArray(stream);
        String versionInHexString = 
            Hex.encodeHexString(new byte[]{classBytes[6],classBytes[7]});
        System.out.println("bytecode version: "+versionMapping.get(versionInHexString));
    }
}

0

另一个可以检查类是否使用预览功能编译的参考:

    public final class JavaClassVersion {
        private final int major;
        private final int minor;

        private JavaClassVersion(int major, int minor) {
            this.major = major;
            this.minor = minor;
        }

        public int major() {
            return major;
        }

        public int minor() {
            return minor;
        }

        public static JavaClassVersion of(Path artifactPath) {
            try (InputStream in = Files.newInputStream(artifactPath);
                    DataInputStream data = new DataInputStream(in)) {
                if (0xCAFEBABE != data.readInt()) {
                    throw new IOException("invalid header");
                }
                int minor = data.readUnsignedShort();
                int major = data.readUnsignedShort();
                return new JavaClassVersion(major, minor);
            } catch (IOException ex) {
                throw new UncheckedIOException(ex);
            }
        }

        String getJavaVersion() {
            switch (major) {
                case 50:
                    return "1.6";
                case 51:
                    return "1.7";
                case 52:
                    return "1.8";
                case 53:
                    return "9";
                case 54:
                    return "10";
                case 55:
                    return "11";
                case 56:
                    return "12";
                case 57:
                    return "13";
                case 58:
                    return "14";
                case 59:
                    return "15";
                case 60:
                    return "16";
                case 61:
                    return "17";
                case 62:
                    return "18";
                case 63:
                    return "19";
                case 64:
                    return "20";
                case 65:
                    return "21";
                default:
                    return "";
            }
        }

        boolean isPreview() {
            return minor == 65535;
        }
    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接