1.记一次源码追踪分析,何系从Java到JNI,源源码再到JVM的何系C++:fileChannel.map()为什么快;源码分析map方法,put方法
2.OpenJDK17-JVM 源码阅读 - ZGC - 并发标记 | 京东物流技术团队
3.JVM之创建对象源码分析
4.jvmè°è¯å·¥å
·ç±»ä½¿ç¨ (jvisualvm.exe)
记一次源码追踪分析,源源码从Java到JNI,何系再到JVM的源源码支付挂机软件源码C++:fileChannel.map()为什么快;源码分析map方法,put方法
前言
在系统IO相关的何系系统调用有read/write,mmap,源源码sendfile等这些。何系
其中read/write是源源码普通的读写,每次都需要将buffer从用户空间拷贝到内核空间;
而mmap使用的何系是内存映射,会将磁盘文件对应的源源码页映射(拷贝)到内核空间的page cache,并记录到用户进程的何系页表中,使得用户空间也可以像操作用户空间一样操作该文件的源源码映射,最后再由操作系统来讲该映射(脏页)回写到磁盘;
sendfile则使用的何系是零拷贝技术,在mmap的基础上,当发送数据的时候只拷贝fd和offset等元数据信息,而将数据主体直接拷贝至protocol buffer,实现了内核数据零冗余的tbp指标公式源码零拷贝技术
本文地址:/post//
问题/目的问题1Java中哪些API使用到了mmap问题2怎么知道该API使用到了mmap,如何追踪程序的系统调用目的1源码中分析验证,从Java到JNI,再到C++:fileChannel.map()使用的是系统调用mmap目的2源码验证分析:调用mmapedByteBuffer.put(Byte[])时JVM在搞些什么?mmap比普通的read/write快在哪?揭晓答案1mmap在Java NIO中的体现/使用看一个例子
// 1GBpublic static final int _GB = 1**;File file = new File("filename");FileChannel fileChannel = new RandomAccessFile(file, "rw").getChannel();MappedByteBuffer mmapedByteBuffer = fileChannel.map(FileChannel.MapMode.READ_WRITE, 0, _GB);for (int i = 0; i < _GB; i++) { count++;mmapedByteBuffer.put((byte)0);}其中fileChannel.map()底层使用的就是系统调用mmap,函数签名为: public abstract MappedByteBuffer map(MapMode mode,long position, long size)throws IOException
答案2程序执行的系统调用追踪/** * @author Tptogiar * @description * @date /5/ - : */public class TestMappedByteBuffer{ public static final int _4kb = 4*;public static final int _GB= 1**;public static void main(String[] args) throws IOException, InterruptedException { // 为了方便在日志中找到本段代码的开始位置和结束位置,这里利用文件io来打开始标记FileInputStream startInput = null;try { startInput = new FileInputStream("start1.txt");startInput.read();} catch (IOException e) { e.printStackTrace();}File file = new File("filename");FileChannel fileChannel = new RandomAccessFile(file, "rw").getChannel();MappedByteBuffer map = fileChannel.map(FileChannel.MapMode.READ_WRITE, 0, _GB); //我们想分析的语句问题2for (int i = 0; i < _GB; i++) { map.put((byte)0); // 下文中需要分析的语句目的2}// 打结束标记FileInputStream endInput = null;try { endInput = new FileInputStream("end.txt");endInput.read();} catch (IOException e) { e.printStackTrace();}}}把上面这段代码编译后把“.class”文件拉到linux执行,并用linux上的strace工具记录其系统调用日志,拿到日志文件我们可以在日志中看到以下信息(关于怎么拿到日志可以参照我的博文:无(代写)):
注:日志有多行,这里只选取我们关注的
// ...// 看到了我们打的开始标志openat(AT_FDCWD, "start1.txt", O_RDONLY) = -1 ENOENT (No such file or directory)// ... // 打开文件,文件描述符fd为6openat(AT_FDCWD, "filename", O_RDWR|O_CREAT, ) = 6// 判断文件状态fstat(6, { st_mode=S_IFREG|, st_size=, ...}) = 0// ... // 判断文件状态fstat(6, { st_mode=S_IFREG|, st_size=, ...}) = 0// 进行内存映射mmap(NULL, , PROT_READ|PROT_WRITE, MAP_SHARED, 6, 0) = 0x7f2fd6cd// ...// 程序退出exit(0)// 看到了我们打的结束标志openat(AT_FDCWD, "end.txt", O_RDONLY) = -1 ENOENT (No such file or directory)在上面程序的系统调用日志中我们确实看到了我们打的开始标志,结束标志。在开始标志和结束标志之间我们看到了我们的文件"filename"确实被打开了,文件描述符fd = 6;在打开文件后紧接着又执行了系统调用mmap,这一点我们Java代码一致,这样,我们就验证了我们答案1中的结论,可以开始我们的下文了
源码追踪分析,从Java到JNI,再到JVM的碎燕窝溯源码C++目的1寻源之旅:fileChannel.map()我们知道我们执行Java代码fileChannel.map()确实会在底层调用系统调用,那怎么在源码中得到验证呢?怎么落脚于源码进行分析呢?下面开始我们的寻源之旅
FileChannelImpl.map() 注:由于代码较长,这里代码中略去了一些我们不关注的,比如异常捕获等
public MappedByteBuffer map(MapMode mode, long position, long size)throws IOException{ // ...try { // ...synchronized (positionLock) { // ...long mapPosition = position - pagePosition;mapSize = size + pagePosition;try { // !我们要找的语句就在这!addr = map0(imode, mapPosition, mapSize);} catch (OutOfMemoryError x) { // 如果内存不足,先尝试进行GCSystem.gc();try { Thread.sleep();} catch (InterruptedException y) { Thread.currentThread().interrupt();}try { // 再次试着mmapaddr = map0(imode, mapPosition, mapSize);} catch (OutOfMemoryError y) { // After a second OOME, failthrow new IOException("Map failed", y);}}} // ...} finally { // ...}}上面函数源码中真正执行mmap的语句是在addr = map0(imode, mapPosition, mapSize),于是我们寻着这里继续追踪
FileChannelImpl.map0()
// Creates a new mappingprivate native long map0(int prot, long position, long length)throws IOException;可以看到,该方法是一个native方法,所以后面的源码我们需要到这个FileChannelImpl.class对应的fileChannelImpl.c中去看,所以我们需要去找到JDK的源码
在JDK源码中我们找到fileChannelImpl.c文件
fileChannelImpl.c 根据JNI的对应规则,我们找到该文件内对应的Java_sun_nio_ch_FileChannelImpl_map0方法,其源码如下:
JNIEXPORT jlong JNICALLJava_sun_nio_ch_FileChannelImpl_map0(JNIEnv *env, jobject this, jint prot, jlong off, jlong len){ void *mapAddress = 0;jobject fdo = (*env)->GetObjectField(env, this, chan_fd);jint fd = fdval(env, fdo);int protections = 0;int flags = 0;if (prot == sun_nio_ch_FileChannelImpl_MAP_RO) { protections = PROT_READ;flags = MAP_SHARED;} else if (prot == sun_nio_ch_FileChannelImpl_MAP_RW) { protections = PROT_WRITE | PROT_READ;flags = MAP_SHARED;} else if (prot == sun_nio_ch_FileChannelImpl_MAP_PV) { protections =PROT_WRITE | PROT_READ;flags = MAP_PRIVATE;}// !我们要找的语句就在这里!mapAddress = mmap(0,/* Let OS decide location */len,/* Number of bytes to map */protections,/* File permissions */flags,/* Changes are shared */fd, /* File descriptor of mapped file */off); /* Offset into file */if (mapAddress == MAP_FAILED) { if (errno == ENOMEM) { JNU_ThrowOutOfMemoryError(env, "Map failed");return IOS_THROWN;}return handle(env, -1, "Map failed");}return ((jlong) (unsigned long) mapAddress);}我们要找的语句就上面代码中的mapAddress = mmap(0,len,protections,flags,fd,off),至于为什么不是直接的mmap,而是mmap,是因为这里的mmap是一个宏,在文件上方有其定义,如下:
#define mmap mmap至此,我们就在源码中得到验证了我们问题2中的结论:fileChannelImpl.map()底层使用的是mmap系统调用
目的2寻源之旅:mmapedByteBuffer.put(Byte[ ])接着我们来看看当我们调用mmapedByteBuffer.put(Byte[])JVM底层在搞些什么动作
MappedByteBuffer ?首先我们得知道,当我们执行MappedByteBuffer map = fileChannel.map(FileChannel.MapMode.READ_WRITE,快排算法源码 0, _GB)时,实际返回的对象是DirectByteBuffer类的实例,因为MappedByteBuffer为抽象类,且只有DirectByteBuffer继承了它,看下面两图就明白了
DirectByteBuffer 于是我们找到DirectByteBuffer内的put(Byte[ ])方法
public ByteBuffer put(byte x) { unsafe.putByte(ix(nextPutIndex()), ((x)));return this;}可以看到该方法内实际是调用Unsafe类内的putByte方法来实现功能的,所以我们还得去看Unsafe类
Unsafe.class
public native voidputByte(long address, byte x);该方法在Unsafe内是一个native方法,所以所以我们还得去看unsafe.cpp文件内对应的实现
unsafe.cpp
在JDK源码中,我们找到unsafe.cpp
在这份源码内,没有使用JNI内普通加前缀的方法来形成对应关系
不过我们还是能顺着源码的蛛丝轨迹找到我们要找的方法
注意到源码中有这样的注册机制,所以我们可以知道我们要找的代码就是上图中标注的代码
顺藤摸瓜,我们就找到了该方法的定义
UNSAFE_ENTRY(void, Unsafe_SetNative##Type(JNIEnv *env, jobject unsafe, jlong addr, java_type x)) \UnsafeWrapper("Unsafe_SetNative"#Type); \JavaThread* t = JavaThread::current(); \t->set_doing_unsafe_access(true); \void* p = addr_from_java(addr); \*(volatile native_type*)p = x; \t->set_doing_unsafe_access(false); \UNSAFE_END \该方法内主要的逻辑语句就是以下两句:
/** * @author Tptogiar * @description * @date /5/ - : */public class TestMappedByteBuffer{ public static final int _4kb = 4*;public static final int _GB= 1**;public static void main(String[] args) throws IOException, InterruptedException { // 为了方便在日志中找到本段代码的开始位置和结束位置,这里利用文件io来打开始标记FileInputStream startInput = null;try { startInput = new FileInputStream("start1.txt");startInput.read();} catch (IOException e) { e.printStackTrace();}File file = new File("filename");FileChannel fileChannel = new RandomAccessFile(file, "rw").getChannel();MappedByteBuffer map = fileChannel.map(FileChannel.MapMode.READ_WRITE, 0, _GB); //我们想分析的语句问题2for (int i = 0; i < _GB; i++) { map.put((byte)0); // 下文中需要分析的语句目的2}// 打结束标记FileInputStream endInput = null;try { endInput = new FileInputStream("end.txt");endInput.read();} catch (IOException e) { e.printStackTrace();}}}0至此,我们就知道:其实我们调用mmapedByteBuffer.put(Byte[ ])时,JVM底层并不需要涉及到系统调用(这里也可以用strace工具追踪从而得到验证)。也就是说通过mmap映射的空间在内核空间和用户空间是共享的,我们在用户空间只需要像平时使用用户空间那样就行了————获取地址,设置值,而不涉及用户态,内核态的代驾源码saas切换
总结fileChannelImpl.map()底层用调用系统函数mmap
fileChannelImpl.map()返回的其实不是MappedByteBuffer类对象,而是DirectByteBuffer类对象
在linux上可以通过strace来追踪系统调用
JNI中“.class”文件内方法与“.cpp”文件内函数的对应关系不止是前缀对应的方法,还可以是注册的方式,这一点的追寻代码的时候有很大帮助
directByteBuffer.put()方法底层并没有涉及系统调用,也就不需要涉及切态的性能开销(其底层知识执行获取地址,设置值的操作),所以mmap的性能就比普通读写read/write好
...
原文:/post/OpenJDK-JVM 源码阅读 - ZGC - 并发标记 | 京东物流技术团队
ZGC简介:
ZGC是Java垃圾回收器的前沿技术,支持低延迟、大容量堆、染色指针、读屏障等特性,自JDK起作为试验特性,JDK起支持Windows,JDK正式投入生产使用。在JDK中已实现分代收集,预计不久将发布,性能将更优秀。
ZGC特征:
1. 低延迟
2. 大容量堆
3. 染色指针
4. 读屏障
并发标记过程:
ZGC并发标记主要分为三个阶段:初始标记、并发标记/重映射、重分配。本篇主要分析并发标记/重映射部分源代码。
入口与并发标记:
整个ZGC源码入口是ZDriver::gc函数,其中concurrent()是一个宏定义。并发标记函数是concurrent_mark。
并发标记流程:
从ZHeap::heap()进入mark函数,使用任务框架执行任务逻辑在ZMarkTask里,具体执行函数是work。工作逻辑循环从标记条带中取出数据,直到取完或时间到。此循环即为ZGC三色标记主循环。之后进入drain函数,从栈中取出指针进行标记,直到栈排空。标记过程包括从栈取数据,标记和递归标记。
标记与迭代:
标记过程涉及对象迭代遍历。标记流程中,ZGC通过map存储对象地址的finalizable和inc_live信息。map大小约为堆中对象对齐大小的二分之一。接着通过oop_iterate函数对对象中的指针进行迭代,使用ZMarkBarrierOopClosure作为读屏障,实现了指针自愈和防止漏标。
读屏障细节:
ZMarkBarrierOopClosure函数在标记非静态成员变量的指针时触发读屏障。慢路径处理和指针自愈是核心逻辑,慢路径标记指针,快速路径通过cas操作修复坏指针,并重新标记。
重映射过程:
读屏障触发标记后,对象被推入栈中,下次标记循环时取出。ZGC并发标记流程至此结束。
问题回顾:
本文解答了ZGC如何标记指针、三色标记过程、如何防止漏标、指针自愈和并发重映射过程的问题。
扩展思考:
ZGC在指针上标记,当回收某个region时,如何得知对象是否存活?答案需要结合标记阶段和重分配阶段的代码。
结束语:
本文深入分析了ZGC并发标记的源码细节,对您有启发或帮助的话,请多多点赞支持。作者:京东物流 刘家存,来源:京东云开发者社区 自猿其说 Tech。转载请注明来源。
JVM之创建对象源码分析
欢迎探索我的技术分享:《半栈工程师》 对于Java对象的创建,我过去只是停留在理论层面,但最近研究HotSpot虚拟机时,我深入剖析了JVM创建Java对象的底层机制。Java对象创建流程详解
首先,我们从一个简单的实例开始,看看如何通过代码创建一个Dog对象: 代码中new Dog()在编译成字节码后,会变成new #2,这里的new是实例化对象的关键字,#2则指向常量池中的Dog类索引。常量池是类编译后的存储区域,包含了各种符号引用和常量。new指令源码剖析
接下来,我们将深入new指令的源码。虽然涉及汇编代码,但无需立即深入,先了解一下《JVM之模板解释器》会有所帮助。新指令的运行过程如下:从指令中获取类在常量池的索引,存入rdx寄存器,并记录当前指令地址。
获取常量池地址和元素类型数组_tags,用于后续类型检查。
检查元素类型是否为JVM_CONSTANT_Class,如果不是,进入慢速分配。
获取并入栈类的运行时数据结构InstanceKlass,即类的内存地址。
判断类是否已解析,未解析则执行慢速分配,解析过的进入快速分配。
计算类实例大小并分配内存,首先尝试TLAB区,失败则在Eden区分配。
初始化对象实例数据和对象头。
如果类未解析,执行慢速分配过程。
总结
至此,我们了解了Java对象从创建到初始化的全过程。虽然使用了模板解释器,但理解字节码解释器中的相关方法也是个不错的选择。如果你对HotSpot源码感兴趣,欢迎加入讨论,我的****是wechat:wang_atbeijing。jvmè°è¯å·¥å ·ç±»ä½¿ç¨ (jvisualvm.exe)
ç®æ ï¼ä½¿ç¨JDKèªå¸¦çJVMçæµå·¥å ·è°è¯å å使ç¨æ åµåå æ é®é¢ææ¥ç®è¦è¯´æï¼å¨å®é 项ç®å¼åè¿ç¨ä¸ï¼å¦æ使ç¨å¤çº¿ç¨ï¼ä½æ¯æ²¡ææ§å¶å¥½çº¿ç¨æ°éçæ åµä¸ï¼å°±ä¼åºç°å å å溢åºé®é¢ï¼å¯¼è´åè½æå¡å®æºï¼å¦æ严éå¾å¯è½å¯¼è´æå¡å¨å®æºé®é¢ãå½åºç°å å溢åºæ¶ï¼åªè½çå°ç®åçå å溢åºæ¥å¿ï¼ææ¥é®é¢æ¯«æ 头绪ï¼ä¸ç¥éåªé线ç¨åºç°é®é¢ï¼è¿æ ·å°±å¾é¾è§£å³é®é¢ï¼
è¿æ¶æ们就å¯ä»¥éè¿JDKèªå¸¦çJVMçæ§å·¥å ·æ¥çæ¯ä¸ªçº¿ç¨ççå½å¨æ以åç¸å ³æºç 追溯ï¼è¿æ ·å°±å¯ä»¥æ¸ æ¥æäºççæ¸ é®é¢åºç°å¨åªéï¼ç¶åæ ¹æ®å®é æ åµè§£å³é®é¢ï¼
éè¿ä¸å¾å¯ä»¥çå°æ¬å°æå¡ä¸ææç线ç¨åç¸å ³ç¶æï¼å¦æ线ç¨åºç°é®é¢éè¦ææ¥æ¶ï¼éè¦æ¥çå ·ä½æ§è¡çæ¹æ³ï¼é£ä¹å°±éè¦å¿«ç §æ¹å¼æ¥çï¼å ·ä½æ¹å¼å¦ä¸ï¼
ç¹å»æ½æ ·å¨ï¼ç¶åéæ©CPUæ½æ ·ï¼ç¹å»åæ¢ï¼å¨ç¹å»ä¸é¢çå¿«ç §æé®ï¼å°±å¯ä»¥è·åææææ线ç¨çä¸æ¬¡å¿«ç §ï¼ç¶åå°±å¯ä»¥çå°æ¯ä¸ªçº¿ç¨æ§è¡çæºç ï¼å ·ä½æä½å¦ä¸å¾ï¼
éè¿ä¸è¿°çæä½å³å¯æ¥ç线ç¨å ·ä½æ¶åçæºç ï¼ä»èææ¥é®é¢ï¼
ä¸é¢è¯´çæ¯æ¬å°æå¡çæµï¼ä½æ¯æå¾å¤æ åµæ¬å°æå¡æ¯ææ¥ä¸å°é®é¢çï¼åªæå¨æå¡å¨ä¸é¢æè½çåºé®é¢ï¼é£ä¹æ们就éè¦è¿ç¨è¿æ¥æå¡å¨ä¸é¢æå¡ï¼è¿è¡çæ§ï¼æ¥çå ·ä½çº¿ç¨çè¿è¡æ åµåæºç åæ
è¿ç¨é ç½®éè¦å¨å¯å¨Javaæå¡çæ¶åï¼å¨å¯å¨å½ä»¤ä¸é¢æ·»å æå®å¯å¨åæ°ï¼è¿éæä¾çå½ä»¤æ¯æ£å¸¸æ åµä¸å®æ´çå¯å¨jarå çå½ä»¤ï¼å ·ä½å½ä»¤åæ°ä¹å¾æ¸ æ¥ï¼å½ä»¤å¦ä¸ï¼
ps:portæ¯çæ§æéç端å£ï¼ä¹å°±æ¯å¯å¨æå¡æå®ç端å£ï¼ä½æ¯ç«¯å£è¦å¯¹å¤å¼æ¾ï¼ä¸è¬çæ®éæå¡ç«¯å£æ¯ä¸ä¼å¯¹å®å¼æ¾çï¼è¿ä¸ç¹éè¦æ³¨æ
hostnameæ¯å¯¹åºçæ§çipï¼æè¿éç¨çå°±æ¯æå¡å¨ipï¼å¦æè¿æ¥ä¸ä¸çæ åµä¸ï¼å¯ä»¥å°è¯ä½¿ç¨ hostname -i è·åç对åºipï¼å ·ä½æ åµå ·ä½åæ
ç¶åå¯å¨æå¡ä¹åï¼éä¸è¿ç¨å³é®æ·»å è¿ç¨ä¸»æºï¼è¾å ¥è¿ç¨ä¸»æºipåç¡®å®å³å¯ï¼ç¶åéæ©ä¸»æºipï¼å³é®æ·»å JMXè¿æ¥ï¼è¾å ¥è®¾å®ç端å£å·ï¼ç¹å»ç¡®å®å³å¯ï¼ç¶åå°±å¯ä»¥çæ§è¿ç¨æå¡å¨äºï¼æ¥ä¸æ¥çæ¥ç线ç¨çæ åµãçæ§æå¡æ åµåææ¥é®é¢ä½¿ç¨å¿«ç §ï¼å°±åä¸é¢æ¬å°çæä½ä¸æ ·äºï¼
ç»è¿ä¸è¿°æä½å°±å¯ä»¥éè¿JDKèªå¸¦ççæ§è½¯ä»¶ï¼è¿è¡çæ§Javaç¨åºçè¿è¡æ åµä»¥åæå¡å¨çè¿è¡æ åµå¦ï¼
æ¬äººåèï¼å¦æé®é¢æ¬¢è¿å¤§å®¶ææ£ï¼å ±åè¿æ¥ï¼