zero's Blog

持续迭代

前几天在一个开源项目的 github 里面看到这样的一个 pr:
500efdf515ee1c0dfca67a7544aeb993.png

光是看这个名字,里面有个 MemorySafe,我就有点陷进去了。我先给你看看这个东西:

64726d0b970a92e9d5033b1c1c85a441.png

这是阿里巴巴开发规范中截的图。为什么不建议使用 FixedThreadPool 和 SingleThreadPool 呢?

因为队列太长了,请求会堆积,请求一堆积,容易造成 OOM。前面提到的线程池用的队列,使用 LinkedBlockingQueue 是可能会导致 OOM 的。

如果想避免这个 OOM 就需要在初始化的时候指定一个合理的值。“合理的值”,听起来轻描淡写的四个字,但是这个值到底是多少呢,基本上说不准。

所以,当我看到 pr 上的 MemorySafeLinkedBlockingQueue 这个名字的时候,我就陷进去了。在 LinkedBlockingQueue 前面加上了 MemorySafe 这个限定词。表示这是一个内存安全的 LinkedBlockingQueue。

Read more »

转载自:https://blog.csdn.net/cradle2006/article/details/95073133
    https://blog.csdn.net/u012503481/article/details/100896507

  
  很多开发场景需要用到Java Bean的属性名,直接写死属性名字符串的形式容易产生bug(属性名一旦变化,IDE不会提示字符串需要同步修改)。JDK8的Lambda可以通过方法引用简化代码,同样也可以通过getter/setter的方法引用拿到属性名,避免潜在的bug。期望实现效果:

1
2
3
4
// 传统方式:hard code写死属性名
// String ITEM_NAME = "orgName";
// 方法引用:替代hard code字符串,当属性名变化时IDE会同步提示,避免未同步产生bug
String ITEM_NAME = BeanUtils.convertToFieldName(User::getOrgName);

  原理是使用SerializedLambda类来完成,SerializedLambda是Lambda表达式在序列化的时候,用来描述Lambda表达式信息的类:

1
2
3
4
5
6
7
8
9
10
11
12
public final class SerializedLambda implements Serializable {
private static final long serialVersionUID = 8025925345765570181L;
private final Class<?> capturingClass;
private final String functionalInterfaceClass;
private final String functionalInterfaceMethodName;
private final String functionalInterfaceMethodSignature;
private final String implClass;
private final String implMethodName;
private final String implMethodSignature;
private final int implMethodKind;
private final String instantiatedMethodType;
private final Object[] capturedArgs;

  获取方法引用的方法名只需要用到implMethodName即可。
  需要注意的是,SerializedLambda是对Lambda表达式进行描述的对象,在Lambda表达式可序列化的时候(函数式接口继承Serializable)才能得到,也就是说,定义的FunctionalInterface必须实现继承Serializable。函数式接口继承Serializable时,编译器在编译Lambda表达式时,生成了一个writeReplace方法,这个方法会返回SerializedLambda,可以反射调用这个方法。

  

Read more »

Java为什么需要lambda表达式?

答: 在一定程度上能够提升代码简洁性、提高代码可读性。

例如,在平时的开发过程中,把一个列表转换成另一个列表或map等等这样的转换操作是一种常见需求。
在没有lambda之前通常都是这样实现的。

1
2
3
4
5
List<Long> idList = Arrays.asList(1L, 2L, 3L);
List<Person> personList = new ArrayList<>();
for (long id : idList) {
personList.add(getById(id));
}

代码重复多了之后,大家就会对这种常见代码进行抽象,形成一些类库便于复用。
上面的需求可以抽象成:对一个列表中的每个元素调用一个转换函数转换并输出结果列表。

1
2
3
4
5
6
7
8
9
10
interface Function {
<T, R> R fun(T input);
}
<T, R> List<R> map(List<T> inputList, Function function) {
List<R> mappedList = new ArrayList<>();
for (T t : inputList) {
mappedList.add(function.fun(t));
}
return mappedList;
}

有了这个抽象,最开始的代码便可以”简化”成

1
2
3
4
5
6
7
List<Long> idList = Arrays.asList(1L, 2L, 3L);
List<Person> personList = map(idList, new Function<Long, Person>() {
@Override
public Person fun(Long input) {
return getById(input);
}
});

虽然实现逻辑少了一些,但是同样也遗憾地发现,代码行数还变多了。
因为Java语言中函数并不能作为参数传递到方法中,函数只能寄存在一个类中表示。为了能够把函数作为参数传递到方法中,我们被迫使用了匿名内部类实现,需要加相当多的冗余代码。
在一些支持函数式编程的语言(Functional Programming Language)中(例如Python, Scala, Kotlin等),函数是一等公民,函数可以成为参数传递以及作为返回值返回。
例如在Kotlin中,上述的代码可以缩减到很短,代码只包含关键内容,没有冗余信息。

1
val personList = idList.map { id -> getById(id) }

这样的编写效率差距也导致了一部分Java用户流失到其他语言,不过最终终于在JDK8也提供了Lambda表达式能力,来支持这种函数传递。

1
List<Person> personList = map(idList, input -> getById(input));

Lambda表达式只是匿名内部类的语法糖吗?

如果要在Java语言中实现lambda表达式,初步观察,通过javac把这种箭头语法还原成匿名内部类,就可以轻松实现,因为它们功能基本是等价的(IDEA中经常有提示)。

但是匿名内部类有一些缺点。

