JVM 虚拟机手册

前言

前段时间翻看本身多年以来攒下的满满家当 , 忽然有一种满满的知足感 .
可是想一想多年来找资料的艰辛 , 决定将这些文档整理出来, 分享给你们 .
笔记华而不实 , 其中可能也有不正确的地方 , 欢迎指正. 
在此也感谢道友们的奉献 , 文档暂分为几个:

复制代码

另外还有其余的笔记会陆陆续续的分享处理 , 谢谢你们的支持 .javascript

一 . 基础知识

1 . 1 常见的内存溢出

> 堆溢出
> 元空间溢出 , 元数据区的内存溢出
> 直接内存溢出
> 虚拟机栈和本地方法栈溢出
> 运行时常量池溢出
> 方法区的内存溢出
复制代码

1 . 2 内存溢出的常见缘由

复制代码

1 . 3 系统的线程划分

> 串行收集器
      : 用单线程处理全部垃圾回收工做 , 效率高
      : 数据量比较小(100M左右);单处理器下而且对响应时间无要求的应用
> 并行收集器
	  : “对吞吐量有高要求”,多CPU、对应用响应时间无要求的中、大型应用
	
> 并发处理器:
	: 对响应时间有高要求”,多CPU、对应用响应时间有较高要求的中、大型应用
复制代码

1 .4 Java 的四种引用类型

• 强引用
• 软引用(SoftReference)
• 弱引用(WeakReference)
• 虚引用(PhantomReference)

1)强引用
	咱们使用的大部分引用实际上都是强引用,这是使用最广泛的引用
	
软引用(SoftReference)
复制代码

1 . 5 TLAB

> 指针碰撞 
	- 对象经过引用指向实际的内存空间 , 而指向的即为对应的指针 , 在堆内存中 , 一片内存被一个指针一份为2 , 左边为已经分配内存的空间,右侧为空 , 每一次有新的对象建立,指针就会向右移动一个对象size的距离。这就被称为指针碰撞 , 可是当多线程高并发状况下 , 会出现指针来不及修改的状况
    
TLAB的全称是Thread Local Allocation Buffer,即线程本地分配缓存区,这是一个线程专用的内存分配区域。 若是设置了虚拟机参数 -XX:UseTLAB,在线程初始化时,同时也会申请一块指定大小的内存,只给当前线程使用,这样每一个线程都单独拥有一个空间,若是须要分配内存,就在本身的空间上分配,这样就不存在竞争的状况,能够大大提高分配效率。

TLAB空间的内存很是小,缺省状况下仅占有整个Eden空间的1%,也能够经过选项-XX:TLABWasteTargetPercent设置TLAB空间所占用Eden空间的百分比大小。

TLAB的本质实际上是三个指针管理的区域:start,top 和 end,每一个线程都会从Eden分配一块空间,例如说100KB,做为本身的TLAB,其中 start 和 end 是占位用的,标识出 eden 里被这个 TLAB 所管理的区域,卡住eden里的一块空间不让其它线程来这里分配。

TLAB只是让每一个线程有私有的分配指针,但底下存对象的内存空间仍是给全部线程访问的,只是其它线程没法在这个区域分配而已。


// TLAB 的缺陷
1,TLAB空间大小是固定的,可是这时候一个大对象,我TLAB剩余的空间已经容不下它了。(好比100kb的TLAB,来了个110KB的对象)
2,TLAB空间还剩一点点没有用到,有点舍不得。
3,Eden空间够的时候,你再次申请TLAB没问题,我不够了,Heap的Eden区要开始GC,
4,TLAB容许浪费空间,致使Eden区空间不连续,聚沙成塔。之后还要人帮忙打理。
    
复制代码

二 . 虚拟机

2 . 1 Java 虚拟机

Java 虚拟机,是一个能够执行 Java 字节码的虚拟机进程 , 它容许Java 查询在多个任意平台使用 , 可是跨平台的是 Java 程序(包括字节码文件) , 而不是 JVM

> 类加载器,在 JVM 启动时或者类运行时将须要的 class 加载到 JVM 中。 > 内存区,将内存划分红若干个区以模拟实际机器上的存储、记录和调度功能模块,如实际机器上的各类功能的寄存器或者 PC 指针的记录器等。 > 执行引擎,执行引擎的任务是负责执行 class 文件中包含的字节码指令,至关于实际机器上的 CPU 。 > 本地方法调用,调用 CC++ 实现的本地方法的代码返回结果。 // 运行时数据区 > 程序计数器: Java 线程私有,相似于操做系统里的 PC 计数器,它能够看作是当前线程所执行的字节码的行号指示器。 - 若是线程正在执行的是一个 Java 方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;若是正在执行的是 Native 方法,这个计数器值则为空(Undefined) > 虚拟机栈(栈内存):Java线程私有,虚拟机栈描述的是 Java 方法执行的内存模型 > 本地方法栈 :和 Java 虚拟机栈的做用相似,区别是该区域为 JVM 提供使用 Native 方法的服务 > 堆内存(线程共享):全部线程共享的一块区域,垃圾收集器管理的主要区域 - 每一个方法在执行的时候,都会建立一个栈帧用于存储局部变量、操做数、动态连接、方法出口等信息。 - 每一个方法调用都意味着一个栈帧在虚拟机栈中入栈到出栈的过程。 > 方法区(线程共享):各个线程共享的一个区域,用于存储虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据 - 线程共享区域,所以这是线程不安全的区域。 - 方法区也是一个可能会发生OutOfMemoryError的区域。 - 方法区存储的是从Class文件加载进来的静态变量、类信息、常量池以及编译器编译后的代码。 复制代码

2 . 2 内存堆细节

// 内存堆特色
- 存储的是咱们new来的对象,不存放基本类型和对象引用。
- 因为建立了大量的对象,垃圾回收器主要工做在这块区域。
- 线程共享区域,所以是线程不安全的。
- 可以发生内存溢出,主要有OutOfMemoryError和StackOverflowError。
    
// 分代
Java堆区还能够划分为新生代和老年代,新生代又能够进一步划分为Eden区、Survivor 1区、Survivor 2// 注意比例 : 
 8:1:1 + 2:3    
复制代码

2 . 3 内存栈细节

- 线程私有区域,每个线程都有独享一个虚拟机栈,所以这是线程安全的区域。
- 存放基本数据类型以及对象的引用。
- 每个方法执行的时候会在虚拟机栈中建立一个相应栈帧,方法执行完毕后该栈帧就会被销毁。
- 方法栈帧是以先进后出的方式虚拟机栈的。每个栈帧又能够划分为局部变量表、操做数栈、动态连接、方法出口以及额外的附加信息。
- 这个区域可能有两种异常:若是线程请求的栈深度大于虚拟机所容许的深度,将抛出StackOverflowError异常(一般是递归致使的);JVM动态扩展时没法申请到足够内存则抛出OutOfMemoryError异常。


复制代码

2 . 4 Java 内存堆和栈区别

栈内存用来存储基本类型的变量和对象的引用变量;堆内存用来存储Java中的对象,不管是成员变量,局部变量,仍是类变量,它们指向的对象都存储在堆内存中。

栈内存归属于单个线程,每一个线程都会有一个栈内存,其存储的变量只能在其所属线程中可见,即栈内存能够理解成线程的私有内存;堆内存中的对象对全部线程可见。堆内存中的对象能够被全部线程访问。

若是栈内存没有可用的空间存储方法调用和局部变量,JVM 会抛出 java.lang.StackOverFlowError 错误;若是是堆内存没有可用的空间存储生成的对象,JVM 会抛出 java.lang.OutOfMemoryError 错误。

栈的内存要远远小于堆内存,若是你使用递归的话,那么你的栈很快就会充满。-Xss 选项设置栈内存的大小,-Xms 选项能够设置堆的开始时的大小。
复制代码

2 . 6 HotSpot虚拟机

HotSpot 虚拟机将其物理上分为了2个部分 :

> 新生代(young generation)
 : 绝大多数最新被建立的对象会被分配到这里
 : 对象从这个区域消失的过程咱们称之为”minor GC“
 
 -> 新生代三空间
 	:一个伊甸园空间(Eden )
	:两个幸存者空间(Survivor )
	建立后待伊甸园 -- 第一次GC --> 其中一个幸存者空间 -- 不断堆积--> 饱和后移动到第二个幸存者空间 --> 清空饱和空间 --> 几轮后剩下的放入老年代

> 老年代(old generation)
 : 对象没有变得不可达,而且重新生代中存活下来,会被拷贝到这
 : 对象从老年代中消失的过程,咱们称之为”major GC“(或者”full GC“)
 
> card table
 : 存在于老年代 ,512 byte,记录老年代对新生代的应用
 : 由一个 write barrier
 
> 持久代( permanent generation ) 又名 方法区(method area)
 : 保存类常量以及字符串常量
 
> 加快缓存分配
 : bump-the-pointer
   - 跟踪在伊甸园空间建立的最后一个对象 ,放在顶部,下次建立查找该对象
 : TLABs(Thread-Local Allocation Buffers)
   - 该方案为每个线程在伊甸园空间分配一块独享的空间,这样每一个线程只访问他们本身的TLAB空间,再与bump-the-pointer技术结合能够在不加锁的状况下分配内存
复制代码

三 . 垃圾清理

3 . 1 垃圾回收的原由

> 程序员没法自动完成系统的GC ,GC 通常在如下环境被建立

大多数对象会很快变得不可达
只有不多的由老对象(建立时间较长的对象)指向新生对象的引用

复制代码

3 . 2 垃圾回收中的概念

// stop-the-world
: Stop-the-world会在任何一种GC算法中发生
: Stop-the-world意味着 JVM 由于要执行GC而中止了应用程序的执行
: 当Stop-the-world发生时,除了GC所需的线程之外,全部线程都处于等待状态,直到GC任务完成
: GC优化不少时候就是指减小Stop-the-world发生的时间
   
    
// 分代回收 
> 为何垃圾回收要分代: 
  : 不一样的对象生命周期是不同的 ,采用不一样的收集方式,能够提升回收率
  
> 分代的方式 :
  : 年轻代
  : 老年代
  : 持久代 

// 新生代 GC 和老年代 GC
新生代 : 一个 Eden 区 + 两个 Survivor 区
老年代 : 默认新生代(Young)与老年代(Old)的比例的值为 1:2 , 默认的 Eden:from:to=8:1:1
    
新生代GC(MinorGC/YoungGC):指发生在新生代的垃圾收集动做,由于 Java 对象大多都具有朝生夕灭的特性,因此 MinorGC 很是频繁,通常回收速度也比较快。
老年代GC(MajorGC/FullGC):指发生在老年代的 GC,出现了 MajorGC,常常会伴随至少一次的 MinorGC(但非绝对的,在 Parallel Scavenge 收集器的收集策略里就有直接进行 MajorGC 的策略选择过程)。MajorGC 的速度通常会比 MinorGC 慢 10 倍以上。

      
// 触发分代回收的方式
Scavenge GC和Full GC。

Scavenge GC : 新对象生成  , 而且在 Eden 申请空间失败 ,即触发   
    
    
// 垃圾收集器
新生代收集器
	-Serial 收集器
	- ParNew 收集器
		?- ParNew 收集器,是 Serial 收集器的多线程版。
	- Parallel Scavenge 收集器

老年代收集器
	- Serial Old 收集器
		?- Serial Old 收集器,是 Serial 收集器的老年代版本。
	- Parallel Old 收集器
		?- Parallel Old 收集器,是 Parallel Scavenge 收集器的老年代版本。
	- CMS 收集器
新生代 + 老年代收集器
	- G1 收集器
	- ZGC 收集器    
    
    
//  G1 和 CMS 的区别
• CMS :并发标记清除。他的主要步骤有:初始收集,并发标记,从新标记,并发清除(删除)、重置。
• G1:主要步骤:初始标记,并发标记,从新标记,复制清除(整理)
• CMS 的缺点是对 CPU 的要求比较高。G1是将内存化成了多块,全部对内段的大小有很大的要求。
• CMS是清除,因此会存在不少的内存碎片。G1是整理,因此碎片空间较小。
• G1 和 CMS 都是响应优先把,他们的目的都是尽可能控制 STW 时间。
• G1 和 CMS 的 Full GC 都是单线程 mark sweep compact 算法,直到 JDK10 才优化为并行的。

复制代码
收集器 串行、并行or并发 新生代/老年代 算法 目标 适用场景
Serial 串行 新生代 复制算法 响应速度优先 单CPU环境下的Client模式
Serial Old 串行 老年代 标记-整理 响应速度优先 单CPU环境下的Client模式、CMS的后备预案
ParNew 并行 新生代 复制算法 响应速度优先 多CPU环境时在Server模式下与CMS配合
Parallel Scavenge 并行 新生代 复制算法 吞吐量优先 在后台运算而不须要太多交互的任务
Parallel Old 并行 老年代 标记-整理 吞吐量优先 在后台运算而不须要太多交互的任务
CMS 并发 老年代 标记-清除 响应速度优先 集中在互联网站或B/S系统服务端上的Java应用
G1 并发 both 标记-整理+复制算法 响应速度优先 面向服务端应用,未来替换CMS

3 . 3 垃圾回收的分类

复制代码

3 .4 常见得垃圾回收方式

// 方式一 : 调用 system gc 方法 , 开发者手动调用该命令 , 触发 gc
System.gc()
    
// 方式二 : 调用 Runtime.getRuntime().gc() 方式 , 该方法实际上会 invoke system.gc()
 Runtime.getRuntime().gc()
    
// 方式三 : Use jmap to force GC , 经过 jmap 命令执行 gc
// 该命令不能保证万无一失 , 若是 JVM 被占用致使 GC 没法执行会出现异常 
jmap -histo:live 7544

// 方式四 : 使用 Jcmd 命令执行 GC
// 经过 Java diagnostic command (JCMD) JVM 诊断命令触发 GC 
jcmd 7544 GC.run
    
// 方式五 : Use JConsole or Java Mission Control

复制代码

3 . 5 垃圾回收的算法

> 应用计数 
	: 对一个对象有引用/移除 。 即添加/删除数量 , 垃圾回收会回收数量为 0 的对象
    
> 标记清除 
	: 第一阶段从引用根节点开始标记全部被引用的对象
	: 第二阶段遍历整个堆,把未标记的对象清除

> 复制(Copying)
	: 将算法的内存空间分为相等的两个部分,回收时,遍历当前区域,将使用的对象复制到另外的区域

> 标记-整理(Mark-Compact):
	: 第一阶段从根节点开始标记全部被引用对象
	: 第二阶段遍历整个堆,把清除未标记对象而且把存活对象“压缩”到堆的其中一块,按顺序排放

复制代码

3 . 6 常见的垃圾收集器

3 .6 .1 Serial 收集器

Serial 收集器是最基础、历史最悠久的收集器,它在进行垃圾收集的时候会暂停全部的工做线程,直到完成垃圾收集过程。下面是Serial垃圾收集器的运行示意图:
复制代码

3 .6 .2 ParNew 收集器

ParNew 垃圾收集器实则是Serial 垃圾收集器的多线程版本,这个多线程在于ParNew垃圾收集器可使用多条线程进行垃圾回收。
复制代码

3 .6 .3 Parallel Scavenge 收集器

复制代码

3. 6 .4 Serial Old 收集器

Serial Old 收集器是Serial 收集器的老年代版本。其垃圾收集器的运行原理和Serial 收集器是同样的。
复制代码

3 .6 .5 Parallel Old 收集器

Parallel Old 收集器一样是Parallel Scavenge 收集器的老年代版本,支持多线程并发收集。
复制代码

3 .6 .6 CMS 收集器

CMS 垃圾收集器的运做过程相对前面几个垃圾收集器来讲比较复杂,整个过程能够分为四个部分:

初始标记: 须要Stop The World,这里仅仅标记GC Roots可以直接关联的对象,因此速度很快。

并发标记: 从关联对象遍历整个GC Roots的引用链,这个过程耗时最长,可是却能够和用户线程并发运行。

从新标记: 修正并发时间,由于用户线程可能会致使标记产生变更,一样须要Stop The World。

并发清除: 清除已经死亡的对象。


复制代码

3 .6 .7 Garbage First 收集器

Garbage First(简称G 1)收集器是垃圾收集器发展史上里程碑式的成果,主要面向服务端应用程序。另外G 1收集器虽然还保留新生代和老年代的概念,可是新生代和老年代不在固定,它们都是一系列区域的动态集合。

• 并行与并发:G1能充分利用多CPU、多核环境下的硬件优点,使用多个CPU来缩短Stop-The-World停顿的时间,部分其余收集器本来须要停顿Java线程执行的GC动做,G1收集器仍然能够经过并发的方式让Java程序继续执行。
• 分代收集:虽然G1能够不须要其余收集器配合就能独立管理整个GC堆,但它可以采用不一样的方式去处理新建立的对象和已经存活了一段时间、熬过屡次GC的就对象以获取更好的收集效果。
• 空间整合:G1从总体上来看是基于“标记-整理”算法实现的收集器,从局部(两个Region之间)上来看是基于“复制”算法实现的,这意味着G1运做期间不会产生内存空间碎片,收集后能提供规整的可用内存。
• 可预测的停顿:这是G1相对于CMS的另外一大优点。
复制代码

四 . 对象的建立

4 . 1 建立过程

1)检测类是否被加载
当虚拟机遇到 new 指令时,首先先去检查这个指令的参数是否能在常量池中定位到一个类的符号引用,而且检查这个符号引用表明的类是否已被加载、解析和初始化过。若是没有,就执行类加载过程。

2)为对象分配内存
类加载完成之后,虚拟机就开始为对象分配内存,此时所需内存的大小就已经肯定了。只须要在堆上分配所须要的内存便可。
具体的分配内存有两种状况:
    第一种状况是内存空间绝对规整
    第二种状况是内存空间是不连续的。
		- 对于内存绝对规整的状况相对简单一些,虚拟机只须要在被占用的内存和可用空间之间移动指针便可,这种方式被称为“指针碰撞”。
		- 对于内存不规整的状况稍微复杂一点,这时候虚拟机须要维护一个列表,来记录哪些内存是可用的。分配内存的时候须要找到一个可用的内存空间,而后在列表上记录下已被分配,这种方式成为“空闲列表”。

    多线程并发时会出现正在给对象 A 分配内存,还没来得及修改指针,对象 B 又用这个指针分配内存,这样就出现问题了。解决这种问题有两种方案:
		• 第一种,是采用同步的办法,使用 CAS 来保证操做的原子性。
		• 另外一种,是每一个线程分配内存都在本身的空间内进行,便是每一个线程都在堆中预先分配一小块内存,称为本地线程分配缓冲(Thread Local Allocation Buffer, TLAB),分配内存的时候再TLAB上分配,互不干扰。能够经过 -XX:+/-UseTLAB 参数决定。
    
3)为分配的内存空间初始化零值
对象的内存分配完成后,还须要将对象的内存空间都初始化为零值,这样能保证对象即便没有赋初值,也能够直接使用。
    
4)对对象进行其余设置
分配完内存空间,初始化零值以后,虚拟机还须要对对象进行其余必要的设置,设置的地方都在对象头中,包括这个对象所属的类,类的元数据信息,对象的 hashcode ,GC 分代年龄等信息。
    
5)执行 init 方法
执行完上面的步骤以后,在虚拟机里这个对象就算建立成功了,可是对于 Java 程序来讲还须要执行 init 方法才算真正的建立完成,由于这个时候对象只是被初始化零值了,尚未真正的去根据程序中的代码分配初始值,调用了 init 方法以后,这个对象才真正能使用。

复制代码

4 . 2 内存布局

对象的内存布局包括三个部分:
	- 对象头:对象头包括两部分信息。
		• 第一部分,是存储对象自身的运行时数据,如哈希码,GC 分代年龄,锁状态标志,线程持有的锁等等。
		• 第二部分,是类型指针,即对象指向类元数据的指针。
	- 实例数据:就是数据。
	- 对齐填充:不是必然的存在,就是为了对齐。
复制代码

4 . 3 对象的访问定位

句柄定位:Java 堆会画出一块内存来做为句柄池,reference 中存储的就是对象的句柄地址,而句柄中包含了对象实例数据与类型数据各自的具体地址信息。

直接指针访问:Java 堆对象的不居中就必须考虑如何放置访问类型数据的相关信息,而 reference 中存储的直接就是对象地址。
复制代码

4 . 4 对象死亡

引用计数算法: 为对象添加一个引用计数器,每当对象在一个地方被引用,则该计数器加1;每当对象引用失效时,计数器减1。但计数器为0的时候,就表白该对象没有被引用。

可达性分析算法: 经过一系列被称之为“GC Roots”的根节点开始,沿着引用链进行搜索,凡是在引用链上的对象都不会被回收。

// GC Root 对象 : 可达性的根对象
Java虚拟机栈中被引用的对象,各个线程调用的参数、局部变量、临时变量等。
方法区中类静态属性引用的对象,好比引用类型的静态变量。
方法区中常量引用的对象。本地方法栈中所引用的对象。
Java虚拟机内部的引用,基本数据类型对应的Class对象,一些常驻的异常对象。
被同步锁(synchronized)持有的对象。

  
复制代码

4 . 6 类加载器

// 什么是类加载器
类加载器(ClassLoader),用来加载 Java 类到 Java 虚拟机中 , 通常来讲,Java 虚拟机使用 Java 类的方式以下:Java 源程序(.java 文件)在通过 Java 编译器编译以后就被转换成 Java 字节代码(.class 文件) , 类加载器,负责读取 Java 字节代码,并转换成 java.lang.Class 类的一个实例

// 发生的时期1、遇到 new、getstatic、putstatic、invokestatic 这四条字节码指令时,若是类还没进行初始化,则须要先触发其初始化。
• 2、使用 java.lang.reflect 包的方法对类进行反射调用的时候,若是类还没进行初始化,则须要先触发其初始化。
• 3、当初始化了一个类的时候,若是发现其父类还没进行初始化,则须要先触发其父类的初始化。
• 4、当虚拟机启动时,用户须要指定一个执行的主类,即调用其 #main(String[] args) 方法,虚拟机则会先初始化该主类。
• 5、当使用 JDK7 的动态语言支持时,若是一个 java.lang.invoke.MethodHandle 实例最后的解析结果为 REF_getStatic、REF_putStatic、REF_invokeStatic 的方法句柄,而且这个方法句柄所对应的类没有进行过初始化,则须要先触发其初始化。
    
// 加载Class 的方式
• 第一个阶段,加载(Loading),是找到 .class 文件并把这个文件包含的字节码加载到内存中。
• 第二阶段,链接(Linking),又能够分为三个步骤,分别是字节码验证、Class 类数据结构分析及相应的内存分配、最后的符号表的解析。
• 第三阶段,Initialization(类中静态属性和初始化赋值),以及Using(静态块的执行)等。

    
    
复制代码

4 . 7 ClassLoader 详解

// Java 中有三个类加载器
1. Bootstrap CLassloder 
	最顶层的加载类,主要加载核心类库,%JRE_HOME%\lib下的rt.jar、resources.jar、charsets.jar和class等。另外须要注意的是能够经过启动jvm时指定-Xbootclasspath和路径来改变Bootstrap ClassLoader的加载目录。好比java -Xbootclasspath/a:path被指定的文件追加到默认的bootstrap路径中。咱们能够打开个人电脑,在上面的目录下查看,看看这些jar包是否是存在于这个目录。  2. Extention ClassLoader  扩展的类加载器,加载目录%JRE_HOME%\lib\ext目录下的jar包和class文件。还能够加载-D java.ext.dirs选项指定的目录。  3. AppClassLoader 加载当前应用的classpath的全部类 // classLoad 加载流程 Java 基于 Launcher 入口应用 - Launcher初始化了ExtClassLoaderAppClassLoader - // 知识点 1 父加载器不是父类 2 Bootstrap ClassLoader是由C/C++编写的 // 经常使用方法 - 获取父加载器 : cl.getParent() , cl.getParent().getParent() - 经过指定的全限定类名加载class : loadClass() 复制代码

// 双亲委派
1 首先判断这个class是否是已经加载成功 2 当 class 未加载 , 先异常往根节点查找 , 是否上层加载器已经加载 (其中若是某个层已经加载 , 则直接返回) 3 当到 Bootstrap classloader 仍然未加载 , 则由 Bootstrap classloader 到指定的路径查找 , 若是没有查找到 ,则由子加载器继续到其对应路径查找 4 到此时仍然没有查找到 ,则返回异常 // 流程 TODO : // 思考 : 加载对象的时候是从顶层向下 , 查找对象是由底层向上 业务中咱们是可以定义多个 Classloader , 使用双亲委派避免不知道在哪一个 classLoader 中查找 , 也避免重复加载的问题 复制代码

4 . 8 class 文件

// Class 加载流程 
1. .java 文件编译后 , 生成一个class文件 2. classloader经过相关的规则初次找到这个class 3. 而后会读取class的头文件,包括如下几种数据 a. 0xCAFEBABE:判断是否为Java编译 b. 50 , 0:判断版本号 4. String, ArrayList分别有不一样层次的loader加载,最顶层的叫Bootstrap Classloader , 下一次级叫Extension Classloader,最底层App Classloade 5. 接着class会被加载到方法区 , 在堆中new 出的该class类的对象来确认class是否被加载 6. 每一个class会有局部变量区,还有一个操做数栈 , 线程就会按照流程执行,例如取出局部变量区的数据,放入栈中,最后运行后变成一个数后从新放入 7. 接中从栈中取出结果,从新放入变量区 8. 而线程也不必定只有一个工做台,也可能有多个,可是只在最上面工做(多线程状况),这每一个工做台叫栈帧,而多个工做台就是方法调用方法的结果 // Java 对象头 GC分代信息,锁信息,哈希码,指向Class类元信息的指针 Hotspot 虚拟机的对象头主要包括两部分数据:Mark Word(标记字段)、Klass Pointer(类型指针) - Klass Point 是是对象指向它的类元数据的指针,虚拟机经过这个指针来肯定这个对象是哪一个类的实例 - Mark Word 用于存储对象自身的运行时数据,如哈希码(HashCode)、GC 分代年龄、锁状态标志、线程持有的锁、偏向线程 ID、偏向时间戳等等。Java 对象头通常占有两个机器码(在 32 位虚拟机中,1 个机器码等于 4 字节,也就是 32 bits)。可是若是对象是数组类型,则须要三个机器码,由于 JVM 虚拟机能够经过 Java 对象的元数据信息肯定 Java 对象的大小,没法从数组的元数据来确认数组的大小,因此用一块来记录数组长度。 // Java 对象实例数据 实例数据部分是对象真正存储的有效信息 // Java 对象对齐填充 虚拟机规范要求对象大小必须是8字节的整数倍 复制代码

Java 对象头的存储结构 32 位 TODO : 待完善html

好文推荐@ 从一个class文件深刻理解Java字节码结构_四月葡萄的博客-CSDN博客_java字节码java

image.png

查看字节码的方式mysql

// 方法一 : Java 基本工具类
- 查看基本的字节码 : javap java.lang.Object
- 查看基本成员 : javap -p
- 查看详细信息 : javap -v
- 反汇编整个类 : javap -c 

// 方法二 : 使用 ASM 查询
<dependency>
    <groupId>org.ow2.asm</groupId>
    <artifactId>asm</artifactId>
    <version>8.0.1</version>
</dependency>
<dependency>
    <groupId>org.ow2.asm</groupId>
    <artifactId>asm-util</artifactId>
    <version>8.0.1</version>
</dependency>

try {
    ClassReader reader = new ClassReader("java.lang.Object");
    StringWriter sw = new StringWriter();
    TraceClassVisitor tcv = new TraceClassVisitor(new PrintWriter(System.out));
    reader.accept(tcv, 0);
} catch (IOException e) {
    e.printStackTrace();
}

// 方法三 : BCEL
<dependency>
    <groupId>org.apache.bcel</groupId>
    <artifactId>bcel</artifactId>
    <version>6.5.0</version>
</dependency>

try { 
    JavaClass objectClazz = Repository.lookupClass("java.lang.Object");
    System.out.println(objectClazz.toString());
} catch (ClassNotFoundException e) { 
    e.printStackTrace(); 
}

// 方法四 : Javassist 
<dependency>
    <groupId>org.javassist</groupId>
    <artifactId>javassist</artifactId>
    <version>3.27.0-GA</version>
</dependency>

try {
    ClassPool cp = ClassPool.getDefault();
    ClassFile cf = cp.get("java.lang.Object").getClassFile();
    cf.write(new DataOutputStream(new FileOutputStream("Object.class")));
} catch (NotFoundException e) {
    e.printStackTrace();
}

// 方法五 : Jclasslib (IDEA 插件)

复制代码

4 . 9 对象在JVM 中的表示 -- OOP-Klass

HotSpot 经过 OOP-Klass 模型来在虚拟机中表示一个对象 , 这里的 OOP 指的是 Ordinary Object Pointer (普通对象指针),它用来表示对象的实例信息,看起来像个指针其实是藏在指针里的对象。而 Klass 则包含元数据和方法信息,用来描述Java类。

做用 : 
	避免让每一个对象中都含有一个vtable(虚函数表),因此就把对象模型拆成klass和oop,其中oop中不含有任何虚函数,而Klass就含有虚函数表,能够进行method dispatch。

Klass : Java类在HotSpot中的c++对等体,用来描述Java类 , 在加载过程当中建立
	- 实现语言层面的Java类
	- 实现Java对象的分发功能
	
OOP : 在Java程序运行过程当中new对象时建立的 , 包含如下部分
    - instanceOopDesc,也叫对象头
        - Mark Word,主要存储对象运行时记录信息,如hashcode, GC分代年龄,锁状态标志,线程ID,时间戳等
        - 元数据指针,即指向方法区的instanceKlass实例
    - 实例数据
复制代码

五 . GC 监控

5 .1 什么时 GC 监控

GC 监控是指监控 JVM 执行 GC	的过程
例如 :
	> 什么时候一个新生代被移动到老年代,以及其中被花费的时间
	> stop the world 什么时候发生,执行了多长时间
	
> GC 访问的接口 : GUI / CUI 两大类
	: cUI GC 监控方法使用的独立的 jstat 的 CUI 应用
	: cUI 或者在启动的时候选择JVM 参数 verbosegc
	: GUI GC 由一个单独的图形化界面完成 : jconsole ,jvisualvm , Visual GC
	

jstat :
参数名称见附录 

-verbosegc : 启动 Java 应用时可指定
复制代码

5 .2 常见的 GC 监控工具

• jps :虚拟机进程情况工具
	JVM Process Status Tool ,显示指定系统内全部的HotSpot虚拟机进程。
    -q:忽略输出的类名、Jar名以及传递给main方法的参数,只输出pid。
    -m:输出传递给main方法的参数,若是是内嵌的JVM则输出为null。
    -l:输出彻底的包名,应用主类名,jar的彻底路径名
    -v:输出传给jvm的参数
    -V:输出经过标记的文件传递给JVM的参数(.hotspotrc文件,或者是经过参数-XX:Flags=指定的文件)。
    -J 用于传递jvm选项到由javac调用的java加载器中,
        
• jstat :虚拟机统计信息监控工具
	JVM statistics Monitoring ,是用于监视虚拟机运行时状态信息的命令,它能够显示出虚拟机进程中的类装载、内存、垃圾收集、JIT编译等运行数据。 常见的用法包括类的加载及卸载状况 , 查看新生代、老生代及持久代的容量及使用状况 , 查看新生代、老生代及持久代的垃圾收集状况,包括垃圾回收的次数及垃圾回收所占用的时间 , 查看新生代中Eden区及Survior区中容量及分配状况
        
• jinfo :Java 配置信息工具
	JVM Configuration info ,这个命令做用是实时查看和调整虚拟机运行参数。
        
• jmap :Java 内存映射工具
	JVM Memory Map ,命令用于生成 heap dump 文件。
        
• jhat :虚拟机堆转储快照分析工具
	JVM Heap Analysis Tool ,命令是与 jmap 搭配使用,用来分析 jmap 生成的 dump 文件。jhat 内置了一个微型 的HTTP/HTML 服务器,生成 dump 的分析结果后,能够在浏览器中查看。
        
• jstack :Java 堆栈跟踪工具
	Java Stack Trace ,用于生成 Java 虚拟机当前时刻的线程快照。
        
• HSDIS :JIT 生成代码反编译

// Java 自带
• JConsole :Java 监视与管理控制台
	Java Monitoring and Management Console 是从 Java5 开始,在 JDK 中自带的 Java 监控和管理控制台,用于对 JVM 中内存,线程和类等的监控。
• VisualVM :多合一故障处理工具
	JDK 自带全能工具,能够分析内存快照、线程快照、监控内存变化、GC变化等。
	特别是 BTrace 插件,动态跟踪分析工具。

// 其余
• MAT :内存分析工具
• [GChisto](GC 日志分析工具 —— GChisto) :一款专业分析 GC 日志的工具。
    
    
// JMC : Java Mission Control 
-> 完整的图形化界面
-> 提供对象查看    

复制代码

5.3 监控经常使用命令

// 获取 Java 程序使用的内存
Runtime#freeMemory() 方法,返回剩余空间的字节数。
Runtime#totalMemory() 方法,总内存的字节数。
Runtime#maxMemory() 方法,返回最大内存的字节数。
复制代码

5.4 GC 分析方式

// --------------- jconsole 使用
	- 控制台直接输入 : jconsole
	- 1 选择须要调试的本地链接 , 点击链接
	- 2 选择远程链接 , 输入用户名 , 口令链接
        
// -------------- jvisualvm 使用
	- 找到 JDK 的安装目录 , 点击运行 jvisualvm.exe
	- 右侧直接选择运行中的应用     

// --------------- jstat 使用 (命令行)
jstat <option> [-t] [-h] <pid>  <interval> <count>
  参数解释:
    option   能够从下面参数中选择
        -class 显示ClassLoad的相关信息; -compiler 显示JIT编译的相关信息; -gc 显示和gc相关的堆信息; -gccapacity    显示各个代的容量以及使用状况; -gccause 显示垃圾回收的相关信息(通-gcutil),同时显示最后一次或当前正在发生的垃圾回收的诱因; -gcnew 显示新生代信息; -gcnewcapacity 显示新生代大小和使用状况; -gcold 显示老年代和永久代的信息; -gcoldcapacity 显示老年代的大小; -gcpermcapacity 显示永久代的大小; -gcutil   显示垃圾收集信息; -printcompilation输出JIT编译的方法信息; -t 能够在打印的列加上Timestamp列,用于显示系统运行的时间 -h   能够在周期性数据数据的时候,能够在指定输出多少行之后输出一次表头 interval 执行每次的间隔时间,单位为毫秒 count 用于指定输出多少次记录,缺省则会一直打印 案例 :  |- : Jstat -cpmpiler pid |- 查看pid为23814的ClassLoad相关信息,每秒钟打印一次,总共打印5次 : jstat -gc pid 1000 5 |- 显示各个代的容量的信息 : jstat -gccapacity pid |- 显示最近一次GC的缘由 : jstat -gccause pid |- 显示新生代的详细信息 : jstat -gcnew pid: |- 输出新生代各个区的详细信息 : jstat -gcnewcapacity pid |- 显示老年代GC的详细状况 : jstat -gcold pid |- 输出老年代的详细信息 : jstat -gcoldcapacitp pid |- 查看每一个代区域使用的百分比状况 : jstat -gcutil pid // ------------------- jmap 使用 jmap [option] vmid -dump : 生成Java堆转储快照 -heap:显示Java堆详细信息 -histo:显示堆中对象统计信息 案例 : |- 使用jmap 生成快照文件 : jmap -dump:format=b,file=jsconsole.bin 7020
|- 生成一个正常运行的jconsole的快照的实例 : jps
|- 查看堆栈信息 : jmap -heap pid
|- 使用jmap 生成快照文件 : jmap -dump:format=b,file=jsconsole.bin 7020    
    
// --------------------- jhat 使用
Step 1 : 导出栈
	jmap -dump:live,file=a.log pid
Step 2 : 分析堆文件
	jhat -J-Xmx512M a1.log
Step 3 : 查看
	http://ip:7000/
Step 4 : 使用 SQL 查询
    select <javascript expression to select>
    [from [instanceof] <class name> <identifier>] [where <javascript boolean expression to filter>] (1)class namejava类的彻底限定名,如:java.lang.String, java.util.ArrayList, [Cchar数组, [Ljava.io.Filejava.io.File[] (2)类的彻底限定名不足以惟一的辨识一个类,由于不一样的ClassLoader载入的相同的类,它们在jvm中是不一样类型的 (3)instanceof表示也查询某一个类的子类,若是不明确instanceof,则只精确查询class name指定的类 (4)fromwhere子句都是可选的 (5)java域表示:obj.field_namejava数组表示:array[index] 案例 : (1)查询长度大于100的字符串 select s from java.lang.String s where s.count > 100 (2)查询长度大于256的数组 select a from [I a where a.length > 256 (3)显示匹配某一正则表达式的字符串 select a.value.toString() from java.lang.String s where /java/(s.value.toString()) (4)显示全部文件对象的文件路径 select file.path.value.toString() from java.io.File file (5)显示全部ClassLoader的类名 select classof(cl).name from instanceof java.lang.ClassLoader cl (6)经过引用查询对象 select o from instanceof 0xd404d404 o https://www.cnblogs.com/baihuitestsoftware/articles/6406271.html // ------------- jvm jinfo |- 查看 JVM 参数 : jinfo -flags process_id |- 查看java系统参数 : jinfo -sysprops process_id 复制代码

5.5 压测工具扩展

// 经常使用的压测工具
1 LoadRunner  : 预测系统行为和性能的负载测试工具
2 Apache JMeter : 开源压测产品
3 NeoLoad : 负载和性能测试工具
4 WebLOAD : 来自Radview公司的负载测试工具,它可被用以测试系统性能和弹性,也可被用于正确性验证
5 阿里云PTS : 一个SaaS性能测试平台,具备强大的分布式压测能力
6 Loadstorm : 一款针对Web应用的云端负载测试工具,经过模拟海量点击来测试Web应用在大负载下的性能表现
7 CloudTest : 一个集性能和功能测试于一体的综合压力测试云平台
8 Load impact : 一款服务于DevOps的性能测试工具,支持各类平台的网站、Web应用、移动应用和API测试
    
// JMeter 使用
    
复制代码

5.6 Jstack 使用

> Step 1 : 拿到 pid
ps -ef | grep java

> Step 2 : 查看资源进程
top -Hp 30275

printf "%x\n" 3440


> 简单使用
jstack 30275
    
> 查看指定进程
printf "%x\n" 17880  
jstack 17880|grep 45d8 -A 30
    
// 查看 TimeWait 


// Windows 版本
netstat -ano |findstr "80" windows
netstat -an | find "TIME_WAIT" /C    
    
// jstack 统计线程数
jstack -l 28367 | grep 'java.lang.Thread.State' | wc -l    
    
    
// jstack 

Usage:
    jstack [-l] <pid>
        (to connect to running process) 链接活动线程
    jstack -F [-m] [-l] <pid>
        (to connect to a hung process) 链接阻塞线程
    jstack [-m] [-l] <executable> <core>
        (to connect to a core file) 链接dump的文件
    jstack [-m] [-l] [server_id@]<remote server IP or hostname>
        (to connect to a remote debug server) 链接远程服务器

Options:
    -F  to force a thread dump. Use when jstack <pid> does not respond (process is hung) -m to print both java and native frames (mixed mode) -l long listing. Prints additional information about locks -h or -help to print this help message 复制代码

5.7 JOL 使用

JOL:查看Java 对象布局、大小工具

<dependency>
    <groupId>org.openjdk.jol</groupId>
    <artifactId>jol-core</artifactId>
    <version>put-the-version-here</version>
</dependency>

static Object generate() {
	Map<String, Object> map = new HashMap<>();
	map.put("a", new Integer(1));
	map.put("b", "b");
	map.put("c", new Date());
 
	for (int i = 0; i < 10; i++) {
		map.put(String.valueOf(i), String.valueOf(i));
	}
	return map;
}

查看对象内部信息: ClassLayout.parseInstance(obj).toPrintable()
查看对象外部信息:包括引用的对象:GraphLayout.parseInstance(obj).toPrintable()
查看对象占用空间总大小:GraphLayout.parseInstance(obj).totalSize()
复制代码

5.8 Thread Dump

Thread Dump是很是有用的诊断Java应用问题的工具。每个Java虚拟机都有及时生成全部线程在某一点状态的thread-dump的能力,虽然各个 Java虚拟机打印的thread dump略有不一样,可是大多都提供了当前活动线程的快照,及JVM中全部Java线程的堆栈跟踪信息,堆栈信息通常包含完整的类名及所执行的方法,若是可能的话还有源代码的行数。

1. 查找内存泄露,常见的是程序里load大量的数据到缓存;
2. 发现死锁线程;


// Linux 抓取 Dump 的方式 (20810 是 jstack 在Java 目录下 )
jstack -l 20810 | tee -a /opt/jstack.log

// 简单学习 : 
// 虚拟机信息
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.251-b08 mixed mode): // 线程info信息块: // 线程名称 - #36 - 线程类型 (daemon) - 优先级 (prio) // tid : JVM 线程ID // nid : 对应系统线程id // 线程状态:in Object.wait(). // 起始栈地址:[0xae77d000] "Attach Listener" #36 daemon prio=9 os_prio=0 tid=0x00007f5fec001000 nid=0x6658 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

   Locked ownable synchronizers:
	- None
        
// 堆栈信息
线程状态 - java.lang.Thread.State: WAITING (parking)
线程抛出节点 -	at sun.misc.Unsafe.park(Native Method)
	- parking to wait for  <0x00000000e4e7bdf8> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
	at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
	at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2039)
	at java.util.concurrent.LinkedBlockingQueue.take(LinkedBlockingQueue.java:442)
	at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:107)
	at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:33)
	at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1074)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1134)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
	at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
	at java.lang.Thread.run(Thread.java:748)

// 方案
cpu飙高,load高,响应很慢 --> 单请求 dump 屡次
查找占用cpu最多的线程信息 --> 对对应的线程进行 dump , 先 top 查询对应 id
cpu使用率不高可是响应很慢 --> 进行dump,查看是否有不少thread struck在了i/o、数据库等地方,定位瓶颈缘由
请求没法响应 --> 屡次dump,对比是否全部的runnable线程都一直在执行相同的方法

// 常见分析 
死锁 , 热锁  
复制代码

六 . GC 优化

6 . 1 GC 优化的前提

> GC 优化永远是最后一项任务

> 原则 :
	> 将转移到老年代的对象数量降到最少
		:调整新生代空间的大小。
	> 减小 Full GC 的执行时间
		: 你须要将老年代空间设定为一个“合适”的值
复制代码

6 . 2 GC 优化的方案

> 使用  StringBuilder 或者StringBuffer 来替代String
> 尽可能少的输出日志

GC 优化考虑的参数
复制代码

6 . 3 GC 优化须要考虑的参数

定义 参数 描述
堆内存空间 -Xms Heap area size when starting JVM启动JVM时的堆内存空间。
-Xmx Maximum heap area size堆内存最大限制
新生代空间 -XX:NewRatio Ratio of New area and Old area新生代和老年代的占比
-XX:NewSize New area size新生代空间
-XX:SurvivorRatio Ratio of Eden area and Survivor area伊甸园空间和幸存者空间的占比

6 . 4 GC类型可选参数

分类 参数 备注
Serial GC -XX:+UseSerialGC
Parallel GC -XX:+UseParallelGC -XX:ParallelGCThreads=value
Parallel Compacting GC -XX:+UseParallelOldGC
CMS GC -XX:+UseConcMarkSweepGC -XX:+UseParNewGC -XX:+CMSParallelRemarkEnabled -XX:CMSInitiatingOccupancyFraction=value -XX:+UseCMSInitiatingOccupancyOnly
G1 -XX:+UnlockExperimentalVMOptions -XX:+UseG1GC 在JDK6中这两个参数必须同时使用

6 . 5 GC 优化过程

1 > 监控 GC 状态
2 > 分析监控结果 , 考虑是否须要GC
3 > 调整 GC 类型 , 分配存储空间
4 > 分析结果
复制代码

6 . 6 内存溢出的状况及分析

> 1 堆栈溢出
	- java.lang.OutOfMemoryError: ......java heap space.....
	- 看到heap相关的时候就确定是堆栈溢出 , 适当调整 -Xmx和-Xms
	- 访问量太多而且每一个访问的时间太长或者数据太多,致使数据释放不掉
        - java.lang.OutOfMemoryError:GC over head limit exceeded -- 系统处于高频的GC状态,并且回收的效果依然不佳
> 2 PermGen的溢出
	- java.lang.OutOfMemoryError: PermGen space
	- 系统的代码很是多或引用的第三方包很是多、或代码中使用了大量的常量、或经过intern注入常量、或者经过动态代码加载等方法,致使常量池的膨胀
	-XX:PermSize和-XX:MaxPermSize的大小
        
> 3 ByteBuffer中的allocateDirect() 溢出
    - java.lang.OutOfMemoryError: Direct buffer memory
    -直接或间接使用了ByteBuffer中的allocateDirect方法的时候,而不作clear的时候就会出现相似的问题
	-XX:MaxDirectMemorySize
        
> 4 java.lang.StackOverflowError
    - java.lang.StackOverflowError
    - -Xss过小了,咱们申请不少局部调用的栈针等内容是存放在用户当前所持有的线程中的
        
> 5 java.lang.OutOfMemoryError: unable to create new native thread
    - 说明除了heap之外的区域,没法为线程分配一块内存区域了,这个要么是内存自己就不够,要么heap的空间设置得太大了

> 6 java.lang.OutOfMemoryError: request {} byte for {}out of swap
    - 通常是因为地址空间不够而致使
复制代码

6 . 7 Full GC 缘由分析及解决

// 缘由 : 
1 . 应用程序建立了太多没法快速回收的对象。
2 . 当堆被分割时,即便有不少空闲空间,在老代中直接分配也可能失败

https://blog.gceasy.io/2020/06/02/simple-effective-g1-gc-tuning-tips/
https://www.oracle.com/technetwork/tutorials/tutorials-1876574.html
复制代码

七 小知识点

7 . 1 Jar 相关

TODO
复制代码

7 . 2 CPU

TODO
复制代码

八 其余

# 32 位 JVM 和 64 位 JVM 的最大堆内存分别是多少

理论上说上 32 位的 JVM 堆内存能够到达 2^32,即 4GB
4 位 JVM 容许指定最大的堆内存,理论上能够达到 2^64 
复制代码

# 直接内存(堆外内存)

> 直接内存(Direct Memory),并非虚拟机运行时数据区的一部分,也不是 Java 虚拟机规范中农定义的内存区域
> NIO(New Input/Output) 类,引入了一种基于通道(Channel)与缓冲区(Buffer)的 I/O 方式,它可使用 native 函数库直接分配堆外内存,而后通脱一个存储在 Java 堆中的 DirectByteBuffer 对象做为这块内存的引用进行操做

// 对比
直接内存申请空间耗费更高的性能,当频繁申请到必定量时尤其明显
直接内存 IO 读写的性能要优于普通的堆内存,在屡次读写操做的状况下差别明显
复制代码

# 其余工具

> GCEasy : 
复制代码

# JMC 分析流程

// Step 1 : 开启指定应用的飞行记录

// Step 2 : 分析模块 , 飞行记录提供了如下几个模块
> 通常信息 :  | 概述  | JVM 信息 | 系统属性 | 记录
    - CPU 占用率 : 能够判断是否CPU占满致使的缓慢
    - 堆使用率 : 内存使用状况会致使垃圾收集的频率 , Redis 的使用(AOF/RDB持久化异常) ,
    - JVM 信息 : 能够了解到当前使用的虚拟机类型(不一样类型虚拟机会使用不一样的回收策略 , 以及使用的JDK , 配置的 JVM 参数等)
        
> 内存 : | 概述 | 垃圾收集 | GC 时间 | GC 配置 | 分配 | 对象统计信息
    - 概述 : 
		- GC 配置 : 包含 GC 的配置信息 , 以及对应的收集器类型
		- GC 统计时间 :  
    - 垃圾收集 : 包含垃圾收集的次数和消耗的时间
        -> 垃圾收集的频率是否正常 , 是否过于频繁
        -> 每次消耗的时候会不会太长 ?stop-world 后会影响其余的运行
	- GC 时间 : 该时间为不一样年龄代的时间
	- GC 配置 : 配置的 GC 线程数 , 堆内存等配置详情
	- 分配 : 主要是 TLAB 的分配状况 , 

> 代码 : | 概述 | 热点方法 | 调用树 | 异常错误 | 编译 | 类加载
    - 热点方法 : 判断代码中对相关方法的调用是否合理(对应类的堆会不会过大 , 对象会不会过多)
    - 热点方法 : 判断经常使用的对象会不会有多线程风险及死锁风险 , 是否效率太低
    - 调用树 : 经过调用树追溯问题的根源
    - 异常错误 : 判断是否存在异常
    
> 线程 : | 概述 | 热点线程 | 争用 | 等待时间 | 线程转储 | 锁定实例
    - 判断死锁
    - 判断线程的销毁状况
    - 判断是否有切换线程带来的损失 (频繁切换) , 热锁的状况
    - 判断线程是否合理使用了线程池等工具
    
> IO :   | 概述 | 文件读写 | 套接字读写 | 
    - 这个模块能够有效的分析是否为文件读写时间致使的延迟或者套接字访问致使的系统缓慢
    
> 系统信息及环境变量 , 略
    
> 事件 : 发生的事件比例 , 包括 Java Thread Park , Java Thread start , Java Thread end 等    
复制代码

# skywalking 分析流程

skywalking 是链路分析工具 , 是很好的辅助工具 , 能快速的分析瓶颈点

// 使用方式(不过多简述 , 不少) , 注意须要点击一下刷新才会出数据
-javaagent:D:\java\plugins\shywalking\agent82\skywalking-agent.jar    
    
// 经常使用用法: 
- 仪表盘 : 用于查看各服务器状态    
- 拓扑图 : 用于分析服务器的结构是否合理 (redis 服务器 , mysql 服务器 , 等等其余的)
- 追踪 : 能够快速判断慢SQL , 慢 接口    
- 性能分析 : (建立的端点是追踪里面的端点,点击分析后能够直接追踪到对应的代码行)    
复制代码

九 优化的流程

9.1 压测流程

> 对链接数进行优化 , 包括
	- Mysql 链接数 
	- redis 链接数
	- Mysql 链接池数量 (链接池数量不是越大月好)
	- Redis 链接池数量
	- tomcat 链接数
	- TCP 链接数 (包括指定端口 , 随机端口 , 端口总数限制)
	- LDAP 链接数
	- OpenFile 链接数
	
> 对 GC 进行分析
	- 1 查看 CPU 使用状况 : top
	- 2 查看指定进程的使用 : top -Hp [进程ID]
	- 3 分析当前进程栈 : jstack [进程ID] > jstack_01
	- 4 查看 GC 状况 : jstat -gcutil [进程id] 1000
	- 5 查看堆内存 : jmap -histo [进程id] > jmap.txt
	- 6 打印堆内存快照 : jmap -dump:format=b,file=aa.bin 1232134
	
> 对 GC 进行优化
	// 80 时进行 GC
	- -XX:CMSInitiatingOccupancyFraction=80 -XX:+UseCMSInitiatingOccupancyOnly
	// 保留 GC log 
	- -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintHeapAtGC -Xloggc:gc.log
	
>  负载均衡
	- 判断负载的方式是轮询仍是压力 
	
// 优化 新生代容积
-Xmn350M -> -Xmn800M
-XX:SurvivorRatio=4 -> -XX:SurvivorRatio=8
-Xms1000m ->-Xms1800m
    
// 优化 metaspace
-Xmn350M -> -Xmn800M
-Xms1000M ->1800M
-XX:MetaspaceSize=200M
-XX:CMSInitiatingOccupancyFraction=75    	
复制代码

附录 :

Jstat 参数名称

参数名称 描述
gc 输出每一个堆区域的当前可用空间以及已用空间(伊甸园,幸存者等等),GC执行的总次数,GC操做累计所花费的时间。
gccapactiy 输出每一个堆区域的最小空间限制(ms)/最大空间限制(mx),当前大小,每一个区域之上执行GC的次数。(不输出当前已用空间以及GC执行时间)。
gccause 输出-gcutil提供的信息以及最后一次执行GC的发生缘由和当前所执行的GC的发生缘由
gcnew 输出新生代空间的GC性能数据
gcnewcapacity 输出新生代空间的大小的统计数据。
gcold 输出老年代空间的GC性能数据。
gcoldcapacity 输出老年代空间的大小的统计数据。
gcpermcapacity 输出持久带空间的大小的统计数据。
gcutil 输出每一个堆区域使用占比,以及GC执行的总次数和GC操做所花费的事件。
说明 Jstat参数
S0C 输出Survivor0空间的大小。单位KB。 -gc -gccapacity -gcnew -gcnewcapacity
S1C 输出Survivor1空间的大小。单位KB。 -gc -gccapacity -gcnew -gcnewcapacity
S0U 输出Survivor0已用空间的大小。单位KB。 -gc -gcnew
S1U 输出Survivor1已用空间的大小。单位KB。 -gc -gcnew
EC 输出Eden空间的大小。单位KB。 -gc -gccapacity -gcnew -gcnewcapacity
EU 输出Eden已用空间的大小。单位KB。 -gc -gcnew
OC 输出老年代空间的大小。单位KB。 -gc -gccapacity -gcold -gcoldcapacity
OU 输出老年代已用空间的大小。单位KB。 -gc -gcold
PC 输出持久代空间的大小。单位KB。 -gc -gccapacity -gcold -gcoldcapacity -gcpermcapacity
PU 输出持久代已用空间的大小。单位KB。 -gc -gcold
YGC 新生代空间GC时间发生的次数。 -gc -gccapacity -gcnew -gcnewcapacity -gcold -gcoldcapacity -gcpermcapacity -gcutil -gccause
YGCT 新生代GC处理花费的时间。 -gc -gcnew -gcutil -gccause
FGC full GC发生的次数。 -gc -gccapacity -gcnew -gcnewcapacity -gcold -gcoldcapacity -gcpermcapacity -gcutil -gccause
FGCT full GC操做花费的时间 -gc -gcold -gcoldcapacity -gcpermcapacity -gcutil -gccause
GCT GC操做花费的总时间。 -gc -gcold -gcoldcapacity -gcpermcapacity -gcutil -gccause
NGCMN 新生代最小空间容量,单位KB。 -gccapacity -gcnewcapacity
NGCMX 新生代最大空间容量,单位KB。 -gccapacity -gcnewcapacity
NGC 新生代当前空间容量,单位KB。 -gccapacity -gcnewcapacity
OGCMN 老年代最小空间容量,单位KB。 -gccapacity -gcoldcapacity
OGCMX 老年代最大空间容量,单位KB。 -gccapacity -gcoldcapacity
OGC 老年代当前空间容量制,单位KB。 -gccapacity -gcoldcapacity
PGCMN 持久代最小空间容量,单位KB。 -gccapacity -gcpermcapacity
PGCMX 持久代最大空间容量,单位KB。 -gccapacity -gcpermcapacity
PGC 持久代当前空间容量,单位KB。 -gccapacity -gcpermcapacity
PC 持久代当前空间大小,单位KB -gccapacity -gcpermcapacity
PU 持久代当前已用空间大小,单位KB -gc -gcold
LGCC 最后一次GC发生的缘由 -gccause
GCC 当前GC发生的缘由 -gccause
TT 老年化阈值。被移动到老年代以前,在新生代空存活的次数。 -gcnew
MTT 最大老年化阈值。被移动到老年代以前,在新生代空存活的次数。 -gcnew
DSS 幸存者区所需空间大小,单位KB。 -gcnew

虚拟机常见配置快查

说明 命令
开启 GC Log (java8) -XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:{file-path}
开启 GC Log (java9) -Xlog:gc*:file={file-path}

致谢

// 此篇笔记是一个总结分析的笔记 , 时间周期较长 , 不少知识点已经难以追溯出处 , 若是此处遗漏了某位道友 ,敬请谅解

Java 技术驿站 , 一系列死磕看的至关爽
http://cmsblogs.com/?p=5140

CSDN
http://blog.csdn.net/linxdcn/article/details/72896616

芋道源码 , 很不错的源码博客
http://www.iocoder.cn/

掘金老哥
https://juejin.cn/post/6844903753296920583

CSDN 
https://blog.csdn.net/briblue

以及全部对该文章有所帮助的表示感谢

复制代码
相关文章
相关标签/搜索