zero's Blog

持续迭代

MethodHandle

  MethodHandle即方法句柄,使用方法和效果上与Reflection都有众多相似之处。不过,它们也有以下这些区别:

  1. Reflection和MethodHandle机制本质上都是在模拟方法调用,但是Reflection是在模拟Java语言层面的方法调用,而MethodHandle是在模拟Java字节码层面的方法调用,MethodHandle效率更高。
    在MethodHandles.Lookup上的三个方法findStatic()、findVirtual()、findSpecial(),正是为了对应于invokestatic、invokevirtual & invokeinterface和invokespecial这几条字节码指令的执行权限校验行为,而这些底层细节在使用Reflection API时是不需要关心的。

    invokespecial:调用一个初始化(构造)方法,私有方法或者父类的方法
    invokestatic:调用静态方法
    invokevirtual:调用实例方法
    invokeinterface:调用接口方法

  2. Reflection中的java.lang.reflect.Method对象远比MethodHandle机制中的java.lang.invoke.MethodHandle对象所包含的信息来得多。前者是方法在Java端的全面映像,包含了方法的签名、描述符以及方法属性表中各种属性的Java端表示方式,还包含有执行权限等的运行期信息。而后者仅仅包含着与执行该方法相关的信息。用开发人员通俗的话来讲,Reflection是重量级,而MethodHandle是轻量级。
  3. 和反射相比好处是:调用 invoke() 已经被JVM优化,类似直接调用一样,性能要好很多。
Read more »

什么是Instrumentation?

  Java Instrumentation指的是可以用独立于应用程序之外的代理(agent)程序来监测和协助运行在JVM上的应用程序。这种监测和协助包括但不限于获取JVM运行时状态,替换和修改类定义等。 Java SE5中使用JVM TI替代了JVM PI和JVM DI。提供一套代理机制,支持独立于JVM应用程序之外的程序以代理的方式连接和访问JVM。java.lang.instrument是在JVM TI的基础上提供的Java版本的实现。 Instrumentation提供的主要功能是修改jvm中类的行为。 Java SE6中由两种应用Instrumentation的方式,premain和agentmain(运行时)

premain方式

  在Java SE5时代,Instrument只提供了premain一种方式,即在真正的应用程序(包含main方法的程序)的main方法启动前启动一个代理程序。例如使用如下命令:
java -javaagent:agent_jar_path[=options] java_app_name
  可以在启动名为java_app_name的应用之前启动一个agent_jar_path指定位置的agent jar。 实现这样一个agent jar包,必须满足两个条件:
1. 在这个jar包的manifest文件中包含Premain-Class属性,并且该属性的值为代理类全路径名。
2. 代理类必须提供一个 public static void premain(String args, Instrumentation inst)public static void premain(String args) 方法。

  当在命令行启动该代理jar时,VM会根据manifest中指定的代理类,使用于main类相同的系统类加载器(即ClassLoader.getSystemClassLoader()获得的加载器)加载代理类。在执行main方法前执行premain()方法。如果premain(String args, Instrumentation inst)premain(String args)同时存在时,优先使用前者。其中方法参数args即命令中的options,类型为String(注意不是String[]),因此如果需要多个参数,需要在方法中自行处理(比如用”;”分割多个参数之类);inst是运行时由VM自动传入的Instrumentation实例,可以用于获取VM信息。

premain实例-打印所有的方法调用
  premain方式的agent类必须提供premain方法,代码如下:

1
2
3
4
5
6
public class Agent {
public static void premain(String args, Instrumentation inst){
System.out.println("Hi, I'm agent!");
inst.addTransformer(new Transformer());
}
}

  premain有两个参数,args为自定义传入的代理类参数,inst为VM自动传入的Instrumentation实例。Transformer是自定义的类的转换器,用于转换类的行为。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public class Transformer implements ClassFileTransformer {

public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
if (className.startsWith("java")
|| className.startsWith("javax")
|| className.startsWith("sun")
|| className.startsWith("jdk")
|| className.startsWith("com/sun")
|| className.startsWith("com/intellij")) {
return null;
}

ClassReader cr = new ClassReader(classfileBuffer);
ClassNode cn = new ClassNode();
cr.accept(cn, 0);
for (Object obj : cn.methods) {
MethodNode md = (MethodNode) obj;
if ("<init>".endsWith(md.name) || "<clinit>".equals(md.name)) {
continue;
}
InsnList insns = md.instructions;
InsnList il = new InsnList();
il.add(new FieldInsnNode(Opcodes.GETSTATIC, "java/lang/System",
"out", "Ljava/io/PrintStream;"));
il.add(new LdcInsnNode("invoke -> " + cn.name + "." + md.name));
il.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL,
"java/io/PrintStream", "println", "(Ljava/lang/String;)V"));
insns.insert(il);
md.maxStack += 3;
}