  1. 每个匿名内部类都会在编译时创建一个对应的class,并且是有文件的,因此在运行时不可避免的会有加载、验证、准备、解析、初始化的类加载过程。
  2. 每次调用都会创建一个这个匿名内部类class的实例对象,无论是有状态的(capturing,从上下文中捕获一些变量)还是无状态(non-capturing)的内部类。

invokedynamic介绍

如果有一种函数引用、指针就好了,但JVM中并没有函数类型表示。
Java中有表示函数引用的对象吗,反射中有个Method对象,但它的问题是性能问题,每次执行都会进行安全检查,且参数都是Object类型,需要boxing等等。

还有其他表示函数引用的方法吗?MethodHandle,它是在JDK7中与invokedynamic指令等一起提供的新特性。

但直接使用MethodHandle来实现,由于没有签名信息,会遇不能重载的问题。并且MethodHandle的invoke方法性能不一定能保证比字节码调用好。

invokedynamic出现的背景

JVM上的动态语言(JRuby, Scala等),要实现dynamic typing动态类型,是比较麻烦的。
这里简单解释一下什么是dynamic typing,与其相对的是static typing静态类型。
static typing: 所有变量的类型在编译时都是确定的,并且会进行类型检查。
dynamic typing: 变量的类型在编译时不能确定,只能在运行时才能确定、检查。

例如如下动态语言的例子,a和b的类型都是未知的,因此a.append(b)这个方法是什么也是未知的。

1
2
def add(val a, val b)
a.append(b)

而在Java中a和b的类型在编译时就能确定。

1
2
3
SimpleString add(SimpleString a, SimpleString b) {
return a.append(b);
}

编译后的字节码如下,通过invokevirtual明确调用变量a的函数签名为(LSimpleString;)LSimpleString;的方法。

1
2
3
4
0: aload_1
1: aload_2
2: invokevirtual #2 // Method SimpleString.append:(LSimpleString;)LSimpleString;
5: areturn

关于方法调用的字节码指令,JVM中提供了四种。
invokestatic - 调用静态方法
invokeinterface - 调用接口方法
invokevirtual - 调用实例非接口方法的public方法
invokespecial - 其他的方法调用,private,constructor, super
这几种方法调用指令,在编译的时候就已经明确指定了要调用什么样的方法,且均需要接收一个明确的常量池中的方法的符号引用,并进行类型检查,是不能随便传一个不满足类型要求的对象来调用的,即使传过来的类型中也恰好有一样的方法签名也不行。

invokedynamic功能

这个限制让JVM上的动态语言实现者感到很艰难,只能暂时通过性能较差的反射等方式实现动态类型。
这说明在字节码层面无法支持动态分派,该怎么办呢,又用到了大家熟悉的”All problems in computer science can be solved by another level of indirection”了。
要实现动态分派,既然不能在编译时决定,那么我们把这个决策推迟到运行时再决定,由用户的自定义代码告诉给JVM要执行什么方法。

在jdk7,Java提供了invokedynamic指令来解决这个问题,同时搭配的还有java.lang.invoke包。
这个指令大部分用户不太熟悉,因为不像invokestatic等指令,它在Java语言中并没有和它相关的直接概念。

关键的概念有如下几个

