coderplay
12/30/2012 - 7:38 AM

Direct Memory Tricks

Direct Memory Tricks

$java -XX:+UseCompressedOops DirectMemoryTricky
Detected JVM data model settings of: 64-Bit HotSpot JVM with Compressed OOPs
24
offheapPointer & 0xffffffff00000000L = 139736760975360
pointerOffset: 12, valueOffset:16
Exception in thread "main" java.lang.NullPointerException
  at DirectMemoryTricky.main(DirectMemoryTricky.java:977)

$ java -XX:+UseCompressedOops DirectMemoryTricky
Detected JVM data model settings of: 64-Bit HotSpot JVM with Compressed OOPs
24
offheapPointer & 0xffffffff00000000L = 140647294042112
pointerOffset: 12, valueOffset:16
#
# A fatal error has been detected by the Java Runtime Environment:
#
#  SIGSEGV (0xb) at pc=0x00007feb4101cb53, pid=4374, tid=140648546432768
#
# JRE version: 6.0_37-b06
# Java VM: Java HotSpot(TM) 64-Bit Server VM (20.12-b01 mixed mode linux-amd64 compressed oops)
# Problematic frame:
# j  DirectMemoryTricky.main([Ljava/lang/String;)V+177
#
# An error report file with more information is saved as:
# /home/min/code/java/benchmark/hs_err_pid4374.log
#
# If you would like to submit a bug report, please visit:
#   http://java.sun.com/webapps/bugreport/crash.jsp
#
Aborted (core dumped)


$ java -XX:-UseCompressedOops DirectMemoryTricky
Detected JVM data model settings of: 64-Bit HotSpot JVM
32
offheapPointer & 0xffffffff00000000L = 140067473457152
pointerOffset: 16, valueOffset:24
30
import java.lang.management.GarbageCollectorMXBean;
import java.lang.management.ManagementFactory;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;

import javax.management.MBeanServer;
import javax.management.ObjectName;
import javax.management.openmbean.CompositeData;

import sun.misc.Unsafe;

/**
 * Detects and represents JVM-specific properties that relate to the memory data
 * model for java objects that are useful for size of calculations.
 * 
 * @author jhouse
 * @author Chris Dennis
 */
enum JvmInformation {

  /**
   * Represents HotSpot 32-bit
   */
  HOTSPOT_32_BIT {

    /* default values are for this vm */

    @Override
    public String getJvmDescription() {
      return "32-Bit HotSpot JVM";
    }

    @Override
    public int getPointerSize() {
      return 4;
    }

    @Override
    public int getJavaPointerSize() {
      return 4;
    }
  },

  /**
   * Represents 32-Bit HotSpot JVM with Concurrent Mark-and-Sweep GC
   */
  HOTSPOT_32_BIT_WITH_CONCURRENT_MARK_AND_SWEEP {

    @Override
    public int getMinimumObjectSize() {
      return 16;
    }

    @Override
    public String getJvmDescription() {
      return "32-Bit HotSpot JVM with Concurrent Mark-and-Sweep GC";
    }

    @Override
    public int getPointerSize() {
      return 4;
    }

    @Override
    public int getJavaPointerSize() {
      return 4;
    }
  },

  /**
   * Represents 64-Bit HotSpot JVM
   */
  HOTSPOT_64_BIT {

    @Override
    public int getPointerSize() {
      return 8;
    }

    @Override
    public int getJavaPointerSize() {
      return 8;
    }

    @Override
    public String getJvmDescription() {
      return "64-Bit HotSpot JVM";
    }
  },

  /**
   * Represents 64-Bit HotSpot JVM with Concurrent Mark-and-Sweep GC
   */
  HOTSPOT_64_BIT_WITH_CONCURRENT_MARK_AND_SWEEP {

    @Override
    public int getPointerSize() {
      return 8;
    }

    @Override
    public int getJavaPointerSize() {
      return 8;
    }

    @Override
    public int getMinimumObjectSize() {
      return 24;
    }

    @Override
    public String getJvmDescription() {
      return "64-Bit HotSpot JVM with Concurrent Mark-and-Sweep GC";
    }
  },

