public static long getObjectSize(Object obj) throws UnsupportedOperationException {
    // JDK versions 16 or later enforce strong encapsulation and block illegal reflective access.
    // In effect, we cannot calculate object size by deep reflection and invoking `setAccessible` on a field,
    // especially when the `isAccessible` is false. More details in JEP 403. While integrating Hudi with other
    // software packages that compile against JDK 16 or later (e.g. Trino), the IllegalAccessException will be thrown.
    // In that case, we use Java Object Layout (JOL) to estimate the object size.
    //
    // NOTE: We cannot get the object size base on the amount of byte serialized because there is no guarantee
    //       that the incoming object is serializable. We could have used Java's Instrumentation API, but it
    //       needs an instrumentation agent that can be hooked to the JVM. In lieu of that, we are using JOL.
    //       GraphLayout gives the deep size of an object, including the size of objects that are referenced from the given object.
    return obj == null ? 0 : GraphLayout.parseInstance(obj).totalSize();
  }
@Test
  public void testGetObjectSize() {
    EmptyClass emptyClass = new EmptyClass();
    StringClass stringClass = new StringClass();
    PayloadClass payloadClass = new PayloadClass();
    String emptyString = "";
    String string = "hello";
    String[] stringArray = {emptyString, string, " world"};
    String[] anotherStringArray = new String[100];
    List<String> stringList = new ArrayList<>();
    StringBuilder stringBuilder = new StringBuilder(100);
    int maxIntPrimitive = Integer.MAX_VALUE;
    int minIntPrimitive = Integer.MIN_VALUE;
    Integer maxInteger = Integer.MAX_VALUE;
    Integer minInteger = Integer.MIN_VALUE;
    long zeroLong = 0L;
    double zeroDouble = 0.0;
    boolean booleanField = true;
    Object object = new Object();
    String name = "Alice Bob";
    Person person = new Person(name);

    if (getJavaVersion() == 11 || getJavaVersion() == 17) {
      assertEquals(48, getObjectSize(string));
      assertEquals(168, getObjectSize(stringArray));
      assertEquals(144, getObjectSize(stringBuilder));
      assertEquals(72, getObjectSize(DayOfWeek.TUESDAY));
      assertEquals(HoodieAvroUtils.gteqAvro1_9() ? 1256 : 1176,
          getObjectSize(Schema.create(Schema.Type.STRING)));
      assertEquals(96, getObjectSize(person));
    } else {
      assertEquals(56, getObjectSize(string));
      assertEquals(184, getObjectSize(stringArray));
      assertEquals(240, getObjectSize(stringBuilder));
      assertEquals(80, getObjectSize(DayOfWeek.TUESDAY));
      // Since avro 1.9, Schema use ConcurrentHashMap instead of LinkedHashMap to
      // implement props, which will change the size of the object.
      assertEquals(HoodieAvroUtils.gteqAvro1_9() ? 1320 : 1240,
          getObjectSize(Schema.create(Schema.Type.STRING)));
      assertEquals(104, getObjectSize(person));
    }

    assertEquals(40, getObjectSize(emptyString));
    assertEquals(416, getObjectSize(anotherStringArray));
    assertEquals(40, getObjectSize(stringList));
    assertEquals(16, getObjectSize(maxIntPrimitive));
    assertEquals(16, getObjectSize(minIntPrimitive));
    assertEquals(16, getObjectSize(maxInteger));
    assertEquals(16, getObjectSize(minInteger));
    assertEquals(24, getObjectSize(zeroLong));
    assertEquals(24, getObjectSize(zeroDouble));
    assertEquals(16, getObjectSize(booleanField));
    assertEquals(16, getObjectSize(object));
    assertEquals(32, getObjectSize(emptyClass));
    assertEquals(40, getObjectSize(stringClass));
    assertEquals(40, getObjectSize(payloadClass));
  }