JVisualJVM
JVisualJvm 【可视化JVM】,可分析JDK1.6及其以上版本的JVM运行时的JVM参数、系统参数、堆栈、CPU使用等信息。可分析本地应用及远程应用,在JDK1.6以上版本中自带。工具的使用暂不展开说明, 想快速使用此工具,只需要在 IDE 中安装个 VisualVM Launcher
插件
然后在进行基本的配置
然后在IDE的右上角或当前类鼠标右键就可以点击运行查看了
运行起 VisualJVM 就是这样子了
不要走,还没结束,在总结这篇文章的时候,我还发现了「新大陆」
HashCode 真是根据对象内存地址生成的?
脑海中的印象不知道为何,很根深蒂固的接受了Object hashCode 是根据对象内存地址生成的,这次刚好想探求一下 hashCode 的本质,还着实打破了我的固有印象 (以 JDK1.8 为例)
OpenJDK 定义 hashCode 的方法在下面两个文件中
src/share/vm/prims/jvm.h
src/share/vm/prims/jvm.cpp
逐步看下去,最终会来到 get_next_hash 这个方法中,方便大家查看我先把方法截图至此:
总的来说有 6 种生成 hashCode 的方式:
- 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(else): Thread state combined with xorshift1
那在 JDK1.8 种用的哪一种呢?
可以看到在 JDK1.8 中生成 hashCode 的方式是 5, 也就是走程序的 else 路径,即使用 Xorshift,并不是之前认为的对象内存地址「1」,以为老版本是采用对象内存地址的方式,所以继续查看其他版本
从图中可以看出,JDK1.62 和 JDK1.73 版本生成 hashCode 的方式「1」随机数的形式,和我们原本认为的并不一样,别的版本没有继续查询,至于「流传下来」说是对象内存地址生成的 hashCode 我也木有再深入研究,有了解的同学还请留言赐教
那么问题来了:
假设用的 JDK1.6或 JDK1.7,它们生成 hashCode 的方式是随机生成的,那一个对象多次调用hashCode是会有不同的hashCode 呢?(排除服务重启的情况)
显然应该不会的,因为如果每次都变化, 存储到集合中的对象那就很容易丢失了,那问题又来了:
它们存在哪了?
hash 值是存在对象头中的,我们还知道对象头中还可能存储线程ID,所以他们在某些情形中还会存在冲突
对象头中 hashCode 和 偏向锁的冲突
jvm 启动时,可以使用 -XX:+UseBiasedLocking=true
开启偏向锁,(关于偏向锁,轻量级锁,重量级锁大家查阅 synchronized 相关文档就可以),这里引 OpenJDK Wiki4 里面的图片加以文字说明整个冲突过程
所以,调用 Object 的 hashCode() 方法或者 System.identityHashCode() 方法会让对象不能使用偏向锁。到这里你也就应该知道了,如果你还想使用偏向锁,那最好重写 hashCode() 方法,避免使偏向锁失效
总结
为了解决群的这个问题,发现新大陆的同时也差点让我掉入【追问无底洞】,不过通过本文你应该了解内存溢出和内存泄漏的差别,以及他们的解决方案,另外 hashCode5 生成方式还着实让人有些惊讶,如果你知道「hashCode的生成是根据对象内存地址生成的来源,还请留言赐教」。除此之外,小小的 hashCode 还有可能让偏向锁失效,所有的这些细节问题都有可能是导致程序崩溃的坑,所以勿以「恶」小而为之,毋以「善」小而不为,良好的编程习惯能避免很多问题
当然想要更好的理解内存泄漏,当然是要更好的理解 GC 机制,而想要更好的理解 GC,当然是更好的理解 JVM,咱们后续慢慢分析吧
灵魂追问
- 为了清除 ThreadLocal 线程变量值,不用
ThreadLocal.remove()
方法,而是用ThreadLocal.set(null)
会达到同样的效果吗?
- 你曾经遇到哪些不易察觉的内存泄漏问题呢?