  /**
   * Represents 64-Bit HotSpot JVM with Compressed OOPs
   */
  HOTSPOT_64_BIT_WITH_COMPRESSED_OOPS {

    @Override
    public int getPointerSize() {
      return 8;
    }

    @Override
    public int getJavaPointerSize() {
      return 4;
    }

    @Override
    public String getJvmDescription() {
      return "64-Bit HotSpot JVM with Compressed OOPs";
    }
  },

  /**
   * Represents 64-Bit HotSpot JVM with Compressed OOPs and Concurrent
   * Mark-and-Sweep GC
   */
  HOTSPOT_64_BIT_WITH_COMPRESSED_OOPS_AND_CONCURRENT_MARK_AND_SWEEP {

    @Override
    public int getPointerSize() {
      return 8;
    }

    @Override
    public int getJavaPointerSize() {
      return 4;
    }

    @Override
    public int getMinimumObjectSize() {
      return 24;
    }

    @Override
    public String getJvmDescription() {
      return "64-Bit HotSpot JVM with Compressed OOPs and Concurrent Mark-and-Sweep GC";
    }
  },

  /**
   * Represents 32-Bit JRockit JVM"
   */
  JROCKIT_32_BIT {

    @Override
    public int getAgentSizeOfAdjustment() {
      return 8;
    }

    @Override
    public int getFieldOffsetAdjustment() {
      return 8;
    }

    @Override
    public int getObjectHeaderSize() {
      return 16;
    }

    @Override
    public String getJvmDescription() {
      return "32-Bit JRockit JVM";
    }

    @Override
    public boolean supportsReflectionSizeOf() {
      return false;
    }

    @Override
    public int getPointerSize() {
      return 4;
    }

    @Override
    public int getJavaPointerSize() {
      return 4;
    }
  },

  /**
   * Represents 64-Bit JRockit JVM (with no reference compression)
   */
  JROCKIT_64_BIT {

    @Override
    public int getAgentSizeOfAdjustment() {
      return 8;
    }

    @Override
    public int getFieldOffsetAdjustment() {
      return 8;
    }

    @Override
    public int getObjectHeaderSize() {
      return 16;
    }

    @Override
    public String getJvmDescription() {
      return "64-Bit JRockit JVM (with no reference compression)";
    }

    @Override
    public boolean supportsReflectionSizeOf() {
      return false;
    }

    @Override
    public int getPointerSize() {
      return 4;
    }

    @Override
    public int getJavaPointerSize() {
      return 4;
    }
  },

  /**
   * Represents 64-Bit JRockit JVM with 4GB Compressed References
   */
  JROCKIT_64_BIT_WITH_4GB_COMPRESSED_REFS {

    @Override
    public int getAgentSizeOfAdjustment() {
      return 8;
    }

    @Override
    public int getFieldOffsetAdjustment() {
      return 8;
    }

    @Override
    public int getObjectHeaderSize() {
      return 16;
    }

    @Override
    public String getJvmDescription() {
      return "64-Bit JRockit JVM with 4GB Compressed References";
    }

    @Override
    public boolean supportsReflectionSizeOf() {
      return false;
    }

    @Override
    public int getPointerSize() {
      return 4;
    }

    @Override
    public int getJavaPointerSize() {
      return 4;
    }
  },

  /**
   * Represents 64-Bit JRockit JVM with 32GB Compressed References
   */
  JROCKIT_64_BIT_WITH_32GB_COMPRESSED_REFS {

    @Override
    public int getAgentSizeOfAdjustment() {
      return 8;
    }

    @Override
    public int getFieldOffsetAdjustment() {
      return 8;
    }

    @Override
    public int getObjectHeaderSize() {
      return 16;
    }

    @Override
    public String getJvmDescription() {
      return "64-Bit JRockit JVM with 32GB Compressed References";
    }

    @Override
    public boolean supportsReflectionSizeOf() {
      return false;
    }

    @Override
    public int getPointerSize() {
      return 4;
    }

    @Override
    public int getJavaPointerSize() {
      return 4;
    }
  },

  /**
   * Represents 64-Bit JRockit JVM with 64GB Compressed References
   */
  JROCKIT_64_BIT_WITH_64GB_COMPRESSED_REFS {

    @Override
    public int getObjectAlignment() {
      return 16;
    }

    @Override
    public int getAgentSizeOfAdjustment() {
      return 16;
    }

    @Override
    public int getFieldOffsetAdjustment() {
      return 16;
    }

    @Override
    public int getObjectHeaderSize() {
      return 24;
    }

    @Override
    public String getJvmDescription() {
      return "64-Bit JRockit JVM with 64GB Compressed References";
    }

    @Override
    public boolean supportsReflectionSizeOf() {
      return false;
    }

    @Override
    public int getPointerSize() {
      return 4;
    }

    @Override
    public int getJavaPointerSize() {
      return 4;
    }
  },

