天天看点

openjdk 源码分析 v0.1第一节 参考 第二节.openjdk源码模块 第三节.常用类 第四节.垃圾回收算法第五节.多线程,同步与可见性,内存模型(重排序)第六节.IO/NIO/AIO,reactor/proactor第七节 对象结构,字节码与模板解释器第八节 监控工具

第一节 参考

一.openjdk7:

https://blog.csdn.net/hcj116/article/details/54946551

https://blog.csdn.net/j754379117/article/details/53695426

https://www.jianshu.com/p/e53e7964db03

http://www.txazo.com/jvm/openjdk-compile.html

https://super2bai.github.io/JVM/build.html

https://www.jianshu.com/p/5107fc72558f

http://www.cnblogs.com/zyx1314/p/5638596.html

https://stackoverflow.com/questions/6000554/clang-complete-where-is-the-libclang-so-dylib-in-os-xhttps://liuzhengyang.github.io/2017/04/28/buildopenjdk/

sudo ln -s /Users/feivirus/Documents/software/apache-ant-1.8.2/bin/ant  /usr/bin

sudo ln -s /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/libclang.dylib /usr/local/lib/

二.openjdk8:

https://blog.csdn.net/manageer/article/details/72812149

三.openjdk9:

https://juejin.im/post/5a6d7d106fb9a01ca47abd8b

https://segmentfault.com/a/1190000005082098

https://www.gonwan.com/2017/12/01/building-debugging-openjdk8-from-source-on-macos-high-sierra/

https://www.zhihu.com/question/52169710

process handle SIGSEGV --stop=false

https://medium.com/@maxraskin/background-1b4b6a9c65be

add-dsym /Users/feivirus/Documents/project/eclipse/jdk9/build/macosx-x86_64-normal-server-slowdebug/support/modules_libs/java.base/server/libjvm.dylib.dSYM

https://apple.stackexchange.com/questions/309017/unknown-error-2-147-414-007-on-creating-certificate-with-certificate-assist

第二节.openjdk源码模块

 主要内容: 类二分模型,类加载,堆栈结构,解释器的模板表与转发表,编译器,函数分发指令集.

一.hotspot源码结构

(一)vm根目录下:

 1.Adlc:平台描述文件(cpu或os_cpu目录中的.ad文件)的编译器

 2.asm:汇编器

 3.c1 Client编译器

 4.ci 动态编译器的公共服务(从动态编译器到VM的接口)

 5.classfile 处理类文件(包括类加载和系统符号表等)

 6.code 管理动态生成的代码

 7.compiler 从VM调用动态编译器的接口

 8.gc_implementation GC实现

 9.gc_interface GC接口

 10.interpreter 解释器,包括模板解释器(官方版使用)和C++解释器(官方版未用)

 11.libadt:抽象数据结构

 12.memory 内存管理相关实现(老的分代式 GC 框架也位于此处)

 13.oops HotSpot VM的对象系统的实现

 14.opto Server编译器(即C2)

 15.prims HotSpot VM的对外接口,包括部分标准库的native部分实现和JVMTI实现

 16.rumtime 运行时支持库(包括线程管理、编译器调度、锁、反射等)

 17.services 用于支持JMX之类的管理功能的接口

 18.shark 基于LLVM的JIT编译器(官方版未用)

 19.utilities 一些基本工具类

(二).prims对外接口模块

主要包括jni,perf,jvm,jvmti四个模块.

1.jni模块(java native interface,允许java代码与本地代码交互,以 jni_前缀)

jni知识参考 https://blog.csdn.net/column/details/blogjnindk.html

2.jvm模块(对jni的补充,以jvm_前缀)

涉及jvm.h文件等.主要包含导出函数,比如访问jvm状态,字节码和class文件格式校验,各种IO和网络操作.

3.jvmti模块

监控和调优java程序

4.perf模块

以perf_为前缀,监控虚拟机.

(三).services模块

通过jmx监控和管理java应用。jmx参考https://www.cnblogs.com/dongguacai/p/5900507.html

分为Management,MemoryService,ThreadService,RuntimeService,MemoryManager,HeapDumper,ClassLoadingService,MemoryPool,AttachListener九个子模块.

(四)Runtime模块

