java的native是什么意思,怎么查看源码,例如hashcode

相信很多小伙伴都在java的lang包下,加过很多方法带有native,表示Java本地接口,用来调用操作系统的方法,比如java的根类Object下的hashcode方法。

native方法的具体实现是用C语言实现的,由于jdk就是用C语言编写的。当有一些须要和硬件打交道的方法,java是作不了的,因而它就声明一个native方法让c去写一个方法去和硬件打交道,c写好以后java直接调用便可。
    /**
     * Returns a hash code value for the object. This method is
     * supported for the benefit of hash tables such as those provided by
     * {@link java.util.HashMap}. 
     * <p>
     * The general contract of {@code hashCode} is:
     * <ul>
     * <li>Whenever it is invoked on the same object more than once during
     *     an execution of a Java application, the {@code hashCode} method
     *     must consistently return the same integer, provided no information
     *     used in {@code equals} comparisons on the object is modified.
     *     This integer need not remain consistent from one execution of an
     *     application to another execution of the same application.
     * <li>If two objects are equal according to the {@code equals(Object)}
     *     method, then calling the {@code hashCode} method on each of
     *     the two objects must produce the same integer result.
     * <li>It is <em>not</em> required that if two objects are unequal
     *     according to the {@link java.lang.Object#equals(java.lang.Object)}
     *     method, then calling the {@code hashCode} method on each of the
     *     two objects must produce distinct integer results.  However, the
     *     programmer should be aware that producing distinct integer results
     *     for unequal objects may improve the performance of hash tables.
     * </ul>
     * <p>
     * As much as is reasonably practical, the hashCode method defined by
     * class {@code Object} does return distinct integers for distinct
     * objects. 

     * (This is typically implemented by converting the internal
     * address of the object into an integer, but this implementation
     * technique is not required by the
     * Java&trade; programming language.)默认情况下,对象的哈希码是通过将该对象的内部地址* 
     * 转换成一个整数来实现的。
     *
     * @return  a hash code value for this object.
     * @see     java.lang.Object#equals(java.lang.Object)
     * @see     java.lang.System#identityHashCode
     */
    public native int hashCode();

该方法返回对象的哈希码,主要使用在哈希表中,比如JDK中的HashMap。

哈希码的通用约定如下:

  1. 在java程序执行过程中,在一个对象没有被改变的前提下,无论这个对象被调用多少次,hashCode方法都会返回相同的整数值。对象的哈希码没有必要在不同的程序中保持相同的值。
  2. 如果2个对象使用equals方法进行比较并且相同的话,那么这2个对象的hashCode方法的值也必须相等。
  3. 如果根据equals方法,得到两个对象不相等,那么这2个对象的hashCode值不需要必须不相同。但是,不相等的对象的hashCode值不同的话可以提高哈希表的性能。

通常情况下,不同的对象产生的哈希码是不同的。默认情况下,对象的哈希码是通过将该对象的内部地址转换成一个整数来实现的。

这个我们应该怎么查看源码,native修饰的方法是用非java语言写的,是没有办法直接点击查看的。今天小编就来演示下java中的native方法怎么查看。

我们目前能看到jvm源码就是OpenJDK的源码,OpenJDK的源码大部分和Oracle的JVM源码一致。

1、下载openjdk源码,openJDK的项目

    链接:OpenJDK Mercurial Repositorieshttp://hg.openjdk.java.net/),如下图所示

2、选择 jdk8u,openJDK8u60

3、点击"browse"链接

4、点击图中的"zip"链接 下载,开启我们的源码之旅。

5、打开open-jdk文件夹,跳转到目录jdk/src/share/native/。

根据java.lang.Object的包路径定位到目录java\lang下的Object.c文件。

直接查看地址:http://hg.openjdk.java.net/jdk8u/jdk8u60/jdk/file/935758609767/src/share/native/java/lang 

打开openjdk\jdk\src\share\native\java\lang\目录,查看Object.c文件,可以看到hashCode()的方法被注册成有JVM_IHashCode方法指针来处理:

3.JVM_IHashCode方法指针在 openjdk\hotspot\src\share\vm\prims\jvm.cpp中定义,如下:地址:http://hg.openjdk.java.net/jdk8u/jdk8u60/hotspot/file/37240c1019fd/src/share/vm/prims

至此,我们的推测hashcode方法返回值和内存地址有关,是否正确呢,我们先跑一下程序验证一下(JDK8环境)。

第一次

    public static void main(String[] args) {
        Object o=new Object();
        System.out.println(o.hashCode());
    }

   
第二次 

	public static void main(String[] args) {
		Object object1=new Object();
		Object object2=new Object();
		Object object3=new Object();
		Object object=new Object();
		System.out.println(object.hashCode());
	}


我们可以发现,这两次运行的结果一样,这说明hashcode方法返回值确实和内存地址无关。我们在第二次运行时,新建了三个无关的Object对象使得内存分布和第一次不同,即便这样,程序第二次运行得到的hahscode方法返回值依然和第一次相同。 



真正的hashCode方法

hashCode方法的实现依赖于jvm,不同的jvm有不同的实现,我们目前能看到jvm源码就是OpenJDK的源码,OpenJDK的源码大部分和Oracle的JVM源码一致。


// java.lang.Object ///


JVM_ENTRY(jint, JVM_IHashCode(JNIEnv* env, jobject handle))
  JVMWrapper("JVM_IHashCode");
  // as implemented in the classic virtual machine; return 0 if object is NULL
  return handle == NULL ? 0 : ObjectSynchronizer::FastHashCode (THREAD, JNIHandles::resolve_non_null(handle)) ;
JVM_END

  如上可以看出,JVM_IHashCode方法中调用了ObjectSynchronizer::FastHashCode方法

ObjectSynchronizer::fashHashCode方法的实现:

     ObjectSynchronizer::fashHashCode()方法在 openjdk\hotspot\src\share\vm\runtime\synchronizer.cpp 文件中实现,其核心代码实现如下所示:

ObjectSynchronizer :: FastHashCode() 也是通过调用identity_hash_value_for方法返回值的,System.identityHashCode()调用的也是这个方法。

708 intptr_t ObjectSynchronizer::identity_hash_value_for(Handle obj) {
709   return FastHashCode (Thread::current(), obj()) ;
710 }

我们可能会认为 ObjectSynchronizer :: FastHashCode() 会判断当前的hash值是否为0,如果是0则生成一个新的hash值。实际上没那么简单,来看看其中的代码。

685   mark = monitor->header();
...
687   hash = mark->hash();
688   if (hash == 0) {
689     hash = get_next_hash(Self, obj);
...
701   }
...
703   return hash;

上边的片段展示了hash值是如何生成的,可以看到hash值是存放在对象头中的,如果hash值不存在,则使用get_next_hash方法生成。



真正的 identity hash code 生成

我们找到了生成hash的最终函数 get_next_hash,这个函数提供了6种生成hash值的方法。