  /**
   * Represents 64-Bit IBM JVM (with reference compression)
   */
  IBM_64_BIT_WITH_COMPRESSED_REFS {

    @Override
    public int getPointerSize() {
      return 4;
    }

    @Override
    public int getJavaPointerSize() {
      return 4;
    }

    @Override
    public int getObjectHeaderSize() {
      return 16;
    }

    @Override
    public boolean supportsReflectionSizeOf() {
      return false;
    }

    @Override
    public String getJvmDescription() {
      return "IBM 64-Bit JVM with Compressed References";
    }
  },

  /**
   * Represents 64-Bit IBM JVM (with no reference compression)
   */
  IBM_64_BIT {

    @Override
    public int getPointerSize() {
      return 8;
    }

    @Override
    public int getJavaPointerSize() {
      return 8;
    }

    @Override
    public int getObjectHeaderSize() {
      return 24;
    }

    @Override
    public boolean supportsReflectionSizeOf() {
      return false;
    }

    @Override
    public String getJvmDescription() {
      return "IBM 64-Bit JVM (with no reference compression)";
    }
  },

  /**
   * Represents IBM 32-bit
   */
  IBM_32_BIT {

    /* default values are for this vm */

    @Override
    public String getJvmDescription() {
      return "IBM 32-Bit JVM";
    }

    @Override
    public int getPointerSize() {
      return 4;
    }

    @Override
    public int getJavaPointerSize() {
      return 4;
    }

    @Override
    public int getObjectHeaderSize() {
      return 16;
    }

    @Override
    public boolean supportsReflectionSizeOf() {
      return false;
    }
  },

  /**
   * Represents Generic 32-bit
   */
  UNKNOWN_32_BIT {

    /* default values are for this vm */

    @Override
    public String getJvmDescription() {
      return "Unrecognized 32-Bit JVM";
    }

    @Override
    public int getPointerSize() {
      return 4;
    }

    @Override
    public int getJavaPointerSize() {
      return 4;
    }
  },

  /**
   * Represents 64-Bit Generic JVM
   */
  UNKNOWN_64_BIT {

    @Override
    public int getPointerSize() {
      return 8;
    }

    @Override
    public int getJavaPointerSize() {
      return 8;
    }

    @Override
    public String getJvmDescription() {
      return "Unrecognized 64-Bit JVM";
    }
  };

  /**
   * The JvmInformation instance representing the current JVM
   */
  public static final JvmInformation CURRENT_JVM_INFORMATION;

  private static final long THREE_GB = 3L * 1024L * 1024L * 1024L;
  private static final long TWENTY_FIVE_GB = 25L * 1024L * 1024L * 1024L;
  private static final long FIFTY_SEVEN_GB = 57L * 1024L * 1024L * 1024L;

  static {
    CURRENT_JVM_INFORMATION = getJvmInformation();
    System.out.println("Detected JVM data model settings of: "
        + CURRENT_JVM_INFORMATION.getJvmDescription());
  }

  /**
   * Size of a pointer in bytes on this runtime
   */
  public abstract int getPointerSize();

  /**
   * Size of a java pointer in bytes on this runtime (that differs when
   * compressedOops are being used)
   */
  public abstract int getJavaPointerSize();

  /**
   * Minimal size an object will occupy on the heap in bytes.
   */
  public int getMinimumObjectSize() {
    return getObjectAlignment();
  }

  /**
   * Object alignment / padding in bytes
   */
  public int getObjectAlignment() {
    return 8;
  }

  /**
   * The size of an object header in bytes.
   */
  public int getObjectHeaderSize() {
    return getPointerSize() + getJavaPointerSize();
  }

  /**
   * The size of the jvm-specific field offset adjustment in bytes.
   */
  public int getFieldOffsetAdjustment() {
    return 0;
  }

  /**
   * The size of the jvm-specific agent result adjustment in bytes.
   */
  public int getAgentSizeOfAdjustment() {
    return 0;
  }

