第23篇-虚拟机对象操作指令之getstatic
Java虚拟机规范中定义的对象操作相关的字节码指令如下表所示。
0xb2 | getstatic | 获取指定类的静态域,并将其值压入栈顶 |
0xb3 | putstatic | 为指定的类的静态域赋值 |
0xb4 | getfield | 获取指定类的实例域,并将其值压入栈顶 |
0xb5 | putfield | 为指定的类的实例域赋值 |
0xbb | new | 创建一个对象,并将其引用值压入栈顶 |
0xbc | newarray | 创建一个指定原始类型(如int,、float,、char等)的数组,并将其引用值压入栈顶 |
0xbd | anewarray | 创建一个引用型(如类、接口或数组)的数组,并将其引用值压入栈顶 |
0xbe | arraylength | 获得数组的长度值并压入栈顶 |
0xc0 | checkcast | 检验类型转换,检验未通过将抛出ClassCastException |
0xc1 | instanceof | 检验对象是否是指定的类的实例,如果是将1压入栈顶,否则将0压入栈顶 |
0xc5 | multianewarray | 创建指定类型和指定维度的多维数组(执行该指令时,操作栈中必须包含各维度的长度值),并将其引用值压入栈顶 |
字节码指令的模板定义如下:
def(Bytecodes::_getstatic , ubcp|____|clvm|____, vtos, vtos, getstatic , f1_byte );
def(Bytecodes::_putstatic , ubcp|____|clvm|____, vtos, vtos, putstatic , f2_byte );
def(Bytecodes::_getfield , ubcp|____|clvm|____, vtos, vtos, getfield , f1_byte );
def(Bytecodes::_putfield , ubcp|____|clvm|____, vtos, vtos, putfield , f2_byte ); def(Bytecodes::_new , ubcp|____|clvm|____, vtos, atos, _new , _ );
def(Bytecodes::_newarray , ubcp|____|clvm|____, itos, atos, newarray , _ );
def(Bytecodes::_anewarray , ubcp|____|clvm|____, itos, atos, anewarray , _ );
def(Bytecodes::_multianewarray , ubcp|____|clvm|____, vtos, atos, multianewarray , _ ); def(Bytecodes::_arraylength , ____|____|____|____, atos, itos, arraylength , _ ); def(Bytecodes::_checkcast , ubcp|____|clvm|____, atos, atos, checkcast , _ ); def(Bytecodes::_instanceof , ubcp|____|clvm|____, atos, itos, instanceof , _ );
new字节码指令的生成函数为TemplateTable::_new(),这在《深入剖析Java虚拟机:源码剖析与实例详解(基础卷)》的第9章类对象创建时详细介绍过,这里不再介绍。
getstatic字节码指令获取指定类的静态域,并将其值压入栈顶。格式如下:
getstatic indexbyte1 indexbyte2
无符号数indexbyte1和indexbyte2构建为(indexbyte1<<8)|indexbyte2,这个值指明了一个当前类的运行时常量池索引值,指向的运行时常量池项为一个字段的符号引用。
getstatic字节码指令的生成函数为TemplateTable::getstatic(),还有个类似的getfield指令,这些生成函数如下:
void TemplateTable::getfield(int byte_no) {
getfield_or_static(byte_no, false); // getfield的byte_no值为1
} void TemplateTable::getstatic(int byte_no) {
getfield_or_static(byte_no, true); // getstatic的byte_no的值为1
}
最终都会调用getfield_or_static()函数生成机器指令片段。此函数生成的机器指令片段对应的汇编代码如下:
// 获取ConstantPoolCache中ConstantPoolCacheEntry的index
0x00007fffe101fd10: movzwl 0x1(%r13),%edx
// 从栈中获取ConstantPoolCache的首地址
0x00007fffe101fd15: mov -0x28(%rbp),%rcx
// 左移2位,因为%edx中存储的是ConstantPoolCacheEntry index,
// 左移2位是因为ConstantPoolCacheEntry的内存占用是4个字
0x00007fffe101fd19: shl $0x2,%edx
// 计算%rcx+%rdx*8+0x10,获取ConstantPoolCacheEntry[_indices,_f1,_f2,_flags]中的_indices
// 因为ConstantPoolCache的大小为0x16字节,%rcx+0x10定位到第一个ConstantPoolCacheEntry的开始位置
// %rdx*8算出来的是相对于第一个ConstantPoolCacheEntry的字节偏移
0x00007fffe101fd1c: mov 0x10(%rcx,%rdx,8),%ebx
// _indices向右移动16位后获取[get bytecode,set bytecode,original constant pool index]中的get bytecode与set bytecode
0x00007fffe101fd20: shr $0x10,%ebx
// 获取set bytecode字段的值
0x00007fffe101fd23: and $0xff,%ebx
// 0xb2是getstatic指令的Opcode,比较值,如果相等就说明已经连接,跳转到resolved
0x00007fffe101fd29: cmp $0xb2,%ebx
0x00007fffe101fd2f: je 0x00007fffe101fdce // 将getstatic字节码的Opcode存储到%ebx中
0x00007fffe101fd35: mov $0xb2,%ebx // 省略通过调用MacroAssembler::call_VM()函数来执行InterpreterRuntime::resolve_get_put()函数的汇编代码
// ...
调用MacroAssembler::call_VM()函数生成如下代码,通过这些代码来执行InterpreterRuntime::resolve_get_put()函数。MacroAssembler::call_VM()函数的汇编在之前已经详细介绍过,这里不再介绍,直接给出汇编代码,如下:
0x00007fffe101fd3a: callq 0x00007fffe101fd44
0x00007fffe101fd3f: jmpq 0x00007fffe101fdc2 0x00007fffe101fd44: mov %rbx,%rsi
0x00007fffe101fd47: lea 0x8(%rsp),%rax
0x00007fffe101fd4c: mov %r13,-0x38(%rbp)
0x00007fffe101fd50: mov %r15,%rdi
0x00007fffe101fd53: mov %rbp,0x200(%r15)
0x00007fffe101fd5a: mov %rax,0x1f0(%r15)
0x00007fffe101fd61: test $0xf,%esp
0x00007fffe101fd67: je 0x00007fffe101fd7f
0x00007fffe101fd6d: sub $0x8,%rsp
0x00007fffe101fd71: callq 0x00007ffff66b567c
0x00007fffe101fd76: add $0x8,%rsp
0x00007fffe101fd7a: jmpq 0x00007fffe101fd84
0x00007fffe101fd7f: callq 0x00007ffff66b567c
0x00007fffe101fd84: movabs $0x0,%r10
0x00007fffe101fd8e: mov %r10,0x1f0(%r15)
0x00007fffe101fd95: movabs $0x0,%r10
0x00007fffe101fd9f: mov %r10,0x200(%r15)
0x00007fffe101fda6: cmpq $0x0,0x8(%r15)
0x00007fffe101fdae: je 0x00007fffe101fdb9
0x00007fffe101fdb4: jmpq 0x00007fffe1000420
0x00007fffe101fdb9: mov -0x38(%rbp),%r13
0x00007fffe101fdbd: mov -0x30(%rbp),%r14
0x00007fffe101fdc1: retq
如上代码完成的事情很简单,就是调用C++函数编写的InterpreterRuntime::resolve_get_put()函数,此函数会填充常量池缓存中ConstantPoolCacheEntry信息,关于ConstantPoolCache以及ConstantPoolCacheEntry,还有ConstantPoolCacheEntry中各个字段的含义在《深入剖析Java虚拟机:源码剖析与实例详解(基础卷)》中已经详细介绍过,这里不再介绍。
InterpreterRuntime::resolve_get_put()函数的实现比较多,我们首先看一部分实现,如下:
IRT_ENTRY(void, InterpreterRuntime::resolve_get_put(JavaThread* thread, Bytecodes::Code bytecode))
// resolve field
fieldDescriptor info;
constantPoolHandle pool(thread, method(thread)->constants());
bool is_put = (bytecode == Bytecodes::_putfield || bytecode == Bytecodes::_putstatic);
bool is_static = (bytecode == Bytecodes::_getstatic || bytecode == Bytecodes::_putstatic); {
JvmtiHideSingleStepping jhss(thread);
int x = get_index_u2_cpcache(thread, bytecode); // 根据线程栈中的bcp来获取常量池缓存索引
LinkResolver::resolve_field_access(info, pool, x ,bytecode, CHECK); // 向info中收集信息
} // check if link resolution caused cpCache to be updated
if (already_resolved(thread)){
return;
} ...
}
调用get_index_u2_cpcache()函数从当前方法对应的栈帧中获取bcp,然后通过bcp来获取字节码指令的操作数,也就是常量池索引,得到常量池索引后调用LinkResolver::resolve_field_access()函数可能会连接类和字段,然后将查询到的字段相关信息存储到fieldDescriptor中。resolve_field_access()函数的实现如下:
void LinkResolver::resolve_field_access(
fieldDescriptor& result,
constantPoolHandle pool,
int index, // 常量池索引
Bytecodes::Code byte,
TRAPS
) {
Symbol* field = pool->name_ref_at(index);
Symbol* sig = pool->signature_ref_at(index); // resolve specified klass 连接特定的类
KlassHandle resolved_klass;
resolve_klass(resolved_klass, pool, index, CHECK); KlassHandle current_klass(THREAD, pool->pool_holder());
resolve_field(result, resolved_klass, field, sig, current_klass, byte, true, true, CHECK);
}
从pool中查找到的index处的索引项为CONSTANT_NameAndType_info,格式如下:
CONSTANT_NameAndType_info {
u1 tag;
u2 name_index; // 占用16位
u2 descriptor_index; // 占用16位
}
常量池中的一个CONSTANT_NameAndType_info数据项, 可以看做CONSTANT_NameAndType类型的一个实例 。 从这个数据项的名称可以看出, 它描述了两种信息,第一种信息是名称(Name), 第二种信息是类型(Type) 。这里的名称是指方法的名称或者字段的名称, 而Type是广义上的类型,它其实描述的是字段的描述符或方法的描述符。 也就是说, 如果Name部分是一个字段名称,那么Type部分就是相应字段的描述符; 如果Name部分描述的是一个方法的名称,那么Type部分就是对应的方法的描述符。 也就是说,一个CONSTANT_NameAndType_info就表示了一个方法或一个字段。
调用resolve_klass()连接类,调用resolve_field()连接字段。在resolve_field()函数中有如下实现:
InstanceKlass* tmp = InstanceKlass::cast(resolved_klass());
KlassHandle sel_klass(THREAD, tmp->find_field(field, sig, &fd));
最重要的就是调用InstanceKlass的find_field()函数查找字段,将查找到的相关信息存储到fieldDescriptor类型的fd中。关于字段在InstanceKlass中的存储以及具体的布局在《深入剖析Java虚拟机:源码剖析与实例详解(基础卷)》中已经详细介绍过,这里不再介绍。
fieldDescriptor类及重要属性的定义如下:
class fieldDescriptor VALUE_OBJ_CLASS_SPEC {
private:
AccessFlags _access_flags;
int _index; // the field index
constantPoolHandle _cp;
...
}
其中的_access_flags可用来表示字段是否有volatile、final等关键字修饰,_index表示字段是存储在InstanceKlass中相应数组的第几个元组中。_cp表示定义当前字段的类的常量池。
通过调用resolve_klass()和resolve_field()函数后就可拿到这些信息,然后返回到InterpreterRuntime::resolve_get_put()函数继续查看实现逻辑:
TosState state = as_TosState(info.field_type()); Bytecodes::Code put_code = (Bytecodes::Code)0; InstanceKlass* klass = InstanceKlass::cast(info.field_holder());
bool uninitialized_static = ( (bytecode == Bytecodes::_getstatic || bytecode == Bytecodes::_putstatic) &&
!klass->is_initialized() );
Bytecodes::Code get_code = (Bytecodes::Code)0; if (!uninitialized_static) {
get_code = ((is_static) ? Bytecodes::_getstatic : Bytecodes::_getfield);
// 1、是putfield或putstatic指令
// 2、是getstatic或getfield指令并且不是获取final变量的值
if (is_put || !info.access_flags().is_final()) {
put_code = ((is_static) ? Bytecodes::_putstatic : Bytecodes::_putfield);
}
} ConstantPoolCacheEntry* cpce = cache_entry(thread);
cpce->set_field(
get_code, // 设置的是_indices中的b1,当为getstatic或getfield时,则其中存储的是Opcode
put_code, // 设置的是_indices中的b2,当为setstatic或setfield时,则其中存储的是Opcode,所以get_code与put_code如果要连接了,其值不为0
info.field_holder(), // 设置的是_f1字段,表示字段的拥有者
info.index(), // field_index,设置的是flags
info.offset(), // field_offset,设置的是_f2字段,Offset (in words) of field from start of instanceOop / Klass*
state, // field_type,设置的是flags
info.access_flags().is_final(), // 设置的是flags
info.access_flags().is_volatile(), // 设置的是flags
pool->pool_holder()
);
通过info中的信息就可以得到字段的各种信息,然后填充ConstantPoolEntry信息,这样下次就不用对字段进行连接了,或者说不用从InstanceKlass中查找字段信息了,可直接从ConstantPoolCacheEntry中找到所有想得到的信息。
上图在《深入剖析Java虚拟机:源码剖析与实例详解(基础卷)》一书中详细介绍过,通过我们解读getstatic字节码的解释执行过程,可以清楚的知道常量池缓存项的作用。对于getstatic来说,开始就会判断_indices中的高8位存储的是否为getstatic的操作码,如果不是,则表示没有连接,所以要调用InterpreterRuntime::resolve_get_put()函数进行连接操作。
在连接完成或已经连接完成时会继续执行如下汇编代码:
// 将ConstantPoolCacheEntry的索引存储么%edx
0x00007fffe101fdc2: movzwl 0x1(%r13),%edx
// 将ConstantPoolCache的首地址存储到%rcx
0x00007fffe101fdc7: mov -0x28(%rbp),%rcx
// 获取对应的ConstantPoolCacheEntry对应的索引
0x00007fffe101fdcb: shl $0x2,%edx // --resolved -- // 获取[_indices,_f1,_f2,_flags]中的_f2,由于ConstantPoolCache占用16字节,而_indices
// 和_f2各占用8字节,所以_f2的偏移为32字节,也就是0x32
// _f2中保存的是字段在java.lang.Class实例中的字节偏移,通过此偏移就可获取此字段存储在
// java.lang.Class实例的值
0x00007fffe101fdce: mov 0x20(%rcx,%rdx,8),%rbx
// 获取[_indices,_f1,_f2,_flags]中的_flags
0x00007fffe101fdd3: mov 0x28(%rcx,%rdx,8),%eax
// 获取[_indices,_f1,_f2,_flags]中的_f1,_f1保存了字段拥有者,
// 也就是java.lang.Class对象
0x00007fffe101fdd7: mov 0x18(%rcx,%rdx,8),%rcx // 从_f1中获取_java_mirror属性的值
0x00007fffe101fddc: mov 0x70(%rcx),%rcx
// 将_flags向右移动28位,剩下TosState
0x00007fffe101fde0: shr $0x1c,%eax
0x00007fffe101fde3: and $0xf,%eax
// 如果不相等,说明TosState的值不为0,则跳转到notByte
0x00007fffe101fde6: jne 0x00007fffe101fdf6 // btos
// btos的编号为0,代码执行到这里时,可能栈顶缓存要求是btos
// %rcx中存储的是_java_mirror,%rbx中存储的是_f2,由于静态变量存储在_java_mirror中,所以要获取
// 对应的首地址并压入栈中
0x00007fffe101fdec: movsbl (%rcx,%rbx,1),%eax
0x00007fffe101fdf0: push %rax
// 跳转到Done
0x00007fffe101fdf1: jmpq 0x00007fffe101ff0c
// -- notByte --
// %eax中存储的是TosState,如果不为atos,则跳转到notObj
0x00007fffe101fdf6: cmp $0x7,%eax
0x00007fffe101fdf9: jne 0x00007fffe101fe90 // atos
// %rcx中存储的是_java_mirror,%rbx中存储的是_f2,
// 所以要获取静态变量的首地址并压入栈内
0x00007fffe101fdff: mov (%rcx,%rbx,1),%eax
0x00007fffe101fe02: push %r10
0x00007fffe101fe04: cmp 0x163a8d45(%rip),%r12 # 0x00007ffff73c8b50
0x00007fffe101fe0b: je 0x00007fffe101fe88
0x00007fffe101fe11: mov %rsp,-0x28(%rsp)
0x00007fffe101fe16: sub $0x80,%rsp
0x00007fffe101fe1d: mov %rax,0x78(%rsp)
0x00007fffe101fe22: mov %rcx,0x70(%rsp)
0x00007fffe101fe27: mov %rdx,0x68(%rsp)
0x00007fffe101fe2c: mov %rbx,0x60(%rsp)
0x00007fffe101fe31: mov %rbp,0x50(%rsp)
0x00007fffe101fe36: mov %rsi,0x48(%rsp)
0x00007fffe101fe3b: mov %rdi,0x40(%rsp)
0x00007fffe101fe40: mov %r8,0x38(%rsp)
0x00007fffe101fe45: mov %r9,0x30(%rsp)
0x00007fffe101fe4a: mov %r10,0x28(%rsp)
0x00007fffe101fe4f: mov %r11,0x20(%rsp)
0x00007fffe101fe54: mov %r12,0x18(%rsp)
0x00007fffe101fe59: mov %r13,0x10(%rsp)
0x00007fffe101fe5e: mov %r14,0x8(%rsp)
0x00007fffe101fe63: mov %r15,(%rsp)
0x00007fffe101fe67: movabs $0x7ffff6d4d828,%rdi
0x00007fffe101fe71: movabs $0x7fffe101fe11,%rsi
0x00007fffe101fe7b: mov %rsp,%rdx
0x00007fffe101fe7e: and $0xfffffffffffffff0,%rsp
0x00007fffe101fe82: callq 0x00007ffff6872e3a
0x00007fffe101fe87: hlt
0x00007fffe101fe88: pop %r10
0x00007fffe101fe8a: push %rax
0x00007fffe101fe8b: jmpq 0x00007fffe101ff0c // -- notObj --
0x00007fffe101fe90: cmp $0x3,%eax
// 如果不为itos,则跳转到notInt
0x00007fffe101fe93: jne 0x00007fffe101fea2 // itos
0x00007fffe101fe99: mov (%rcx,%rbx,1),%eax
0x00007fffe101fe9c: push %rax
// 跳转到Done
0x00007fffe101fe9d: jmpq 0x00007fffe101ff0c
// -- notInt --
// 如果不为ctos,则跳转到notChar
0x00007fffe101fea2: cmp $0x1,%eax
0x00007fffe101fea5: jne 0x00007fffe101feb5 // ctos
0x00007fffe101feab: movzwl (%rcx,%rbx,1),%eax
0x00007fffe101feaf: push %rax
// 跳转到Done
0x00007fffe101feb0: jmpq 0x00007fffe101ff0c
// -- notChar --
// 如果不为stos,则跳转到notShort
0x00007fffe101feb5: cmp $0x2,%eax
0x00007fffe101feb8: jne 0x00007fffe101fec8 // stos
0x00007fffe101febe: movswl (%rcx,%rbx,1),%eax
0x00007fffe101fec2: push %rax
// 跳转到done
0x00007fffe101fec3: jmpq 0x00007fffe101ff0c
// -- notShort --
// 如果不为ltos,则跳转到notLong
0x00007fffe101fec8: cmp $0x4,%eax
0x00007fffe101fecb: jne 0x00007fffe101fee2 // ltos
0x00007fffe101fed1: mov (%rcx,%rbx,1),%rax
0x00007fffe101fed5: sub $0x10,%rsp
0x00007fffe101fed9: mov %rax,(%rsp)
// 跳转到Done
0x00007fffe101fedd: jmpq 0x00007fffe101ff0c
// -- notLong --
// 如果不为ftos,则跳转到notFloat
0x00007fffe101fee2: cmp $0x5,%eax
0x00007fffe101fee5: jne 0x00007fffe101fefe // ftos
0x00007fffe101feeb: vmovss (%rcx,%rbx,1),%xmm0
0x00007fffe101fef0: sub $0x8,%rsp
0x00007fffe101fef4: vmovss %xmm0,(%rsp)
// 跳转到Done
0x00007fffe101fef9: jmpq 0x00007fffe101ff0c
// -- notFloat --
0x00007fffe101fefe: vmovsd (%rcx,%rbx,1),%xmm0
0x00007fffe101ff03: sub $0x10,%rsp
0x00007fffe101ff07: vmovsd %xmm0,(%rsp)
// -- Done --
如上汇编代码虽然多,但是完成的逻辑却非常简单,就是通过ConstantPoolCacheEntry中存储的信息(所谓的字节码连接完成指的就是对应的常量池缓存项的信息已经完善)完成压栈的逻辑。由于静态字段的值存储在java.lang.Class实例中,所以需要获取到对应的值,然后根据栈顶缓存要求的状态将值压入表达式栈即可。
推荐阅读:
第2篇-JVM虚拟机这样来调用Java主类的main()方法
第13篇-通过InterpreterCodelet存储机器指令片段
第20篇-加载与存储指令之ldc与_fast_aldc指令(2)
如果有问题可直接评论留言或加作者微信mazhimazh
关注公众号,有HotSpot VM源码剖析系列文章!
第23篇-虚拟机对象操作指令之getstatic的更多相关文章
- 第24篇-虚拟机对象操作指令之getfield
getfield指令表示获取指定类的实例域,并将其值压入栈顶.其格式如下: getstatic indexbyte1 indexbyte2 无符号数indexbyte1和indexbyte2构建为(i ...
- 第25篇-虚拟机对象操作指令之putstatic
之前已经介绍了getstatic与getfield指令的汇编代码执行逻辑,这一篇介绍putstatic指令的执行逻辑,putfield将不再介绍,大家可以自己去研究,相信大家有这个实力. putsta ...
- Linux连接虚拟机及操作指令
Linux的安装(虚拟机环境)与基础配置 一.背景 本文介绍如何安装虚拟机VMware以及如果在虚拟机上安装Linux系统以及Linux安装完毕之后的基础配置 需要准备的东西有VMware以及Li ...
- 深入理解JVM(1)——栈和局部变量操作指令
将常量压入栈的指令 aconst_null 将null对象引用压入栈iconst_m1 将int类型常量-1压入栈iconst_0 将int类型常量0压入栈iconst_1 将int类型常量1压入栈i ...
- 前端学PHP之面向对象系列第五篇——对象操作
× 目录 [1]对象克隆 [2]对象比较[3]对象串行化[4]json 前面的话 本文主要介绍面向对象中的一些对象操作 对象克隆 对象复制,又叫对象克隆,可以通过 clone 关键字来完成 在多数情况 ...
- Javascript 笔记与总结(2-3)Javascript 运算符、控制结构与对象操作
[连接运算符 + ] <script> console.log(1+2+'a'+3+4); </script> 输出: 3a34 [逻辑运算符]返回的是最早能判断表达式结果的那 ...
- JVM(四)-虚拟机对象
概述: 上一篇文章,介绍了虚拟机类加载的过程,那么类加载好之后,虚拟机下一步该干什么呢.我们知道java是面向对象的编程语言,所以对象可以说是java'的灵魂,这篇文章我们就来介绍 虚拟机是如何创建对 ...
- HotSpot虚拟机对象相关内容
一.对象的创建 1.类加载检查 普通对象的创建过程:虚拟机遇到一条new指令时,首先将去检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已被加载.解析和初始化 ...
- 深入理解Java虚拟机读书笔记1----Java内存区域与HotSpot虚拟机对象
一 Java内存区域与HotSpot虚拟机对象 1 Java技术体系.JDK.JRE? Java技术体系包括: · Java程序设计语言: · 各种硬件平台上的 ...
随机推荐
- 作为Java开发工程师,如何高效优雅地编写接口文档
作为一名优秀的Java开发工程师,编写接口文档向来是一件很头疼的事情.本来就被bug纠缠的很累了,你还让我干这? 其实,你可以试试ApiPost. ApiPost的定位是Postman+Swagger ...
- 爱了!阿里大神最佳总结“Flutter进阶学习笔记”,理论与实战
前言 "小步快跑.快速迭代"的开发大环境下,"一套代码.多端运行"是很多开发团队的梦想,美团也一样.他们做了很多跨平台开发框架的尝试:React Native. ...
- 年薪60W的Android程序员究竟多累、多苦?一条高赞评论扎了无数人的心
为啥经常听到程序员朋友说自己太辛苦了.太累了,其实原因很简单:干活苦.竞争大.技术致富难. 干活苦: 有新闻说晚上十点某领导在微信群要求下属在十分钟内回复,一员工因休息早而没及时回复被裁了. 对于程序 ...
- AspNetCore添加API限流
最近发现有客户在大量的请求我们的接口,出于性能考虑遂添加了请求频率限制. 由于我们接口请求的是.Net Core写的API网关,所以可以直接添加一个中间件,中间件中使用请求的地址当key,通过配置中心 ...
- 连通图与Tarjan算法
引言 Tarjan算法是一个基于深度优先搜索的处理树上连通性问题的算法,可以解决,割边,割点,双连通,强连通等问题. 首先要明白Tarjan算法,首先要知道它能解决的问题的定义. 连通图 无向图 由双 ...
- SpringBoot开发五-社区首页开发
需求介绍-社区首页 根据之前的学习,我们一般都是先按照DAO->Service->Controller这个顺序去开发 分布实现: 开发社区首页,显示前十个帖子. 开发分页组件,分页显示所有 ...
- docker搭建kafka集群(高级版)
1. 环境docker, docker-compose 2.zookeeper集群 /data/zookeeper/zoo1/config/zoo.cfg # The number of millis ...
- 解决docker-compose下载过慢
https://blog.csdn.net/baidu_21349635/article/details/104628772
- 003 TCP/IP协议详解(二)
一.ping ping可以说是ICMP的最著名的应用,是TCP/IP协议的一部分.利用"ping"命令可以检查网络是否连通,可以很好地帮助我们分析和判定网络故障. 例如:当我们某一 ...
- 一种简易但设计全面的ID生成器思考
分布式系统中,全局唯一 ID 的生成是一个老生常谈但是非常重要的话题.随着技术的不断成熟,大家的分布式全局唯一 ID 设计与生成方案趋向于趋势递增的 ID,这篇文章将结合我们系统中的 ID 针对实际业 ...