分为Thread(线程队列),Arguments(vm参数解析),StubRoutines/StubCodeGenerator(Stub例程),Frame栈帧(frame.hpp),CompilationPolicy(编译策略),Init(系统初始化),VmThread模块(全局单例线程,维护操作队列VmOperationQueue,执行GC,对外提供监控), VMOperation(比如ThreadStop,FindDeadlocks,ForceSafepoint,ParallelGCSystemGC等),互斥锁,安全点,PerfData,,反射,

二.虚拟机启动

(一)虚拟机的生命周期

启动器分为通用启动器(Generic Launcher)和调试版启动器(gamma)两种.

通用启动器就是java和javaw,区别是javaw没有控制台窗口.gamma启动器入口位于hotspot/src/share/tools/luncher/java.c中.通用启动器入口在jdk/src/share/bin/main.c中(jdk9改在jdk/src/java.base/share/native/launcher下).

jdk/src/share/bin/main.c的main()->jdk/src/share/bin/java.c的JLI_Launch()->jdk/src/solaris/bin/java_md_solinux.c的JVMInit()新建线程调用JavaMain()->jdk/src/share/bin/java.c的JavaMain()(jvm启动核心操作)在该方法中依次调用InitializeJVM()初始化jvm(此方法进入hotspot/src/share/vm/prims/jni.cpp的JNI_CreateJavaVM()中->hotspot/src/share/vm/runtime/thread.cpp中的Threads::create_vm()->hotspot/src/share/vm/runtime/vmThread.cpp的VMThread::create()),调用LoadMainClass()加载主类,调用GetStaticMethodID()获取main方法的id号,调用CreateApplicationArgs()创建java的main方法的参数,调用CallStaticVoidMethod()进入java的main方法->javac中的DetachCurrentThread()->javac中的DestroyJavaVM()

启动过程的堆栈如下图

openjdk 源码分析 v0.1第一节 参考 第二节.openjdk源码模块 第三节.常用类 第四节.垃圾回收算法第五节.多线程,同步与可见性,内存模型(重排序)第六节.IO/NIO/AIO,reactor/proactor第七节 对象结构,字节码与模板解释器第八节 监控工具

进入java代码的main()方法前的堆栈如下图:

openjdk 源码分析 v0.1第一节 参考 第二节.openjdk源码模块 第三节.常用类 第四节.垃圾回收算法第五节.多线程,同步与可见性,内存模型(重排序)第六节.IO/NIO/AIO,reactor/proactor第七节 对象结构,字节码与模板解释器第八节 监控工具

其中StubRoutines::call_stub()是个函数指针,指向被调用的java的main函数地址.

(二)过程分析

1.main.c文件的main方法,主要是获取java进程参数,调用LoadJavaVM()加载libjvm,启动新线程启动JavaMain()方法.

第三节.常用类

主要是集合相关类,比如

Hashmap的红黑树,synchronized,proxy,serviceloader,threadpoolexecutor,classloader,aqs等.

第四节.垃圾回收算法

一.看垃圾回收的日志

2019-12-19T11:26:16.942-0800: 0.493: [GC (Allocation Failure) [PSYoungGen: 33264K->5110K(38400K)] 33264K->5950K(125952K), 0.0045817 secs] [Times: user=0.01 sys=0.01, real=0.00 secs] 

2019-12-19T11:26:16.942:gc开始时间

-0800:中国所在的东8区

0.493:相对于JVM启动时的间隔时间,单位是秒

GC:代表Minor GC。此字段还可以为Full GC.

Allocation Failure:触发gc的原因.此字段还可以是Ergonomics,代表jvm的自动gc.

PSYoungGen:年轻代还是年老代.

33264K->5110K:gc之前和gc之后,年轻代的使用量

38400K:年轻代总的空间大小

33264K->5950K:gc之前和gc之后,整个堆内存的使用情况

125952K:可用的堆的总的空间大小

0.0045817 secs:gc持续时间大小

user,sys,real:分别是gc消耗时间和,系统调用时间,stw的时间.

二.不同垃圾回收算法的核心区别

1.gc的过程是标记->清除->整理.

2.垃圾收集器的种类

Serial/Serial Old:串行GC.最古老的,单线程收集器.年轻代使用mark-copy,年老代使用mark-sweep-compact(标记-清除-整理)算法.两者都会触发STW.设置-XX:+UseSerialGC.

ParNew:Serial的多线程版本

Parallel Scavenge:年轻代的多线程收集器,默认收集器,采用mark-copy算法,回收期间不stw.