  /**
   * Whether the jvm can support AgentSizeOf implementation.
   */
  public boolean supportsAgentSizeOf() {
    return true;
  }

  /**
   * Whether the jvm can support UnsafeSizeOf implementation.
   */
  public boolean supportsUnsafeSizeOf() {
    return true;
  }

  /**
   * Whether the jvm can support ReflectionSizeOf implementation.
   */
  public boolean supportsReflectionSizeOf() {
    return true;
  }

  /**
   * A human-readable description of the JVM and its relevant enabled options.Os
   */
  public abstract String getJvmDescription();

  /**
   * Determine the JvmInformation for the current JVM.
   */
  private static JvmInformation getJvmInformation() {
    JvmInformation jif = null;

    jif = detectHotSpot();

    if (jif == null) {
      jif = detectJRockit();
    }
    if (jif == null) {
      jif = detectIBM();
    }

    if (jif == null && is64Bit()) {
      // unknown 64-bit JVMs
      jif = UNKNOWN_64_BIT;
    } else if (jif == null) {
      // unknown 32-bit JVMs
      jif = UNKNOWN_32_BIT;
    }

    return jif;
  }

  private static JvmInformation detectHotSpot() {
    JvmInformation jif = null;

    if (isHotspot()) {
      if (is64Bit()) {
        if (isHotspotCompressedOops() && isHotspotConcurrentMarkSweepGC()) {
          jif =
              HOTSPOT_64_BIT_WITH_COMPRESSED_OOPS_AND_CONCURRENT_MARK_AND_SWEEP;
        } else if (isHotspotCompressedOops()) {
          jif = HOTSPOT_64_BIT_WITH_COMPRESSED_OOPS;
        } else if (isHotspotConcurrentMarkSweepGC()) {
          jif = HOTSPOT_64_BIT_WITH_CONCURRENT_MARK_AND_SWEEP;
        } else {
          jif = HOTSPOT_64_BIT;
        }
      } else {
        jif = HOTSPOT_32_BIT;
      }
    }

    return jif;
  }

  private static JvmInformation detectJRockit() {
    JvmInformation jif = null;

    if (isJRockit()) {
      if (is64Bit()) {
        if (isJRockit4GBCompression()) {
          jif = JROCKIT_64_BIT_WITH_4GB_COMPRESSED_REFS;
        } else if (isJRockit32GBCompression()) {
          jif = JROCKIT_64_BIT_WITH_32GB_COMPRESSED_REFS;
        } else if (isJRockit64GBCompression()) {
          jif = JROCKIT_64_BIT_WITH_64GB_COMPRESSED_REFS;
        } else {
          jif = JROCKIT_64_BIT;
        }
      } else {
        jif = JROCKIT_32_BIT;
      }
    }

    return jif;
  }

  private static JvmInformation detectIBM() {
    JvmInformation jif = null;

    if (isIBM()) {
      if (is64Bit()) {
        if (isIBMCompressedRefs()) {
          jif = IBM_64_BIT_WITH_COMPRESSED_REFS;
        } else {
          jif = IBM_64_BIT;
        }
      } else {
        jif = IBM_32_BIT;
      }
    }

    return jif;
  }

  private static boolean isJRockit32GBCompression() {

    if (getJRockitVmArgs().contains("-XXcompressedRefs:enable=false")) {
      return false;
    }
    if (getJRockitVmArgs().contains("-XXcompressedRefs:size=64GB")
        || getJRockitVmArgs().contains("-XXcompressedRefs:size=4GB")) {
      return false;
    }

    if (getJRockitVmArgs().contains("-XXcompressedRefs:size=32GB")) {
      return true;
    }
    if (Runtime.getRuntime().maxMemory() > THREE_GB
        && Runtime.getRuntime().maxMemory() <= TWENTY_FIVE_GB
        && getJRockitVmArgs().contains("-XXcompressedRefs:enable=true")) {
      return true;
    }

    return false;
  }