ClassWriter cw = new ClassWriter(0);
cn.accept(cw);
return cw.toByteArray();
}
}

  Transformer实现了ClassFileTransformer接口,该接口只有一个transform方法,参数传入包括该类的类加载器,类名,原字节码字节流等,返回被转换后的字节码字节流。
  当方法返回后,Java虚拟机会使用所返回的byte数组,来完成接下来的类加载工作;返回null或者抛出异常,那么Java虚拟机将使用原来的byte数组完成类加载工作。
  上面的例子,主要使用ASM实现在所有的类定义的方法中,在方法开始出添加了一段打印该类名和方法名的字节码。

  设置MANIFEST.MF文件中的属性,文件内容如下:

1
2
3
4
5
6
Manifest-Version: 1.0
Premain-Class: com.zero.AgentMain
Can-Redefine-Classes: true
Can-Retransform-Classes: true
Created-By: Apache Maven 3.3.9
Build-Jdk: 1.8.0_171

  maven配置如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.1.0</version>
<configuration>
<archive>
<manifestEntries>
<Project-name>${project.name}</Project-name>
<Project-version>${project.version}</Project-version>
<Agent-Class>com.zero.AgentMain</Agent-Class>
<Premain-Class>com.zero.AgentMain</Premain-Class>
<Can-Redefine-Classes>true</Can-Redefine-Classes>
<Can-Retransform-Classes>true</Can-Retransform-Classes>
</manifestEntries>
</archive>
</configuration>
</plugin>

agentmain方式

  premain时Java SE5开始就提供的代理方式,给了开发者诸多惊喜,不过也有不便,由于其必须在命令行指定代理jar,并且代理类必须在main方法前启动。因此,要求开发者在应用前就必须确认代理的处理逻辑和参数内容等等,在有些场合下,这是比较困难的。
  比如正常的生产环境下,一般不会开启代理功能,但是在发生问题时,我们不希望停止应用就能够动态的去修改一些类的行为,以帮助排查问题,这在应用启动前是无法确定的。 为解决运行时启动代理类的问题,Java SE6开始,提供了在应用程序的VM启动后在动态添加代理的方式,即agentmain方式。 与Permain类似,agent方式同样需要提供一个agent jar,并且这个jar需要满足:

  1. 在manifest中指定Agent-Class属性,值为代理类全路径
  2. 代理类需要提供public static void agentmain(String args, Instrumentation inst)public static void agentmain(String args)方法。并且再二者同时存在时以前者优先。args和inst和premain中的一致。

  不过如此设计的再运行时进行代理有个问题——如何在应用程序启动之后再开启代理程序呢? JDK6中提供了Java Tools API,其中Attach API可以满足这个需求。Attach API中的VirtualMachine代表一个运行中的VM。其提供了loadAgent()方法,可以在运行时动态加载一个代理jar。

agentmain实例-打印当前已加载的类

1
2
3
4
5
6
7
8
9
10
11
12
13
package loaded;

import java.lang.instrument.Instrumentation;

public class AgentMain {
@SuppressWarnings("rawtypes")
public static void agentmain(String args, Instrumentation inst){
Class[] classes = inst.getAllLoadedClasses();
for(Class cls :classes){
System.out.println(cls.getName());
}
}
}

  设置MANIFEST.MF文件,指定Agent-Class:

1
2
3
4
5
6
Manifest-Version: 1.0
Agent-Class: com.zero.AgentMain
Can-Redefine-Classes: true
Can-Retransform-Classes: true
Created-By: Apache Maven 3.3.9
Build-Jdk: 1.8.0_171

  将agent类和MANIFEST.MF文件编译打成agent.jar后,由于agent main方式无法向pre main方式那样在命令行指定代理jar,因此需要借助Attach Tools API。

1
2
3
4
5
6
7
public class Test {
public static void main(String[] args) throws AttachNotSupportedException,
IOException, AgentLoadException, AgentInitializationException {
VirtualMachine vm = VirtualMachine.attach(args[0]);
vm.loadAgent("/Users/xxx/agent.jar");
}
}

  该程序接受一个参数为目标应用程序的进程id,通过Attach Tools API的VirtualMachine.attach方法绑定到目标VM,并向其中加载代理jar。


  在 agent 的 manifest 里加入 Boot-Class-Path 其实一样可以在动态地载入 agent 的同时加入自己的 boot class 路径。

