《HotSpot实战》—— 2.1 HotSpot内核-阿里云开发者社区

开发者社区> 异步社区> 正文

《HotSpot实战》—— 2.1 HotSpot内核

简介: 我们知道,HotSpot项目主要是由C++语言开发的,对于Java程序员来说,直接阅读这部分源代码可能会有些吃力。因此,我们有必要先阐释一些语言上的差异,扫清这些学习障碍。
+关注继续查看

本节书摘来异步社区《HotSpot实战》一书中的第2章,第2.1节,作者:陈涛,更多章节内容可以访问云栖社区“异步社区”公众号查看。

2.1 HotSpot内核

在引入HotSpot内核模块之前,我们有必要掌握一些阅读源代码的技巧。

2.1.1 如何阅读源代码

我们知道,HotSpot项目主要是由C++语言开发的,对于Java程序员来说,直接阅读这部分源代码可能会有些吃力。因此,我们有必要先阐释一些语言上的差异,扫清这些学习障碍。

1.宏

实际上,Java语言在语法上与C和C++是颇为相似的。除了一些在Java中没有提供的语法和特性,大多数C/C++代码还是很容易被Java程序员理解的。在这里,我们首先对在C和C++中大量使用的“宏”做一个简单介绍。

宏是一个较为简单的概念,在编译C/C++代码前,预处理器将宏代码展开,这样编译出来的代码在运行时就省去了一些额外的空间和时间开销。因此,使用宏可以在不影响程序运行性能的前提下提高代码的可读性。HotSpot项目中,在许多情形下都使用了宏。

(1)功能相似的数据结构。

在HotSpot中出现的所有内部对象,都需要一个类型进行定义。HotSpot为了能够更好地管理内部的对象表示,设计了一套面向对象表示系统,即OOP-Klass二分模型。在第3章中,我们将继续深入认识这一系统。简单来说,在这个模型中需要定义许多内部类型。考虑到很多类型具有相似的定义方式,出于模块化和可扩展性的设计思维,HotSpot的设计者运用宏巧妙地实现了对各种相似类型的通用定义。如清单2-1所示,在定义OOP结构类型时,设计了DEF_OOP宏,它能根据传入的type参数定义出不同的oopDesc类型。

清单2-1

来源:hotspot/src/share/vm/oops/oopsHierarchy.hpp

描述:定义<type>oopDesc类型
1   #define DEF_OOP(type)                                                      \
2      class type##OopDesc;                                                    \
3      class type##Oop : public oop {                                          \
4        public:                                                               \
5          type##Oop() : oop() {}                                              \
6          type##Oop(const volatile oop& o) : oop(o) {}                        \
7          type##Oop(const void* p) : oop(p) {}                                \
8          operator type##OopDesc* () const { return (type##OopDesc*)obj(); }  \
9          type##OopDesc* operator->() const {                                 \
10              return (type##OopDesc*)obj();                                  \
11         }                                                                   \
13     };                                                                   \

在DEF_OOP宏的定义中,预处理器根据外部传入的参数type,将宏分别展开为不同的代码块。符号“##”表示将实际传入的“type”值与“##”右边的字符串连接在一起。显然,在调用DEF_OOP宏时,如果传递给它不同的“type”值,那么在展开的代码块中,最终定义的类型将肯定不同。当定义好DEF_OOP后,我们每次调用DEF_OOP宏,就完成了一个oopDesc类型的定义,如清单2-2所示。

清单2-2

来源:hotspot/src/share/vm/oops/oopsHierarchy.hpp

描述:定义<type>oopDesc类型
1   DEF_OOP(instance);
2   DEF_OOP(method);
3   DEF_OOP(methodData);
4   DEF_OOP(array);
5   DEF_OOP(constMethod);
6   DEF_OOP(constantPool);
7   DEF_OOP(constantPoolCache);
8   DEF_OOP(objArray);
9   DEF_OOP(typeArray);
10  DEF_OOP(klass);
11  DEF_OOP(compiledICHolder);

在清单2-2中,定义了11种相似的oopDesc子类型,如instanceOopDesc、methodOopDesc和methodDataOopDesc等。在定义句柄时也使用了相同的方式,如清单2-3所示。

清单2-3

来源:hotspot/src/share/vm/runtime/handles.hpp

描述:定义句柄
1   #define DEF_HANDLE(type, is_a)                   \
2     class type##Handle;                            \
3     class type##Handle: public Handle {            \
4      protected:                                    \
5       type##Oop    obj() const                     { return (type##Oop)Handle::obj(); } \
6       type##Oop    non_null_obj() const            { return (type##Oop)Handle::non_null_ obj(); } \
7                                                    \
8      public:                                       \
9       /* 构造函数 */                           \
10      type##Handle ()                              : Handle()                 {} \
11      type##Handle (type##Oop obj) : Handle((oop)obj) {                         \
12        assert(SharedSkipVerify || is_null() || ((oop)obj)->is_a(),             \
13               "illegal type");                                                 \
14      }                                                                         \
15      type##Handle (Thread* thread, type##Oop obj) : Handle(thread, (oop)obj) { \
16        assert(SharedSkipVerify || is_null() || ((oop)obj)->is_a(), "illegal type");  \
17      }                                                                         \
18      \
19      type##Handle (type##Oop *handle, bool dummy) : Handle((oop*)handle, dummy) {} \
20                                                   \
21      /* 操作符重载 */              \
22      type##Oop    operator () () const            { return obj(); } \
23      type##Oop    operator -> () const            { return non_null_obj(); } \
24    };                                                                      \

