MethodHandles

Method Hanldes是在Java 7引入的概念。全限定名是java.lang.invoke.MethodHandles。 1.介绍 Method Handles的引入是为了与已经存在的java.lang.reflect API相配合。他们分别是为了解决不同的问题而出现的。从性能角度上说,MethodHandle api要比反射快很多因为访问检查在创建的时候就已经完成了,而不是像反射一样等到运行时候才检查。但同时,Method Handles比反射更难用,因为没有列举类中成员,获取属性访问标志之类的机制。另外,MethodHandles可以操作方法,更改方法参数的类型和他们的顺序。而反射则没有这些功能。从以上角度看,反射更通用,但是安全性更差,因为可以在不授权的情况下使用反射对象。而method Handles遵从了分享者的能力。所以method handle是一种更低级的发现,适配和调用方法的方式,唯一的优点就是更快。所以反射更适合主流Java开发者,而method handle更适用于对编译和运行性能有要求的人。 MethodHandle是可对直接执行的方法(或域、构造方法等)的类型的引用,或者说,它是一个有能力安全调用方法的对象。换个方法来说,通过句柄我们可以直接调用该句柄所引用的底层方法。作用类似于反射中的Method类,但它比Method类要更加灵活和轻量级。通过MethodHandle进行方法调用一般需要以下几步: (1)创建MethodType对象,指定方法的签名; (2)在MethodHandles.Lookup中查找类型为MethodType的MethodHandle; (3)传入方法参数并调用MethodHandle.invoke或者MethodHandle.invokeExact方法。 MethodType:是表示方法签名类型的不可变对象。每个方法句柄都有一个MethodType实例,用来指明方法的返回类型和参数类型。它的类型完全由参数类型和方法类型来确定,而与它所引用的底层的方法的名称和所在的类没有关系。可以通过MethodHandle类的type方法查看其类型,返回值是MethodType类的对象。也可以在得到MethodType对象之后,调用MethodHandle.asType(mt)方法适配得到MethodHandle对象。可以通过调用MethodType的静态方法创建MethodType实例,有三种创建方式: (1)methodType及其重载方法:需要指定返回值类型以及0到多个参数; (2)genericMethodType:需要指定参数的个数,类型都为Object; (3)fromMethodDescriptorString:通过方法描述来创建。 需注意:MethodType的ptypes需要与目标方法签名一致(不支持向上/向下转型,比如用Integer去获取入参为Number的方法是不允许的,同样用Number去获取入参为Integer的方法也不行。这个还能理解),rtype需要与目标方法的返回值一致(不允许向上/向下转型。不允许向下很容易理解,为何要不允许向上转型,有点不清楚) 创建好MethodType对象后,还可以对其进行修改,MethodType类中提供了一系列的修改方法,比如:changeParameterType、changeReturnType等。 MethodType的对象实例只能通过MethodType类中的静态工厂方法来创建,而且MethodType类的所有对象实例都是不可变的。如果修改了MethodType实例中的信息,就会生成另外一个MethodType实例。 Lookup:MethodHandle.Lookup相当于MethodHandle工厂类,通过findxxx方法可以得到相应的MethodHandle,还可以配合反射API创建MethodHandle,对应的方法有unreflect、unreflectSpecial等。 invoke:在得到MethodHandle后就可以进行方法调用了,有三种调用形式: (1)invokeExact:调用此方法与直接调用底层方法一样,需要做到参数类型精确匹配; (2)invoke:参数类型松散匹配,通过asType自动适配; (3)invokeWithArguments:直接通过方法参数来调用。其实现是先通过genericMethodType方法得到MethodType,再通过MethodHandle的asType转换后得到一个新的MethodHandle,最后通过新MethodHandle的invokeExact方法来完成调用。 2.使用 1.要使用method handle,首先需要得到Lookup。这是创造方法,构造函数,属性的method handles的工厂类。 2.要创建MethodHandle,lookup需要一个定义了它的类型的MethodType对象。这里的类型包括了传入参数的类型,和最后返回的类型,要一一对应。第一个是返回类型,如果没有返回值就是Void.class, 后面是可变的传入参数的类型。 a MethodType represents the arguments and return type accepted and returned by a method handle or passed and expected by a […]

awk