  1. invokedynamic指令: 运行时JVM第一次到这里的时候会进行linkage,会调用用户指定的bootstrap method来决定要执行什么方法,之后便不需要这个解析步骤。这个invokedynamic指令出现的地方也叫做dynamic call site
  2. Bootstrap Method: 用户可以自己编写的方法,实现自己的逻辑最终返回一个CallSite对象。
  3. CallSite: 负责通过getTarget()方法返回MethodHandle
  4. MethodHandle: MethodHandle表示的是要执行的方法的指针

再串联起来梳理下

invokedynamic在最开始时处于未链接(unlinked)状态,这时这个指令并不知道要调用的目标方法是什么。
当JVM要第一次执行某个地方的invokedynamic指令的时候,invokedynamic必须先进行链接(linkage)。
链接过程通过调用一个boostrap method,传入当前的调用相关信息,bootstrap method会返回一个CallSite,这个CallSite中包含了MethodHandle的引用,也就是CallSite的target。
invokedynamic指令便链接到这个CallSite上,并把所有的调用delegate到它当前的targetMethodHandle上。根据target是否需要变换,CallSite可以分为MutableCallSiteConstantCallSiteVolatileCallSite等,可以通过切换target MethodHandle实现动态修改要调用的方法。

图片

lambda表达式真正是如何实现的

下面直接看一下目前java实现lambda的方式

以下面的代码为例

1
2
3
4
5
6
public class RunnableTest {
void run() {
Function<Integer, Integer> function = input -> input + 1;
function.apply(1);
}
}

编译后通过javap查看生成的字节码

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
38
39
40
41
void run();
descriptor: ()V
flags:
Code:
stack=2, locals=2, args_size=1
0: invokedynamic #2, 0 // InvokeDynamic #0:apply:()Ljava/util/function/Function;
5: astore_1
6: aload_1
7: iconst_1
8: invokestatic #3 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
11: invokeinterface #4, 2 // InterfaceMethod java/util/function/Function.apply:(Ljava/lang/Object;)Ljava/lang/Object;
16: pop
17: return
LineNumberTable:
line 12: 0
line 13: 6
line 14: 17
LocalVariableTable:
Start Length Slot Name Signature
0 18 0 this Lcom/github/liuzhengyang/invokedyanmic/RunnableTest;
6 12 1 function Ljava/util/function/Function;
LocalVariableTypeTable:
Start Length Slot Name Signature
6 12 1 function Ljava/util/function/Function<Ljava/lang/Integer;Ljava/lang/Integer;>;

private static java.lang.Integer lambda$run$0(java.lang.Integer);
descriptor: (Ljava/lang/Integer;)Ljava/lang/Integer;
flags: ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
Code:
stack=2, locals=1, args_size=1
0: aload_0
1: invokevirtual #5 // Method java/lang/Integer.intValue:()I
4: iconst_1
5: iadd
6: invokestatic #3 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
9: areturn
LineNumberTable:
line 12: 0
LocalVariableTable:
Start Length Slot Name Signature
0 10 0 input Ljava/lang/Integer;

对应Function<Integer, Integer> function = input -> input + 1;这一行的字节码为

1
2
0: invokedynamic #2,  0              // InvokeDynamic #0:apply:()Ljava/util/function/Function;
5: astore_1

这里再复习一下invokedynamic的步骤。

  1. JVM第一次解析时,调用用户定义的bootstrap method
  2. bootstrap method会返回一个CallSite
  3. CallSite中能够得到MethodHandle,表示方法指针
  4. JVM之后调用这里就不再需要重新解析,直接绑定到这个CallSite上,调用对应的target MethodHandle,并能够进行inline等调用优化

第一行invokedynamic后面有两个参数,第二个0没有意义固定为0 第一个参数是#2,指向的是常量池中类型为CONSTANT_InvokeDynamic_info的常量。

1
#2 = InvokeDynamic      #0:#32         // #0:apply:()Ljava/util/function/Function;

这个常量对应的#0:#32中第二个#32表示的是这个invokedynamic指令对应的动态方法的名字和方法签名(方法类型)

1
#32 = NameAndType        #43:#44        // apply:()Ljava/util/function/Function;

第一个#0表示的是bootstrap method在BootstrapMethods表中的索引。在javap结果的最后看到是

1
2
3
4
5
6
BootstrapMethods:
0: #28 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
Method arguments:
#29 (Ljava/lang/Object;)Ljava/lang/Object;
#30 invokestatic com/github/liuzhengyang/invokedyanmic/RunnableTest.lambda$run$0:(Ljava/lang/Integer;)Ljava/lang/Integer;
#31 (Ljava/lang/Integer;)Ljava/lang/Integer;

再看下BootstrapMethods属性对应JVM虚拟机规范里的说明。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
BootstrapMethods_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 num_bootstrap_methods;
{ u2 bootstrap_method_ref;
u2 num_bootstrap_arguments;
u2 bootstrap_arguments[num_bootstrap_arguments];
} bootstrap_methods[num_bootstrap_methods];
}