在DEF_HANDLE宏的定义中,根据外部传入的type和is_a等参数,可以定义出不同的handle类型,如清单2-4所示。

清单2-4

来源:hotspot/src/share/vm/oops/oopsHierarchy.hpp

描述:定义<type>Handle类型局部
1   DEF_HANDLE(instance         , is_instance         )
2   DEF_HANDLE(method           , is_method           )
3   DEF_HANDLE(constMethod      , is_constMethod      )
4   DEF_HANDLE(methodData       , is_methodData       )
5   DEF_HANDLE(array            , is_array            )
6   DEF_HANDLE(constantPool     , is_constantPool     )
7   DEF_HANDLE(constantPoolCache, is_constantPoolCache)
8   DEF_HANDLE(objArray         , is_objArray         )
9   DEF_HANDLE(typeArray        , is_typeArray        )

(2)函数定义。

在定义一组具有相似结构的函数时,如以“jmm”或“JVM”为前缀命名的虚拟机接口函数。在这些函数中,都需要做一些相似的工作。如果为每个函数都重复编写相同的代码去做同一件事,这显然不利于维护和扩展。因此,HotSpot中将那些事务性的工作,如处理接口返回值类型、JNI声明、获取当前线程和调试开关的代码提取出来,由JVM_ENTRY宏来代替。这样依赖,我们在HotSpot项目中就看到了很多利用JVM_ENTRY宏定义的接口函数,如jmm_GetMemoryUsage()函数,如清单2-5所示。

清单2-5

来源:hotspot/src/share/vm/services/management.cpp

描述:GetMemoryUsage的底层实现
1   // Returns a java/lang/management/MemoryUsage object representing
2   // the memory usage for the heap or non-heap memory.
**3   JVM_ENTRY(jobject, jmm_GetMemoryUsage(JNIEnv* env, jboolean heap))**
4     ResourceMark rm(THREAD);

5     // Calculate the memory usage
6     size_t total_init = 0;
7     size_t total_used = 0;
8     size_t total_committed = 0;
9     size_t total_max = 0;
10    bool   has_undefined_init_size = false;
11    bool   has_undefined_max_size = false;

12    for (int i = 0; i < MemoryService::num_memory_pools(); i++) {
13      MemoryPool* pool = MemoryService::get_memory_pool(i);
14      if ((heap && pool->is_heap()) || (!heap && pool->is_non_heap())) {
15        MemoryUsage u = pool->get_memory_usage();
16        total_used += u.used();
17        total_committed += u.committed();

18        // if any one of the memory pool has undefined init_size or max_size,
19        // set it to -1
20        if (u.init_size() == (size_t)-1) {
21          has_undefined_init_size = true;
22        }
23        if (!has_undefined_init_size) {
24          total_init += u.init_size();
25        }

26        if (u.max_size() == (size_t)-1) {
27          has_undefined_max_size = true;
28        }
29        if (!has_undefined_max_size) {
30          total_max += u.max_size();
31        }
32      }
33    }

34    // In our current implementation, we make sure that all non-heap
35    // pools have defined init and max sizes. Heap pools do not matter,
36    // as we never use total_init and total_max for them.
37    assert(heap || !has_undefined_init_size, "Undefined init size");
38    assert(heap || !has_undefined_max_size,  "Undefined max size");

39    MemoryUsage usage((heap ? InitialHeapSize : total_init),
40                      total_used,
41                      total_committed,
42                      (heap ? Universe::heap()->max_capacity() : total_max));

43    Handle obj = MemoryService::create_MemoryUsage_obj(usage, CHECK_NULL);
44    return JNIHandles::make_local(env, obj());
**45  JVM_END**

与JVM_ENTRY相伴生的宏是JVM_END,相当于语句“}}”的作用,表示函数定义的结束。

(3)共同基类。

如果有大量的类都继承自同一类型,可以将继承基类的语句抽象成一个宏。

在HotSpot内部定义一个继承自_ValueObj(表示对象类型为值)的类时,就使用了宏VALUE_OBJ_CLASS_SPEC,如清单2-6所示。

清单2-6

来源:hotspot/src/share/vm/runtime/thread.cpp - Threads::threads_do()

描述:迭代线程列表
#define VALUE_OBJ_CLASS_SPEC    : public _ValueObj```   
当定义一个本身是值类型的新类型时,可以这样做,如清单2-7所示。

清单2-7

来源:hotspot/src/share/vm/runtime/thread.cpp - Threads::threads_do()

描述:迭代线程列表
class frame VALUE_OBJ_CLASS_SPEC {…}

(4)循环条件。

在线程模块中,常常需要迭代活跃线程列表,那么可以将遍历Threads静态成员_thread_list的操作提取出来,定义成一个名为ALL_JAVA_THREADS的宏,如清单2-8所示。

清单2-8

来源:hotspot/src/share/vm/runtime/thread.cpp

描述:遍历线程列表

define ALL_JAVA_THREADS(X) for (JavaThread* X = _thread_list; X; X = X->next())

这样,在使用时,只需要调用ALL_JAVA_THREADS(X) ,接下来的循环体就可以直接使用X作为线程元素进行业务处理了,而不必关心循环的创建和终止,如清单2-9所示。

清单2-9