  private static boolean isJRockit64GBCompression() {
    if (getJRockitVmArgs().contains("-XXcompressedRefs:enable=false")) {
      return false;
    }
    if (getJRockitVmArgs().contains("-XXcompressedRefs:size=4GB")
        || getJRockitVmArgs().contains("-XXcompressedRefs:size=32GB")) {
      return false;
    }

    if (getJRockitVmArgs().contains("-XXcompressedRefs:size=64GB")) {
      return true;
    }
    if (Runtime.getRuntime().maxMemory() > TWENTY_FIVE_GB
        && Runtime.getRuntime().maxMemory() <= FIFTY_SEVEN_GB
        && getJRockitVmArgs().contains("-XXcompressedRefs:enable=true")) {
      return true;
    }

    return false;
  }

  private static boolean isJRockit4GBCompression() {
    if (getJRockitVmArgs().contains("-XXcompressedRefs:enable=false")) {
      return false;
    }
    if (getJRockitVmArgs().contains("-XXcompressedRefs:size=64GB")
        || getJRockitVmArgs().contains("-XXcompressedRefs:size=32GB")) {
      return false;
    }

    if (getJRockitVmArgs().contains("-XXcompressedRefs:size=4GB")) {
      return true;
    }
    if (Runtime.getRuntime().maxMemory() <= THREE_GB) {
      return true;
    }

    return false;
  }

  /**
   * Returns true if VM is JRockit
   * 
   * @return true, if JRockit
   */
  public static boolean isJRockit() {
    return System.getProperty("jrockit.version") != null
        || System.getProperty("java.vm.name", "").toLowerCase()
            .indexOf("jrockit") >= 0;
  }

  /**
   * Return true if the VM's vendor is Apple
   * 
   * @return true, if OS X
   */
  public static boolean isOSX() {
    final String vendor = System.getProperty("java.vm.vendor");
    return vendor != null && vendor.startsWith("Apple");
  }

  /**
   * Returns true if VM vendor is Hotspot
   * 
   * @return true, if Hotspot
   */
  public static boolean isHotspot() {
    return System.getProperty("java.vm.name", "").toLowerCase()
        .contains("hotspot");
  }

  /**
   * Returns true if VM vendor is IBM
   * 
   * @return true, if IBM
   */
  public static boolean isIBM() {
    return System.getProperty("java.vm.name", "").contains("IBM")
        && System.getProperty("java.vm.vendor").contains("IBM");
  }

  private static boolean isIBMCompressedRefs() {
    return System.getProperty("com.ibm.oti.vm.bootstrap.library.path", "")
        .contains("compressedrefs");
  }

  private static boolean isHotspotCompressedOops() {
    String value = getHotSpotVmOptionValue("UseCompressedOops");
    if (value == null) {
      return false;
    } else {
      return Boolean.valueOf(value);
    }
  }

  private static String getHotSpotVmOptionValue(String name) {
    try {
      MBeanServer server = ManagementFactory.getPlatformMBeanServer();
      ObjectName beanName =
          ObjectName.getInstance("com.sun.management:type=HotSpotDiagnostic");
      Object vmOption =
          server.invoke(beanName, "getVMOption", new Object[] { name },
              new String[] { "java.lang.String" });
      return (String) ((CompositeData) vmOption).get("value");
    } catch (Throwable t) {
      return null;
    }
  }

  private static String getPlatformMBeanAttribute(String beanName,
      String attrName) {
    try {
      MBeanServer server = ManagementFactory.getPlatformMBeanServer();
      ObjectName name = ObjectName.getInstance(beanName);
      Object attr = server.getAttribute(name, attrName).toString();
      if (attr != null) {
        return attr.toString();
      }
      return null;
    } catch (Throwable t) {
      return null;
    }
  }

  private static String getJRockitVmArgs() {
    return getPlatformMBeanAttribute(
        "oracle.jrockit.management:type=PerfCounters", "java.rt.vmArgs");
  }

  private static boolean isHotspotConcurrentMarkSweepGC() {
    for (GarbageCollectorMXBean bean : ManagementFactory
        .getGarbageCollectorMXBeans()) {
      if ("ConcurrentMarkSweep".equals(bean.getName())) {
        return true;
      }
    }
    return false;
  }

  private static boolean is64Bit() {
    String systemProp;
    systemProp = System.getProperty("com.ibm.vm.bitmode");
    if (systemProp != null) {
      return systemProp.equals("64");
    }
    systemProp = System.getProperty("sun.arch.data.model");
    if (systemProp != null) {
      return systemProp.equals("64");
    }
    systemProp = System.getProperty("java.vm.version");
    if (systemProp != null) {
      return systemProp.contains("_64");
    }
    return false;
  }
}