bootstrap_method_ref
The value of the bootstrap_method_ref item must be a valid index into the constant_pool table. The constant_pool entry at that index must be a CONSTANT_MethodHandle_info structure

bootstrap_arguments[]
Each entry in the bootstrap_arguments array must be a valid index into the constant_pool table. The constant_pool entry at that index must be a CONSTANT_String_info, CONSTANT_Class_info, CONSTANT_Integer_info, CONSTANT_Long_info, CONSTANT_Float_info, CONSTANT_Double_info, CONSTANT_MethodHandle_info, or CONSTANT_MethodType_info structure

CONSTANT_MethodHandle_info The CONSTANT_MethodHandle_info structure is used to represent a method handle

这个BootstrapMethod属性可以告诉invokedynamic指令需要的boostrap method的引用以及参数的数量和类型。
#28对应的是bootstrap_method_ref,为

1
#28 = MethodHandle       #6:#40         // invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;

按照JVM规范,BootstrapMethod接收3个标准参数和一些自定义参数,标准参数如下

  1. MethodHandles.Loop类型的caller参数,这个对象能够通过类似反射的方式拿到在执行invokedynamic指令这个环境下能够调动到的方法,比如其他类的private方法是调用不到的。这个参数由JVM来入栈
  2. String类型的invokedName参数,表示invokedynamic要实现的方法的名字,在这里是apply,是lambda表达式实现的方法名,这个参数由JVM来入栈
  3. MethodType类型的invokedType参数,表示invokedynamic要实现的方法的类型,在这里是()Function,这个参数由JVM来入栈

#29,#30,#31是可选的自定义参数类型

1
2
3
#29 = MethodType         #41            //  (Ljava/lang/Object;)Ljava/lang/Object;
#30 = MethodHandle #6:#42 // invokestatic com/github/liuzhengyang/invokedyanmic/RunnableTest.lambda$run$0:(Ljava/lang/Integer;)Ljava/lang/Integer;
#31 = MethodType #21 // (Ljava/lang/Integer;)Ljava/lang/Integer;

通过java.lang.invoke.LambdaMetafactory#metafactory的代码说明下

1
2
3
4
5
6
public static CallSite metafactory(MethodHandles.Lookup caller,
String invokedName,
MethodType invokedType,
MethodType samMethodType,
MethodHandle implMethod,
MethodType instantiatedMethodType)

前面三个介绍过了,剩下几个为
MethodType samMethodType: sam(SingleAbstractMethod)就是#29 = MethodType #41 // (Ljava/lang/Object;)Ljava/lang/Object;,表示要实现的方法对象的类型,不过它没有泛型信息,(Ljava/lang/Object;)Ljava/lang/Object;
MethodHandle implMethod: 真正要执行的方法的位置,这里是com.github.liuzhengyang.invokedyanmic.Runnable.lambda$run$0(Integer)Integer/invokeStatic,这里是javac生成的一个对lambda解语法糖之后的方法,后面进行介绍
MethodType instantiatedMethodType: 和samMethod基本一样,不过会包含泛型信息,(Ljava/lang/Integer;)Ljava/lang/Integer;

private static java.lang.Integer lambda$run$0(java.lang.Integer);这个方法是有javac把lambda表达式desugar解语法糖生成的方法,如果lambda表达式用到了上下文变量,则为有状态的,这个表达式也叫做capturing-lambda,会把变量作为这个生成方法的参数传进来,没有状态则为non-capturing。
另外如果使用的是java8的MethodReference,例如Main::run这种语法则说明有可以直接调用的方法,就不需要再生成一个中间方法。

继续看5: astore_1这条指令,表示把当前操作数栈的对象引用保存到index为1的局部变量表中,即赋值给了function变量。
说明前面执行完invokedynamic #2, 0后,在操作数栈中插入了一个类型为Function的对象。
这里的过程需要继续看一下LambdaMetafactory#metafactory的实现。

1
2
3
4
5
6
mf = new InnerClassLambdaMetafactory(caller, invokedType,
invokedName, samMethodType,
implMethod, instantiatedMethodType,
false, EMPTY_CLASS_ARRAY, EMPTY_MT_ARRAY);
mf.validateMetafactoryArgs();
return mf.buildCallSite();

创建了一个InnerClassLambdaMetafactory,然后调用buildCallSite返回CallSite