附:agent jar中manifest的属性
  Premain-Class: 当在VM启动时,在命令行中指定代理jar时,必须在manifest中设置Premain-Class属性,值为代理类全类名,并且该代理类必须提供premain方法。否则JVM会异常终止。
  Agent-Class: 当在VM启动之后,动态添加代理jar包时,代理jar包中manifest必须设置Agent-Class属性,值为代理类全类名,并且该代理类必须提供agentmain方法,否则无法启动该代理。
  Boot-Class-Path: Bootstrap class loader加载类时的搜索路径,可选。
  Can-Redefine-Classes: true/false;标示代理类是否能够重定义类。可选。
  Can-Retransform-Classes: true/false;标示代理类是否能够转换类定义。可选。
  Can-Set-Native-Prefix:true/false;标示代理类是否需要本地方法前缀,可选。

  当一个代理jar包中的manifest文件中既有Premain-Class又有Agent-Class时,如果以命令行方式在VM启动前指定代理jar,则使用Premain-Class;反之如果在VM启动后,动态添加代理jar,则使用Agent-Class


Caused by: java.lang.UnsupportedOperationException: redefineClasses is not supported in this environment
通过在MANIFEST.MF增加配置Can-Redefine-Classes: true解决

Caused by: java.lang.UnsupportedOperationException: adding retransformable transformers is not supported in this environment
通过在MANIFEST.MF增加配置Can-Retransform-Classes: true解决

  


应用场景:

  1. 监控,例如APM
  2. 代码注入
  3. 故障模拟,例如对某个method改造抛异常
  4. 流量录制
  5. 在线dug,例如arthas

转载自:
https://blog.csdn.net/productshop/article/details/50623626

  线上服务器jmap发现 Perm Generation使用量持续增长, 查看dump信息发现有很多sun.reflect.DelegatingClassLoader、sun.reflect.GeneratedConstructorAccessor77。原因是反射调用引起的,类越來越多。

  当使用Java反射时,Java虚拟机有两种方法获取被反射的类的信息。它可以使用一个JNI存取器。如果使用Java字节码存取器,则需要拥有它自己的Java类和类加载器(sun/reflect/GeneratedMethodAccessor类和sun/reflect/DelegatingClassLoader)。这些类和类加载器使用本机内存。字节码存取器也可以被JIT编译,这样会增加本机内存的使用。如果Java反射被频繁使用,会显著地增加本机内存的使用。
  Java虚拟机会首先使用JNI存取器,然后在访问了同一个类若干次后,会改为使用Java字节码存取器。注意使用字节码方式第一次有加载的成本比正常执行慢3-4倍,但是后面的执行会有20倍以上的性能提升,这样整体性能会有很大的提升。

Read more »

在用MyBatis-Plus时,构造 where 条件的时候,可以直接通过方法引用的方式去指定属性名:

1
2
3
4
LambdaQueryWrapper<Book> qw = new LambdaQueryWrapper<>();
qw.eq(Book::getId, 2);
List<Book> list = bookMapper.selectList(qw);
System.out.println("list = " + list);

Book::getId这就是方法引用,为什么 MP 通过 Book::getId 就可以识别出来这里的属性名。

Read more »

1 背景

业务处理过程,发现了以下问题,代码一是原代码能正常执行,代码二是经过迭代一次非正常执行代码。

代码一:以下代码开启线程后,代码正常执行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 10, 200, TimeUnit.MILLISECONDS,
new ArrayBlockingQueue<Runnable>(5));

@Transactional
public Long test() {
// ......
// 插入记录
Long studentId = studentService.insert(student);
// 异步线程
writeStatisticsData(studentId);
return studentId;
}

private void writeStatisticsData(Long studentId) {
executor.execute(() -> {
Student student = studentService.findById(studentId);
//........
});
}

代码二:以下代码开启线程后,代码不正常执行(获取不到student对象了):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Transactional
public Long test() {
// ......
// 插入记录
Long studentId = studentService.insert(student);
// 异步线程
writeStatisticsData(studentId);
// 插入学生地址记录
Long addressId = addressService.insert(address);
return studentId;
}

private void writeStatisticsData(Long studentId) {
executor.execute(() -> {
Student student = studentService.findById(studentId);
//........
});
}
Read more »

[toc]