转自 shell相关的,后续需进一步学习 awk是对文本和数据进行处理的编程语言 其实sed和awk都是每次读入一行来处理的,区别是: sed 适合简单的文本替换和搜索; 而awk除了自动给你分列之外,里面丰富的函数大大增强了awk的功能。数据统计,正则表达式搜索,逻辑处理,前后置脚本等。 sed的核心是正则,awk的核心是格式化。 因此基本上sed能做的,awk可以全部完成并且做的更好。 补充说明 awk 是一种编程语言,用于在linux/unix下对文本和数据进行处理。数据可以来自标准输入(stdin)、一个或多个文件,或其它命令的输出。它支持用户自定义函数和动态正则表达式等先进功能,是linux/unix下的一个强大编程工具。它在命令行中使用,但更多是作为脚本来使用。awk有很多内建的功能,比如数组、函数等,这是它和C语言的相同之处,灵活性是awk最大的优势。 awk命令格式和选项 语法形式 常用命令选项 -F fs fs指定输入分隔符,fs可以是字符串或正则表达式,如-F: -v var=value 赋值一个用户定义变量,将外部变量传递给awk -f scripfile 从脚本文件中读取awk命令 -m[fr] val 对val值设置内在限制,-mf选项限制分配给val的最大块数目;-mr选项限制记录的最大数目。这两个功能是Bell实验室版awk的扩展功能,在标准awk中不适用。 awk模式和操作 awk脚本是由模式和操作组成的。 模式 模式可以是以下任意一个: /正则表达式/:使用通配符的扩展集。 关系表达式:使用运算符进行操作,可以是字符串或数字的比较测试。 模式匹配表达式:用运算符~(匹配)和~!(不匹配)。 BEGIN语句块、pattern语句块、END语句块:参见awk的工作原理 操作 操作由一个或多个命令、函数、表达式组成,之间由换行符或分号隔开,并位于大括号内,主要部分是: 变量或数组赋值 输出命令 内置函数 控制流语句 awk脚本基本结构 一个awk脚本通常由:BEGIN语句块、能够使用模式匹配的通用语句块、END语句块3部分组成,这三个部分是可选的。任意一个部分都可以不出现在脚本中,脚本通常是被 单引号 或 双引号 中,例如: awk的工作原理 第一步:执行BEGIN{ commands }语句块中的语句; 第二步:从文件或标准输入(stdin)读取一行,然后执行pattern{ commands }语句块,它逐行扫描文件,从第一行到最后一行重复这个过程,直到文件全部被读取完毕。 第三步:当读至输入流末尾时,执行END{ […]

反射在JVM层面的实现

转自 该篇文章,将从我们熟悉的Java反射API出发,一直到JVM源码对Java反射的支持。本文分析用的是JDK18。与原文的1.7相比,变化还是不少的,但总体思想没变 首先看使用的示例代码: 这段代码的逻辑十分简单,就是利用反射来获取一个字符串的哈希值。该示例毫无实际利用的价值,但是拿来举例子是足够的了。 这篇文章我们要弄明白的问题是: getDeclaredMethod方法是如何正确查找到方法的; invoke方法是如何被执行的; 这两个方法的调用,在虚拟机的层面上究竟发生了什么; JDK分析 反射的源码在JDK层面上是比较简单的,getDeclaredMethod方法的源码是: 其中最重要的就是 searchMethods是依据传入的方法名字和方法参数,从privateGetDeclaredMethods返回的Method[]中找到对应的方法,返回该方法的 副本。所以重要的逻辑是在privateGetDeclaredMethods中,该方法的源码是: 该方法大体上可以分成两个部分: 从本地缓存中获取Method[]; 如果本地没有缓存,则从虚拟机中获取; 本地缓存 先考察从本地缓存获取,其中关键的一句话是: 这一句话就是访问本地缓存。ReflectionData是Class的一个内部类,它里面存储了一个Class所含有的各色信息,包括字段、方法,并且进行了一定的分类,源码是: 这个源码要注意的是其构造函数,在这个构造函数中,只设置了redefinedCount,其余字段都会是Null。reflectionData()方法源码是: 这段代码有一个很关键的点:本地缓存的ReflectionData是使用SoftReference的,这意味着,在内存紧张的时候会被回收掉。如果在回收掉之后再次请求获得Method[],那么就会新建一个SoftReference,作为缓存。也就是其中newReflectionData的逻辑: 这段逻辑看上去很简单,但是其中有一些很容易遗漏的点。首先要注意的是,当走到 这一句的时候,rd里面只有一个redefinedCount字段被设置,其余字段都还是null。该方法的核心是: 该方法的底层是使用Unsafe的compareAndSwapObject方法来实现的: 这个方法的含义就是:如果clazz内存分布中,在reflectionDataOffset位置的地方,如果期望的值是oldData,那么就会使用newData来替换掉oldData。而在clazz内存的reflectionDataOffset的地方,恰好就是Class类中reflectionData域引用的地方。所以这个方法的真实含义,借助CAS原子操作,将老的reflectionData替换为新的reflectionData。 但是,这里要注意的一点是,此时的newData,也就是 它是一个新建的ReflectionData的实例的soft reference。而新建的ReflectionData,也就是rd,前面已经提到了,它只有redefinedCount字段是被设置好了的,其余字段都还是null。所以如果没有本地缓存,在方法返回了之后,一直到privateGetDeclaredMethods方法的 此刻的rd就是方才新建的ReflectionData,只有redefinedCount字段是设置的。所以判断: 是必然不成立的,因此最终会走到从JVM中获取Method[]的代码处。我们总结一下,在这种情况下,Class的实例类似于: 从JVM获取 前面已经提到,在本地缓存失效,或者被回收了之后,需要从JVM当中获得Method[]: 所以最后是通过调用native方法。下面是C++的环节了 从JVM中取到了Method[]。该方法在JVM中的头文件声明是: 该方法的实现是: 如我在其中注释的一样,整个过程可以分成几步: 先处理基本类型和数组类型; 确保类已经完成链接,在此处就是确保String已经完成了链接 统计符合要求的方法,并根据方法个数为结果分配内存 创建Method[]数组 下面我们将对2和4进行详细的分析。在这之前先对JVM的oop-klass做一个简单的介绍: oop: ordinary object pointer,普通对象指针,用于描述对象的实例信息 klass:Java类在JVM中的表示,是对Java类的描述,看名称是元数据对于我们日常说的一个对象来说,它们的oop-klass模型如图: JVM就是用这种方式,将一个对象的数据和对象模型进行分离。普遍意义上来说,我们说持有一个对象的引用,指的是图中的handle,它是oop的一个封装。 处理基本类型和数组类型 JNIHandles::resolve_non_null方法将ofClass转化为JVM内部的一个oop,因为JVM只会直接操作oop实例。顾名思义,is_primitive是判断是否属于基本类型,其源码实现是: 这一段的逻辑十分简单,取到java_class这个oop中在klass字段上的值,如果是Null则被认为是基本类型。因为对于任何一个非基本类型的对象来说,oop中必然包含着一个指向其klass实例的指针。 另外一个判断条件: java_lang_Class::as_klass方法将一个oop包装成一个klass,逻辑和前面的is_primitive十分相像: 在JVM中,对象在内存中的基本存在形式就是oop,正如前面的图所描述的那样。那么,对象所属的类,在JVM中也是一种对象,因此它们实际上也会被组织成一种oop,即klassOop。同样的,对于klassOop,也有对应的一个klass来描述,它就是klassKlass,也是klass的一个子类。在这种设计下,JVM对内存的分配和回收,都可以采用统一的方式来管理。oop-klass-klassKlass关系如图: 我们接下来看调用的 is_array_klass […]

JPMS(Project Jigsaw)部分链接

一些整理 Project Jigsaw warn: requires transitive directive for an automatic module jmod-example 迁移中遇到的一些问题 module xxx read package xxx from both xxx and xxx can’t extract module name from xxx.jar: Provider class xxx not int module Java 9 Migration Guide: The Seven Most Common Challenges 强烈推荐这一篇文章 基于下面这俩链接,基本说明了在下一个大版本,将对JPMS进行支持 Spring Boot Java 9+ modularity Declare Spring modules with […]

MySQL 内核原理总结

转自 这篇文章还不错,尤其是三大日志那里,虽然不复杂,但讲的很明白了 学一个技术,我们先要 跳出来,看整体,先要在脑中有一个这个技术的全貌。然后再 钻进去,看本质,深入的研究细节。这样方便我们建立一个立体的知识网络。不然单学多个知识点,是串不起来的。不容易记住,理解也不会深刻。 所以,我们先把 MySQL 拆解一下,看看内部有哪些组件,我们 Java系统执行一条SQL,MySQL的内部是如何运作,给我们返回结果的。 我们先从我们访问数据库说起 – 我们想要查询数据库,首先得建立网络连接 MySQL 驱动负责建立网络连接,然后请求 MySQL 数据库 其实就是创建了一个数据库连接 ​ Java系统的 数据库连接池 – 如果我们的系统所有线程访问数据库时,都使用一个连接会怎样 – 所有线程抢夺一个连接,没有连接 就操作不了数据库,效率极低,因为需要后面的线程需要等待前面的线程处理完才行 ​ – 我们的系统如果每个线程访问数据库时,都创建一个连接,然后销毁,会怎样 – 创建连接需要网络通信,网络通信是很耗时的 好不容易创建了连接,查询完就给销毁了,那效率肯定低 ​ – 所以,我们要使用数据库连接池 – 数据库连接池里,会有多个数据库连接 每个线程使用完连接后,会放回池子,连接不会销毁 常用的数据库连接池有 DBCP、C3P0、Druid ​ MySQL 的 连接器 – Java 系统要和MySQL 建立多个连接,那 MySQL 自然也需要维护与系统之间的连接 所以,MySQL 整体架构的第一个组件就是 连接器 ​ MySQL 连接器的功能 […]

Jstat命令

jstat命令可以查看堆内存各部分的使用量,以及加载类的数量。命令的格式如下: jstat [-命令选项] [vmid] [间隔时间/毫秒] [查询次数] GC调优,当前主要是在Java 8下PerNew + CMS可以通过调优来尽可能的减少FGC,但在9引入G1后,调优的点已经发生了变化,在11引入ZGC后更是如此(并发、不再分代),技术迭代。。。。 jstat参数 jstat -h​-h requires an integer argumentUsage: jstat -help|-options       jstat -<option> [-t] [-h<lines>] <vmid> [<interval> [<count>]]​Definitions: <option>     An option reported by the -options option <vmid>       Virtual Machine Identifier. A vmid takes the following form:       […]

lWoHvYe 无悔,专一