来源:hotspot/src/share/vm/runtime/thread.cpp - Threads::threads_do()

描述:迭代线程列表
1 ALL_JAVA_THREADS(p) {
2 tc->do_thread(p);
3 }

(5)调试技术。

这种类型的宏常用于内部调试逻辑,如在关键代码路径处判断程序状态是否出错,决定是否输出一些错误信息或退出程序,如清单2-10所示。

清单2-10

来源:hotspot/src/share/vm/utilities/debug.hpp

描述:调试宏
1 #define ShouldNotReachHere() \
2 do { \
3 report_should_not_reach_here(__FILE__, __LINE__); \
4 BREAKPOINT; \
5 } while (0)

在JVM内部流程中,当流程走到本不该走的地方时,调用ShouldNotReachHere()进行错误处理。

2.内联函数
内联函数用于消除函数调用和返回时的寄存器存储和恢复开销,它通常应用于频繁执行的函数中。由于函数调用时,需要将程序执行流程转移到被调用函数中,并要求函数返回时回到原先的执行流程继续执行。这就要求在调用时保存现场并记住执行的地址,以待函数返回后恢复并按原程序流程继续执行。因此,函数调用会带来一定的时间和空间方面的开销,影响效率。而使用内联函数,编译器可以实现在函数调用时将内联函数展开,这样就取消了函数的入栈和出栈工作,减少了程序的开销。当函数被频繁调用的时候,节省下来的开销就十分可观了。

通常,将那些时间要求比较高,而本身长度比较短的函数定义成内联函数。例如,在定义OOP结构类型的成员函数时,可以采用如清单2-11所示的做法。

清单2-11

来源:hotspot/src/share/vm/oops/oop.inline.hpp

描述:对象头内联函数—设置mark word
1 inline markOop oopDesc::cas_set_mark(markOop new_mark, markOop old_mark) {
2 return (markOop) Atomic::cmpxchg_ptr(new_mark, &_mark, old_mark);
3 }

在第1行代码中,关键字inline表明oopDesc::cas_set_mark()是一个内联函数。

3.内部锁
在JVM内部,有些操作在同一时刻,只允许一个线程执行,因此需要互斥锁来保证系统的绝对安全。例如,对诸如SystemDictionary(系统字典)、Safepoint(安全点)或Heap(堆)等功能实体进行操作时,需要先取得相应的锁。表2-1定义了这些内部锁。

<div style="text-align: center"><img src="https://yqfile.alicdn.com/f700a90989b84862f8ba2d242d5216ce58f6cdea.png" width="" height="">
</div>

4.可移植性
Java自一问世,便具有了“一次编译,到处运行”的跨平台的特点。因此,JVM应当能够在各种系统平台上运行,这得益于JVM能够在不同CPU和操作系统类型上保持着良好的可移植性。我们知道,HotSpot大部分的业务逻辑位于各个平台共享的公共代码(在hotspot/src/share路径下),而对于平台依赖的逻辑,往往使用一种简单有效的办法能够在不削弱系统可维护性前提下保证可移植性。

清单2-12

来源:hotspot/src/share/vm/oops/oop.inline.hpp

描述:oop.hpp中的内联函数
1 #ifdef TARGET_OS_ARCH_linux_x86
2 # include "orderAccess_linux_x86.inline.hpp"
3 #endif
4 #ifdef TARGET_OS_ARCH_linux_sparc
5 # include "orderAccess_linux_sparc.inline.hpp"
6 #endif
7 #ifdef TARGET_OS_ARCH_linux_zero
8 # include "orderAccess_linux_zero.inline.hpp"
9 #endif
10 #ifdef TARGET_OS_ARCH_solaris_x86
11 # include "orderAccess_solaris_x86.inline.hpp"
12 #endif
13 #ifdef TARGET_OS_ARCH_solaris_sparc
14 # include "orderAccess_solaris_sparc.inline.hpp"
15 #endif
16 #ifdef TARGET_OS_ARCH_windows_x86
17 # include "orderAccess_windows_x86.inline.hpp"
18 #endif
19 #ifdef TARGET_OS_ARCH_linux_arm
20 # include "orderAccess_linux_arm.inline.hpp"
21 #endif
22 #ifdef TARGET_OS_ARCH_linux_ppc
23 # include "orderAccess_linux_ppc.inline.hpp"
24 #endif

这种办法就是根据目标系统的类型选择性的包含不同的头文件。例如,在清单2-12中,就是利用这种方式保证了在目标运行环境为Linux、Windows和Solaris操作系统以及Sparc、ARM、x86和PowerPC等体系结构时能够引入正确的头文件。

5.VM选项
对于经常与虚拟机调优打交道的人员来说,有必要了解VM选项(即flag)。为方便用户使用,虚拟机选项可以分为如下几类。

