为什么要implements Serializable? JVM中如何用jmap查看堆?

发布于:2024-04-26 ⋅ 阅读:(20) ⋅ 点赞:(0)

例子

这里定义了一个User的序列化对象:

image.png

这里做个写入文件的测试,也就是尝试序列化对象:

@RestController
public class TestController {

    @GetMapping("/write")
    public static void writeObj() {
        User user = new User("sc", 1);
        try {
            ObjectOutputStream objectOutputStream = new ObjectOutputStream(Files.newOutputStream(Paths.get("/Users/suncong/Desktop/user.txt")));
            objectOutputStream.writeObject(user);
            objectOutputStream.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @GetMapping("/read")
    public void readObj(){
        try {
            ObjectInputStream objectInputStream = new ObjectInputStream(Files.newInputStream(Paths.get("/Users/suncong/Desktop/user.txt")));
            try {
                Object object = objectInputStream.readObject();
                User user = (User) object;
                System.out.println(user);
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

触发Write方法,我们可以看到user.txt存储的是一对二进制文件

image.png

触发Read请求,从序列化中读出来了:

image.png

去掉implements

触发Read 报错 反序列化非法

image.png

改变serialVersionUID

可以很清楚的看到报错:

image.png

这样就有个严重的问题了,? 不同的后端反序列化同一个对象的话 指定的ID不一样怎么办 serialVersionUID是Java中用于序列化的一个重要属性,用于标识序列化对象的版本号。在序列化和反序列化过程中,Java虚拟机会根据serialVersionUID来确定对象的版本信息,从而保证对象的正确性。

在Java中,serialVersionUID的写法有两种:

  1. 显式声明serialVersionUID变量

    显式声明serialVersionUID变量是最常用的方式,可以通过在类中定义一个名为serialVersionUID的静态变量来指定序列化版本号。例如:

    public class MyClass implements Serializable {
        private static final long serialVersionUID = 123456789L;
        // ...
    }
    

    在这个例子中,我们通过定义一个名为serialVersionUID的静态变量,并赋予一个固定值来指定序列化版本号。这个值可以是任何长整型数字,通常建议使用随机数或时间戳等唯一标识符。

  2. 自动生成serialVersionUID变量

    如果一个类没有显式声明serialVersionUID变量,则Java虚拟机会自动为它生成一个序列化版本号。这个自动生成的版本号是根据类的结构和成员变量等信息计算得出的,因此可以保证在不同的JVM上生成相同的值。例如:

    public class MyClass implements Serializable {
        // ...
    }
    

    在这个例子中,我们没有显式声明serialVersionUID变量,因此Java虚拟机会自动为它生成一个版本号。如果需要查看自动生成的版本号,可以使用serialver命令行工具(例如:serialver MyClass)来获取。

  3. 用JSON或者XML格式的 更好的兼容的

public class MyClass {
    private int id;
    private String name;

    // getters and setters

    public static void main(String[] args) {
        MyClass obj = new MyClass();
        obj.setId(1);
        obj.setName("Alice");

        Gson gson = new Gson();
        String json = gson.toJson(obj);
        System.out.println(json); // {"id":1,"name":"Alice"}
    }
}

jmap查看

注意,在开始前(包括本机jvm命令),确保两点:

1.如果你用的macOS 会稍微有点麻烦,详情看这个博客:

  1. 如果你的IDEA用的版本和本机装的版本不一致 记得将IDEA的更换成本地的才可以,要不然会报错JVM版本不对,即使两个jdk8都不行,jdk8后面的版本号要一致

开始:

jmap + pid

使用不带选项参数的jmap打印共享对象映射,将会打印目标虚拟机中加载的每个共享对象的起始地址、映射大小以及共享对象文件的路径全称

image.png

jmap -heap pid

打印一个堆的摘要信息,包括使用的GC算法、堆配置信息和各内存区域内存使用信息 image.png

jmap -histo:live pid

存活的对象 我这里有两千多 我就截短图了 image.png

jmap -clstats pid

打印类加载器信息 image.png

jmap -dump:format=b,file=heapdump.phrof pid

以hprof二进制格式转储Java堆到指定filename的文件中。live子选项是可选的。如果指定了live子选项,堆中只有活动的对象会被转储。想要浏览heap dump,你可以使用jhat(Java堆分析工具)读取生成的文件。

这个命令执行,JVM会将整个heap的信息dump写入到一个文件,heap如果比较大的话,就会导致这个过程比较耗时,并且执行的过程中为了保证dump的信息是可靠的,所以会暂停应用, 线上系统慎用。