本篇文章的标题很怪,我来解释一下。
这是我自己的一种体会,就是很多时候了解一个原理的时候,我就总想看见它。
研究 CPU 原理的时候,我想看见一条条指令执行时 CPU 里的电信号究竟是怎么走的。
研究操作系统原理的时候,我想看见从计算机启动到操作系统加载,内存布局实实在在发生了哪些变化。
同样研究 Java 原理的时候,我想看见每一条 Java 代码最终翻译成了什么机器指令在执行,我想看见一个 Java 对象在内存中究竟长什么样子,等等等等...
总之,我就想看见!
如果你也时时刻刻有这种纠结,并且不希望只停留在 Java 语言层面和网上一些博客所 "讲述" 出来的原理上,今天我就给你分享一些 看见 Java 的小技巧,也让你能从不同层级来分析一个原理。
- javap:看见字节码
- strace:看见系统调用
- hsdis+ jitwatch:看见机器码
- openJDK:看见 native 方法
- JOL:看见对象
- fastthread:看见线程
- JProfile:看见运行时的各种状态
这个命令大家应该很熟悉,接触了一段时间的 Java 后总会不满足于语言层面的原理了解,这时候就需要通过字节码来更深入地掌握一些底层原理了。
此时 javap
命令就很关键,它其实是解析整个的 .class 文件,但我们通常用它来分析里面的字节码指令。
命令的使用方法大家自己网上搜吧,这里提供一个平时研究字节码最友好的方式,就是使用 IDEA 的插件,你可以叫他字节码学习神器 jclasslib。
下载好插件后,你的 view 菜单会多出这么一项
点击它就可以分析当前类的字节码了
这一步,跨越了了解 Java 底层原理的第一层,下面接着往底层走~
当吃透了从 JVM 层级了解原理后,可能又感觉不够味道了,想看看 JVM 底层是怎么实现的。但又不想直接深入到最终的机器指令来看,那其实还有一个很好的中间层就是系统调用啦。
如果不了解系统调用可以先复习下操作系统相关知识。
strace
命令就可以查看某一个程序运行过程中,发起了哪些系统调用,并且这个过程是实时的。
下面我们用这个命令,看一下 Java 中传统的 BIO 服务端程序,会走哪些系统调用,并从这个层面对 BIO 的原理进行简单剖析,你会发现这种视角对传统视角来说有点降维打击了。
第一步:写一个贼简单的 socket 服务端程序,开放 8080 端口并监听
public static void main(String[] args) throws Exception {
ServerSocket serverSocket = new ServerSocket(8080);
Socket socket = serverSocket.accept();
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
System.out.println(bufferedReader.readLine());
}
第二步:用 strace 命令查看这个程序启动后的系统调用(这里我们只看网络相关的系统调用)
[bash ~]# strace -ff -e trace=network java SocketDemo
...
[pid 28226] socket(AF_INET6, SOCK_STREAM, IPPROTO_IP) = 5
...
[pid 28226] bind(5, {sa_family=AF_INET6, sin6_port=htons(8080), inet_pton(AF_INET6, "::", &sin6_addr), sin6_flowinfo=htonl(0), sin6_scope_id=0}, 28) = 0
[pid 28226] listen(5, 50) = 0
可以看到,实际上服务端的 socket 程序,向 linux 操作系统发起了三个系统调用socket
、bind
、listen
,然后就不往下走了。这时 Java 进程并没有结束,说明产生了一个阻塞(具体就是阻塞在 Java 代码的那句 accept 上)
第三步:用 nc 命令连接一下 8080 端口
[bash ~]# nc localhost 8080
...
[pid 28226] socket(AF_INET6, SOCK_STREAM, IPPROTO_IP) = 5
...
[pid 28226] bind(5, {sa_family=AF_INET6, sin6_port=htons(8080), inet_pton(AF_INET6, "::", &sin6_addr), sin6_flowinfo=htonl(0), sin6_scope_id=0}, 28) = 0
[pid 28226] listen(5, 50) = 0
[pid 28226] accept(5, {sa_family=AF_INET6, sin6_port=htons(11103), inet_pton(AF_INET6, "::1", &sin6_addr), sin6_flowinfo=htonl(0), sin6_scope_id=0}, [28]) = 6
[pid 28226] recvfrom(6,
可以看到用 nc 命令与 socket 服务端建立 TCP 连接后,系统调用多了 accept
和 recvfrom
,又不再往下走了,而此时 Java 进程仍然没有结束,说明又发生了阻塞(这次是阻塞在了 Java 代码的那句 readline,对应系统调用时 recvfrom)
第四步:继续在刚刚的 nc 命令中,传入一个字符串 "hello",敲回车发送
[bash ~]# nc localhost 8080
hello
...
[pid 28226] socket(AF_INET6, SOCK_STREAM, IPPROTO_IP) = 5
...
[pid 28226] bind(5, {sa_family=AF_INET6, sin6_port=htons(8080), inet_pton(AF_INET6, "::", &sin6_addr), sin6_flowinfo=htonl(0), sin6_scope_id=0}, 28) = 0
[pid 28226] listen(5, 50) = 0
[pid 28226] accept(5, {sa_family=AF_INET6, sin6_port=htons(11103), inet_pton(AF_INET6, "::1", &sin6_addr), sin6_flowinfo=htonl(0), sin6_scope_id=0}, [28]) = 6
[pid 28226] recvfrom(6, "hello\n", 8192, 0, NULL, NULL) = 6
hello
+++ exited with 0 +++
可以看到刚刚的 recvfrom 函数补全了,有了返回值,并且整个程序也结束了。
通过 strace
这个命令,我们从更底层的角度全面了解了 Java 中 BIO 的流程,如果你同样也了解操作系统并且熟悉 linux 下的各种系统调用的细节,那可以说这么一个小实验就帮你几乎完全掌握了 BIO 的原理。同样 NIO 也是如此,这样是不是就比单从 Java 层面了解 BIO NIO 这些知识点形成了降维打击呢?
当然,很多方法不走系统调用,如果核心原理不是系统调用组成的话,就不适合用这种方法研究了,那下面介绍一种终极办法,如果你能肉眼读汇编的话,你通过下面这个方式"理论上"可以了解所有原理。
这个可谓是最降维打击的一种方式了,可以直接看到 Java 代码最终在 CPU 层级上是执行了哪些机器指令。
通过这个文章可以很好地搭建这个环境:
https://zhuanlan.zhihu.com/p/158168592?from_voters_page=true
最终实现的效果就是如下
当然这个方法有点过于深入了,对于现在的程序和复杂的 JVM 而言,不可能通过看机器码去了解全貌的,只能说想要特别特别深入地去扣某一行代码最本质的执行细节的时候,知道有这种方式就行了。
如果想了解最底层的实现,其实不用非要观察正在执行的机器码,因为我们有源码呀。比如 native 方法,或者 sychronized 关键字的虚拟机实现原理,下面这个方式就适合你了。
读 jdk 源码,有的时候走到了 native 方法,往往很绝望,因为代表着自己跟了这么久的努力白费了。
public class Object {
...
public native int hashCode();
...
}
如果真的还想了解 native 方法的底层实现,其实下载 openJDK 的源码即可:https://github.com/openjdk/jdk
有了源码后首先进入 ./jdk/src/share/native
,可以看到如下目录结构
com
common
java
sun
再进入 java 目录后
io
lang
net
nio
security
util
有没有发现和 jdk 目录基本一样?那基本就知道怎么查了吧。
比如我想查找 Object 类的 hashcode 方法,我就可以根据 Object 在 jdk 中的目录,找到其 native 方法的目录
./jdk/src/share/native/java/lang/Object.c
...
#include "java_lang_Object.h"
static JNINativeMethod methods[] = {
{"hashCode", "()I", (void *)&JVM_IHashCode},
{"wait", "(J)V", (void *)&JVM_MonitorWait},
{"notify", "()V", (void *)&JVM_MonitorNotify},
{"notifyAll", "()V", (void *)&JVM_MonitorNotifyAll},
{"clone", "()Ljava/lang/Object;", (void *)&JVM_Clone},
};
...
再往下跟,九曲十八弯之后,会跟到这个方法 openjdk\hotspot\src\share\vm\runtime\synchronizer.cpp --> FastHashCode
,以下代码省略十万行...
intptr_t ObjectSynchronizer::FastHashCode (Thread * Self, oop obj) {
...
hash = mark->hash();
if (hash == 0) {
hash = get_next_hash(Self, obj);
temp = mark->copy_set_hash(hash); // merge hash code into header
...
}
...
// We finally get the hash
return hash;
native 方法中 C++ 代码很多,所以如果真的想通过自己阅读源码了解 native 底层实现,可以学习一下 c++ 的基本语法。
当然 openJDK 还可以看虚拟机的一些实现原理,比如看看 sychronized
关键字在虚拟机层面的实现,这里就不展开了。
面试总是问一个对象在内存中的布局,能不能有一种直观的方法,可以直接看到某个对象的内存布局呢?并且我可以不断地做实验,来观察一个对象内存中数据的变化。
当然有,而且还是官方的呢,就是 openJDK 官方提供的工具 JOL
使用起来十分简单,只需要 maven 引入即可
<dependency>
<groupId>org.openjdk.jol</groupId>
<artifactId>jol-core</artifactId>
<version>put-the-version-here</version>
</dependency>
然后代码中可以直接调用 ClassLayout.parseInstance(o).toPrintable()
,如下
public static void main(String[] args) {
Object o = new Object();
System.out.println(ClassLayout.parseInstance(o).toPrintable());
}
看控制台输出,可以直接看到对象头的二进制表示。由于 Object 没有成员变量,所以成员变量就没有体现出来。
java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
是不是非常直观?由于锁的信息正是存储在对象头中,所以我们可以用这个工具,看一下锁升级的过程。这样自己亲自操作一遍之后,锁升级就也变得直观起来了。
因为锁的信息是记录在对象头的 Mark Word 里,所以通过自己做实验,打印出不同阶段的对象信息,可以 看见 锁升级的过程。
这里我写了一段代码,有点长就不粘贴过来了,粘了估计你也不会点开看嘿嘿~
我只把输出结果打出来,信息太多,只取了表示锁类型的信息
001
(无锁)101
(匿名偏向锁)101
偏向锁)101
偏向锁)00
(轻量级锁)001
(无锁)10
(重量级锁)10
(重量级锁)001
(无锁)我们知道用 jstack
命令可以将当前 JVM 的线程快照 dump 成一个文件。
但这个文件不够直观,一堆文本,也有很多工具可以将其可视化,不过我用的最顺手也最漂亮的可视化工具,就是 fastthread 了,更方便的是它是个网页。
反正我第一次看这个网站时,是被它美到了。官网上看就非常的科技范,很带感,让人很想上传一个 dump 文件分析分析。
我上传了一个用 jstack 生成的 dump 文件,经过一段时间分析后,可以看到一个非常漂亮的页面
查看线程整体情况
查看线程分组情况
查看线程细节
还有很多,就不一一演示了,官网非常友好,大家可以前去逛逛~
看见 JVM 运行时的实时状态,非常有利于我们从系统层面了解程序的运行过程,不知道你有没有用过 jdk 自带的可视化工具 JConsole
有点丑,而且功能不是很强大,于是就有了 JProfile
JProfiler 是由 ej-technologies GmbH 公司开发的一款性能瓶颈分析工具,说白了就是 JConsole 的高级美化版,我们可以先来欣赏下它的外观。
这里我们用这样一段程序来跑,可以预想到这个程序不断占用堆内存并且无法被 gc,最后 OOM
public static void main(String[] args) throws Exception {
List<Byte[]> list = new ArrayList<>();
while (true) {
list.add(new Byte[1024*1024]);
Thread.sleep(100);
}
}
打开 JProfile 综合页面,实时跟进一些性能信息
最后 Java 程序也果然报出了 OOM
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
我们到对象监控面板可以查看到所有的对象信息,并且按照指定规则排序
当然针对本个实例,我们直接可以用里面的大对象分析视图来看(Biggest Object)
这样一下就找出了占内存空间最大的就是我们不断增长的 ArrayList 对象。
当然我们也可以用它分析线程,但一般分析某一瞬间的快照,还是到 fastthread 网站上更为直观,功能更强大一些。
JProfile 也可以通过插件,直接与 IDEA 相连,最终效果就是在 IDEA 里点击用 JProfile 去 run,就自动跳转到 JProfile 的监控程序里啦。
通过 javap
查看字节码,通过 strace
和 HSDIS
查看与计算机更底层的交互,再通过阅读 openJDK
的源码了解 native 方法和 JVM 实现,再手握一本 Java 语言规范
和 虚拟机规范
的官方文档,理论上说你可以自己摸索出 Java 这门语言的全部原理了。所以平时通过这些小工具自己多做一些实验,还是很有帮助的,你会得到一些比看博文甚至看书更为深入的理解。
看见 Java 的一些技巧就介绍到这里啦,如果大家还有类似这样的一些 看见 类型的工具或技巧,欢迎在留言区讨论,如果还有很多类似的工具,我可以在写一篇补充版,所以就拜托大家啦~
完
你觉得哪个工具最酷呢?
本文由哈喽比特于2年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/zBfvjq3gry2zsMoMir6oZA
京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。
日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。
据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。
今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。
日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。
近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。
据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。
9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...
9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。
据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。
特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。
据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。
近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。
据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。
9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。
《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。
近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。
社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”
2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。
罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。