基本配置类:对虚拟机主要组件或策略的配置或选择,如Java堆的大小、垃圾收集器的选择、编译模式的选择等。举例来说,-agentlib选项用来加载本机代理库;-Xint配置虚拟机以纯解释模式运行,-Xmixed以解释器+即时编译混合模式运行;-Xmx配置最大Java堆大小;-XX:UseG1GC配置G1收集器等。
调优类:对虚拟机组件或策略进行较为细致的配置,往往是尝试对虚拟机调优的重要参数。主要集中在-XX选项。如-XX:ThreadStackSize选项允许自定义线程栈大小。
性能监控类:开启监控选项,当虚拟机运行状态符合预定条件时,能够将相关信息输出以便定位问题。如使用-Xloggc选项运行系统记录GC事件,即我们常说的GC日志;-XX:ErrorFile选项可以配置当虚拟机遇到内部错误(JVM Crash)时,可以将错误日志写入文件,文件名格式默认为hs_err_pid<pid>.log。
内部校验类:增强虚拟机内部过程校验。如开启-Xcheck:jni选项,虚拟机内部将对JNI函数进行额外的校验;-XX:ImplicitNullChecks和-XX:ImplicitDiv0Checks选项使虚拟机内部加强对空值和除零行为的校验。额外的内部校验在牺牲少量系统性的前提下增强了系统的健壮性。
调试和跟踪类:对虚拟机内部过程进行跟踪调试并输出跟踪日志,主要由一些-XX选项组成。这类选项一般在product版中是受限的,仅在debug版或fastdebug版中彩允许使用,一般用作了解虚拟机的重要工具。
在系统初始化阶段,Arguments模块会对传入的VM选项进行解析。

如果想提高自己的虚拟机调优技术,那么熟悉虚拟机选项应当成为必修课。虚拟机提供的配置选项至少有数百个,我们不能通过死记硬背了解每一个选项的含义和用法。在调优时,需要通过一定的实践、对比和分析,才能确定某个选项或参数对实际应用的确具有积极作用。但需要指出的是,我们应避免在实际应用中盲目尝试虚拟机调优,只有在对虚拟机的基本原理和运作机制理解的基础上才能够驾驭它。

如果你想系统了解虚拟机支持的所有选项,可以在非product版本虚拟机中打开选项-XX:PrintFlagsWithComments,这个选项允许我们查看虚拟机支持的所有选项及简要的功能描述。此外,还有一些辅助选项,如-XX:PrintVMOptions、-XX:PrintFlagsInitial等也有可以派上用场。

本书将在各个章节中附上相关的虚拟机选项和基本功能描述,以供读者参考。

练习1

阅读源代码,了解宏JVM_ENTRY和JVM_END的定义,并了解使用它们定义的函数。
####2.1.2 HotSpot内核框架
在扫清阅读源码的障碍后,现在是时候介绍HotSpot内核框架了。图2-1展示了HotSpot内核模块的基本结构。内核由一些顶层模块构成系统的主要功能组件,我们在上一章(如图1-4所示)也接触过这些顶层模块,如Oops、Classfile等功能模块。
<div style="text-align: center"><img src="https://yqfile.alicdn.com/29fbd6c9e162e6bbb18aa60b3218acd2df5a6d55.png" width="" height="">
</div>

HotSpot内核主要由C/C++实现,可能很多Java程序员会觉得阅读源码会觉得有些吃力。但事实上,只要掌握了正确的阅读源码的方法,我们完全可以打消这个疑虑。

当我们阅读任何一个开源项目源代码时,核心目标都是去理解系统的运作原理,了解功能组件如何协作和发挥作用。那么我们的着眼点应当在于抓住数据结构这一核心,去了解功能的实现算法,而不是陷入编程语言的细节。

数据结构的设计反映了功能组件的本质,从数据结构出发,可以了解组件在实现一个功能时需要考虑哪些因素:是否依赖其他组件、需要设置哪些状态、是否提供优化措施等。数据结构包括结构体、枚举、类和接口,它定义了数据成员,用以支撑算法(含功能性操作函数)的实现。而算法往往反映了功能的实现逻辑。因此从了解数据结构出发,结合算法的实现,便可以了解一个模块的具体作用,进而理解系统功能组件的实现原理。

我们知道,HotSpot由多个顶层模块组成,主要包括Service、Prims、Runtime、Classfile、Interpreter、Code、Memory、Compiler、Oops、C1/Opto/Shark和GC。

其中每个顶层模块又是由若干子模块组成。在每个子模块中,定义了一些数据结构和算法,它们相互协作实现子模块的逻辑功能。

以顶层模块Classfile为例,它包含了许多子模块,其中一个子模块叫做类文件解析器,位于ClassFileParser子模块中。在ClassFileParser模块中,定义了数据结构ClassFileParser类,用做解析*.class格式文件(也称为Class文件或类文件,下同)。ClassFileParser类用_major_version和_minor_version字段记录Class文件的主版本号和次版本号,并用_class_name字段记录类名。在算法层面,则提供了一些列的解析函数,这些解析函数实现了类文件解析器的功能主体,如parse_constant_pool()解析常量池、parse_interfaces()解析接口、parse_fields()解析字段、parse_method()解析Java方法、parse_localvariable_table()解析局部变量表等。

在本书的后续章节中,将会陆续介绍HotSpot的内核模块。接下来,我们来了解一下对外接口模块。

####2.1.3 Prims
JVM应当具有外部访问的通道,允许外部程序访问内部状态信息。如图2-2所示,HotSpot在这方面提供了丰富的通信接口,可以供JDK或其他应用程序调用。

在HotSpot内核中,由Prims模块定义外部接口。图2-3列举了Prims模块的部分子模块,主要包括4个模块。
<div style="text-align: center"><img src="https://yqfile.alicdn.com/85193bfde2978de2286a1705ca95e3126a133f08.png" width="" height="">
</div>

