JVM 菜鸟进阶高手之路九(解惑)

在第八系列最后有些疑惑的地方,后来仍是在我坚持不懈不断打扰笨神,阿飞,ak大神等,终于解决了该问题。第八系列地址: http://blog.csdn.net/lirenzuo/article/details/77480092


关于MAT工具相关知识解惑

MAT 不是一个万能工具,它并不能处理全部类型的堆存储文件。可是比较主流的厂家和格式,例如 Sun, HP, SAP 所采用的 HPROF 二进制堆存储文件,以及 IBM 的 PHD 堆存储文件等都能被很好的解析,MAT下载地址以及相关文档
      获取堆信息:

咱们使用以下命令获取:
jmap -dump:format=b,file=heap.bin pid
后面讲堆信息下载本地,经过MAT打开便可,第一个问题来了,我经过jmap相关命令获得的信息与MAT观察的信息不一致问题。
如图所示:


而MAT里面显示以下:
有没有以为很奇怪,明明4G的堆,dump文件也是4G多,为何用MAT打开就220M左右????? 奇怪吧,我也疑惑很久,由于由于MAT把不可达对象没加在里面,若是须要显示在MAT工具进行选择设置便可,以下:

第一个问题相对其余几个问题简单点。


关于第八篇Xmn问题

第八篇问题早上过来查看gc日志发现如图效果:

看到这个日志以为很是奇怪 real为何会这么大呢?先来看看关于 user、sys、real的意思吧。
咱们先来看看linux下面的time相关命令,执行
time ls

Real, User and Sys process time statisticsphp

One of these things is not like the other. Real refers to actual elapsed time; User and Sys refer to CPU time used only by the process.html

  • Real is wall clock time - time from start to finish of the call. This is all elapsed time including time slices used by other processes and time the process spends blocked (for example if it is waiting for I/O to complete).java

  • User is the amount of CPU time spent in user-mode code (outside the kernel) within the process. This is only actual CPU time used in executing the process. Other processes and time the process spends blocked do not count towards this figure.linux

  • Sys is the amount of CPU time spent in the kernel within the process. This means executing CPU time spent in system calls within the kernel, as opposed to library code, which is still running in user-space. Like 'user', this is only CPU time used by the process. See below for a brief description of kernel mode (also known as 'supervisor' mode) and the system call mechanism.git

User+Sys will tell you how much actual CPU time your process used. Note that this is across all CPUs, so if the process has multiple threads (and this process is running on a computer with more than one processor) it could potentially exceed the wall clock time reported by Real (which usually occurs). Note that in the output these figures include the User and Sys time of all child processes (and their descendants) as well when they could have been collected, e.g. by wait(2) or waitpid(2), although the underlying system calls return the statistics for the process and its children separately.github

Origins of the statistics reported by time (1)算法

The statistics reported by time are gathered from various system calls. 'User' and 'Sys' come from wait (2) or times (2), depending on the particular system. 'Real' is calculated from a start and end time gathered from the gettimeofday (2) call. Depending on the version of the system, various other statistics such as the number of context switches may also be gathered by time.oracle

On a multi-processor machine, a multi-threaded process or a process forking children could have an elapsed time smaller than the total CPU time - as different threads or processes may run in parallel. Also, the time statistics reported come from different origins, so times recorded for very short running tasks may be subject to rounding errors, as the example given by the original poster shows.app

A brief primer on Kernel vs. User modeless

On Unix, or any protected-memory operating system, 'Kernel' or 'Supervisor' mode refers to a privileged mode that the CPU can operate in. Certain privileged actions that could affect security or stability can only be done when the CPU is operating in this mode; these actions are not available to application code. An example of such an action might be manipulation of the MMU to gain access to the address space of another process. Normally, user-mode code cannot do this (with good reason), although it can request shared memory from the kernel, which could be read or written by more than one process. In this case, the shared memory is explicitly requested from the kernel through a secure mechanism and both processes have to explicitly attach to it in order to use it.

The privileged mode is usually referred to as 'kernel' mode because the kernel is executed by the CPU running in this mode. In order to switch to kernel mode you have to issue a specific instruction (often called a trap) that switches the CPU to running in kernel mode and runs code from a specific location held in a jump table. For security reasons, you cannot switch to kernel mode and execute arbitrary code - the traps are managed through a table of addresses that cannot be written to unless the CPU is running in supervisor mode. You trap with an explicit trap number and the address is looked up in the jump table; the kernel has a finite number of controlled entry points.

The 'system' calls in the C library (particularly those described in Section 2 of the man pages) have a user-mode component, which is what you actually call from your C program. Behind the scenes, they may issue one or more system calls to the kernel to do specific services such as I/O, but they still also have code running in user-mode. It is also quite possible to directly issue a trap to kernel mode from any user space code if desired, although you may need to write a snippet of assembly language to set up the registers correctly for the call. A page describing the system calls provided by the Linux kernel and the conventions for setting up registers can be found here.

More about 'sys'

There are things that your code cannot do from user mode - things like allocating memory or accessing hardware (HDD, network, etc.). These are under the supervision of the kernel, and it alone can do them. Some operations that you do (like malloc orfread/fwrite) will invoke these Kernel functions and that then will count as 'sys' time. Unfortunately it's not as simple as "every call to malloc will be counted in 'sys' time". The call to malloc will do some processing of its own (still counted in 'user' time) and then somewhere along the way it may call the function in kernel (counted in 'sys' time). After returning from the kernel call, there will be some more time in 'user' and then malloc will return to your code. As for when the switch happens, and how much of it is spent in kernel mode... you cannot say. It depends on the implementation of the library. Also, other seemingly innocent functions might also use malloc and the like in the background, which will again have some time in 'sys' then.