看一下InnerClassLambdaMetafactory是做什么的: Lambda metafactory implementation which dynamically creates an inner-class-like class per lambda callsite.

怎么回事!饶了一大圈还是创建了一个inner class!先不要慌,先看完,最后分析下和普通inner class的区别。

创建InnerClassLambdaMetafactory的过程大概是参数的一些赋值和初始化等
再看buildCallSite,这个复杂一些,方法描述说明为Build the CallSite. Generate a class file which implements the functional interface, define the class, if there are no parameters create an instance of the class which the CallSite will return, otherwise, generate handles which will call the class' constructor.

创建一个实现functional interface的的class文件,define这个class,如果是没有参数non-capturing类型的创建一个类实例,CallSite可以固定返回这个实例,否则有状态,CallSite每次都要通过构造函数来生成新对象。
这里相比普通的InnerClass,有一个内存优化,无状态就使用一个对象。

方法实现的第一步是调用spinInnerClass(),通过ASM生成一个function interface的实现类字节码并且进行类加载返回。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
只保留关键代码
cw.visit(CLASSFILE_VERSION, ACC_SUPER + ACC_FINAL + ACC_SYNTHETIC, lambdaClassName, null, JAVA_LANG_OBJECT, interfaces);
for (int i = 0; i < argDescs.length; i++) {
FieldVisitor fv = cw.visitField(ACC_PRIVATE + ACC_FINAL, argNames[i], argDescs[i], null, null);
fv.visitEnd();
}
generateConstructor();
if (invokedType.parameterCount() != 0) {
generateFactory();
}
// Forward the SAM method
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, samMethodName, samMethodType.toMethodDescriptorString(), null, null);
mv.visitAnnotation("Ljava/lang/invoke/LambdaForm$Hidden;", true);
new ForwardingMethodGenerator(mv).generate(samMethodType);

byte[] classBytes = cw.toByteArray();

return UNSAFE.defineAnonymousClass(targetClass, classBytes, null);

生成方法为

  1. 声明要实现的接口
  2. 创建保存参数用的各个字段
  3. 生成构造函数,如果有参数,则生成一个static Factory方法
  4. 实现function interface里的要实现的方法,forward到implMethodName上,也就是javac生成的方法或者MethodReference指向的方法
  5. 生成完毕,通过ClassWrite.toByteArray拿到class字节码数组
  6. 通过UNSAFE.defineAnonymousClass(targetClass, classBytes, null) define这个内部类class。这里的defineAnonymousClass比较特殊,它创建出来的匿名类会挂载到targetClass这个宿主类上,然后可以用宿主类的类加载器加载这个类。但是不会但是并不会放到SystemDirectory里,SystemDirectory是类加载器对象+类名字到kclass地址的映射,没有放到这个Directory里,就可以重复加载了,来方便实现一些动态语言的功能,并且能够防止一些内存泄露情况。

这些比较抽象,直观的看一下生成的结果

1
2
3
4
5
6
7
8
9
10
// $FF: synthetic class
final class RunnableTest$$Lambda$1 implements Function {
private RunnableTest$$Lambda$1() {
}

@Hidden
public Object apply(Object var1) {
return RunnableTest.lambda$run$0((Integer)var1);
}
}

如果有参数的情况呢,例如从外部类中使用了一个非静态字段,并使用了一个外部局部变量

1
2
3
4
5
6
private int a;
void run() {
int b = 0;
Function<Integer, Integer> function = input -> input + 1 + a + b;
function.apply(1);
}

对应的结果为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
final class RunnableTest$$Lambda$1 implements Function {
private final RunnableTest arg$1;
private final int arg$2;

private RunnableTest$$Lambda$1(RunnableTest var1, int var2) {
this.arg$1 = var1;
this.arg$2 = var2;
}

private static Function get$Lambda(RunnableTest var0, int var1) {
return new RunnableTest$$Lambda$1(var0, var1);
}

@Hidden
public Object apply(Object var1) {
return this.arg$1.lambda$run$0(this.arg$2, (Integer)var1);
}
}

创建完inner class之后,就是生成需要的CallSite了。如果没有参数,则生成这个inner class的一个function interface对象示例,创建一个固定返回这个对象的MethodHandle,再包装成ConstantCallSite返回。
如果有参数,则返回一个需要每次调用Factory方法产生function interface的对象实例的MethodHandle,包装成ConstantCallSite返回。

这样就完成了bootstrap的过程。invokedynamic链接完之后,后面的调用就直接调用到对应的MethodHandle了,具体是实现就是返回固定的内部类对象,或每次创建新内部类对象。

