天天看點

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

繼續閱讀