jvm两种方式获取对象所占用的内存

简介: 在开发过程中,我们有时需要来获取某个对象的大小,以方便我们参考,来决定开发的技术方案。jvm中提供了两种方式来获取一个对象的大小。

在开发过程中,我们有时需要来获取某个对象的大小,以方便我们参考,来决定开发的技术方案。jvm中提供了两种方式来获取一个对象的大小。

通过Instrumentation来计算对象的大小

  • 编写计算代码:
package com.java.basic;

import java.lang.instrument.Instrumentation;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.IdentityHashMap;
import java.util.Map;
import java.util.Stack;

public class SizeOfAgent  
{  
    private static Instrumentation inst;  
      
    /** initializes agent */  
    public static void premain(String agentArgs, Instrumentation instP)   
    {  
        inst = instP;  
    }  
  
    /** 
     * Returns object size without member sub-objects. 
     * @param o object to get size of 
     * @return object size 
     */  
    public static long sizeOf(Object o)   
    {  
        if(inst == null)   
        {  
            throw new IllegalStateException("Can not access instrumentation environment.\n" +  
                            "Please check if jar file containing SizeOfAgent class is \n" +  
                            "specified in the java's \"-javaagent\" command line argument.");  
        }  
        return inst.getObjectSize(o);  
    }  
                    
    /** 
     * Calculates full size of object iterating over 
     * its hierarchy graph. 
     * @param obj object to calculate size of 
     * @return object size 
     */  
    public static long fullSizeOf(Object obj)   
    {  
        Map<Object, Object> visited = new IdentityHashMap<Object, Object>();  
        Stack<Object> stack = new Stack<Object>();  
            
        long result = internalSizeOf(obj, stack, visited);  
        while (!stack.isEmpty())   
        {  
            result += internalSizeOf(stack.pop(), stack, visited);  
        }  
        visited.clear();  
        return result;  
    }                 
              
    private static boolean skipObject(Object obj, Map<Object, Object> visited)   
    {  
        if (obj instanceof String) {//这个if是bug,应当去掉--teasp  
            // skip interned string  
            if (obj == ((String) obj).intern()) {  
                return true;  
            }  
        }  
        return (obj == null) || visited.containsKey(obj);  
    }  
    
    @SuppressWarnings("rawtypes")  
    private static long internalSizeOf(Object obj, Stack<Object> stack, Map<Object, Object> visited)   
    {  
        if (skipObject(obj, visited))  
        {  
            return 0;  
        }  
        visited.put(obj, null);  
                        
        long result = 0;  
        // get size of object + primitive variables + member pointers   
        result += SizeOfAgent.sizeOf(obj);  
                    
        // process all array elements  
        Class clazz = obj.getClass();  
        if (clazz.isArray())   
        {  
            if(clazz.getName().length() != 2)   
            {// skip primitive type array  
                int length =  Array.getLength(obj);  
                for (int i = 0; i < length; i++)   
                {  
                    stack.add(Array.get(obj, i));  
                }   
            }         
            return result;  
        }  
                    
        // process all fields of the object  
        while (clazz != null)   
        {  
            Field[] fields = clazz.getDeclaredFields();  
            for (int i = 0; i < fields.length; i++)   
            {  
                if (!Modifier.isStatic(fields[i].getModifiers()))   
                {  
                    if (fields[i].getType().isPrimitive())   
                    {  
                        continue; // skip primitive fields  
                    }   
                    else   
                    {  
                        fields[i].setAccessible(true);  
                        try   
                        {  
                            // objects to be estimated are put to stack  
                            Object objectToAdd = fields[i].get(obj);  
                            if (objectToAdd != null)   
                            {                          
                                stack.add(objectToAdd);  
                            }  
                        }   
                        catch (IllegalAccessException ex)   
                        {   
                            assert false;   
                        }  
                    }  
                }  
            }  
            clazz = clazz.getSuperclass();  
        }  
        return result;  
    }  
}  

其中sizeof方法仅仅获取的是当前对象的大小,而该对象的如果存在对其他对象的引用,则不在计算范围以内,而fullsizeof则会计算整体的大小。

  • 将该java文件进行编译,并打成jar包
  1. com.java.basic.SizeOfAgent .java