Parallel Old:Parallel Scavenge收集器的年老版本,采用Mark-sweep-Compact算法.

CMS:采用Mark-Sweep算法,最短的stw时间.年轻代使用Parallel New,mark-copy算法.年老代使用CMS,mark-sweep算法.在Initial Mark初始标记和Final REMARK重新标记时会stw.缺点是老年代内存碎片问题,在某些情况下GC会造成不可预测的暂停时间,特别是堆内存较大的情况下.依次经历Initial Mark->Concurrent Mark->Concurrent Preclean->Concurrent Abortable Preclean->Final Remark->Concurrent Sweep->Concurrent Reset过程.

G1:预测STW时间.

3.标记之后可以有清除,整理,复制三种处理方式.

清除的缺点是还有很多空闲内存, 却可能没有一个区域的大小能够存放需要分配的对象, 从而导致分配失败(在Java中就是OutOfMemoryError).

整理的缺点就是GC暂停时间会增加, 因为需要将所有对象复制到另一个地方, 然后修改指向这些对象的引用。

复制的缺点则是需要一个额外的内存区间, 来存放所有的存活对象。

4.年轻代和老年代处理方式不同。年轻代的特点是时间短,小对象。年老代的特点是默认都是存活的,时间长,大对象.

默认的gc设置是-XX:+UseParallelGC.在年轻代使用Parallel Scavenge,年老代使用Parallel Old.

第五节.多线程,同步与可见性,内存模型(重排序)

一.线程同步

内存一致性:对同一个变量,在物理内存中存一个,在不同的线程中,各有一份自己的缓存,需要保持这个变量的读写相等.

主要包括原子性,有序性,可见性.jvm自己的多线程的可见性.

缓存一致性协议:cpu的L1,L2,每个cpu有自己的写缓冲区,interl的mesi.主要是cpu硬件可见性.

二.重排序

编译器重排序

处理器重排序:指令重排序,内存系统重排序

内存屏障指令禁止处理器重排序:LoadLoad,StoreStore,LoadStore,StoreLoad

三.happens-befores规则

四.volatile

1.源码在jdk的LIRGenerator::volatile_field_load()方法中处理.

2.读写前后加内存屏障指令

3.写volatile变量,线程本地变量会刷新到主内存.读volatile的变量,线程本地变量会置无效,从主内存读取值.

五.final 也会加屏障,防止初始化时,读到前后两个值.

六.synchronized

1.分类:

锁对象(普通方法,加ACC_SYNCHRONIZED修饰符),锁class(静态方法),锁代码块(通过monitorenter,monitorexit指令实现).

2.实现方式:

(1).java对象头的Mark Word有轻量级锁,重量级锁和偏向锁的标志位.

(2).Monitor机制.每个线程有个monitor列表.

3.锁

(1)种类:自旋锁(默认自旋10次),适应性自旋锁(根据上次自旋成功或失败判断),锁消除,锁粗化,偏向锁,轻量级锁.

(2)状态:无锁,偏向锁,轻量级,重量级,锁只能升级不能降级.

七.AQS

1.使用CLH同步队列.lock时,入队列,当前节点变为尾节点,自旋判断prev节点是否释放锁.unlock出队时,把当前节点的锁状态释放。这样,后继节点就能拿到锁.

2.分为独占式和共享式.

3.使用LockSupport,通过Unsafe阻塞或者唤醒线程.

八.ReentrantLock

1.排他锁.继承AQS,分为公平锁,非公平锁,FairSync和NonFairSync.

九.ReentrantReadWriteLock

1.读写锁.可重入,支持公平性和非公平性.锁降级(写锁降级为读锁,获取写,获取读,释放写).

2.读锁内部维护HoldCounter计数器。

十.condition

1.包含一个FIFO队列,每个节点包含一个线程引用.调用await方法时加入队列尾部.signal方法唤醒队列的第一个节点线程.

十一.CAS

1.实现方式:通过处理器的LOCK#信号做总线加锁.缓存加锁.

2.问题:循环时间长.只能保证一个共享变量原子操作.ABA问题(加版本号).

十二.CyclicBarrier

1.内部使用ReentrantLock和Condition.

十三.CountDownLatch

1.内部依赖Sync实现,Sync继承自AQS.

2.await()在计数器到0之前一直等待.countDown()线程递减计数器.

十四.Semaphore

可用数量,获取到一个Semaphore,可用数量减1.Semaphore为0时,线程等待.

