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

Classloader负责将Class加载到JVM中,并且规定由更加ClassLoader来加载(父优先的阶段加载机制)。还有一个义务就是将Class字节码重新解释为JVM统一要求的格式

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

澳门金沙4787.com官网 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 Sample
      • 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)//启动载入格局
        • 澳门金沙4787.com官网,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 内存管理
  • 总结

去哪儿网二零零五年建立至今,随着系统规模的逐步伸张,已经有许多少个应用系统,这个体系里面的耦合度和链路的复杂度不断抓好,对于大家营造分布式高可用的系统架构具有极大挑战。大家需求一个阳台在运行期自动注入故障,检验故障预案是还是不是起效——故障演练平台。

(2)落成自定义ClassLoader一般会持续URLClassLoader类,因为这一个类已毕了绝半数以上措施。

什么是 JVM

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

澳门金沙4787.com官网 2

java-tutorial.png

Jvm,Jre,Jdk 都是 java 语言的支柱,他们分工合作。但区其他是 Jdk 和 Jre
是实事求是存在的,而 Jvm 是一个空洞的概念,并不实事求是存在。

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

澳门金沙4787.com官网 3

jdk.png

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

澳门金沙4787.com官网 4

jre.png

JVM
JVM(Java Virtual Machine)
是一种软件达成,执行像物理机程序的机械(即电脑)。
本来,Java被规划基于从物理机械分离完结WORA( 写五次,遍地运行
)的虚拟机上运行,即便那么些目的已经大约被淡忘。
JVM 并不是专为 Java
所完结的运作时,实际上假如有任何编程语言的编译器能生成正确 Java bytecode
文件,则那些语言也能落成在JVM上运行。
为此,JVM 通过举行 Java bytecode 可以使 java
代码在不改动的图景下运作在各样硬件之上。
jVM 有如下特征:

  • 根据堆栈的虚构机
    最风靡的总括机序列布局,如英特尔X86架构和ARM架构上运行基于寄存器
    。 但是,JVM是基于栈的。
  • 标记引用
    除却宗旨项目以外的数据(类和接口)都是通过标记来引用,而不是经过显式地行使内存地址来引用。
  • 垃圾堆收集
    一个类的实例是由用户显然创设的代码和污染源回收自动销毁。
    透过明确界定的中央数据类型的保险平台的独立性 :传统的语言,如C / C
    ++根据平台有两样的int型的尺寸。
    JVM中明确规定了着力数据类型,以保全它的包容性和保障平台的独立性。
  • 互联网字节顺序 :Java
    class文件用互连网字节码顺序来拓展仓储:为了保障和小端的Intelx86架构以及多方的RISC种类的架构保持非亲非故性,JVM使用用于网络传输的网络字节顺序,也就是多方面。

**【澳门金沙4787.com官网】深远解析ClassLoader加载机制,去何方系统高可用之法。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实例为止生命。

澳门金沙4787.com官网 5

(2)Jvm加载class文件到内所有二种办法,隐式加载和显示加载,经常那二种艺术是勾兑使用的
  • <1>隐式加载:是透过JVM来自动加载必要的类到内存的法子,当某个类被运用时,JVM发现此类不在内存中,那么它就会自行加载该类到内存

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

JVM 的总体架构

先看一下 java 代码执行过程

澳门金沙4787.com官网 6

jvm.png

疑问:

  • Class Loader
  • Excution Engine
  • Runtime Data Areas

系统里面的信赖十分复杂、调用链路很深、服务中间没有分支。在那种复杂的借助下,系统暴发了几起故障:

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

Class Loader

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

JDK 默许提供了二种 ClassLoader

澳门金沙4787.com官网 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)。

澳门金沙4787.com官网 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
    虚拟机将衡量上面三种时光费用:将字节码编译花费地代码必要的附加时间和分解施行字节码消耗更加多的年月。

澳门金沙4787.com官网 9

java_compiler_and_jit_compiler.png

那边插入一下 Android 5.0 以后用的 ART 虚拟机使用的是 AOT 机制。

Dalvik 是依赖一个 Just-In-Time
(JIT)编译器去解释字节码。开发者编译后的行使代码须求经过一个解释器在用户的装置上运行,这一体制并不敏捷,但让动用能更易于在差异硬件和架构上运
行。ART
则统统改变了那套做法,在行使设置时就预编译字节码到机器语言,这一编制叫
Ahead-Of-提姆e
(AOT)编译。在移除解释代码这一进程后,应用程序执行将更有功效,启动更快。

两个故障原因:

(1)加载字节码到内存:(这一步平时通过findclass()方法达成)

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

运行时数据区

JVM 运行时数据结构图:

澳门金沙4787.com官网 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等方法)

澳门金沙4787.com官网 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中加密后再加载

澳门金沙4787.com官网 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的事件模型

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

澳门金沙4787.com官网 13

BEFORE在格局执行前事件、THROWS抛出相当事件、RETURN再次回到事件。那三类事件可以在措施执行前、重临和抛出非凡那二种状态做字节码编织。

正如代码:

// BEFORE

try {

/*

* do something…

*/

foo();

// RETURN

return;

} catch (Throwable e) {

// THROWS

}

事件模型可以做到七个功效:

  • 在方法体执行以前从来再次来到自定义结果对象,原有艺术代码将不会被执行;
  • 在方法体重临此前再度布局新的结果对象,甚至可以变更为抛出非凡;
  • 在方法体抛出分外之后再也抛出新的老大,甚至可以改变为健康再次来到。

Agent怎样预防“类污染”

在支付Agent的时候,第三个使用是故障演练平台,那么那几个时候实在大家并不必要Agent执行的经过中有自定义结果对象的归来,所以率先个本子的Agent接纳硬编码的法门开展动态织入:

澳门金沙4787.com官网 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为例表明下什么注入故障和解除故障:

澳门金沙4787.com官网 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后方可通过反射的方法来调用具体的事件完成。

澳门金沙4787.com官网 16

类加载模型

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

Agent的总体架构

Agent的共同体架构如图所示:

澳门金沙4787.com官网 17

  • 援救差其他模块的投入,比如Mock、流量录制、故障演练等;
  • 支撑QSSO的权力验证;
  • 支撑测试和虚假环境的无资金接入;
  • 支撑电动布署不需要人工加入;
  • 帮助各样故障命令的发布和施行、 超时 、分外以及数额的回到;
  • 扶助艺术级其他编织以及代码执行流程的编制;
  • 扶助在肆意的Web容器执行Agent代理。

四、怎么样利用

运用的功利是很让人惊叹标:

  • 零开销接入,无需申请其余资源;
  • 故障注入解除,无需重启服务;
  • 可以提供具有集群的拓扑结构。

只是什么才能科学运用呢?如下图所示:

澳门金沙4787.com官网 18

接纳办法

步骤一、输入AppCode;

手续二、选取故障方法;

步骤三、指定机器;

步骤四、注入故障。

五、总结

故障演练平台最大旨的就是Agent组件——字节码编织框架,这几个框架是纯Java的基于Instrumentation-API的AOP解决方案。它可以方便研发人士对此字节码插桩拆桩操作,可以很不难的已毕故障演练、流量录制以及别的的行使模块。

作者:王鹏

起点:Qunar技术沙龙订阅号(ID:QunarTL)

dbaplus社群欢迎广大技术人士投稿,投稿邮箱:editor@dbaplus.cn归来今日头条,查看更加多

权利编辑:

相关文章