1.JNI模块
Java本地接口(Java Native Interface,缩写为JNI)是Java标准的重要组成部分。它允许Java代码与本地代码进行交互,如与C/C++代码实现相互调用。虽然Java在平台移植性方面具有天然的优势,但是有的时候Java程序需要使用一些与底层操作系统或硬件相关的功能,这时就需要一种机制,允许调用本地库或JVM库。在JDK中定义了很多函数都依赖这些由本地语言实现的库。JNI模块提供Java运行时接口,定义了许多以“jni_”为前缀命名的函数,允许JDK或者外部程序调用由C/C++实现的库函数。

2.JVM模块
在JVM模块中,虚拟机向外提供了一些函数,以“JVM_”为前缀命名,作为标准JNI接口的补充。这些函数可以归纳为三个部分。

首先,是一些与JVM相关的函数,用来支持一些需要访问本地库的Java API。java.lang.Object需要这些函数实现wait和notify监视器(monitor),如清单2-13所示。

清单2-13

来源:hotspot/src/share/vm/prims/jvm.h

描述:JVM模块导出函数举例
1 JNIEXPORT void JNICALL
2 JVM_MonitorWait(JNIEnv *env, jobject obj, jlong ms);

3 JNIEXPORT void JNICALL
4 JVM_MonitorNotify(JNIEnv *env, jobject obj);

其次,是一些函数和常量定义,用来支持字节码验证和Class文件格式校验。清单2-14列举了部分由“JVM_”作为前缀命名的函数和常量,用作Class文件解析。

清单2-14

来源:hotspot/src/share/vm/prims/jvm.h

描述:JVM模块导出函数和常量举例
1 /*
2 * Returns the constant pool types in the buffer provided by "types."
3 */
4 JNIEXPORT void JNICALL
5 JVM_GetClassCPTypes(JNIEnv env, jclass cb, unsigned char types);

6 /*
7 Returns the number of declared* fields or methods.
8 */
9 JNIEXPORT jint JNICALL
10 JVM_GetClassFieldsCount(JNIEnv *env, jclass cb);

11 #define JVM_ACC_PUBLIC 0x0001 / visible to everyone /
12 #define JVM_ACC_PRIVATE 0x0002 / visible only to the defining class /
13 #define JVM_ACC_PROTECTED 0x0004 / visible to subclasses /

最后,是各种I/O和网络操作,用来支持Java I/O和网络API。清单2-15列举了部分由JVM模块导出的I/O函数和网络操作函数。

清单2-15

来源:hotspot/src/share/vm/prims/jvm.h

描述:JVM模块导出I/O和network函数
1 JNIEXPORT jint JNICALL
2 JVM_Open(const char *fname, jint flags, jint mode);

3 JNIEXPORT jint JNICALL
4 JVM_Read(jint fd, char *buf, jint nbytes);

5 JNIEXPORT jint JNICALL
6 JVM_Socket(jint domain, jint type, jint protocol);

7 JNIEXPORT jint JNICALL
8 JVM_Recv(jint fd, char *buf, jint nBytes, jint flags);

9 JNIEXPORT jint JNICALL
20 JVM_Send(jint fd, char *buf, jint nBytes, jint flags);

JVM模块的导出函数均在头文件jvm.h中声明。HotSpot项目中的jvm.h文件与JDK使用的头文件是一致的。而函数的具体实现则是在源文件jvm.cpp中使用JVM_ENTRY宏方式定义。

3.JVMTI模块
Java虚拟机工具接口(Java Virtual Machine Tool Interface,缩写为JVMTI)提供了一种编程接口,允许程序员创建代理以监视和控制Java应用程序。JVMTI代理常用于对应用程序进行监控、调试或调优。例如监控内存实际使用情况、CPU利用率以及锁信息等。该模块为外部程序提供JVMTI接口。

4.Perf模块
JDK中sun.misc.Perf类的底层实现,定义了一些以“Perf_”为前缀命名的函数,由外部程序调用,以监控虚拟机内部的Perf Data计数器。

####2.1.4 Services
Services模块为JVM提供了JMX等功能。JMX(即Java Management Extensions)是为支持对Java应用程序进行管理和监控而定义的一套体系结构、设计模式、API以及服务。通常使用JMX来监控系统的运行状态或者对系统进行灵活的配置,比如清空缓存、重新加载配置文件、更改配置等。

JMX可以跨越一系列异构操作系统平台、系统体系结构和网络传输协议,为程序员开发无缝集成的系统、网络和服务管理应用提供一定程度的灵活性。

Services模块包含以下9个主要子模块,如图2-4所示。
<div style="text-align: center"><img src="https://yqfile.alicdn.com/3ce8967581bbd52011cde2b75c73d992efb77d63.png" width="" height="">
</div>

1.Management模块
Management模块提供JMX底层实现的基础。例如,在Java层开发JXM程序时,会遇到如下几个函数:

Java_sun_management_MemoryManagerImpl_getMemoryPools0;
Java_sun_management_MemoryImpl_getMemoryManagers0;
Java_sun_management_MemoryImpl_getMemoryUsage0;
Java_sun_management_ThreadImpl_dumpThreads0等。

JMX示例程序会输出内存池及垃圾收集器等信息,如清单2-16所示。

清单2-16

来源:com.hotspotinaction.demo.chap2.MemoryPoolInfo

