1.4.13 根据正文中的假设分别给出表示以下数据类型的一个对象所需的内存量:

类型定义的核心代码如下:

public class Accumulator {
    private double m;
    private double s;
    private int N;
}

public class Transaction {
    private final String who;
    private final SmartDate when;
    private final double amount;
}

public class FixedCapacityStackOfStrings {
    private String[] a;
    private int N;
}

public final class Point2D implements Comparable<Point2D> {

    /**
     * Compares two points by x-coordinate.
     */
    public static final Comparator<Point2D> X_ORDER = new XOrder();

    /**
     * Compares two points by y-coordinate.
     */
    public static final Comparator<Point2D> Y_ORDER = new YOrder();

    /**
     * Compares two points by polar radius.
     */
    public static final Comparator<Point2D> R_ORDER = new ROrder();

    private final double x;    // x coordinate
    private final double y;    // y coordinate
}

public class Interval1D {

    /**
     * Compares two intervals by min endpoint.
     */
    public static final Comparator<Interval1D> MIN_ENDPOINT_ORDER  = new MinEndpointComparator();

    /**
     * Compares two intervals by max endpoint.
     */
    public static final Comparator<Interval1D> MAX_ENDPOINT_ORDER = new MaxEndpointComparator();

    /**
     * Compares two intervals by length.
     */
    public static final Comparator<Interval1D> LENGTH_ORDER = new LengthComparator();

    private final double min;
    private final double max;
}

public class Interval2D {
    private final Interval1D x;
    private final Interval1D y;
}
  • a. Accumulator:

  • b. Transaction:

  • c. FixedCapacityStackOfStrings, 其容量为 C, 且有N个元素.

  • d. Point2D

  • e. Interval1D

  • f. Interval2D

  • g: Double

先自己基于文中数据估算(初版答案, 可能不对. 请以最后的答案为准):

先整理下正文中提到的需要用到的 内存 知识:

  • 原始数据类型的常见内存(用 字节 来表示)

    • boolean: 1
    • byte: 1
    • char: 2
    • int: 4
    • float: 4
    • long: 8
    • double: 8
  • Java 对象:
    • 本身的开销: 一般是 16 字节.
    • 其他对像的引用: 8 字节.
    • 填充为 8字节 的倍数.
    • 如果有 嵌套 的非静态类(即 内部类), 需要额外的 8 字节,来存储对该内部类的引用.
    • 然后, 还需要补充加上: 引用的其他对像占用的内存情况.(也可以不补. 只统计引用. 看场景和目的.)
  • 数组:
    • 24字节的头信息.
      • 16字节的对象自己的开销.
      • 4字节,用于保存数组长度.
      • 4 填充字节.
    • 保存值所需的内存.
    • 如果保存的是对应的引用, 还需要再额外加上被保存的对象自身的 内存 开销.
  • 长度为 N 的 String 对象, 需要字节: 40 + 24 * 2N:
    • 40字节, 为 String 对象本身的内存占用.
      • 16字节, 对象本身.
      • 三个int实例各4字节.
      • 字符数组引用的 8 字节.
      • 4个填充字节.
    • 24 + 2N. 字符数组的内存占用. (一般不统计字符本身的内存. 因为有字符数据共享机制. 取决于具体实现)

我的初版答案:

  • a. Accumulator: 40字节.
    • 16字节, 对象自身.
    • 2个double, 共 16字节.
    • 1个 int, 共4字节.
    • 补齐为 8 的倍数, 需要 4 字节.
  • b. Transaction: 40字节.
    • 对象自身: 16
    • String 对象的引用: 8
    • SmartDate 对象的引用: 8
    • 1个 double: 8
    • 补齐为8的倍数: 0
  • c. FixedCapacityStackOfStrings, 其容量为 C, 且有N个元素: 48 + 8 * C + 40 * N.
    • 对象自身: 16
    • 1个 int: 4
    • 容量为 C, 且有N个元素: 24 + 8 * C + 40 * N
      • 数组头信息: 24字节
      • 容量为C,即需要预留 C 个引用的存储: 8 * C
      • N 个 String元素: 40 * N
    • 补齐为8的倍数: 4
  • d. Point2D: 56字节.
    • 对象自身: 16
    • 3 个 对象的引用: 3 * 8
    • 2 个 double: 2 * 8
    • 补齐为8的倍数: 0
  • e. Interval1D: 56字节.
    • 对象自身: 16
    • 3 个 对象的引用: 3 * 8
    • 2 个 double: 2* 8
    • 补齐为8的倍数: 0
  • f. Interval2D: 32 字节.
    • 对象自身: 16
    • 2 个 对象的引用: 2 * 8
    • 补齐为8的倍数: 0
  • g: Double: 24字节.
    • 对象自身: 16
    • 1个dobule: 8
    • 补齐为8的倍数: 0

