# jdk8u-nvm **Repository Path**: lc-1024/jdk8u-nvm ## Basic Information - **Project Name**: jdk8u-nvm - **Description**: 该项目仅为小组作业,不做其他用途 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2023-01-10 - **Last Updated**: 2023-01-10 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # README 这里只提交了修改过的几个代码,搜索nvm相关的就可以看到更改了什么 ## 安装 安装VMware workstation pro,使用激活码激活 从ftp.sjtu.edu.cn下载centos7的DVD iso,通过VMware启动 配置好账号密码,以及一些基础设置就可以了 ## 环境配置 由于没有图形化界面,所以网络连接需要通过终端启动 ```bash cd /etc/sysconfig/network-scripts/ vi ifcfg-ens33 ## 将最后一行ONBOOT改成yes即可 service network restart ## 然后就可以通过以下命令找到ip然后从外部使用ssh连接虚拟机了 ip address ``` 可以参考[[Set up zsh & tmux]]安装zsh并进行个性化配置 ## C++环境配置 1. 安装g++,clang++,make等编译环境,我们尝试采用Red Hat提供的devtoolset和llvm-toolset,其中devtoolset最新版本为11,支持g++ 11.2.1,能够较好地编译C++20([g++ status](https://gcc.gnu.org/projects/cxx-status.html)),llvm-toolset最新版本好像是10,但是仅在rhel上才能使用,centos中貌似只能安装7,支持到clang++ 5.0.1,只能较好地编译C++17([clang++ status](https://clang.llvm.org/cxx_status.html)),so we can try new features of C++20 (even C++2b) using g++, and just compile project with clang++. ```bash yum upgrade yum install centos-release-scl yum install devtoolset-11 yum install llvm-toolset-7 scl enable devtoolset-11 zsh scl enable llvm-toolset-7 zsh ``` 2. 为了进行更好的项目管理,我们还需要安装cmake来编译的C++项目,从[cmake官网](https://cmake.org/download/)找到最新版本的cmake源码并下载(最好先下载到电脑然后scp到虚拟机) ```bash tar zxvf cmake-3.25.0-rc1.tar.gz cd cmake-3.25.0-rc1 ./bootstrap # 我实际用的是./configure gmake gmake install ## 不知道make可不可以,可以的话make -j 6指定6个core并行编译 ln -s /usr/local/bin/cmake /usr/bin/cmake ``` ## 下载源码,配置环境 使用centos7,源码下载与更新 ``` hg clone http://hg.openjdk.java.net/jdk8u/jdk8u jdk8u cd jdk8u bash ./get_source.sh ``` 然后安装编译的jdk,`java-1.7.0-openjdk-devel`或者干脆安装`java-1.8.0-openjdk-devel` ## 配置,编译jdk 编译时,禁止压缩debuginfo ``` bash ./configure --with-target-bits=64 --with-debug-level=slowdebug --enable-debug-symbols ZIP_DEBUGINFO_FILES=0 make all ZIP_DEBUGINFO_FILES=0 ``` make的时候按照要求yum install对应的东西就可以了 `ZIP_DEBUGINFO_FILES=0`可以在编译后`unzip libjvm.diz`解压debuginfo,之后应该就可以只是`make hotspot ZIP_DEBUGINFO_FILES=0`来编译hotspot而不编译别的部分了 ## 测试 写个Hello.java,用make得到的javac编译,然后使用java运行 ``` public class Hello{   public static void main(String[] args){     System.out.println("hello world");   } } ``` ``` ./build/linux-x86_64-normal-server-slowdebug/jdk/bin/javac Hello.java -g ./build/linux-x86_64-normal-server-slowdebug/jdk/bin/java Hello ``` 能正常运行打出hello world ## 调试 GDB调试java,调试时忽略segfault ``` gdb ./build/linux-x86_64-normal-server-slowdebug/jdk/bin/java b hotspot/src/os/linux/vm/os_linux.cpp:3275 handle SIGSEGV nostop noprint pass run Hello ``` 然后就可以看到函数停在了mmap的调用函数`anon_mmap`(需要continue两次,前两次是在CodeCache_init,不是在universe_init) ![[mmap调用backtrace.png]] 在`initial_heap()`的`ParallelScavengeHeap::initialize()`中,调用了多次anon_map: 1. 真正的`heap_rs`初始化,也就是申请堆内存,这肯定是我们需要修改的,这次的申请带了requested_addr=0x86000000,总共申请了1952MB的空间 2. `barrier_set`,CardTableModRefBS分配(不清楚是什么) 3. `psOldGen`分配内存,很奇怪,oldGen分配了内存但是youngGen没有申请内存 4. `PSParallelCompact`申请了三块内存,分别用于存储`ParMarkBitMap`,`_summary_data._region_data`和`_summary_data._block_data` 在`Metaspace::global_initialize()`中,也调用了两次anon_mmap,一次是用来分配metaspace的空间的(申请的是requested_addr=0x100000000开始的空间),另一次是用来分配VirtualSpaceNode的 但是,如果仔细观察就会发现,上述所有的anon_mmap都是`ReservedSpace::initialize()`调用的,而heap_rs的初始化是`ReservedHeapSpace`创建`ReservedSpace`然后申请的内存,所以如果要修改内存分配的方式的话至少需要修改`ReservedHeapSpace`的构造函数,让他通过另一种方式分配内存(或者还是修改`ReservedSpace`,但是要针对heap的分配进行特殊的处理) ## new 编译和调试下面这段java代码 ``` class Student{     private String name="lc";     private int age = 11;     public Student(int a) {         age = a;     }  } public class StudentDemo {     public static void main(String[] args) {         Student s = new Student(12);     } } ``` ``` b jdk/src/share/bin/java.c:478 run StudentDemo continue # 跳过SIGSEGV ``` 此时会停在JavaMain函数,然后就会开始运行java代码,这个时候我们再给`_new`,`newarray`或者`anewarray`打上断点,就可以定位到java代码里面产生的new了 ``` b hotspot/src/share/vm/interpreter/interpreterRuntime.cpp:154 b hotspot/src/share/vm/interpreter/interpreterRuntime.cpp:155 c ``` 需要注意的是,这时候会有三次调用_new。 此时运行c能够跳过第一次出现的SIGSEGV,可以进去154行看获取klass发生了什么,然后到155行就能查看`k_oop`存着的name,`p k_oop->_name->base()`可以看到第一个class是`Student`,同样的方法可以看到第二个class是`jdk/internal/misc/TerminatingThreadLocal$1`,第三个是`java/lang/Shutdown$Lock\253\024`,那么真正获取申请空间的就是第一次_new了。 一路n运行到177行,运行三次s就能进到allocate_instance函数里面,同样n到obj_allocate,几次s也可以调试到该函数内部,然后还是一样n到关键的函数,s几次进去common_mem_allocate_init,common_mem_allocate_noinit,运行到`hotspot/src/share/vm/gc_interface/collectedHeap.inline.hpp:136`发现使用TLAB(ThreadLocalAllocBuffer)分配空间 ![[tlab分配空间.png]] 而所谓的分配也只不过是将top往后移并将当前的top赋予该变量而已。 但是获取了这个地址发现这个地址有点莫名其妙`0xd75daae0`,看起来是堆上的位置(0x8600,0000~0x10000,0000),看文章说是Eden上的空间(看了一下eden的bottom,`p ((ParallelScavengeHeap*)Universe::heap())->young_gen()->eden_space()->_bottom`,0xd7580000说明tlab确实是eden开头分配的空间),查看这个top地址里面的东西,显示的是`0xbaad babe baad babe`,初始化之后显示的东西就有点让人看不懂了,我在怀疑初始化出来的到底是不是instance,还是就只是klass的instance。 看文章忽然感觉貌似就是Java对象的instance,然后我就去修改java代码,让类的内容更多一点,发现确实obj申请的空间也越来越大,并且之后几个byte都被初始化成了设定的数字,所以可以肯定这里确实就是最终分配的对象实例的地方了。可以看出来还是在堆上把一块地址拿过来用,没有问题。 *所以说,allocate_instance就是申请对象的地方* ## youngGen & oldGen 由于在上面的过程中看到eden的地址很奇怪,所以还是下定决心把young-oldGen的分配和初始化再看一遍,下面low的是OldGen(0x8600 0000~0xd758 0000),high是YoungGen(0xd758 0000~0x10000 0000)的大小,总的大小是1952MB,初始时是122MB,和我们之前观察到的一致。 ![[AdjoiningGenerations分配old-youngGen空间.png]] 然后还发现,youngGen在initial_word的时候`SpaceMangler::mangle_region(cmr);`会把所有的word填充为`0xbaad babe`,范围是`5308416*8`,也就是init_high_byte_size的大小,所以理论上来说到了这一步就应该看到堆上的空间被修改了才对,修改时调用的是`Copy::fill_to_words(mr.start(), mr.word_size(), badHeapWord);`,其中badHeapWord就是上面那个`0xbaadbabe`。仔细看fill_to_words其实也就只是循环把每个word都赋值一遍而已。 ![[youngGen初始化空间.png]] 相比之下oldGen貌似更复杂一些,内部有很多组件需要初始化和申请,但是看下来没有什么值得注意的,如果是在堆上申请的,那么原本的堆也就能处理,如果是其他方式申请的,那更不需要做修改。 # 修改代码 在`ReservedHeapSpace`的构造函数调用`ReservedSpace`的构造函数时,增加一个nvm的参数为true,然后在`ReservedSpace.initialize()`中判断是否使用nvm,如果使用的话便不调用原来的`attempt_reserve_memory_at`函数而调用新的`attempt_reserve_nvm_at`函数(该函数最终调用使用pmdk的`nvm_mmap()`函数) ## 现在的nvm_mmap 现在暂时还是调用mmap申请空间,但是即便是改成pmem_map_file最终也只是调用mmap(除非设备支持dax)。但是pmem_map_file不能指定虚拟内存的地址,也就是会随机映射,不过已经测试过,即便不能映射到requested_addr也还是能正常运行。 ``` static char* nvm_mmap(char* requested_addr, size_t bytes, bool fixed) {   char * addr;   int flags, fd;   // char *pmemaddr;   // size_t mapped_len;   // int is_pmem;   // /* memory map it */   // if ((pmemaddr = (char*)pmem_map_file("/run/nvm_file", bytes, O_CREAT, 0666, &mapped_len, &is_pmem)) == NULL) {   //  perror("pmem_map");   //  exit(1);   // }   fd = open("/run/nvm_file", O_RDWR);   assert(fd > 0, "can't open /run/nvm_file");   flags = MAP_SHARED | MAP_NORESERVE; // | MAP_ANONYMOUS;   if (fixed) {     assert((uintptr_t)requested_addr % os::Linux::page_size() == 0, "unaligned address");     flags |= MAP_FIXED;   }   // Map reserved/uncommitted pages PROT_NONE so we fail early if we   // touch an uncommitted page. Otherwise, the read/write might   // succeed if we have enough swap space to back the physical page.   addr = (char*)::mmap(NULL, bytes, PROT_READ | PROT_WRITE,                        flags, fd, 0);   if (addr != MAP_FAILED) {     // nvm_mmap() should only get called during VM initialization,     // don't need lock (actually we can skip locking even it can be called     // from multiple threads, because _highest_vm_reserved_address is just a     // hint about the upper limit of non-stack memory regions.)     if ((address)addr + bytes > _highest_vm_reserved_address) {       _highest_vm_reserved_address = (address)addr + bytes;     }   }   return addr == MAP_FAILED ? NULL : addr; } ```