0. A randomly generated number.
1. A function of memory address of the object.
2. A hardcoded 1 (used for sensitivity testing.)
3. A sequence.
4. The memory address of the object, cast to int.
5. Thread state combined with xorshift (https://en.wikipedia.org/wiki/Xorshift)

那么默认用哪一个呢?根据globals.hpp,OpenJDK8默认采用第五种方法(线程状态相关+xorshift算法生成随机数)。而 OpenJDK7 和 OpenJDK6 都是使用第一种方法,即 随机数生成器。

大家也看到了,JDK的注释算是欺骗了我们,明明在6,7,8版本上都是随机生成的值,为什么要引导说是内存地址映射呢?我的理解可能以前的jdk版本就是通过第4种方法实现的。




总结

  • OpenJDK默认的hashCode方法实现和对象内存地址无关,在版本6和7中,它是随机生成的数字,在版本8中,它是基于线程状态的数字。(AZUL-ZING的hashcode是基于地址的)
  • 在HotSpot中,hash值会存在标记字中。(Java虚拟机有多种,从JDK1.3以后,HotSpot虚拟机成为JDK1.3及其以后所有JDK版本的默认Java虚拟机。
  • hashCode方法和System.identityHashCode()会让对象不能使用偏向锁,所以如果想使用偏向锁,那就最好重写hashCode方法。
  • 如果大量对象跨线程使用,可以禁用偏向锁。
  • 使用-XX:hashCode=来修改默认的hash方法实现。

拓展:关于对象的hashCode和对象头

对象头格式

在上一节,我们知道了hash值是放在对象头里的,那就来了解一下对象头的结构吧。

markOop.hpp

30 // The markOop describes the header of an object.
31 //
32 // Note that the mark is not a real oop but just a word.
33 // It is placed in the oop hierarchy for historical reasons.
34 //
35 // Bit-format of an object header (most significant first, big endian layout below):
36 //
37 //  32 bits:
38 //  --------
39 //             hash:25 ------------>| age:4    biased_lock:1 lock:2 (normal object)
40 //             JavaThread*:23 epoch:2 age:4    biased_lock:1 lock:2 (biased object)
41 //             size:32 ------------------------------------------>| (CMS free block)
42 //             PromotedObject*:29 ---------->| promo_bits:3 ----->| (CMS promoted object)
43 //
44 //  64 bits:
45 //  --------
46 //  unused:25 hash:31 -->| unused:1   age:4    biased_lock:1 lock:2 (normal object)
47 //  JavaThread*:54 epoch:2 unused:1   age:4    biased_lock:1 lock:2 (biased object)
48 //  PromotedObject*:61 --------------------->| promo_bits:3 ----->| (CMS promoted object)
49 //  size:64 ----------------------------------------------------->| (CMS free block)
50 //
51 //  unused:25 hash:31 -->| cms_free:1 age:4    biased_lock:1 lock:2 (COOPs && normal object)
52 //  JavaThread*:54 epoch:2 cms_free:1 age:4    biased_lock:1 lock:2 (COOPs && biased object)
53 //  narrowOop:32 unused:24 cms_free:1 unused:4 promo_bits:3 ----->| (COOPs && CMS promoted object)
54 //  unused:21 size:35 -->| cms_free:1 unused:7 ------------------>| (COOPs && CMS free block)

它的格式在32位和64位上略有不同,64位有两种变体,具体取决于是否启用了压缩对象指针。




对象头中偏向锁和hashcode的冲突

normal object和biased object分别存放的是hashcode和java的线程id。因此也就是说如果调用了本地方法hashCode,就会占用偏向锁对象使用的位置,偏向锁将会失效,晋升为轻量级锁。
这个过程我们可以看看这个图:
在这里插入图片描述
这里我来简单解读一下,首先在jvm启动时,可以使用-XX:+UseBiasedLocking=true参数开启偏向锁。

接下来,如果偏向锁可用,那分配的对象中标记字格式为可包含线程ID,当未锁定时,线程ID为0,第一次获取锁时,线程会把自己的线程ID写到ThreadID字段内,这样,下一次获取锁时直接检查标记字中的线程ID和自身ID是否一致,如果一致就认为获取了锁,因此不需要再次获取锁。

假设这时有别的线程需要竞争锁了,此时该线程会通知持有偏向锁的线程释放锁,假设持有偏向锁的线程已经销毁,则将对象头设置为无锁状态,如果线程活着,则尝试切换,如果不成功,那么锁就会升级为轻量级锁。

这时有个问题来了,如果需要获取对象的identity hash code,偏向锁就会被禁用,然后给原先设置线程ID的位置写入hash值。

如果hash有值,或者偏向锁无法撤销,则会进入轻量级锁。轻量级锁竞争时,每个线程会先将hashCode值保存到自己的栈内存中,然后通过CAS尝试将自己新建的记录空间地址写入到对象头中,谁先写成功谁就拥有了该对象。

轻量级锁竞争失败的线程会自旋尝试获取锁一段时间,一段时间过后还没获取到锁,则升级为重量级锁,没获取锁的线程会被真正阻塞。

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值