再看看 GPT4 的回复:

先分析下两次 GPT4 回复的特点:

  • 第一次回复:
    • 关于 c 的回复, 让我意识到自己漏掉了对数组本身的引用.
    • d 和 e 的回复.存疑. 明显和我给定的类型对不上.
    • 考虑到了对象的 对齐填充 特性.
  • 第二次回复:
    • a 和 c 的回复, 不对.没有考虑到 对齐填充.
    • 关于de和e的回复, 指出了一个书中没有的知识细节: 静态变量并不被每个实例对象单独保存.
    • 最后的总结中, 提到了部分 VM 参数可以修改 对象引用的字节数.

从GPT的回复, 延申出的两个疑问:

  • 8字节引用, 最大可管理多大的空间?

1 字节等于 2^(-30) GB ,那么,2^64 字节就等于 (2^64) * (2^-30) GB = 2^(64-30) GB = 2^34 GB = 2^24 TB. 约 1700万 TB.

相关: 如果是 引用大小为4字节, 则最大可管理的内存空间为: 2^2 GB = 4GB

  • 是否是一定会 对齐?

总的来说,虽然在许多情况下,“对象内存对齐为 8 的倍数”这是事实,但这并非一成不变的规则,具体实现会根据 JVM 的设计和版本有所差异。

基于 GPT4 的3次回复, 做出我的最新题解(c, d, e有修改):

  • a. Accumulator: 40字节.
    • 16字节, 对象自身.
    • 2个double, 共 16字节.
    • 1个 int, 共4字节.
    • 补齐为 8 的倍数, 需要 4 字节.
  • b. Transaction: 40字节.
    • 对象自身: 16
    • String 对象的引用: 8
    • SmartDate 对象的引用: 8
    • 1个 double: 8
    • 补齐为8的倍数: 0
  • c. FixedCapacityStackOfStrings, 其容量为 C, 且有N个元素: 56 + 8 * C + 40 * N.
    • 对象自身: 16
    • 对数组对象的直接引用: 8
    • 1个 int: 4
    • 容量为 C, 且有N个元素: 24 + 8 * C + 40 * N
      • 数组头信息: 24字节
      • 容量为C,即需要预留 C 个引用的存储: 8 * C
      • N 个 String元素: 40 * N
    • 补齐为8的倍数: 4
  • d. Point2D: 32字节.
    • 对象自身: 16
    • 2 个 double: 2 * 8
    • 补齐为8的倍数: 0
  • e. Interval1D: 32字节.
    • 对象自身: 16
    • 2 个 double: 2 * 8
    • 补齐为8的倍数: 0
  • f. Interval2D: 32 字节.
    • 对象自身: 16
    • 2 个 对象的引用: 2 * 8
    • 补齐为8的倍数: 0
  • g: Double: 24字节.
    • 对象自身: 16
    • 1个dobule: 8
    • 补齐为8的倍数: 0

其他:

  • 看正文时, 不用担心会漏知识点. 因为真正重要的地方, 必然有专门的针对性的练习. ==> 当然: 前提是你找到了一本 “好” 书!

  • 1 bytes(字节) == 8 bits(位). 有些文章或者标准, 喜欢直接用 bit(位) 来描述数据, 需要看清数据 单位.

  • 第一次对 Java 对象内存估算, 有了如此清晰深刻的理解.顺便其他数据的大致内存估算要考虑的因素有了一定的认知. 目前不再那么畏惧估算内存占用了.

GPT 回答附图:

  • 第1次回答:

1413_0

  • 第2次回答:

1413_1

  • 第3次回答:

1413_2

参考: