深切解析ClassLoader加载机制,搭建故障演练平台

原标题:去哪里系统高可用之法:搭建故障演练平台

Classloader负责将Class加载到JVM中,并且确定由更加ClassLoader来加载(父优先的级差加载机制)。还有一个职务就是将Class字节码重新诠释为JVM统一需要的格式

agent有两种: native(jvmti接口) 和 java层面的(instrumentation)

亚洲必赢登录 1

小编介绍

1.Classloader类结构分析

  • c/c++ 层面的 jvmti 接口

    • jvmti官方文档
    • JVM TI是JDK提供的一套用于支付JVM监控,
      难题一定与特性调优工具的通用编程接口(API)。
      通过JVMTI,大家可以支付各样各种的JVMTI
      Agent。那几个Agent的表现方式是一个以c/c++语言编写的动态共享库
    • JVMTI Agent原理
      • Java启动或运行时,动态加载一个外部基于JVM TI编写的dynamic
        module到Java进度内,然后触发JVM源生线程Attach
        Listener来执行那一个dynamic
        module的回调函数。在函数体内,你可以收获各类各种的VM级新闻,注册感兴趣的VM事件,甚至决定VM的行为。
    • jvmti api
      • JVMTI是依照事件驱动的,JVM每执行到早晚的逻辑就会调用一些事变的回调接口(若是有的话),这么些接口可以供开发者去扩张自己的逻辑。
      • 可以获得种种各类的音讯
    • 开发jvm ti
      agent,不难的来讲,就是付出一个c/c++的共享库。在windows下后缀是dll,linux/unix下是so,mac下就是dylib。所以大家创造工程和编译环境的时候,记得以共享库(share
      library)的款型来打造
    • 二种艺术载入
      • 随java进度启动时,自动载入共享库
        • 共享库路径是环境变量路径: java
          -agentlib:foo=opt1,opt2,java启动时会从linux的LD_LIBRARY_PATH或windows的PATH环境变量定义的路径处装载foo.so或foo.dll,找不到则抛至极
        • 以相对路径的主意装载共享库: java
          -agentpath:/home/admin/agentlib/foo.so=opt1,opt2 萨姆ple
      • java运行时,通过attach api动态载入
        public static void main(String[] args) throws Exception { // args[0]为java进程id VirtualMachine virtualMachine = com.sun.tools.attach.VirtualMachine.attach(args[0]); // args[1]为共享库路径,args[2]为传递给agent的参数 virtualMachine.loadAgentPath(args[1], args[2]); virtualMachine.detach(); }
    • 开发jvmti agent
      • jvmti.h头文件里含有了所有jvm
        ti要用到的数据结构和回调函数定义
      • 确定JVMTI的启航格局
        • JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM vm, char
          options, void *reserved)//启动载入方式
        • JNIEXPORT jint JNICALL Agent_OnAttach(JavaVM jvm, char
          options, void *reserved)//动态载入格局
        • JNIEXPORT void JNICALL Agent_OnUnload(JavaVM
          *vm)//卸载都是一模一样
      • 切实事例可以google或jvmti官网上找
  • java层面的instrumentation

    • Instrumentation是Java5提供的新特点,使用Instrumentation,开发者能够构建一个代理,用来监测运行在JVM上的顺序

    • java.lang.instrument.ClassFileTransformer

      • 每个代理类必须贯彻
        ClassFileTransformer接口,这几个接口提供了一个transform方法:
      • byte[] transform(ClassLoader loader, String className,
        Class<?> classBeingRedefined, ProtectionDomain
        protectionDomain, byte[] classfileBuffer) throws
        IllegalClassFormatException
      • 通过那一个措施,代理可以获取虚拟机载入的类的字节码,并可对其开展修改,已毕字节码级的改动。
      • classfileBuffer这几个便是被代理类字节码流,正是通过操作这几个buffer完毕对字节码的改动
      • 对此函数的再次来到值,借使回去null,则象征不对类的字节码做其他的改动,否则应当回到修改过的byte[]对象
    • 提供一个集体的静态方法 public static void premain(String
      agentArgs, Instrumentation inst)

      • 貌似会在那一个措施中创建一个代理对象,通过Instrumentation对象的addTransformer()方法,将开创的代理对象再传递给虚拟机
        public class HelloWorld implements ClassFileTransformer { @Override public byte[] transform(ClassLoader loader, String className, Class<>; classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException { System.out.println("java.lang.instrument, hello world!"); return null; } public static void premain(String args,Instrumentation inst){ inst.addTransformer(new HelloWorld()); } }

      public class Example { public static void main(String[] args){ System.out.println("main class of proxy!"); } }

      • 将agent类HelloWorld编译成可运行的jar(helloworld.jar),那里注目的在于manifest文件中添加premain入口,也即其陈设为:
        • Premain-Class: cn.dstrace.instrument.HelloWorld
      • 运行
        • java -javaagent:helloworld.jar
          cn.dstrace.instrument.Example
    • 小节

      • java的各类品质监控工具中都有instrument的人影,如jconsole等
      • 这么的表征实际上提供了一种虚拟机级别帮忙的 AOP 落成格局
    • 动态的javaagent

      • 在 Java SE6 里面,最大的转移使运行时的 Instrumentation
        成为可能;java attach api
      • 唯独在实际上的不少的图景下,大家从没大意在虚拟机启动之时就为其设定代理
      • jdk5局限
        • 在 Java SE 5 中等,开发者能够让 Instrumentation 代理在
          main 函数运行前履行。
        • 在 Java SE 5 中间,开发者只好在 premain
          当中施展想象力,所作的 Instrumentation 也仅限与 main
          函数执行前,那样的艺术存在必然的局限性。
        • 在 Java SE 5 的基本功上,Java SE 6
          针对那种光景做出了改革,开发者可以在main
          函数初步实践未来,再起步自己的 Instrumentation 程序。
        • 在 Java SE 6 的 Instrumentation 当中,有一个跟
          premain“并肩前进”的“agentmain”方法,可以在 main
          函数开头运行之后再运行。跟 premain 函数一样,
          开发者可以编写一个富含“agentmain”函数的 Java 类:
        • 在 Java SE 6
          的新特点里面,有一个不太起眼的地方,揭破了 agentmain
          的用法。那就是Java SE 6 当中提供的 Attach API
  • javaagent原理完全解读

    • javaagent的重中之重的效劳如下
      • 可以在加载class文件以前做阻止把字节码做修改
      • 可以在运行期将已经加载的类的字节码做变更,不过这种景色下会有为数不少的限定
      • 还有别的的有些小众的作用
        • 赢得具有曾经被加载过的类
        • 收获具有曾经被初步化过了的类(执行过了clinit方法,是地点的一个子集)
        • 得到某个对象的尺寸
        • 将某个jar插足到bootstrapclasspath里作为高优先级被bootstrapClassloader加载
        • 将某个jar加入到classpath里供AppClassloard去加载
        • 设置某些native方法的前缀,首要在摸索native方法的时候做规则匹配
    • 实在大家天天都在和JVMTIAgent打交道,只是你恐怕没有察觉到而已,比如大家日常使用eclipse等工具对java代码做调试,其实就选取了jre自带的jdwp
      agent来落成的,只是由于eclipse等工具在没让你意识的情况下将有关参数(类似-agentlib:jdwp=transport=dt_socket,suspend=y,address=localhost:61349)给电动加到程序启动参数列表里了,其中agentlib参数就是用来跟要加载的agent的名字,比如此处的jdwp(然则那不是动态库的名字,而JVM是会做一些称号上的扩展,比如在linux下会去找libjdwp.so的动态库举行加载,也就是在名字的底子上加前缀lib,再加后缀.so),接下去会跟一堆相关的参数,会将那么些参数传给Agent_OnLoad或者Agent_OnAttach函数里对应的options参数。
    • javaagent
      • 说到javaagent必必要讲的是一个称呼instrument的JVMTIAgent(linux下相应的动态库是libinstrument.so),因为就是它来落成javaagent的机能的,别的instrument
        agent还有个别名叫JPLISAgent
        (Java Programming Language
        Instrumentation Services
        Agent),从那名字里也全然反映了其最本色的功用:就是尤其为java语言编写的插桩服务提供支撑的。
    • instrument agent (libinstrument.so实现)
      • instrument agent实现了Agent_OnLoad和Agent_OnAttach两方法
      • instrument agent的主题数据结构如下
        • tobecontinued…

  • References

    • 笨神-JVM源码分析之javaagent原理完全解读

jvm.png

王鹏,前年参加去何地机票事业部,首要从事后端研发工作,近期在机票事业部负责行程单和故障演练平台以及公共服务ES、数据同步中间件等有关的研发工作。

(1)首要由八个办法,分别是defineClass,findClass,loadClass,resolveClass
  • <1>defineClass(byte[] , int ,int)
    将byte字节流解析为JVM可以分辨的Class对象(直接调用这一个法子生成的Class对象还未曾resolve,这些resolve将会在那一个目的真正实例化时resolve)

  • <2>findClass,通过类名去加载对应的Class对象。当大家落到实处自定义的classLoader寻常是重写那几个方法,依照传入的类名找到对应字节码的文书,并透过调用defineClass解析出Class独享

  • <3>loadClass运行时方可透过调用此措施加载一个类(由于类是动态加载进jvm,用有些加载多少的?)

  • <4>resolveClass手动调用那么些使得被加到JVM的类被链接(解析resolve这几个类?)

前言

 JVM(Java Virtual Machine)Java 虚拟机是整个 java 平台的基石,是 java 系统实现硬件无关与操作系统无关的关键部分,是保障用户机器免于恶意代码损害的屏障。Java开发人员不需要了解JVM是如何工作的,**但是,**了解 JVM 有助于我们更好的开(通)发(过) java(公司) 程(面)序(试)。

写这篇文章的目标:

  • 小结所学的 JVM 知识
  • 支持想询问 JVM 的爱人,知无不言,言无不尽

本篇小说将会介绍一下内容:

  • 什么是 JVM
  • JVM 用来做哪些工作
  • JVM 生命周期
  • JVM 的完好架构
  • JVM 内存管理
  • 总结

去哪儿网2005年树立至今,随着系统规模的逐级扩张,已经有诸七个应用系统,那么些系统里头的耦合度和链路的复杂度不断加强,对于我们营造分布式高可用的系统架构具有巨大挑衅。我们需求一个平台在运行期自动注入故障,检验故障预案是还是不是起效——故障演练平台。

(2)完毕自定义ClassLoader一般会继续URLClassLoader类,因为这几个类落成了超过半数主意。

什么是 JVM

要想说明白什么 JVM 就不得不提另外两个概念,JRE 和 JDK,初学者总是把这几个概念搞混

亚洲必赢登录 2

java-tutorial.png

Jvm,Jre,Jdk 都是 java 语言的柱子,他们分工协作。但不一致的是 Jdk 和 Jre
是真性存在的,而 Jvm 是一个虚幻的定义,并不诚实存在。

JDK
JDK(Java Development Kit) 是 Java 语言的软件开发工具包(SDK)。JDK
物理存在,是 programming tools、JRE 和 JVM 的一个凑合

亚洲必赢登录 3

jdk.png

JRE
JRE(Java Runtime Environment)Java 运作时环境,JRE 物理存在,首要由Java
API 和 JVM 组成,提供了用于执行 java 应用程序最低要求的条件。

亚洲必赢登录 4

jre.png

JVM
深切解析ClassLoader加载机制,搭建故障演练平台。JVM(Java Virtual Machine)
是一种软件已毕,执行像物理机程序的机械(即电脑)。
理所当然,Java被设计基于从物理机械分离完结WORA( 写三回,四处运行
)的虚拟机上运行,即使这几个目的已经大约被忘记。
JVM 并不是专为 Java
所已毕的运作时,实际上只要有任何编程语言的编译器能生成正确 Java bytecode
文件,则这几个语言也能已毕在JVM上运行。
所以,JVM 通过实施 Java bytecode 可以使 java
代码在不改动的情事下运作在各类硬件之上。
jVM 有如下特征:

  • 依照堆栈的虚构机
    最流行的电脑种类布局,如AMDX86架构和ARM架构上运行基于寄存器
    。 不过,JVM是基于栈的。
  • 标志引用
    除开主导类型以外的数码(类和接口)都是因此标志来引用,而不是通过显式地使用内存地址来引用。
  • 污染源收集
    一个类的实例是由用户明显成立的代码和废品回收自动销毁。
    由此明确限定的骨干数据类型的有限支撑平台的独立性 :传统的言语,如C / C
    ++根据平台有差其他int型的轻重缓急。
    JVM中明确规定了基本数据类型,以保全它的兼容性和担保平台的独立性。
  • 互连网字节顺序 :Java
    class文件用互联网字节码顺序来进行仓储:为了有限支撑和小端的英特尔x86架构以及多方的RISC体系的架构保持毫无干系性,JVM使用用于互连网传输的互连网字节顺序,也就是多方面。

**Java bytecode **
为了兑现WORA,JVM使用Java字节码,java(用户语言)和机器语言之间的中档语言。
该Java字节码是布置Java代码的蝇头单位。

一、背景

2.ClassLoader的阶段加载机制

JVM 用来做什么样

基于安全方面考虑,JVM 要求在 class 文件中使用许多强制性的语法和机构化约束,但任意一门功能性语言都可以表示为一个能被 JVM 接受的有效的 class 文件。作为一个通用的、机器无关的执行平台,任何其他语言的实现者都可将 JVM 当作他的语言产品交付媒介。

JVM 中实施以下操作:

  • 加载代码
  • 说西汉码
  • 履行代码
  • 提供周转条件

JVM 提供定义了:

  • 存储区
  • 类文件格式
  • 寄存器组
  • 污染源回收堆
  • 沉重错误报告等

那是某事业部的连串拓扑图:

(1)JVM平台提供三层的ClassLoader,那三层ClassLoader可以分成两类,分别是服务JVM自身的,和劳务广大普通类的。分别是:
  • <1>BootstrapClassLoader:首要加载JVM自身工作所必要的类,该ClassLoader没有父类加载器和子类加载器

  • <2>ExtClassLoader:这些类加载器同样是JVM自身的一有的,不过不是由JVM完毕,首要用于加载System.getProperty(“java.ext.dirs”)目录地下的类,如本机的值“D:\java\jdk7\jre\lib\ext;C:\Windows\Sun\Java\lib\ext”

  • <3>AppClassLoader:加载System.getProperty(“java.class.path”)(注意了在ide中运作程序时,该值平时是该项目标classes文件夹)中的类。所有的自定义类加载器不管直接促成ClassLoader,是继续自URLClassLoader或其子类,其父加载器(注意:父加载器与父类的个别)都是AppClassLoader,因为无论调用哪个父类的构造器,最后都将调用getSystemClassLoader作为父加载器,而该措施再次来到的正是AppClassLoader。(当应用程序中绝非别的自定义的classLoader,那么除了System.getProperty(“java.ext.dirs”)目录中的类,其余类都由AppClassLoader加载)

JVM 生命周期

  • 启动:别的一个具备main函数的class都足以视作JVM实例运行的起源
  • 运行:main函数为起源,程序中的其余线程均有它启动,包蕴daemon守护线程和non-daemon普通线程。daemon是JVM自己使用的线程比如GC线程,main方法的开始线程是non-daemon。
  • 消亡:抱有线程终止时,JVM实例甘休生命。

亚洲必赢登录 5

(2)Jvm加载class文件到内享有二种方式,隐式加载和出示加载,寻常那两种方法是混合使用的
  • <1>隐式加载:是通过JVM来自动加载须求的类到内存的方法,当某个类被拔取时,JVM发现此类不在内存中,那么它就会活动加载该类到内存

  • <2>突显加载:通过调用this.getClasss.getClassLoader.loadClass(),Class.forName,自己落成的ClassLoader的findClass方法

JVM 的完整架构

先看一下 java 代码执行过程

亚洲必赢登录 6

jvm.png

疑问:

  • Class Loader
  • Excution Engine
  • Runtime Data Areas

系统里面的信赖极度复杂、调用链路很深、服务时期从未分支。在那种复杂的借助下,系统发出了几起故障:

(3)上级委托机制:当一个加载器加载类字时,先委托其父加载器加载,若加载成功则反映给该加载器,若父加载器不可以加载,则由该加载器加载

Class Loader

类加载器负责加载程序中的类型(类和接口),并赋予唯一的名字。

JDK 默许提供了三种 ClassLoader

亚洲必赢登录 7

classloader.png

关系

  1. Bootstrp loader 是在Java虚拟机启动后先河化的。
  • Bootstrp loader 负责加载 ExtClassLoader,并且将 ExtClassLoade
    r的父加载器设置为 Bootstrp loader。
  • Bootstrp loader 加载完 ExtClassLoader 后,就会加载
    AppClassLoader,并且将 AppClassLoader 的父加载器指定为
    ExtClassLoader。
Class Loader 实现 负责加载
Bootstrp loader C++ %JAVA_HOME%/jre/lib,-Xbootclasspath参数指定的路径以及%JAVA_HOME%/jre/classes中的类
ExtClassLoader Java %JAVA_HOME%/jre/lib/ext,此路径下的所有classes目录以及java.ext.dirs系统变量指定的路径中类库
AppClassLoader Java classpath所指定的位置的类或者是jar文档,它也是Java程序默认的类加载器

养父母委托模型
Java中ClassLoader的加载选取了二老委托机制,选拔双亲委托机制加载类的时候利用如下的多少个步骤:

  1. 最近ClassLoader首先从自己曾经加载的类中查询是还是不是此类已经加载,要是已经加载则一贯回到原来已经加载的类。
  2. 此时此刻classLoader的缓存中从未找到被加载的类的时候,委托父类加载器去加载,父类加载器选取相同的政策,首先查看自己的缓存,然后委托父类的父类去加载,一贯到bootstrp
    ClassLoader.
  3. 当所有的父类加载器都没有加载的时候,再由近来的类加载器加载,并将其放入它和谐的缓存中,以便下次有加载请求的时候向来重返。

干什么选取双亲委托模型——ClassLoader 隔离难题
各类类装载器都有一个和好的命名空间用来保存已装载的类。当一个类装载器装载一个类时,它会通过保留在命名空间里的类全局限定名(Fully
Qualified Class Name)举办搜寻来检测这么些类是还是不是业已被加载了。
世家觉得一个运作程序中有没有可能还要设有多少个包名和类名完全一致的类?
JVM 及 Dalvik 对类唯一的分辨是 ClassLoader id + PackageName +
ClassName,所以一个运作程序中是有可能存在七个包名和类名完全一致的类的。并且只要那八个”类”不是由一个
ClassLoader 加载,是力不从心将一个类的示例强转为其它一个类的,那就是
ClassLoader 隔离。
大人委托是 ClassLoader 难题的一种缓解方案,也是 Android
差价化开发和热修复的基本功。

Android 插件化
动态升级
Android
热补丁动态修复框架小结

类装载器特点
Java提供了动态加载特性;他会在运作时的首回引用到一个class的时候对它进行装载(Loading)、链接(Linking)和初步化(Initialization),而不是在编译时展开。分裂的JVM的落到实处不一样,本文所讲述的内容均只限于Hotspot
Jvm。JVM的类装载器负责动态装载,Java的类装载器有如下多少个特点:

  • 层级结构:Java里的类装载器被协会成了有父子关系的层级结构。Bootstrap类装载器是具备装载器的阿爸。
  • 代办格局:
    基于层级结构,类的代办可以在装载器之间开展代理。当装载器装载一个类时,首先会检讨它在父装载器中是或不是举行了装载。即使上层装载器已经装载了那个类,那一个类会被直接行使。反之,类装载器会请求装载这一个类
  • 可知性限制:一个子装载器可以搜索父装载器中的类,可是一个父装载器不可能查找子装载器里的类。
  • 不容许卸载:类装载器可以装载一个类可是不可以卸载它,然则可以去除当前的类装载器,然后成立一个新的类装载器装载。

过程

加载(Loading)是如此一个经过,找到代表这几个类的class文件或基于特定的名字找到接口类型,然后读取到一个字节数组中。接着,那些字节会被解析检验它们是否代表一个Class对象并包括正确的major、minor版本消息。直接父类的类和接口也会被加载进来。那么些操作一旦完毕,类照旧接口对象就从二进制表示中成立出来了。

链接(Linking)是检验类或接口并预备类型和父类接口的长河。链接进度包括三步:校验(Verifying)、准备(Preparing)、部分分析(Optionally
resolving)。

亚洲必赢登录 8

loadclass.png

  • 验证:那是类装载中最复杂的进度,并且用度的时刻也是最长的。职务是确保导入类型的准头,验证阶段做的检讨,运行时不须要再做,就算减慢加了载速度,不过幸免了反复检查。
  • 准备:分配一个社团用来存储类音信,这么些结构中蕴藏了类中定义的分子变量,方法和接口的消息。
  • 解析:可选阶段,把这些类的常量池中的所有的标志引用改变成直接引用。假诺不进行,符号解析要等到字节码指令使用这么些引用时才会进展

初始化(Initialization)把类中的变量初步化成合适的值。执行静态初叶化程序,把静态变量早先化成指定的值。

JVM规范定义了上面的几个任务,不过它允许具体执行的时候能够有些灵活的变动。
  • 弱着重挂掉,主流程挂掉,修改报销凭据的开发情形,下单主流程失利;
  • 骨干服务调用量陡增,某服务超时引起相关联的所有服务“雪崩”;
  • 机房互联网或者某些机器挂掉,无法提供基本服务。

3.如何加载class文件:

分成多少个步骤 加载字节码到内存、Linking、类字节初叶化赋值

履行引擎(Execution Engine)

通过类装载器装载的,被分配到JVM的运行时数据区的字节码会被执行引擎执行。执行引擎以指令为单位读取 Java 字节码。它就像一个 CPU 一样,一条一条地执行机器指令。每个字节码指令都由一个1字节的操作码和附加的操作数组成。执行引擎取得一个操作码,然后根据操作数来执行任务,完成后就继续执行下一条操作码。
不过 Java 字节码是用一种人类可以读懂的语言编写的,而不是用机器可以直接执行的语言。因此,执行引擎必须把字节码转换成可以直接被 JVM 执行的语言。字节码可以通过以下两种方式转换成合适的语言。
  • 解释器:一条一条地读取,解释并且实施字节码指令。因为它一条一条地解释和履行命令,所以它可以便捷地演说字节码,然则执行起来会相比慢。那是表明实施的言语的一个败笔。字节码那种“语言”基本来说是解释施行的。

  • 即时(Just-In-Time)编译器:马上编译器被引入用来弥补解释器的瑕疵。执行引擎首先根据解释施行的法门来执行,然后在适度的时候,即时编译器把整段字节码编译开销地代码。然后,执行引擎就从不要求再去解释实施办法了,它可以平素通过本地代码去履行它。执行本地代码比一条一条进行表明施行的快慢快很多。编译后的代码可以举办的飞跃,因为地点代码是保留在缓存里的。

    Java 字节码是演讲施行的,不过尚未直接在 JVM
    宿主执行原生代码快。为了增强品质,Oracle Hotspot
    虚拟机会找到实践最频仍的字节码片段并把它们编译成原生机器码。编译出的原生机器码被储存在非堆内存的代码缓存中。通过那种办法(JIT),Hotspot
    虚拟机将衡量下边二种时光消耗:将字节码编译费用地代码须要的额外时间和释疑实施字节码消耗更加多的时光。

亚洲必赢登录 9

java_compiler_and_jit_compiler.png

此间插入一下 Android 5.0 未来用的 ART 虚拟机使用的是 AOT 机制。

Dalvik 是凭借一个 Just-In-提姆e
(JIT)编译器去解释字节码。开发者编译后的应用代码需求经过一个解释器在用户的设施上运行,这一编制并不高速,但让动用能更便于在不一样硬件和架构上运
行。ART
则统统改观了那套做法,在行使设置时就预编译字节码到机器语言,这一编制叫
Ahead-Of-提姆e
(AOT)编译。在移除解释代码这一历程后,应用程序执行将更有功效,启动更快。

多个故障原因:

(1)加载字节码到内存:(这一步平时经过findclass()方法完结)

以URLClassLoader为例:该类的构造函数返现必须制定一个URL数据才能创设该目的,该类中隐含一个URLClassPath对象,URLClassPath会判断传过来的URL是文件或者Jar包,成立相应的FileLoader或者JarLoader或者默许加载器,当jvm调用findclass时,那几个加载器将class文件的字节码加载到内存中

运作时数据区

JVM 运行时数据结构图:

亚洲必赢登录 10

runtime-data-access-configuration.png

PC寄存器(PC Register)
也叫程序计数器(Program Counter
Register)是一块较小的内存空间,它的功力可以视作是当前线程所举行的字节码的信号提示器。
每一条JVM线程都有谈得来的PC寄存器
在随心所欲时刻,一条 JVM
线程只会实施一个格局的代码。该方法称为该线程的目前艺术(Current
Method)
即使该方法是 java 方法,那PC寄存器保存 JVM 正在实践的字节码指令的地方
即使该办法是 native,那 PC 寄存器的值是 undefined。
此内存区域是唯一一个在 Java
虚拟机规范中并未确定任何OutOfMemoryError情形的区域。

JVM 栈(Java Virtual Machine Stack)
与 PC 寄存器一样,java 虚拟机栈(Java Virtual Machine
Stack)也是线程私有的。每一个JVM线程都有和好的java虚拟机栈,这一个栈与线程同时创建,它的生命周期与线程相同,用来保存栈帧。JVM
只会在 JVM 栈上进行 push 和 pop 的操作。
JVM stack 得以被已毕成固定大小,也得以依照总计动态增添。
只要选用一定大小的JVM stack设计,那么每一条线程的JVM
Stack容量应该在线程创制时单身地选定。JVM完成应有提供调节JVM
Stack初阶容量的手段。
即使选择动态扩充和收缩的JVM
Stack情势,应该提供调节最大、最小容量的伎俩。

  • JVM 栈非凡情形

  • StackOverflowError:当线程请求分配的栈容量超越JVM允许的最大容量时抛出

  • OutOfMemoryError:若是JVM
    Stack可以动态扩大,不过在品尝增添时无法申请到丰硕的内存去完毕伸张,或者在确立新的线程时从没充足的内存去创设对应的虚拟机栈时抛出。

  • 栈帧(stack frame)
    栈帧随着方法调用而创办,随着方法停止而销毁——无论方式是正常完结或者那多少个达成(抛出了在章程内未被破获的不得了)都不失为方法甘休。栈帧的储存空间分配在
    Java 虚拟机栈之中,每一个栈帧都有友好的局地变量表(Local
    Variables)、操作数栈(Operand
    Stack)和指向当前格局所属的类的运行时常量池的引用。

  • 部分变量数组(Local variable array)
    各种栈帧内部都包涵一组称为局地变量表(Local
    Variables)的变量列表。栈帧中有的变量表的长度由编译期决定。
    有的变量使用索引来拓展定位访问,第三个部分变量的索引值为零,局地变量的索引值是从零至小于局地变量表最大容量的保有整数。
    Java
    虚拟机使用部分变量表来完毕章程调用时的参数传递,当一个措施被调用的时候,它的参数将会传递至从
    0
    开始的接连的一些变量表地方上。更加地,当一个实例方法被调用的时候,第
    0 个部分变量一定是用来囤积被调用的实例方法所在的靶子的引用(即 Java
    语言中的“this”关键字)。后续的其它参数将会传递至从 1
    起首的接连的一部分变量表地方上。

  • 操作数栈(Operand stack)
    每一个栈帧内部都包蕴一个叫作操作数栈(Operand
    Stack)的后进先出(Last-In-First-Out,LIFO)栈。栈帧中操作数栈的长度由编译期决定。
    操作数栈所属的栈帧在刚刚被创设的时候,操作数栈是空的。Java
    虚拟机提供一些字节码指令来从部分变量表或者目的实例的字段中复制常量或变量值到操作数栈中,也提供了一部分限令用于从操作数栈取走多少、操作数据和把操作结果再行入栈。在点子调用的时候,操作数栈也用来准备调用方法的参数以及接受格局再次回到结果。

  • 动态链接(Dynamic Linking)
    每个栈帧都有一个运转时常量池的引用。那个引用指向栈帧当前运作格局所在类的常量池。通过这一个引用协助动态链接(dynamic
    linking)。
    C/C++
    代码一般被编译成对象文件,然后两个目标文件被链接到一起爆发可执行文件或者
    dll。在链接阶段,每个对象文件的符号引用被替换成了最后实施文书的冲突偏移内存地址。在
    Java中,链接阶段是运行时动态达成的。
    当 Java
    类文件编译时,所有变量和措施的引用都被当做符号引用存储在那一个类的常量池中。符号引用是一个逻辑引用,实际上并不指向物理内存地址。JVM
    能够选取符号引用解析的时机,一种是当类文件加载并校验通过后,那种分析方法被称作饥饿格局。其它一种是符号引用在率先次选择的时候被解析,那种分析方法叫做惰性格局。无论怎样,JVM
    必必要在率先次利用标志引用时形成解析并抛出可能暴发的剖析错误。绑定是将对象域、方法、类的标志引用替换为直接引用的长河。绑定只会爆发一回。一旦绑定,符号引用会被完全替换。假使一个类的号子引用还向来不被解析,那么就会载入那几个类。每个直接引用都被储存为相对于储存结构(与运行时变量或格局的任务相关联的)偏移量。

  • 艺术正常调用完毕
    在那种情景下,当前栈帧承担着过来调用者状态的任务,其情景包含调用者的一对变量表、操作数栈和被科学扩充过来表示执行了该方法调用指令的次序计数器等。使得调用者的代码能在被调用的艺术再次回到并且重返值被推入调用者栈帧的操作数栈后继续健康地执行。

  • 方法丰裕调用达成
    方法丰盛调用已毕是指在措施的实践进度中,某些指令导致了 Java
    虚拟机抛出非凡,并且虚拟机抛出的尤其在该方法中从未办法处理,或者在实践进度中境遇了
    athrow
    字节码指令显式地抛出更加,并且在该情势内部从不把那个捕获住。固然形式格外调用已毕,那必然不会有方法重回值重返给它的调用者。

本土方法栈(Native method stack)
Java虚拟机可能会利用到观念的栈来接济native方法(使用Java语言以外的此外语言编写的艺术)的实践,这几个栈就是本地方法栈(Native
Method Stack)
比方JVM不辅助native方法,也不借助于与观念方法栈的话,可以不必帮助本地点法栈。
只要协助本地点法栈,则那些栈一般会在线程创制的时候按线程分配。
相当情形:

  • StackOverflowError:倘使线程请求分配的栈容量超越当地方法栈允许的最大容量时抛出
  • OutOfMemoryError:假若地方方法栈可以动态伸张,并且增加的动作已经尝试过,可是当前不能够申请到丰盛的内存去做到伸张,或者在创制新的线程时从没丰硕的内存去制造对应的本地点法栈,那Java虚拟机将会抛出一个OutOfMemoryError卓殊。

方法区(Method area)
在Java虚拟机中,被加载类型的音信都保留在方法区中。包含类型音讯(Type
Information)和措施列表(Method
Tables)。方法区是有着线程共享的,所以访问方法区新闻的措施必须是线程安全的。如若您有四个线程都去加载一个叫Lava的类,那只能由一个线程被容许去加载那几个类,另一个亟须等待。
它是在JVM启动的时候创设的。
存储了每一个类的结构音信,例如运行时常量池(Runtime Constant
Pool)、字段和方式数据、构造函数和一般方法的字节码内容、还包罗部分在类、实例、接口起初化时用到的出格格局。
方法区的容量可以是定位大小的,也可以趁机程序执行的须求动态增添,并在不须求过多空间时自动减弱。
方法区在骨子里内存空间中可以是不接二连三的。
Java虚拟机贯彻应有提需求程序员或者最后用户调节方法区先导容量的伎俩,对于能够动态扩大和收缩方法区来说,则应当提供调节其最大、最小容量的手腕。
是或不是对方法区举办垃圾回收对JVM的兑现是可选的。
Java 方法区分外:

  • OutOfMemoryError:
    如若方法区的内存空间不可能满意内存分配请求,这Java虚拟机将抛出一个OutOfMemoryError非常。

运作时常量池(Runtime constant pool)
运行时常量池是每一个类或接口的常量池(Constant_Pool)的运作时表现形式,它包罗了多少种常量:编译器可见的数值字面量到必须运行期解析后才能赢得的措施或字段的引用。简单的讲,当一个办法仍然变量被引述时,JVM通过运行时常量区来查找方法依然变量在内存里的实际上地址。
运作时常量池是方法区的一片段。每一个运行时常量池都分配在JVM的方法区中,在类和接口被加载到JVM后,对应的周转时常量池就被成立。
在创设类和接口的运作时常量池时,可能会蒙受的充裕:

  • OutOfMemoryError:当创设类和接口时,如果社团运行时常量池所需的内存空间超越了方法区所能提供的最大内存空间后就会抛出OutOfMemoryError

堆(Heap)
在 JVM
中,堆(heap)是可供各条线程共享的运转时内存区域,也是供所有类实例和数目对象分配内存的区域。
Java堆载虚拟机启动的时候就被制造,堆中蕴藏了各样对象,这几个目的被自动管理内存系统(Automatic
Storage Management System,也即是常说的“Garbage
Collector(垃圾回收器)”)所管理。那几个目的无需、也不可能凸显地被销毁。
Java堆的容量能够是稳定大小,也可以趁机须要动态增加,并在不需要过多空间时自动缩短。
Java堆所使用的内存不须要确保是情理一而再的,只要逻辑上是连连的即可。
JVM完毕应有提需要程序员调节Java
堆起始容量的招数,对于可动态扩大和裁减的堆来说,则应该提供调节其最大和微小容量的伎俩。
Java 堆异常:

  • OutOfMemoryError:要是实在所需的堆超越了自动内存管理种类能提供的最大容量时抛出。
  • 系统强弱看重混乱、弱依赖无降级;
  • 系统流量剧增,系统容量不足,没有限流熔断机制;
  • 硬件资源网络出现难题影响系统运行,没有高可用的互连网架构。
(2)Linking:验证与分析,包罗3步:
  • <1>字节码验证

  • <2>类准备:准备代表每个类中定义的字段、方法和贯彻接口所需的数据结构

  • <3>解析:那些阶段类装入器转入类所使用的其他类

堆内的内存回收—— JVM GC

待续。。

不乏先例的题材,在那种复杂的看重性结构下被推广,一个信赖30个SOA服务的系统,每个服务99.99%可用。99.99%的30次方≈99.7%。0.3%象征一亿次呼吁会有3,000,00次失利,换算成时间大体每月有2个钟头服务不安静。随着服务依赖数量的变多,服务不安宁的几率会呈指数性提升,这个难题最终都会转接为故障表现出来。

(3)起首化class对象,执行静态起首化器并在那阶段末尾开端化静态字段为默许值

二、系统高可用的方法论

4.广泛加载类错误分析

什么样创设一个高可用的种类啊?首先要分析一下不可用的要素都有怎么着:

(1)ClassNotFoundException:

一般说来是jvm要加载一个文书的字节码到内存时,没有找到这一个字节码(如forName,loadClass等方法)

亚洲必赢登录 11

(2)NoClassDefFoundError:

平常是行使new关键字,属性引用了某个类,继承了某个类或接口,但JVM加载这个类时发现这一个类不存在的分外

高可用系统非凡实践

(3)UnsatisfiedLinkErrpr:

如native的形式找不到本机的lib

辩论上的话,当图中有所的作业都做完,大家就足以认为系统是一个的确的高可用系统。但正是如此啊?

5.常用classLoader(书本此处其实是对tom加载servlet使用的classLoader分析)

那就是说故障演练平台就热闹出色登场了。当上述的高可用实践都做完,利用故障演练平台做一回真正的故障演练,在系统运行期动态地流入一些故障,从而来表明下系统是不是听从故障预案去履行相应的降级或者熔断策略。

(1)AppClassLoader:

加载jvm的classpath中的类和tomcat的宗旨类

三、故障演练平台

(2)StandardClassLoader:

加载tomcat容器的classLoader,别的webAppClassLoader在loadclass时,发现类不在JVM的classPath下,在PackageTriggers(是一个字符串数组,包涵一组不能够利用webAppClassLoader加载的类的包名字符串)下的话,将由该加载器加载(注意:StandardClassLoader并没有覆盖loadclass方法,所以其加载的类和AppClassLoader加载没什么分别,并且动用getClassLoader再次来到的也是AppClassLoader)(其余,尽管web应用直接放在tomcat的webapp目录下该行使就会透过StandardClassLoader加载,猜想是因为webapp目录在PackageTriggers中?)

故障演练平台:视察故障预案是或不是真正的起功用的平台。

(3)webAppClassLoader如:

Servlet等web应用中的类的加载(loadclass方法的平整详见P169)

故障类型:首要包蕴运行期至极、超时等等。通过对系统某些服务动态地注入运行期十分来达成模拟故障的目的,系统依据预案执行相应的策略验证系统是不是是真正的高可用。

6.自定义的classloader

1、故障演练平台的总体架构

(1)必要运用自定义classloader的图景
  • <1>不在System.getProperty(“java.class.path”)中的类公事不可以被AppClassLoader找到(LoaderClass方法只会去classpath下加载特定类名的类),当class文件的字节码不在ClassPath就要求自定义classloader

  • <2>对加载的某些类需求作越发处理

  • <3>定义类的实效机制,对曾经修改的类重新加载,已毕热布署

故障演练平台架构主要分为四局地:

(2)加载自定义路径中的class文件
  • <1>加载特定来源的一点类:重写find方法,使特定类或者特定来源的字节码
    通过defineClass获得class类并回到(应该符合jvm的类加载规范,其他类仍使用父加载器加载)

  • <2>加载自顶一个是的class文件(如通过互联网流传的经过加密的class文件字节码):findclass中加密后再加载

亚洲必赢登录 12

7.完成类的热布署:

  • (1)同一个classLoader的多个实例加载同一个类,JVM也会识别为多个

  • (2)不可能重复加载同一个类(全名相同,并选择同一个类加载器),会报错

  • (3)不应当动态加载类,因为对象呗引用后,对象的特性结构被涂改会吸引难点

留神:使用不一致classLoader加载的同一个类公事得到的类,JVM将用作是多个差距类,使用单例方式,强制类型转换时都可能因为那些缘故出难点。

  • 前台体现系统(WEB):体现系统之间的拓扑关系以及各类AppCode对应的集群和艺术,能够选用具体的主意举办故障的注入和清除;
  • 揭橥连串(Deploy):其一连串关键用于将故障演练平台的Agent和Binder包发表到目标APP的机械上还要启动推行。前台体现系统会传递给公布平台要进行故障注入的AppCode以及目标APP的IP地址,通过那五个参数宣布连串可以找到相应的机械举办Jar包的下载和开行;
  • 劳动和指令分发系统(Server):那几个序列紧要性是用于命令的分发、注入故障的情状记录、故障注入和消除操作的逻辑、权限校验以及有关的Agent的回到新闻接收效果。前台页面已经接入QSSO会对当前人可以操作的IP列表做故障注入,防患危机。后端命令分发的模块会和安顿在目标APP上的Agent举办通讯,将下令推送到Agent上进行字节码编织,Agent执行命令后回到的始末通过Server和Agent的长连接传回Server端;
  • 亚洲必赢登录 ,Agent和Binder程序:Agent负责对目的APP做代办并且做字节码增强,具体代理的法子可以由此传输的通令来支配,代理方法后对章程做动态的字节码增强,那种字节码增强所有无侵入、实时生效、动态可插拔的性状。Binder程序紧若是透过发布体系传递过来的AppCode和起步端口(ServerPort)找到对象APP的JVM进度,之后执行动态绑定,完毕运行期代码增强的意义。

原书链接

以上内容只是私家笔记纪录,愈来愈多完整内容请购买小编原书籍查看。《深入解析JavaWeb技术内幕》

2、 Agent全部架构

脚下AOP的落实有三种格局:

  • 静态编织:静态编织暴发在字节码生成时按照早晚框架的平整提前将AOP字节码插入到目的类和格局中;
  • 动态编织:在JVM运行期对指定的方式成功AOP字节码增强。常见的方法一大半用到重命名原有艺术,再新建一个同名方法做代办的工作情势来成功。

静态编织的标题是只要想更改字节码必须重启,那给支付和测试进程导致了很大的诸多不便。动态的格局即使可以在运行期注入字节码落成动态增进,但未曾统一的API很简单操作不当。基于此,我们使用动态编织的办法、规范的API来规范字节码的变型——Agent组件。

Agent组件:由此JDK所提供的Instrumentation-API达成了选择HotSwap技术在不重启JVM的情状下达成对随意方法的加强,无论大家是做故障演练、调用链追踪(QTrace)、流量录制平台(Ares)以及动态扩张日志输出BTrace,都亟待一个拥有无侵入、实时生效、动态可插拔的字节码增强组件。

Agent的事件模型

如图所示,事件模型首要可分为三类事件:

亚洲必赢登录 13

BEFORE在艺术执行前事件、THROWS抛出尤其事件、RETURN重回事件。那三类事件可以在点子执行前、再次来到和抛出卓殊这三种意况做字节码编织。

正如代码:

// BEFORE

try {

/*

* do something…

*/

foo();

// RETURN

return;

} catch (Throwable e) {

// THROWS

}

事件模型可以达成多个成效:

  • 在方法体执行之前一向重临自定义结果对象,原有办法代码将不会被执行;
  • 在方法体再次回到此前再度社团新的结果对象,甚至足以更改为抛出尤其;
  • 在方法体抛出万分之后再行抛出新的不胜,甚至可以更改为正规重回。

Agent如何预防“类污染”

在支付Agent的时候,第二个应用是故障演练平台,那么那些时候其实我们并不须求Agent执行的历程中有自定义结果对象的归来,所以首先个版本的Agent拔取硬编码的格局展开动态织入:

亚洲必赢登录 14

故障类加载模型

率先介绍下多少个类加载器:

  • BootstrapClassLoader指点类加载器加载的是JVM自身必要的类,那些类加载使用C++语言完结的,是虚拟机自身的一片段;
  • ExtClassLoader它承担加载<JAVA_HOME>/lib/ext目录下或者由系统变量-Djava.ext.dir指定位路径中的类库;
  • AppClassLoader它承受加载系统类路径java-classpath或-D
    java.class.path指定路线下的类库,也就是咱们平日使用的classpath路径;
  • CommonClassLoader以及上面的都是汤姆cat定义的ClassLoader。

Agent和有关的lib会放到AppClassLoader这一层去加载,利用Javasist做字节码的织入,所以Javasist的加载器就是AppClassLoader。

而是想改变的是汤姆cat
WebClassLoader所加载的com.xxx.InvocationHandler这么些类的Invoke方法,分歧的ClassLoader之间的类是不可能相互走访的,做字节码的转移并不须要这一个类的实例,也不必要再次回到结果,所以可以通过Instrument
API获得那一个类加载器,并且可以根据类名称获取到这么些类的字节码举行字节码变换。故障类Drill.class和变形后的com.xxx.InvocationHandler.class重新load到JVM中,已毕了插桩操作。

以Dubbo为例表达下何以注入故障和排除故障:

亚洲必赢登录 15

Dubbo调用的注入进度

  • 服务A调用服务B在Client端的Proxy层做AOP;
  • 开行Agent并且生成一个Drill类invoke方法,抛出一个运行期十分;
  • 字节码变形:在代码第一行往日增添Drill.invoke();
  • 若是想更换至极类型,改变Drill类即可,换成Sleep 3s
    ClassRedifine过后会重复load到JVM落成故障类型的转账或者解除。

赶上的题材

下边的格局相似很完美的化解了难点,不过随着平台的施用工作线要对众多接口和办法同时开展故障演练,那么大家转变的Drill类里面就会有各类:

if method==业务线定义方法

do xxx

并且很不难拼接出错并且难以调试,只好把转变的类输出为文件,查看自己写的字节码编译成class文件是或不是正确,简直太悲伤了!

怎么化解?

新的架构须求缓解七个难题:

  • 类隔离的难题:不要污染原生APP;
  • 事件的落到实处是可编译的;
  • 支撑回到自定义的结果。

下一版本的Agent完毕就生出了,把持有Agent的类和促成的功力抽象出来,放到一个自定义的AgentClassLoader里面,字节码注入到对象APP后得以经过反射的艺术来调用具体的轩然大波完结。

亚洲必赢登录 16

类加载模型

  • 在BootstrapClassLoader里面注入Drill类作为通讯类;
  • Agent会接受命令,根据事件类型对InvocationHandler做字节码变形,注入到目标APP;
  • 在对象APP调用的时候,调用Drill.invoke(targetJavaClass,targetJavaMethod,
    targetThis,
    args)传递过来多少个参数(目的类、方法、实例、本身参数等);
  • Drill类通过反射的办法调用AppClassLoader里面的切切实实事件达成,比如BEFORE事件的施行代码,来形成注入后的逻辑执行。

Agent的完整架构

Agent的完好架构如图所示:

亚洲必赢登录 17

  • 支撑分裂的模块的参加,比如Mock、流量录制、故障演练等;
  • 协助QSSO的权能验证;
  • 协理测试和虚假环境的无花费接入;
  • 辅助电动布署不要求人工插手;
  • 支撑各类故障命令的表露和推行、 超时 、十分以及数额的回到;
  • 支持方式级其余编制以及代码执行流程的编制;
  • 支撑在自由的Web容器执行Agent代理。

四、怎么样行使

运用的裨益是很扎眼的:

  • 零花费接入,无需申请其他资源;
  • 故障注入解除,无需重启服务;
  • 能够提供具有集群的拓扑结构。

不过怎么才能科学行使呢?如下图所示:

亚洲必赢登录 18

采纳格局

步骤一、输入AppCode;

手续二、拔取故障方法;

步骤三、指定机器;

步骤四、注入故障。

五、总结

故障演练平台最基本的就是Agent组件——字节码编织框架,那一个框架是纯Java的依照Instrumentation-API的AOP解决方案。它可以便宜研发人士对此字节码插桩拆桩操作,可以很不难的达成故障演练、流量录制以及其余的运用模块。

作者:王鹏

发源:Qunar技术沙龙订阅号(ID:QunarTL)

dbaplus社群欢迎广大技术人士投稿,投稿邮箱:editor@dbaplus.cn回来博客园,查看愈来愈多

权利编辑:

网站地图xml地图