描述:利用JMX获取GC信息
1 List pools = ManagementFactory.getMemoryPoolMXBeans();
2 int poolsFound = 0;
3 int poolsWithStats = 0;
4 for (int i = 0; i < pools.size(); i++) {
5 MemoryPoolMXBean pool = pools.get(i);
6 String name = pool.getName();
7 System.out.println("found pool: " + name);

8 if (name.contains(poolName)) {
9 long usage = pool.getCollectionUsage().getUsed();
10 System.out.println(name + ": usage after GC = " + usage);
11 poolsFound++;
12 if (usage > 0) {
13 poolsWithStats++;
14 }
15 }
16 }
17 if (poolsFound == 0) {
18 throw new RuntimeException("无匹配的内存池:请打开-XX:+UseConcMarkSweepGC");
19 }

20 List collectors = ManagementFactory.getGarbageCollectorMX Beans();
21 int collectorsFound = 0;
22 int collectorsWithTime = 0;
23 for (int i = 0; i < collectors.size(); i++) {
24 GarbageCollectorMXBean collector = collectors.get(i);
25 String name = collector.getName();
26 System.out.println("found collector: " + name);
27 if (name.contains(collectorName)) {
28 collectorsFound++;
29 System.out.println(name + ": collection count = " + collector.getCollectionCount ());
30 System.out.println(name + ": collection time = " + collector.getCollectionTime ());
31 if (collector.getCollectionCount() <= 0) {
32 throw new RuntimeException("collection count <= 0");
33 }
34 if (collector.getCollectionTime() > 0) {
35 collectorsWithTime++;
36 }
37 }
38 }

运行得到如清单2-17所示的日志。

清单2-17
found pool: Code Cache
found pool: Par Eden Space
found pool: Par Survivor Space
found pool: CMS Old Gen
CMS Old Gen: usage after GC = 208123288
found pool: CMS Perm Gen
CMS Perm Gen: usage after GC = 2626528
found collector: ParNew
found collector: ConcurrentMarkSweep
ConcurrentMarkSweep: collection count = 7
ConcurrentMarkSweep: collection time = 17

JVM以management动态链接库的形式,向JDK提供一套监控和管理虚拟机的jmm接口,如清单2-18所示。动态链接库被安装在JRE/bin目录下。例如,在Windows平台上,JRE/bin目录下名为management.dll的文件即为该库。

清单2-18

来源:hotspot/src/share/vm/services/management.cpp

描述:jmm_interface
1 const struct jmmInterface_1_ jmm_interface = {
2 NULL,
3 NULL,
4 jmm_GetVersion,
5 jmm_GetOptionalSupport,
6 jmm_GetInputArguments,
7 jmm_GetThreadInfo,
8 jmm_GetInputArgumentArray,
9 jmm_GetMemoryPools,
10 jmm_GetMemoryManagers,
11 jmm_GetMemoryPoolUsage,
12 jmm_GetPeakMemoryPoolUsage,
13 jmm_GetThreadAllocatedMemory,
14 jmm_GetMemoryUsage,
15 jmm_GetLongAttribute,
16 jmm_GetBoolAttribute,
17 jmm_SetBoolAttribute,
18 jmm_GetLongAttributes,
19 jmm_FindMonitorDeadlockedThreads,
20 jmm_GetThreadCpuTime,
21 jmm_GetVMGlobalNames,
22 jmm_GetVMGlobals,
23 jmm_GetInternalThreadTimes,
24 jmm_ResetStatistic,
25 jmm_SetPoolSensor,
26 jmm_SetPoolThreshold,
27 jmm_GetPoolCollectionUsage,
28 jmm_GetGCExtAttributeInfo,
29 jmm_GetLastGCStat,
30 jmm_GetThreadCpuTimeWithKind,
31 jmm_GetThreadCpuTimesWithKind,
32 jmm_DumpHeap0,
33 jmm_FindDeadlockedThreads,
34 jmm_SetVMGlobal,
35 NULL,
36 jmm_DumpThreads,
37 jmm_SetGCNotificationEnabled
38 };

如果读者想了解JMX在虚拟机中是如何实现的,可以在management模块中查看上述jmm接口函数(使用JVM_ENTRY和JVM_END宏定义)。

例如,清单2-5中由JVM_ENTRY宏定义的函数getMemoryUsage0,实现的便是JMX接口Java_sun_management_MemoryImpl_getMemoryUsage0。

虚拟机启动过程中,在初始化management模块时,将调用顶层模块Runtime中的ServiceThread模块,启动一个名为“Service Thread”的守护线程(见清单2-33,在较早版本中也称“Low Memory Detector”),该守护线程负责向JVM上报内存不足报警。

若系统开启了选项-XX:ManagementServer,则加载并创建sun.management.Agent类,执行其startAgent()方法启动JMX Server。

除了JXM功能模块以外,还有其他几个模块,下面依次介绍。

2.MemoryService模块
提供JVM内存管理服务。如堆的分配和内存池的管理等。

3.MemoryPool模块
内存池管理模块。内存池表示由JVM管理的内存区域,是内存管理的基本单元。JVM拥有至少一块内存池,它可以在JVM运行期间创建或删除。一块内存池可以由堆或非堆拥有,也可以由它们同时拥有。MemoryPool分为两类:CollectedMemoryPool和CodeHeapPool。其中,CollectedMemoryPool又可分为如下几种子类型:

ContiguousSpacePool;
SurvivorContiguousSpacePool;
CompactibleFreeListSpacePool;
GenerationPool。