十五.Exchanger

Exchanger对象理解为一个包含两个格子的容器,通过exchanger方法可以向两个格子中填充信息。

十六.ConcurrentHashMap

第六节.IO/NIO/AIO,reactor/proactor

第七节 对象结构,字节码与模板解释器

一.对象结构

(一)Object结构

在jdk1.8,mac 64位下,空对象占用16个字节

Object = Header + Primitive Fields + Reference Fields + Alignment & Padding

1.对象头Header

Header: mark word + Klass pointer

mark word:64位是8字节,32位是4字节.

Klass pointer:32位是4字节,64位默认开启UseCompressedOops是4字节,不开启8字节.

空对象64位下是8+4=12 字节.

2.成员属性Primitive Fields + Reference Fields

空对象没有这部分.不同数据类型大小不同,比如int 4 bytes,double 8 bytes,引用一个word大小.数组也是对象,header中有一个int类型的length值,多4个字节.

3.对齐Alignment和补齐Padding

对齐:任何对象都是 8字节对齐.比如空对象,12 bytes的header部分,对齐加4个字节.

补齐:字节补齐4个字节.

(二)对象重排序

在heap中对field成员属性进行重排序,节省空间.

分配顺序原则:1.double > long > int > float > char > short > byte > boolean > object reference

2.Padding Size(4 bytes)的类型的优先级就高于大小>Padding Size的类型

3.子类和父类的field不混在一起,父类的field分配完再分配子类.

4.父类的的最后一个字段与子类的第一个字段以一个Padding Size(4 bytes)对齐

(三).Oop/Klass 模型

当在java中new一个对象时,在c++层面是在堆内存创建一个instanceOopDesc对象.instanceOopDesc对象通过元数据指针_metadata指向InstanceKlass元数据,比如方法区信息.

1.oop:Ordinary Object Pointer,表示对象的实例信息.class instanceOopDesc描述java层面的对象,就是对象头.继承自oopDesc.

class oopDesc {

  ...

 private:

  //对象头信息,markOop是个markOopDesc*类指针?但实际是个Word,8字节,一个字长,存储具体的数据

  //markOopDesc在markOop.hpp中定义.

  //成员变量有hashcode,gc分代年龄,锁状态标志等.

  //在markOop.hpp中文件注释有说明各种标志位。通过各种位操作函数修改这里面比特位的值.

  volatile markOop  _mark;

  //联合,只存一种

  union _metadata {

    //InstanceKlass 对象的指针

    Klass*      _klass;

    //压缩指针

    narrowKlass _compressed_klass;

  } _metadata;

  ...

}

markOop部分文件注释如下:

//  32 bits:

//  --------

//             hash:25 ------------>| age:4    biased_lock:1 lock:2 (normal object)

//             JavaThread*:23 epoch:2 age:4    biased_lock:1 lock:2 (biased object)

//             size:32 ------------------------------------------>| (CMS free block)

//             PromotedObject*:29 ---------->| promo_bits:3 ----->| (CMS promoted object)

//

//  64 bits:

//  --------

//  unused:25 hash:31 -->| unused:1   age:4    biased_lock:1 lock:2 (normal object)

//  JavaThread*:54 epoch:2 unused:1   age:4    biased_lock:1 lock:2 (biased object)

//  PromotedObject*:61 --------------------->| promo_bits:3 ----->| (CMS promoted object)

//  size:64 ----------------------------------------------------->| (CMS free block)

//

//  unused:25 hash:31 -->| cms_free:1 age:4    biased_lock:1 lock:2 (COOPs && normal object)

//  JavaThread*:54 epoch:2 cms_free:1 age:4    biased_lock:1 lock:2 (COOPs && biased object)

//  narrowOop:32 unused:24 cms_free:1 unused:4 promo_bits:3 ----->| (COOPs && CMS promoted object)

//  unused:21 size:35 -->| cms_free:1 unused:7 ------------------>| (COOPs && CMS free block)

2.Klass:存储对应c++对象的虚表等类的元数据信息.在元空间方法区.

InstanceKlass类,继承自Klass类,在instanceKlass.cpp的最上方文件注册中画出了InstanceKlass布局,包含

//  InstanceKlass layout:

//    [C++ vtbl pointer           ] Klass

//    [subtype cache              ] Klass

//    [instance size              ] Klass

//    [java mirror                ] Klass

//    [super                      ] Klass