jar cvf sizeOfAgent.jar com/java.basic/SizeOfAgent .class

  • 修改META-INF/MANIFEST.MF文件内容
    Premain-Class: com.java.basic.SizeOfAgent

Boot-Class-Path:
Can-Redefine-Classes: false
注意:每个冒号后面都有一个空格,且最后一行会有一个换行

  • 将该jar包导入项目
  • 添加启动参数:-javaagent:E:sizeOfAgent.jar
    我这边是将该jar包放在e盘,这里填写绝对路径。

这样我们就可以通过调用该类中的sizeOf方法或者fullSizeOf方法即可。

使用Unsafe类来获取对象大小

unsafe对象可以获取到一个对象中各个属性的内存指针的偏移量,可以利用其来计算一个对象的大小。

import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;

import sun.misc.Unsafe;

public class ClassIntrospector {

    private static final Unsafe unsafe;
    /** Size of any Object reference */
    private static final int objectRefSize;
    static {
        try {
            Field field = Unsafe.class.getDeclaredField("theUnsafe");
            field.setAccessible(true);
            unsafe = (Unsafe) field.get(null);

            objectRefSize = unsafe.arrayIndexScale(Object[].class);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public int getObjectRefSize() {
        return objectRefSize;
    }

    /** Sizes of all primitive values */
    private static final Map<Class, Integer> primitiveSizes;

    static {
        primitiveSizes = new HashMap<Class, Integer>(10);
        primitiveSizes.put(byte.class, 1);
        primitiveSizes.put(char.class, 2);
        primitiveSizes.put(int.class, 4);
        primitiveSizes.put(long.class, 8);
        primitiveSizes.put(float.class, 4);
        primitiveSizes.put(double.class, 8);
        primitiveSizes.put(boolean.class, 1);
    }

    /**
     * Get object information for any Java object. Do not pass primitives to
     * this method because they will boxed and the information you will get will
     * be related to a boxed version of your value.
     * 
     * @param obj
     *            Object to introspect
     * @return Object info
     * @throws IllegalAccessException
     */
    public ObjectInfo introspect(final Object obj)
            throws IllegalAccessException {
        try {
            return introspect(obj, null);
        } finally { // clean visited cache before returning in order to make
                    // this object reusable
            m_visited.clear();
        }
    }

    // we need to keep track of already visited objects in order to support
    // cycles in the object graphs
    private IdentityHashMap<Object, Boolean> m_visited = new IdentityHashMap<Object, Boolean>(
            100);

    private ObjectInfo introspect(final Object obj, final Field fld)
            throws IllegalAccessException {
        // use Field type only if the field contains null. In this case we will
        // at least know what's expected to be
        // stored in this field. Otherwise, if a field has interface type, we
        // won't see what's really stored in it.
        // Besides, we should be careful about primitives, because they are
        // passed as boxed values in this method
        // (first arg is object) - for them we should still rely on the field
        // type.
        boolean isPrimitive = fld != null && fld.getType().isPrimitive();
        boolean isRecursive = false; // will be set to true if we have already
                                        // seen this object
        if (!isPrimitive) {
            if (m_visited.containsKey(obj))
                isRecursive = true;
            m_visited.put(obj, true);
        }

        final Class type = (fld == null || (obj != null && !isPrimitive)) ? obj
                .getClass() : fld.getType();
        int arraySize = 0;
        int baseOffset = 0;
        int indexScale = 0;
        if (type.isArray() && obj != null) {
            baseOffset = unsafe.arrayBaseOffset(type);
            indexScale = unsafe.arrayIndexScale(type);
            arraySize = baseOffset + indexScale * Array.getLength(obj);
        }

        final ObjectInfo root;
        if (fld == null) {
            root = new ObjectInfo("", type.getCanonicalName(), getContents(obj,
                    type), 0, getShallowSize(type), arraySize, baseOffset,
                    indexScale);
        } else {
            final int offset = (int) unsafe.objectFieldOffset(fld);
            root = new ObjectInfo(fld.getName(), type.getCanonicalName(),
                    getContents(obj, type), offset, getShallowSize(type),
                    arraySize, baseOffset, indexScale);
        }

        if (!isRecursive && obj != null) {
            if (isObjectArray(type)) {
                // introspect object arrays
                final Object[] ar = (Object[]) obj;
                for (final Object item : ar)
                    if (item != null)
                        root.addChild(introspect(item, null));
            } else {
                for (final Field field : getAllFields(type)) {
                    if ((field.getModifiers() & Modifier.STATIC) != 0) {
                        continue;
                    }
                    field.setAccessible(true);
                    root.addChild(introspect(field.get(obj), field));
                }
            }
        }

        root.sort(); // sort by offset
        return root;
    }

    // get all fields for this class, including all superclasses fields
    private static List<Field> getAllFields(final Class type) {
        if (type.isPrimitive())
            return Collections.emptyList();
        Class cur = type;
        final List<Field> res = new ArrayList<Field>(10);
        while (true) {
            Collections.addAll(res, cur.getDeclaredFields());
            if (cur == Object.class)
                break;
            cur = cur.getSuperclass();
        }
        return res;
    }

    // check if it is an array of objects. I suspect there must be a more
    // API-friendly way to make this check.
    private static boolean isObjectArray(final Class type) {
        if (!type.isArray())
            return false;
        if (type == byte[].class || type == boolean[].class
                || type == char[].class || type == short[].class
                || type == int[].class || type == long[].class
                || type == float[].class || type == double[].class)
            return false;
        return true;
    }

    // advanced toString logic
    private static String getContents(final Object val, final Class type) {
        if (val == null)
            return "null";
        if (type.isArray()) {
            if (type == byte[].class)
                return Arrays.toString((byte[]) val);
            else if (type == boolean[].class)
                return Arrays.toString((boolean[]) val);
            else if (type == char[].class)
                return Arrays.toString((char[]) val);
            else if (type == short[].class)
                return Arrays.toString((short[]) val);
            else if (type == int[].class)
                return Arrays.toString((int[]) val);
            else if (type == long[].class)
                return Arrays.toString((long[]) val);
            else if (type == float[].class)
                return Arrays.toString((float[]) val);
            else if (type == double[].class)
                return Arrays.toString((double[]) val);
            else
                return Arrays.toString((Object[]) val);
        }
        return val.toString();
    }

    // obtain a shallow size of a field of given class (primitive or object
    // reference size)
    private static int getShallowSize(final Class type) {
        if (type.isPrimitive()) {
            final Integer res = primitiveSizes.get(type);
            return res != null ? res : 0;
        } else
            return objectRefSize;
    }

    static class ObjectInfo {
        /** Field name */
        public final String name;
        /** Field type name */
        public final String type;
        /** Field data formatted as string */
        public final String contents;
        /** Field offset from the start of parent object */
        public final int offset;
        /** Memory occupied by this field */
        public final int length;
        /** Offset of the first cell in the array */
        public final int arrayBase;
        /** Size of a cell in the array */
        public final int arrayElementSize;
        /** Memory occupied by underlying array (shallow), if this is array type */
        public final int arraySize;
        /** This object fields */
        public final List<ObjectInfo> children;

        public ObjectInfo(String name, String type, String contents,
                int offset, int length, int arraySize, int arrayBase,
                int arrayElementSize) {
            this.name = name;
            this.type = type;
            this.contents = contents;
            this.offset = offset;
            this.length = length;
            this.arraySize = arraySize;
            this.arrayBase = arrayBase;
            this.arrayElementSize = arrayElementSize;
            children = new ArrayList<ObjectInfo>(1);
        }

        public void addChild(final ObjectInfo info) {
            if (info != null)
                children.add(info);
        }

        /**
         * Get the full amount of memory occupied by a given object. This value
         * may be slightly less than an actual value because we don't worry
         * about memory alignment - possible padding after the last object
         * field.
         * 
         * The result is equal to the last field offset + last field length +
         * all array sizes + all child objects deep sizes
         * 
         * @return Deep object size
         */
        public long getDeepSize() {
            // return length + arraySize + getUnderlyingSize( arraySize != 0 );
            return addPaddingSize(arraySize + getUnderlyingSize(arraySize != 0));
        }

        long size = 0;

        private long getUnderlyingSize(final boolean isArray) {
            // long size = 0;
            for (final ObjectInfo child : children)
                size += child.arraySize
                        + child.getUnderlyingSize(child.arraySize != 0);
            if (!isArray && !children.isEmpty()) {
                int tempSize = children.get(children.size() - 1).offset
                        + children.get(children.size() - 1).length;
                size += addPaddingSize(tempSize);
            }

            return size;
        }

        private static final class OffsetComparator implements
                Comparator<ObjectInfo> {
            @Override
            public int compare(final ObjectInfo o1, final ObjectInfo o2) {
                return o1.offset - o2.offset; // safe because offsets are small
                                                // non-negative numbers
            }
        }

        // sort all children by their offset
        public void sort() {
            Collections.sort(children, new OffsetComparator());
        }

        @Override
        public String toString() {
            final StringBuilder sb = new StringBuilder();
            toStringHelper(sb, 0);
            return sb.toString();
        }

        private void toStringHelper(final StringBuilder sb, final int depth) {
            depth(sb, depth).append("name=").append(name).append(", type=")
                    .append(type).append(", contents=").append(contents)
                    .append(", offset=").append(offset).append(", length=")
                    .append(length);
            if (arraySize > 0) {
                sb.append(", arrayBase=").append(arrayBase);
                sb.append(", arrayElemSize=").append(arrayElementSize);
                sb.append(", arraySize=").append(arraySize);
            }
            for (final ObjectInfo child : children) {
                sb.append('\n');
                child.toStringHelper(sb, depth + 1);
            }
        }

        private StringBuilder depth(final StringBuilder sb, final int depth) {
            for (int i = 0; i < depth; ++i)
                sb.append("\t");
            return sb;
        }

        private long addPaddingSize(long size) {
            if (size % 8 != 0) {
                return (size / 8 + 1) * 8;
            }
            return size;
        }

    }

当我们需要计算一个对象大小时,我们只需要获取ClassIntrospector实例,并调用其introspect方法,参数为需要计算大小的对象,就可以获取到ObjectInfo对象,这个对象中就包含了要计算的对象的各项信息(名字,类型,属性的偏移量等),想要获取对象的大小,我们只需要调用OjbectInfo的getDeepSiz即可。
ClassIntrospector中还定义了一个方法getObjectRefSize,这个方法的作用是获取当前虚拟机对象引用指针所占的空间,如果机器的内存在32G以下,则会默认开启指针压缩,占4个字节,否则占8个,可以使用参数-XX:-UseCompressedOops进行指针压缩

下面我们进行一个简单的验证:

首先我们先定义一个对象:

public class Person {

    private String name;
    private int age;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
}

测试代码:

public static void main(String[] args) throws Exception {
        Person person = new Person();
        System.out.println(SizeOfAgent.fullSizeOf(person));
        ClassIntrospector cIntrospector = new ClassIntrospector();
        ObjectInfo oInfo = cIntrospector.introspect(person);
        System.out.println(oInfo.getDeepSize());
}

运行结果:

24
24

两种方法的运行结果一致,我们进行对象的手动计算,计算公式:
mark头(8字节)+oop指针(4字节)+对象属性
以person对象为例:
mark头(8字节)+oop指针(4字节)+name(String类型引用4字节)+age(int类型引用4字节)=20
jvm会对一个对象进行内存对齐,是的对象的大小为8的倍数,所以最终结果为24。

当然这两种计算方式都是对对象的一个大概计算,当一个对象引用String类型时,其实是有常量池的存在的,所以计算出来的只我们只能做个参考即可。

目录
相关文章
|
4天前
|
存储 算法 Java
散列表的数据结构以及对象在JVM堆中的存储过程
本文介绍了散列表的基本概念及其在JVM中的应用,详细讲解了散列表的结构、对象存储过程、Hashtable的扩容机制及与HashMap的区别。通过实例和图解,帮助读者理解散列表的工作原理和优化策略。
15 1
散列表的数据结构以及对象在JVM堆中的存储过程
|
25天前
|
缓存 算法 Java
JVM知识体系学习六:JVM垃圾是什么、GC常用垃圾清除算法、堆内存逻辑分区、栈上分配、对象何时进入老年代、有关老年代新生代的两个问题、常见的垃圾回收器、CMS
这篇文章详细介绍了Java虚拟机(JVM)中的垃圾回收机制,包括垃圾的定义、垃圾回收算法、堆内存的逻辑分区、对象的内存分配和回收过程,以及不同垃圾回收器的工作原理和参数设置。
50 4
JVM知识体系学习六:JVM垃圾是什么、GC常用垃圾清除算法、堆内存逻辑分区、栈上分配、对象何时进入老年代、有关老年代新生代的两个问题、常见的垃圾回收器、CMS
|
15天前
|
存储 算法 Java
聊聊jvm的内存结构, 以及各种结构的作用
【10月更文挑战第27天】JVM(Java虚拟机)的内存结构主要包括程序计数器、Java虚拟机栈、本地方法栈、Java堆、方法区和运行时常量池。各部分协同工作,为Java程序提供高效稳定的内存管理和运行环境,确保程序的正常执行、数据存储和资源利用。
41 10
|
15天前
|
存储 算法 Java
Java虚拟机(JVM)的内存管理与性能优化
本文深入探讨了Java虚拟机(JVM)的内存管理机制,包括堆、栈、方法区等关键区域的功能与作用。通过分析垃圾回收算法和调优策略,旨在帮助开发者理解如何有效提升Java应用的性能。文章采用通俗易懂的语言,结合具体实例,使读者能够轻松掌握复杂的内存管理概念,并应用于实际开发中。
|
24天前
|
存储 监控 算法
JVM调优深度剖析:内存模型、垃圾收集、工具与实战
【10月更文挑战第9天】在Java开发领域,Java虚拟机(JVM)的性能调优是构建高性能、高并发系统不可或缺的一部分。作为一名资深架构师,深入理解JVM的内存模型、垃圾收集机制、调优工具及其实现原理,对于提升系统的整体性能和稳定性至关重要。本文将深入探讨这些内容,并提供针对单机几十万并发系统的JVM调优策略和Java代码示例。
46 2
|
25天前
|
Java 测试技术 Android开发
让星星⭐月亮告诉你,强软弱虚引用类型对象在内存足够和内存不足的情况下,面对System.gc()时,被回收情况如何?
本文介绍了Java中四种引用类型(强引用、软引用、弱引用、虚引用)的特点及行为,并通过示例代码展示了在内存充足和不足情况下这些引用类型的不同表现。文中提供了详细的测试方法和步骤,帮助理解不同引用类型在垃圾回收机制中的作用。测试环境为Eclipse + JDK1.8,需配置JVM运行参数以限制内存使用。
30 2
|
23天前
|
存储 Kubernetes 架构师
阿里面试:JVM 锁内存 是怎么变化的? JVM 锁的膨胀过程 ?
尼恩,一位经验丰富的40岁老架构师,通过其读者交流群分享了一系列关于JVM锁的深度解析,包括偏向锁、轻量级锁、自旋锁和重量级锁的概念、内存结构变化及锁膨胀流程。这些内容不仅帮助群内的小伙伴们顺利通过了多家一线互联网企业的面试,还整理成了《尼恩Java面试宝典》等技术资料,助力更多开发者提升技术水平,实现职业逆袭。尼恩强调,掌握这些核心知识点不仅能提高面试成功率,还能在实际工作中更好地应对高并发场景下的性能优化问题。
|
3月前
|
存储 编译器 C语言
【C语言篇】数据在内存中的存储(超详细)
浮点数就采⽤下⾯的规则表⽰,即指数E的真实值加上127(或1023),再将有效数字M去掉整数部分的1。
333 0
|
14天前
|
存储 C语言
数据在内存中的存储方式
本文介绍了计算机中整数和浮点数的存储方式,包括整数的原码、反码、补码,以及浮点数的IEEE754标准存储格式。同时,探讨了大小端字节序的概念及其判断方法,通过实例代码展示了这些概念的实际应用。
27 1
|
18天前
|
存储
共用体在内存中如何存储数据
共用体(Union)在内存中为所有成员分配同一段内存空间,大小等于最大成员所需的空间。这意味着所有成员共享同一块内存,但同一时间只能存储其中一个成员的数据,无法同时保存多个成员的值。