4.MemoryManager模块
内存管理器。一个内存管理负责管理一个或多个内存池。垃圾收集器也是一种内存管理器,它负责回收不可达对象的内存空间。在JVM中,允许有一个或多个内存管理器,允许在系统运行期间根据情况添加或删除内存管理器。一个内存池可以由一个或多内存管理器管理。已定义的内存管理器包括CodeCacheMemoryManager、GCMemoryManager。其中GCMemoryManager又可分为如下几种子类型:

CopyMemoryManager;
MSCMemoryManager;
ParNewMemoryManager;
CMSMemoryManager;
PSScavengeMemoryManager;
PSMarkSweepMemoryManager;
G1YoungGenMemoryManager;
G1OldGenMemoryManager等。

5.RuntimeService模块

提供Java运行时的性能监控和管理服务,如applicationTime、jvmCapabilities等。

6.ThreadService模块
提供线程和内部同步系统的性能监控和管理服务,包括维护线程列表、线程相关的性能统计、线程快照、线程堆栈跟踪和线程转储等功能。

7.ClassLoadingService模块
提供类加载模块的性能监控和管理服务。

8.AttachListener模块
JVM系统初始化时启动名为“Attach Listener”的守护线程。它是为客户端的JVM监控工具提供连接(attach)服务,它维护一个操作列表用来接受已连接的客户端进程发送的操作请求,当执行完毕这些操作后,将数据返回给客户端进程。关于Attach机制的详细内容,请参考本书第9章。

9.HeapDumper模块
提供堆转储功能,将堆转储信息写入HPROF格式二进制文件中。关于对转储机制和HRPOF格式的更多内容,可以参考本书的第9章。

练习2

在安装的JRE下面寻找management动态链接库,查看库中分别包含了哪些函数符号。查阅资料,了解这些函数的作用?通过已学过的知识,试着在openjdk源代码中独立找到这些函数的定义。
####2.1.5 Runtime
Runtime是运行时模块,它为其他系统组件提供运行时支持。JVM在运行时需要的很多功能,如线程、安全点、PerfData、Stub例程、反射、VMOperation以及互斥锁等组件,均由Runtime模块定义。在本书后续章节对相关专题展开讲解时,还将看到Runtime模块发挥着重要作用。

如图2-5所示,顶层模块Runtime中定义了大量公共模块,限于篇幅,这里不能一一详解,仅对几个主要模块进行介绍,读者如需要进一步了解,可以参阅源代码。

1.Thread模块
定义了各种线程类型,包含JVM内部工作线程以及Java业务线程。此外,还定义了Threads子模块,它维护着系统的有效线程队列。
<div style="text-align: center"><img src="https://yqfile.alicdn.com/3f1227ba4d29db84d84085aa4e5d98a6391a392a.png" width="" height="">
</div>

2.Arguments模块
记录和传递VM参数和选项,详见第9章。

3.StubRoutines和StubCodeGenerator模块
生成stub,关于stub的更多信息,可以参考第7章。

4.Frame模块
Frame表示一个物理栈帧(又称活动记录,Activation Record,详见第7章),Frame模块定义了表示物理栈帧的数据结构frame,如清单2-19所示。frame是与CPU类型相关的,既可以表示C帧,也可以表示Java帧,对于Java帧既可以是解释帧,也可以是编译帧。

清单2-19

来源:hotspot/src/share/vm/runtime/frame.hpp

描述:frame的成员变量
1 class frame {
2 private:
3 // 成员变量
4 intptr_t* _sp; // stack pointer (from Thread::last_Java_sp)
5 address _pc; // program counter (the next instruction after the call)

6 CodeBlob* _cb; // CodeBlob that "owns" pc
7 enum deopt_state {
8 not_deoptimized,
9 is_deoptimized,
10 unknown
11 };

12 deopt_state _deopt_state;
13 ……
14 }

在frame类型的定义中,会根据具体的CPU类型定义不同的函数实现。一个frame可由栈指针、PC指针、CodeBlob指针和状态位描述。其中,这三个指针的作用为:栈栈帧_sp指向栈顶元素;PC指针_pc指向下一条要执行的指令地址;CodeBlob指针指向持有相应的指令机器码的CodeBlob。在第6章中,我们将会看到frame的设计在结构上十分类似于真实机器的栈帧结构。

5.CompilationPolicy模块
CompilationPolicy模块用来配置编译策略,即选择什么样的方法或循环体来编译。Runtime模块中定义了两种编译策略类型—SimpleThresholdPolicy和AdvancedThresholdPolicy。CompilationPolicy模块初始化时,将根据VM选项CompilationPolicyChoice来配置编译策略,选项为0表示SimpleCompPolicy,为1则表示StackWalkCompPolicy。

6.Init模块
用于系统初始化,如启动init_globals()函数初始化全局模块等。

7.VmThread模块
在虚拟机创建时,将会在全局范围内创建一个单例原生线程VMThread(虚拟机线程),该线程名为“VM Thread”,能够派生出其他的线程。该线程的一个重要职责是:维护一个虚拟机操作队列(VMOperationQueue),接受其他线程请求虚拟机级别的操作(VMOperation),如执行GC等任务。事实上,VMOperation是JVM对外及对内提供的核心服务。甚至是在一些外部虚拟机监控工具(详见第9章)中,也享受到了这些VMOperation所提供的服务。

这些操作根据阻塞类型以及是否进入安全点,可分为4种操作模式(Mode)。