前言

  伴随着分布式应用、Serverless 应用被越来越多开发者及企业所接受,但其背后所隐藏的运维问题也逐渐凸显出来–微服务架构中请求链路过长从而导致问题定位时间长,运维想要日常监控也非常困难。以一个具体问题举例,在分布式应用中完成一个单一用户请求可能会需要多个不同的微服务处理,这其中任何一个服务失败或性能拉垮,都会对用户请求响应造成极大影响。随着业务不断扩张,这个调用链也越来越复杂。仅凭借打印日志或 APM 性能监控很难做到全景浏览或者一竿到底。对于问题排查或性能分析时,这无异于盲人摸象。

  面对这样的问题,Google 发表了论文《“Dapper - a Large-Scale Distributed Systems Tracing Infrastructure”》介绍他们的分布式跟踪技术,并认为分布式跟踪系统应该满足以下业务需求:

  • 性能低损耗: 分布式跟踪系统对服务的性能损耗应尽可能做到忽略不计,尤其是那些对性能敏感的应用。
  • 低侵入: 尽可能做到业务代码的低侵入或无侵入。
  • 快速扩展:能够随着业务或微服务规模快速扩大。
  • 实时展现:低延时采集数据,实时监控系统,对系统的异常状况作出快速的反应。
Read more »

转载自:https://blog.csdn.net/wwd0501/article/details/79474368


  为了确保分布式锁可用,至少要确保锁的实现同时满足以下四个条件:
  1、互斥性。在任意时刻,只有一个客户端能持有锁。
  2、不会发生死锁。即使有一个客户端在持有锁的期间崩溃而没有主动解锁,也能保证后续其他客户端能加锁。
  3、具有容错性。只要大部分的Redis节点正常运行,客户端就可以加锁和解锁。
  4、加锁和解锁必须是同一个客户端,不能把别人加的锁给解了。

Read more »

转载自:https://blog.csdn.net/wangyiyungw/article/details/81002256

  

事务(Transaction)及其ACID属性

事务是由一组SQL语句组成的逻辑处理单元,事务具有以下4个属性,通常简称为事务的ACID属性。

  • 原子性(Atomicity):事务是一个原子操作单元,其对数据的修改,要么全都执行,要么全都不执行。

  • 一致性(Consistent):在事务开始和完成时,数据都必须保持一致状态。这意味着所有相关的数据规则都必须应用于事务的修改,以保持数据的完整性;事务结束时,所有的内部数据结构(如B树索引或双向链表)也都必须是正确的。

  • 隔离性(Isolation):数据库系统提供一定的隔离机制,保证事务在不受外部并发操作影响的“独立”环境执行。这意味着事务处理过程中的中间状态对外部是不可见的,反之亦然。

  • 持久性(Durable):事务完成之后,它对于数据的修改是永久性的,即使出现系统故障也能够保持。

  

Read more »

[toc]

OpenTracing作为调用链规范标准,在实际使用的过程,也有了一些使用上的痛点,例如:

  • 光依据调用链无法很好的定位问题;
  • 调用链与日志系统关联性差,定位具体问题耗时较长;
  • 切换数据后端成本较高;
  • 内部团队有些使用的规范不同,需要适配多套规范;

业界也开始寻找是否有更优的解决方案,OpenTelemetry逐渐突显出来。

Read more »

[toc]

一、云原生是什么

要理解云原生,可以把云原生拆分为云和原生两部分。

云原生的“云”代表“云计算”,因此在了解云原生之前,需要先了解云计算。

云计算的字面意义是通过互联网提供计算资源,其中最为基础的计算资源就是服务器、存储、网络资源等,这些资源是互联网世界的基础设施,因此提供这些资源的租用的服务就叫做 IaaS(Infrastructure as a Service)。显而易见,这些基础设施服务供应商都是业界大佬:AWS(亚马逊云)、Azure(微软云)、阿里云、谷歌云、IBM 云、腾讯云、华为云等。

供应商将大量的基础设施进行统一管理,通过虚拟化等技术手段,形成巨大的可动态分配资源池,大大促进了物理资源的利用率,因此也使得租用成本降低。企业只需要按照使用量进行付费,再不用付出昂贵的设施购买、维护成本。除此之外,云服务还有可随时按需伸缩容量、备份恢复简单、快速部署易于集成等优点。

云资源不仅包含基础设施,在云上搭建的开发平台、应用等都可以作为一种服务提供给用户,这就是Paas(Platform as a Service)和 SaaS(Software as a Service)。根据云服务的开放范围,又可以分为公有云、私有云、混合云。

Read more »
0%