再次对比通过invokedynamic相对于直接匿名内部类语法糖的优势

我们再想一下,Java8实现这一套骚操作的原因是什么。既然lambda表达式又不需要什么动态分派(调动哪个方法是明确的), 为什么要用invokedynamic呢?

JVM虚拟机的一个基本保证就是低版本的class文件也是能够在高版本的JVM上运行的,并且JVM虚拟机通过版本升级,是在不断优化和提升性能的。

直接转换成内部类实现,固然简单,但编译后的二进制字节码(包括第三方jar包等)内容就固定了,实现固定为创建内部类对象+invoke{virtual, static, special, interface}调用。

未来提升性能只能靠提升创建类对象、invoke指令调用这几个地方的优化。换个熟悉点的说法就是这里写死了。

如果通过invokedynamic呢,javac编译后把足够的信息保留了下来,在JVM执行时能够动态决定如何实现lambda,也就能不断优化lambda表达式的实现,并保持兼容性,给未来留下了更多可能。

转载自:https://mp.weixin.qq.com/s/U7lExRHlIC6S3rnV7r5QTg

[toc]

1)强可及对象(strongly reachable):

  可以通过强引用访问的对象,一般来说,平时写代码的方式都是使用的强引用对象,比如下边的代码段:

1
  StringBuilder builder= new StringBuilder();

上边代码builder这个引用将引用内存堆中的一个对象,这种情况下,只要引用存在,垃圾回收器就永远不会释放该对象的存储空间。

这种对象又成为强引用(Strong references),这种强引用方式就是Java语言的原生的Java引用。上边代码JVM存储了一个StringBuilder类型的对象的强引用在变量builder。强引用和GC的交互是这样的,如果一个对象通过强引用可达或者通过强引用链可达的话这种对象就成为强可及对象,这种情况下的对象垃圾回收器不予理睬。

Read more »

[toc]
JAVA对象引用体系除了强引用之外,出于对性能、可扩展性等方面考虑还特地实现了四种其他引用:SoftReference、WeakReference、PhantomReference、FinalReference,本文主要想讲的是FinalReference,因为在使用内存分析工具比如zprofiler、mat等在分析一些oom的heap的时候,经常能看到 java.lang.ref.Finalizer占用的内存大小远远排在前面,而这个类占用的内存大小又和FinalReference有着密不可分的关系。

对于FinalReference及关联的内容,可能有如下印象:自己代码里从没有使用过,线程dump之后,能看到一个叫做Finalizer的java线程,偶尔能注意到java.lang.ref.Finalizer的存在、我们在类里可能会写finalize方法。

那FinalReference到底存在的意义是什么,以怎样的形式和我们的代码相关联呢?

Read more »

[toc]

ReferenceQueue

引用队列,在检测到适当的可到达性更改后,垃圾回收器将已注册的引用对象添加到该队列中,ReferenceQueue实现了入队(enqueue)和出队(poll),还有remove)操作,内部元素head就是泛型的Reference:

1
private volatile Reference<? extends T> head = null;

ReferenceQueue的实现是由Reference自身的链表结构(单向循环链表)所实现的。所以说ReferenceQueue名义上是一个队列,但实际内部并非有实际的存储结构,它的存储是依赖于内部节点之间的关系来表达。来看下它的入队方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
boolean enqueue(Reference<? extends T> r) { /* Called only by Reference class */
synchronized (lock) {
// Check that since getting the lock this reference hasn't already been
// enqueued (and even then removed)
ReferenceQueue<?> queue = r.queue;
if ((queue == NULL) || (queue == ENQUEUED)) {
return false;
}
assert queue == this;
r.queue = ENQUEUED;
r.next = (head == null) ? r : head;
head = r;
queueLength++;
if (r instanceof FinalReference) {
sun.misc.VM.addFinalRefCount(1);
}
lock.notifyAll();
return true;
}
}

​ 从enqueue()代码可以看出来,queue仅存储当前的head节点,而后面的节点由每个reference节点通过自己的next来保持的。而且queue是一个后进先出的队列。当新的节点进入时成为了head,然后出队时也是先出的head。

Read more »

案发现场

昨天晚上突然短信收到 APM (即 Application Performance Management 的简称,我们内部自己搭建了这样一套系统来对应用的性能、可靠性进行线上的监控和预警的一种机制)大量告警 画外音: 监控是一种非常重要的发现问题的手段,没有的话一定要及时建立哦