//    [access_flags               ] Klass

//    [name                       ] Klass

//    [first subklass             ] Klass

//    [next sibling               ] Klass

//    [array klasses              ]

//    [methods                    ]

//    [local interfaces           ]

//    [transitive interfaces      ]

//    [fields                     ]

//    [constants                  ]

//    [class loader               ]

等.每个字段对应一个InstanceKlass的成员属性.ClassFileParser把class文件在运行时解析成InstanceKlass对象.Klass类中有对应java层面Setter/Getter方法的转换函数.

二.字节码

三.模板解释器

核心三个文件模板解释器templateInterpreter.cpp,模板解释器生成器templateInterpreterGenerator,模板表templateTable.cpp.

解释器分为两种,汇编类型TemplateInterpreterGenerator和c++类型CppInterpreterGenerator,共同父类是AbstractInterpreter.

1.解释器结构

每个模板的定义在templateTable.hpp中的Template类中,属性包含标志位,栈状态,目标代码等.

2.初始化过程

(1).字节码处理函数定义

在jvm启动时调用TemplateTable::initialize(),对每一个字节码调用TemplateTable::def(),定义处理函数.def的generator列是对应的处理函数,不过这个是个宏定义.TosState是字节码执行前后的栈顶状态.宏定义在templateTable.cpp中.比如#define istore TemplateTable::istore.

比如如果查找istore指令的处理方法,直接在TemplateTable.cpp或者对应的cpu架构下TemplateTable_xxx.cpp中搜索 TemplateTable::istore就可以搜到对应处理函数.在templateTable_x86_64.cpp中代码如下:

void TemplateTable::istore() {

  transition(itos, vtos);

  locals_index(rbx);

  //存到局部变量表

  __ movl(iaddress(rbx), rax);

}

(2).字节码处理函数使用

在Bytecodes::Code枚举中定义了所有的可以用的字节码.在TemplateInterpreterGenerator::set_entry_points_for_all_bytes()方法中遍历Bytecodes::Code枚举,对每个字节码枚举调用TemplateInterpreterGenerator::set_entry_points()方法设置处理函数.这个方法的实现在templateInterpreter.cpp文件中.set_entry_points()方法针对每个字节码生成两种处理函数,分别调用TemplateTable::template_for()和TemplateTable::template_for_wide(),窄版和宽版.在这两个方法里面进入TemplateInterpreterGenerator::set_short_entry_points()中.调用会调用TemplateInterpreterGenerator::generate_and_dispatch().在这个方法中有个类似aop的操作。

void TemplateInterpreterGenerator::generate_and_dispatch(Template* t, TosState tos_out) {

  if (PrintBytecodeHistogram)                                    histogram_bytecode(t);

#ifndef PRODUCT

  // debugging code

  if (CountBytecodes || TraceBytecodes || StopInterpreterAt > 0) count_bytecode();

  if (PrintBytecodePairHistogram)                                histogram_bytecode_pair(t);

  if (TraceBytecodes)                                            trace_bytecode(t);

  if (StopInterpreterAt > 0)                                     stop_interpreter_at();

  __ verify_FPU(1, t->tos_in());

#endif // !PRODUCT

  int step = 0;

  if (!t->does_dispatch()) {

    step = t->is_wide() ? Bytecodes::wide_length_for(t->bytecode()) : Bytecodes::length_for(t->bytecode());

    if (tos_out == ilgl) tos_out = t->tos_out();

    // compute bytecode size

    assert(step > 0, "just checkin'");

    // setup stuff for dispatching next bytecode

    if (ProfileInterpreter && VerifyDataPointer

        && MethodData::bytecode_has_profile(t->bytecode())) {

      __ verify_method_data_pointer();

    }

    //执行前的pro处理

    __ dispatch_prolog(tos_out, step);

  }

  // generate template

  //产生模板代码

  t->generate(_masm);

  // advance

  //执行字节码

  if (t->does_dispatch()) {

#ifdef ASSERT

    // make sure execution doesn't go beyond this point if code is broken

    __ should_not_reach_here();

#endif // ASSERT

  } else {

    // dispatch to next bytecode

    //调度下一个字节码

    __ dispatch_epilog(tos_out, step);

  }

}

第八节 监控工具

一.工具

hsdb,aprof,gcviewer,hprof,hsdis,jconsole,jhat,jinfo,jmap,jstack,visualvm

继续阅读