Java中如何打印对象内存地址?

Posted 程序员Forlan

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java中如何打印对象内存地址?相关的知识,希望对你有一定的参考价值。

先看一个简单的程序,一般我们打印对象,大部分是下面的情况,可能会重写下toString()方法,这个另说

Frolan frolan = new Frolan();
System.out.println(frolan);

// 输出结果
com.test.admin.entity.Frolan@2b80d80f

这个结果其实是调用了Object.toString打印出来的,就是类路径名+@+hashCode的16进制数

public String toString() 
    return getClass().getName() + "@" + Integer.toHexString(hashCode());

这里的hashCode()是一个native方法,就是我们要的地址值?

我们通过源码来分析一波

static inline intptr_t get_next_hash(Thread * Self, oop obj) 
  intptr_t value = 0 ;
  if (hashCode == 0) 
     // This form uses an unguarded global Park-Miller RNG,
     // so it's possible for two threads to race and generate the same RNG.
     // On MP system we'll have lots of RW access to a global, so the
     // mechanism induces lots of coherency traffic.
     value = os::random() ;
   else
  if (hashCode == 1) 
     // This variation has the property of being stable (idempotent)
     // between STW operations.  This can be useful in some of the 1-0
     // synchronization schemes.
     intptr_t addrBits = cast_from_oop<intptr_t>(obj) >> 3 ;
     value = addrBits ^ (addrBits >> 5) ^ GVars.stwRandom ;
   else
  if (hashCode == 2) 
     value = 1 ;            // for sensitivity testing
   else
  if (hashCode == 3) 
     value = ++GVars.hcSequence ;
   else
  if (hashCode == 4) 
     value = cast_from_oop<intptr_t>(obj) ;
   else 
     // Marsaglia's xor-shift scheme with thread-specific state
     // This is probably the best overall implementation -- we'll
     // likely make this the default in future releases.
     unsigned t = Self->_hashStateX ;
     t ^= (t << 11) ;
     Self->_hashStateX = Self->_hashStateY ;
     Self->_hashStateY = Self->_hashStateZ ;
     Self->_hashStateZ = Self->_hashStateW ;
     unsigned v = Self->_hashStateW ;
     v = (v ^ (v >> 19)) ^ (t ^ (t >> 8)) ;
     Self->_hashStateW = v ;
     value = v ;
  

  value &= markOopDesc::hash_mask;
  if (value == 0) value = 0xBAD ;
  assert (value != markOopDesc::no_hash, "invariant") ;
  TEVENT (hashCode: GENERATE) ;
  return value;

大概意思就是,会根据不同的hashCode返回不同的结果
hashCode=0,返回随机数
hashCode=1,将oop的地址做位运算、异或运算得到的结果
hashCode=2,固定值1
hashCode=3,返回递增序列当前值
hashCode=4,oop的地址
hashCode=其他值,简单理解为移位寄存器,线程安全

我们设置JVM启动参数来模拟一下

// -XX:hashCode=2
for (int i = 0; i < 3; i++) 
	Frolan frolan = new Frolan();
	System.out.println(frolan.hashCode());


// 输出结果
1
1
1
// -XX:hashCode=3
for (int i = 0; i < 3; i++) 
	Frolan frolan = new Frolan();
	System.out.println(frolan.hashCode());


// 输出结果
714
715
716

我们模拟了hashCode=2和3的情况,其它情况没那么好验证,感兴趣的话,后续大家一起交流下~

那么我们不设置启动参数,默认值是什么?

java -XX:+PrintFlagsFinal -version | grep hashCode

     intx hashCode                     = 5                     product
java version "1.8.0_101"
Java(TM) SE Runtime Environment (build 1.8.0_101-b13)
Java HotSpot(TM) 64-Bit Server VM (build 25.101-b13, mixed mode)

可以看到默认值是5,所以我们不设置的情况下,打印的并不是对象的内存地址

网上很多说,hashCode=4,这里就是对象的内存地址,这个是错的,这里拿到的只是oop的地址,System.identityHashCode()方法拿到的也是这个地址

如果想要获取真正的对象地址,可以使用Java 对象布局 ( JOL ) 工具

引入依赖

<dependency>
    <groupId>org.openjdk.jol</groupId>
    <artifactId>jol-core</artifactId>
    <version>0.9</version>
</dependency>
Frolan frolan = new Frolan();
System.out.println(VM.current().addressOf(frolan));
		
// 输出结果
31867940040

Java Object类