class Person {
  String name;
  int age;
}

class Pointer {
  Object pointer;
  Object value;
}

public class DirectMemoryTricky {

  private static final Unsafe unsafe;
  static {
    try {
      Field field = Unsafe.class.getDeclaredField("theUnsafe");
      field.setAccessible(true);
      unsafe = (Unsafe) field.get(null);
    } catch (Exception e) {
      throw new RuntimeException(e);
    }
  }

  public static long sizeOf(Object obj) {
    if (obj.getClass().isArray()) {
      Class<?> klazz = obj.getClass();
      int base = unsafe.arrayBaseOffset(klazz);
      int scale = unsafe.arrayIndexScale(klazz);
      long size = base + (scale * Array.getLength(obj));
      size += JvmInformation.CURRENT_JVM_INFORMATION.getFieldOffsetAdjustment();
      if ((size % JvmInformation.CURRENT_JVM_INFORMATION.getObjectAlignment()) != 0) {
        size +=
            JvmInformation.CURRENT_JVM_INFORMATION.getObjectAlignment()
                - (size % JvmInformation.CURRENT_JVM_INFORMATION
                    .getObjectAlignment());
      }
      return Math.max(
          JvmInformation.CURRENT_JVM_INFORMATION.getMinimumObjectSize(), size);
    } else {
      for (Class<?> klazz = obj.getClass(); klazz != null; klazz =
          klazz.getSuperclass()) {
        long lastFieldOffset = -1;
        for (Field f : klazz.getDeclaredFields()) {
          if (!Modifier.isStatic(f.getModifiers())) {
            lastFieldOffset =
                Math.max(lastFieldOffset, unsafe.objectFieldOffset(f));
          }
        }
        if (lastFieldOffset > 0) {
          lastFieldOffset +=
              JvmInformation.CURRENT_JVM_INFORMATION.getFieldOffsetAdjustment();
          lastFieldOffset += 1;
          if ((lastFieldOffset % JvmInformation.CURRENT_JVM_INFORMATION
              .getObjectAlignment()) != 0) {
            lastFieldOffset +=
                JvmInformation.CURRENT_JVM_INFORMATION.getObjectAlignment()
                    - (lastFieldOffset % JvmInformation.CURRENT_JVM_INFORMATION
                        .getObjectAlignment());
          }
          return Math.max(
              JvmInformation.CURRENT_JVM_INFORMATION.getMinimumObjectSize(),
              lastFieldOffset);
        }
      }

      long size = JvmInformation.CURRENT_JVM_INFORMATION.getObjectHeaderSize();
      if ((size % JvmInformation.CURRENT_JVM_INFORMATION.getObjectAlignment()) != 0) {
        size +=
            JvmInformation.CURRENT_JVM_INFORMATION.getObjectAlignment()
                - (size % JvmInformation.CURRENT_JVM_INFORMATION
                    .getObjectAlignment());
      }
      return Math.max(
          JvmInformation.CURRENT_JVM_INFORMATION.getMinimumObjectSize(), size);
    }
  }

  public static void main(String[] args) throws Exception {
    Person person = new Person();
    person.age = 30;
    long size = sizeOf(person);
    System.out.println(size);
    long offheapPointer = unsafe.allocateMemory(size);
    System.out.println("offheapPointer & 0xffffffff00000000L = " +  (offheapPointer & 0xffffffff00000000L));
    
    unsafe.copyMemory(person,  // source object
                      0,       // source offset is zero - copy an entire object
                      null,   // destination is specified by absolute address, so destination object is null
                      offheapPointer, // destination address
                      size); // test object was copied to off-heap

    Pointer pointer = new Pointer(); // Pointer is just a handler that stores
                                     // address
    // of some object
    long pointerOffset =
        unsafe.objectFieldOffset(Pointer.class.getDeclaredField("pointer"));
    
    long valueOffset =
        unsafe.objectFieldOffset(Pointer.class.getDeclaredField("value"));
    
    System.out.println("pointerOffset: " + pointerOffset + ", valueOffset:" + valueOffset);
    // set pointer to off-heap
    unsafe.putLong(pointer, pointerOffset, offheapPointer);

    // copy of the test object

    person.age = 23; // rewrite x value in the original object
    System.out.println(((Person) pointer.pointer).age); // prints 30
  }

}