- safepoint:阻塞,进入安全点。
- no_safepoint:阻塞,非进入安全点。
- concurrent:非阻塞,非进入安全点。
- async_safepoint:非阻塞,进入安全点。

8.VMOperation模块
虚拟机内部定义的VM操作有ThreadStop、ThreadDump、PrintThreads、FindDeadlocks、ForceSafepoint、ForceAsyncSafepoint、Deoptimize、HandleFullCodeCache、Verify、HeapDumper、GenCollectFull、ParallelGCSystemGC、CMS_Initial_Mark、CMS_Final_Remark、G1CollectFull、G1CollectForAllocation、G1IncCollectionPause、GetStackTrace、HeapWalkOperation、HeapIterateOperation等(详见vm_operations.hpp)。

这些VM操作均继承自共同父类VM_Operation。了解哪些VM操作需要进入安全点,对我们在实践中排查引起程序停顿过久这类问题时大有裨益。可以通过函数evaluation_mode()判断具体的操作模式,如清单2-20所示。

清单2-20

来源:hotspot/src/share/vm/runtime/vm_operations.hpp

描述:VM_Operation::evaluation_mode()
virtual Mode evaluation_mode() const { return _safepoint; }

若具体实现子类有特殊模式要求,将会覆盖该函数,否则默认模式为safepoint。

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
阿里云 Linux 内核优化实战(sysctl.conf 和 ulimits )
一、sysctl.conf优化Linux系统内核参数的配置文件为 /etc/sysctl.conf 和 /etc/sysctl.d/ 目录。其读取顺序为: /etc/sysctl.d/ 下面的文件按照字母排序;然后读取 /etc/sysctl.conf 。
4239 0
阿里云服务器端口号设置
阿里云服务器初级使用者可能面临的问题之一. 使用tomcat或者其他服务器软件设置端口号后,比如 一些不是默认的, mysql的 3306, mssql的1433,有时候打不开网页, 原因是没有在ecs安全组去设置这个端口号. 解决: 点击ecs下网络和安全下的安全组 在弹出的安全组中,如果没有就新建安全组,然后点击配置规则 最后如上图点击添加...或快速创建.   have fun!  将编程看作是一门艺术,而不单单是个技术。
4477 0
《HotSpot实战》—— 2.4 小结
本章首先介绍了HotSpot内核的结构,并引导读者掌握一些阅读源代码的技巧。在内核模块中,介绍了Prims、Service和Runtime模块,它们为HotSpot提供外部接口,并为内核其他模块提供部分公共功能。
1169 0
《HotSpot实战》—— 1.2 动手编译虚拟机
由于开发环境各不相同,每个人遇到的问题可能都不尽相同;即使遇到相同的问题,在不同的平台上解决的方式可能也有所不同。当然,对于相同的问题,也会有多种办法解决。限于篇幅,在这里不能对所有错误信息和解决办法都列举出来。
4514 0
阿里云ECS云服务器初始化设置教程方法
阿里云ECS云服务器初始化是指将云服务器系统恢复到最初状态的过程,阿里云的服务器初始化是通过更换系统盘来实现的,是免费的,阿里云百科网分享服务器初始化教程: 服务器初始化教程方法 本文的服务器初始化是指将ECS云服务器系统恢复到最初状态,服务器中的数据也会被清空,所以初始化之前一定要先备份好。
10767 0
阿里云服务器远程登录用户名和密码的查询方法
阿里云服务器远程连接登录用户名和密码在哪查看?阿里云服务器默认密码是什么?云服务器系统不同默认用户名不同
443 0
阿里云ECS云服务器初始化设置教程方法
阿里云ECS云服务器初始化是指将云服务器系统恢复到最初状态的过程,阿里云的服务器初始化是通过更换系统盘来实现的,是免费的,阿里云百科网分享服务器初始化教程: 服务器初始化教程方法 本文的服务器初始化是指将ECS云服务器系统恢复到最初状态,服务器中的数据也会被清空,所以初始化之前一定要先备份好。
3227 0
阿里云服务器如何登录?阿里云服务器的三种登录方法
购买阿里云ECS云服务器后如何登录?场景不同,云吞铺子总结大概有三种登录方式: 登录到ECS云服务器控制台 在ECS云服务器控制台用户可以更改密码、更换系统盘、创建快照、配置安全组等操作如何登录ECS云服务器控制台? 1、先登录到阿里云ECS服务器控制台 2、点击顶部的“控制台” 3、通过左侧栏,切换到“云服务器ECS”即可,如下图所示 通过ECS控制台的远程连接来登录到云服务器 阿里云ECS云服务器自带远程连接功能,使用该功能可以登录到云服务器,简单且方便,如下图:点击“远程连接”,第一次连接会自动生成6位数字密码,输入密码即可登录到云服务器上。
16804 0
+关注
异步社区
异步社区(www.epubit.com)是人民邮电出版社旗下IT专业图书旗舰社区,也是国内领先的IT专业图书社区,致力于优质学习内容的出版和分享,实现了纸书电子书的同步上架,于2015年8月上线运营。公众号【异步图书】,每日赠送异步新书。
11939
文章
0
问答
文章排行榜
最热
最新
相关电子书
更多
文娱运维技术
立即下载
《SaaS模式云原生数据仓库应用场景实践》
立即下载
《看见新力量:二》电子书
立即下载