Object的toString方法

  • toString 方法返回的是字符串,直接打印对象的名字,就是调用对象的toString,也就是打印对象堆内存中的地址值

  • 重写toString方法
    /**
     * @Version: 1.8.0_201       Java SE 8
     * @Description: toString 方法返回的是字符串,直接打印对象的名字,就是调用对象的toString
     *               也就是打印对象堆内存中的地址值
     */
    public class Student {
        private String name;
        private int age;
    
        public Student(String name, int age) {
            this.name = name;
            this.age = age;
        }
    
        public void methodStudentMassage() {
            System.out.println(
                    "Student{" +
                    "name=‘" + name + ‘‘‘ +
                    ", age=" + age +
                    ‘}‘
            );
        }
    
        /**
         * 重写toString方法
         * @return 学生的信息
         */
        @Override
        public String toString() {
            return "Student{" +
                    "name=‘" + name + ‘‘‘ +
                    ", age=" + age +
                    ‘}‘;
        }
    
    }
  • 测试重写的toString方法
    /**
     * @Version: 1.8.0_201       Java SE 8
     */
    public class DemoStudentToString {
        public static void main(String[] args) {
            Student student = new Student("Lee Hua", 21);
    
            // 不重写toString方法
            student.methodStudentMassage();
    
            // 重写了toString方法
            System.out.println(
                    student.toString()
            );
        }
    }

    输出:

    Student{name=‘Lee Hua‘, age=21}
    Student{name=‘Lee Hua‘, age=21}


    Object类的equals方法

  • quals方法:其他某个对象是否与此对象相等

  • 调用成员方法quals并指定参数为另一个对象,则可判断这两个对象是否相同

    public boolean equals(Object obj) {
            return (this == obj);
    }
    this 表示:哪个对象调用了equals方法,那么this就是那个对象
  • 创建一个Person类,用于测试

    public class Person {
        private String name;
        private int age;
    
        public Person(String name, int age) {
            this.name = name;
            this.age = 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 class DemoPersonEquals {
        public static void main(String[] args) {
            Person person1 = new Person("一号", 20);
            Person person2 = new Person("二号", 21);
    
            // 对象person1 和 对象person2 进行比较
            boolean p = person1.equals(person2);
            System.out.println(person1);
            System.out.println(person2);
            System.out.println(p);
        }
    }
  • 输出:
    Person@61bbe9ba
    Person@610455d6
    false
    
    
    
    
    /**
     * Person类如果重写类Object的toString方法,则返回字符串,而不是地址值
     * 举例:
     */
    @Override
    public String toString() {
        return "Person{" +
                "name=‘" + name + ‘‘‘ +
                ", age=" + age +
                ‘}‘;
    }
  • 在Person类里边覆盖重写equals方法
    public class Person {
        /**
         * 多态,无法使用子类特有内容,所以可进行覆盖重写
         * 覆盖重写equals方法,提高程序的效率
         */
        @Override
        public boolean equals(Object obj) {
            // 如果传递的参数obj是this本身,直接返回true
            if (obj == this) {
                return true;
            }
            // 如果传递参数是null,直接返回false
            if (obj == null) {
                return false;
            }
            // 防止类型转换报:ClassCastException
            if (obj instanceof Person) {
                // 向下转型,将 obj 转换为 Person 类型
                Person person = (Person)obj;
                return this.name.equals(person.name) && this.age == person.age;
            }
            // 不是Person类,也不是null,也直接返回false
            else {
                return false;
            }
        }
    }
  • 这里也可以用 Generate 直接生成与上等功能代码
    public class Person {
        @Override
        public boolean equals(Object o) {
            if (this == o) {
                return true;
            }
            if (o == null || getClass() != o.getClass()) {
                return false;
            }
            Person person = (Person) o;
            return age == person.age &&
                    name.equals(person.name);
        }
    }

    使用equals方法时,防止空指针异常

  • 例子
    import java.util.Objects;
    
    public class DemoObjectEquals {
        public static void main(String[] args) {
            String s1 = "abc";
            String s2 = null;
    
            // 不会出现空指针异常                // 输出 false
            System.out.println(
                    s1.equals(s2)
            );
            
            // 会出现空指针异常NullPointerException         // 报错 Exception in thread "main" java.lang.NullPointerException
            System.out.println(
                    s2.equals(s1)
            );
            
            // 空指针异常,可以使用java.util.Objects的equals方法,防止空指针异常     // 输出 false
            System.out.println(
                    Objects.equals(s1, s2)
            );
        }
    }

     

以上是关于Java中如何打印对象内存地址?的主要内容,如果未能解决你的问题,请参考以下文章

Java中如何打印对象内存地址?

JVM:查看java内存情况命令

Java中,复写了toString方法,如何再调用复写前的toString方法?

JVM:查看java内存情况命令

如何在Java中存储引用

如何查看Java对象占用JVM内存大小