其实若是这些看完就知道了,我当时没有看这块内容,问笨神了,不得不说笨神大大很牛逼啊,直接告诉我有可能那个时候由于swap致使gc慢了,最后通过一系列验证的确是swap问题,把这个关闭就没有这块的问题了。其实相似linux很重要,须要重点学习下。

其实IO也很重要,在后面的一个问题虽然不是IO问题,可是笨神提到了是不是IO问题,也是须要关注的一个点,sar(System Activity Reporter系统活动状况报告)是目前 Linux 上最为全面的系统性能分析工具之一,能够从多方面对系统的活动进行报告,包括:文件的读写状况、系统调用的使用状况、磁盘I/O、CPU效率、内存使用情况、进程活动及IPC有关的活动等。一句话sar很是牛逼。

敲入“sar”,若是提示没有进行安装:

yum install sysstat

安装成功后,sar有不少明白包括不限于, 查看CPU使用率、查看平均负荷、查看内存使用状况、查看页面交换发生情况、查看I/O和传送速率的统计信息等,重点看看IO这块:

-d option in the sar command is used to display the block device statistics report. Using option -p (pretty-print) along with -d make the dev column more readable, example is shown below :

sar -d -p 1


等相关命令。
关于第八篇的Xmn问题也是相似问题,解决顺便学习了一把。


下面开始巨坑,该坑搞的我昨天晚上都没有修改好。

选择UseParallelOldGC垃圾回收器相关坑

为何选择UseParallelOldGC回收器,其实在第八篇里面有介绍,地址:http://blog.csdn.net/lirenzuo/article/details/77480092
问题由来,发现old涨的蛮快,以为很奇怪,在思考为何old涨的那么快呢?通常大概就几种状况:
  1. 新生代对象每经历依次minor gc,年龄会加一,当达到年龄阀值会直接进入老年代。阀值大小通常为15
  2. Survivor空间中年龄全部对象大小的总和大于survivor空间的一半,年龄大于或等于该年龄的对象就能够直接进入老年代,而无需等到年龄阀值
  3. 大对象直接进入老年代
  4. 新生代复制算法须要一个survivor区进行轮换备份,若是出现大量对象在minor gc后仍然存活的状况时,就须要老年代进行分配担保,让survivor没法容纳的对象直接进入老年代
其实笨神分享过,这个参数主要做用是设置在YGC的时候,新生代的对象正常状况下最多通过多少次YGC的过程会晋升到老生代,注意是表示最多而不是必定,也就是说某个对象的age其实并非必定要达到这个值才会晋升到Old的,当你设置这个值的时候,第一次会以它为准,后面的就不必定以它为准了,而是JVM会自动计算在0~15之间决定。若是配置了CMS GC,这个值默认是6,这个值最大你能够设置到15,由于jvm里就4个bit来存这个值,因此最大就是1111,即15。

先经过命令:
jstat -gc pid 3s
发现S0、S1才512k,很奇怪,觉得是这块空间小了修改,-XX:SurvivorRatio=2,奇怪的问题发现啦,如图:


经过各类日志观察,见鬼啦,竟然S0、S1大小在愈来愈小,对、你没有看错,PS下UseAdaptiveSizePolicy默认是打开的,对问题就是在这里,经过命令:
jinfo -flag UseAdaptiveSizePolicy pid
的确是打开的,而CMS默认是关闭的,因此有些同窗可能也并无注意到这块内容, 该jvm实现代码在: https://github.com/dmlloyd/openjdk/blob/jdk9/jdk9/hotspot/src/share/vm/gc/parallel/psAdaptiveSizePolicy.cpp
感谢小峰提供而且读了源码。




ps而后开启UseAdaptiveSizePolicy以后,晋升年龄阈值最后减到1了,是由于ps在开启这个以后,会判断young gc和full gc的上消耗的时间差。若是young gc>full gc*1.1,threshold就会一直减小。反之,若是young*1.1<full,threshold就会一直增长。 而个人应用确实young gc比较频繁,因此threshold一直减到了1,最小是1。

经过 -XX:-UseAdaptiveSizePolicy  关闭,巨坑开始了就从这个参数开始。

再此观察,old涨的仍是涨,以前了解过这个参数,PrintTenuringDistribution,之前了解过,会有以下日志出现:

    再此观察,日志里面一个相似age的都没有,觉得不是对象年龄到了晋升到old的,怀疑是对象过大了,经过加 -XX:PretenureSizeThreshold=100m参数(这个参数其实只对串行回收器和ParNew有效,对ParallelGC无效), 再次观察,仍是old很涨,若是巨坑来了,纠结一夜


 这里的s0 s1空间很大啊 (看使用率不多) 为何每次Old都会涨呢 而且没有发现gc日志有相似age的,大对象也设置啦,纠结啊,又是等笨神解答的,一直在纠结为何old在涨呢,解释不通啦!ps下若是自适应策略关了也不会打印,把自适应策略关了,去掉那个UseAdaptiveSizePolicy参数就行了。
后续会结合jvm来判断蛛丝马迹来进行代码层面或者其余层面的优化,该类操做比较复杂也一直在摸索,但愿一块儿进步。