紧接着运维打来电话告知线上部署的四台机器全部 OOM (out of memory, 内存不足),服务全部不可用,赶紧查看问题!

问题排查
首先运维先重启了机器,保证线上服务可用,然后再仔细地看了下线上的日志,确实是因为 OOM 导致服务不可用。

图片

第一时间想到 dump 当时的内存状态,但由于为了让线上尽快恢复服务,运维重启了机器,导致无法 dump 出事发时的内存。所以我又看了下我们 APM 中对 JVM 的监控图表。

图片

从 16:00 开始应用中创建的线程居然每时每刻都在上升,一直到 3w 左右,重启后(蓝色箭头),线程也一直在不断增长),正常情况下的线程数是多少呢,600!问题找到了,应该是在下午 16:00 左右发了一段有问题的代码,导致线程一直在创建,且创建的线程一直未消亡!查看发布记录,发现发布记录只有这么一段可疑的代码 diff:
在 HttpClient 初始化的时候额外加了一个 evictExpiredConnections 配置

图片

问题定位了,应该是就是这个配置导致的!(线程上升的时间点和发布时间点完全吻合!),于是先把这个新加的配置给干掉上线,上线之后线程数果然恢复正常了。那 evictExpiredConnections 做了什么导致线程数每时每刻在上升呢?这个配置又是为了解决什么问题而加上的呢?

还原事发经过
最近线上出现不少 NoHttpResponseException 的异常, 而上面那个配置就是为了解决这个异常而添加的,那是什么导致了这个异常呢?

在说这个问题之前我们得先了解一下 http 的 keep-alive 机制。

图片

每个 TCP 连接都要经过三次握手建立连接后才能发送数据,要经过四次挥手才能断开连接,如果每个 TCP 连接在 server 返回 response 后都立马断开,则发起多个 HTTP 请求就要多次创建断开 TCP, 这在 Http 请求很多的情况下无疑是很耗性能的, 如果在 server 返回 response 不立即断开 TCP 链接,而是复用这条链接进行下一次的 Http 请求,则无形中省略了很多创建 / 断开 TCP 的开销,性能上无疑会有很大提升。

如下图示,左图是不复用 TCP 发起多个 HTTP 请求的情况,右图是复用 TCP 的情况,可以看到发起三次 HTTP 请求,复用 TCP 的话可以省去两次建立 / 断开 TCP 的开销,理论上一个应用只要开启一个 TCP 连接即可,其他 HTTP 请求都可以复用这个 TCP 连接,这样 n 次 HTTP 请求可以省去 n-1 次创建 / 断开 TCP 的开销。这对性能的提升无疑是有巨大的帮助。

图片

回过头来看 keep-alive (又称持久连接,连接复用)做的就是复用连接, 保证连接持久有效。

虽然 keep-alive 省去了很多不必要的握手/挥手操作,但由于连接长期保活,如果一直没有 http 请求的话,这条连接也就长期闲着了,会占用系统资源,有时反而会比复用连接带来更大的性能消耗。所以我们一般会为 keep-alive 设置一个 timeout, 这样如果连接在设置的 timeout 时间内一直处于空闲状态(未发生任何数据传输),经过 timeout 时间后,连接就会释放,就能节省系统开销。

如果服务端关闭连接,发送 FIN 包(注:在设置的 timeout 时间内服务端如果一直未收到客户端的请求,服务端会主动发起带 FIN 标志的请求以断开连接释放资源),在这个 FIN 包发送但是还未到达客户端期间,客户端如果继续复用这个 TCP 连接发送 HTTP 请求报文的话,服务端会因为在四次挥手期间不接收报文而发送 RST 报文给客户端,客户端收到 RST 报文就会提示异常 (即NoHttpResponseException)。

图片

知道了产生 NoHttpResponseException 的原因,那该怎么解决呢,有两种策略

  • 重试,收到异常后,重试一两次,由于重试后客户端会用有效的连接去请求,所以可以避免这种情况,不过一次要注意重试次数,避免引起雪崩!
  • 设置一个定时线程,定时清理上述的闲置连接,可以将这个定时时间设置为 keep alive timeout 时间的一半以保证超时前回收。

evictExpiredConnections 就是用的上述第二种策略,来看下官方用法使用说明

Makes this instance of HttpClient proactively evict idle connections from the
connection pool using a background thread.

调用这个方法只会产生一个定时线程,那为啥应用中线程会一直增加呢,因为我们对每一个请求都创建了一个 HttpClient! 这样由于创建每一个 HttpClient 实例j时都会调用 evictExpiredConnections ,导致有多少请求就会创建多少个定时线程!

解决问题
所以针对以上提到的问题,我们首先把 HttpClient 改成了单例,这样保证服务启动后只会有一个定时清理线程,另外我们也让运维针对应用的线程数做了监控,如果超过某个阈值直接告警,这样能在应用 OOM 前及时发现处理。

转载自:https://mp.weixin.qq.com/s/eoUFT6yHQhQeNtqPnjMCaw

CompletionService将生产新的异步任务与使用已完成任务的结果分离开来的服务。生产者 submit 执行的任务。使用者take已完成的任务,并按照完成这些任务的顺序处理它们的结果。例如,CompletionService 可以用来管理异步 IO ,执行读操作的任务作为程序或系统的一部分提交,然后,当完成读操作时,会在程序的不同部分执行其他操作,执行操作的顺序可能与所请求的顺序不同。

通常,CompletionService依赖于一个单独的Executor 来实际执行任务,在这种情况下,CompletionService只管理一个内部完成队列。ExecutorCompletionService类提供了此方法的一个实现。


Future poll()
获取并移除表示下一个已完成任务的 Future,如果不存在这样的任务,则返回 null。

Future poll(long timeout, TimeUnit unit)
获取并移除表示下一个已完成任务的 Future,如果目前不存在这样的任务,则将等待指定的时间(如果有必要)。

Future submit(Callable task)
提交要执行的值返回任务,并返回表示挂起的任务结果的Future。

Future submit(Runnable task, V result)
提交要执行的 Runnable 任务,并返回一个表示任务完成的 Future,可以提取或轮询此任务。

Future take()
获取并移除表示下一个已完成任务的 Future,如果目前不存在这样的任务,则等待。


###示例:

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
package com.zero.test;

import java.util.concurrent.Callable;
import java.util.concurrent.CompletionService;
import java.util.concurrent.ExecutorCompletionService;
import java.util.concurrent.Future;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public class CompletionServiceTest {

public static void main(String[] args) {
// TODO Auto-generated method stub
CompletionService<String> completionService = new ExecutorCompletionService<String>(
Executors.newSingleThreadExecutor());
for (int i = 0; i < 3; i++) {
completionService.submit(new Callable<String>() {
@Override
public String call() throws Exception {
for (int j = 0; j < 3; j++) {
System.out.println("calling----" + j);
TimeUnit.SECONDS.sleep(1);
}
return "hello!!!";
}
});
}
try {
for (int i = 0; i < 3; i++) {
Future<String> future = completionService.take();
System.out.println(future.get());
}
} catch (Exception e) {
e.printStackTrace();
}
}
}

CompletionService的实现是维护一个保存Future对象的BlockingQueue。只有当这个Future对象状态是结束的时候,才会加入到这个Queue中,take()方法其实就是Producer-Consumer中的Consumer。它会从Queue中取出 Future对象,如果Queue是空的,就会阻塞在那里,直到有完成的Future对象加入到Queue中。先完成的必定先被取出,这样就减少了采用List来维护Future方式时不必要的等待时间。

[toc]

Archetype

Archetype设计模式的目的是将业务处理逻辑和具体实现分离,所以至少需要两个参与者:Decorator和Delegate,它们都实现同一个接口,Decorator负责处理业务逻辑,而Delegate负责具体的实现,在Decorator的通用业务逻辑处理过程中,会把具体实现委派给Delegate。

Read more »

查询MySQL数据库中每个表占用的空间、表记录的行数的话,可以打开MySQL的 information_schema 数据库。在该库中有一个 TABLES 表,这个表主要字段分别是:

TABLE_SCHEMA : 数据库名
TABLE_NAME:表名
ENGINE:所使用的存储引擎
TABLES_ROWS:记录数
DATA_LENGTH:数据大小
INDEX_LENGTH:索引大小
其他字段请参考MySQL的手册。

示例:
1、进去指定schema 数据库:

1
use information_schema;

2、查询所有数据的大小 :

1
2
select concat(round(sum(DATA_LENGTH/1024/1024), 2), 'MB') as data 
from TABLES;

3、查看指定数据库实例的大小:

1
2
3
select concat(round(sum(DATA_LENGTH/1024/1024), 2), 'MB') as data 
from TABLES
where table_schema='db_name';

4、查看指定数据库的表的大小:

1
2
3
select concat(round(sum(DATA_LENGTH/1024/1024),2),'MB') as data 
from TABLES
where table_schema='db_name' and table_name='tb_name';
0%