当前位置: 首页 > 编程日记 > 正文

HotSpot模板解释器目标代码生成过程源码分析

虽然说解释执行模式是逐字逐句翻译给目标平台运行的,但这样的过程未免太过缓慢,如果能把字节码说的话做成纸条,运行时只要把对应的纸条交给目标平台就可以了,这样,执行速度就会明显提升。JVM的Hotspot虚拟机的模板解释器就是用这种方法来解释执行的。在开始分析之前,先了解一下JVM的执行方式。

  (1).边解释边运行,即每次解释一条字节码并运行其解释的本地代码,这种执行引擎速度相对很慢 
  (2).JIT(即时编译)具有更快的运行速度但是需要更多的内存,方法被第一次调用时,字节码编译生成的本地代码将会被缓存,这样在该方法下次被调用的时候,将取出缓冲的本地代码直接运行 
  (3).自适应优化,对于经常被调用的方法,则会缓存其编译产生成的本地代码,对于其他较少被调用的代码,仍对其采用解释执行的方法。 
  (4).片上虚拟机,即虚拟机的执行引擎直接嵌入在片上

HotSpot虚拟机可以配置为以下运行模式: 
    -Xint:解释模式 
    -Xcomp:编译模式 
    -Xmixed:混合模式 
(通过java -version就可以查看虚拟机的运行模式)

HotSpot在启动时,会为所有字节码创建在目标平台上运行的解释运行的机器码,并存放在CodeCache中,在解释执行字节码的过程中,就会从CodeCache中取出这些本地机器码并执行。

Hotspot虚拟机的细节技术实现值得借鉴,如果你觉得源码甚至汇编代码比较枯燥的话,也可以大致了解相关模块的组件、工作流程,对相关实现有一定的认识。

下面就从模板解释器的初始化开始,分析HotSpot的解释代码的生成。
  在创建虚拟机时,在初始化全局模块过程中,会调用interpreter_init()初始化模板解释器,模板解释器的初始化包括抽象解释器AbstractInterpreter的初始化、模板表TemplateTable的初始化、CodeCache的Stub队列StubQueue的初始化、解释器生成器InterpreterGenerator的初始化

 1 void TemplateInterpreter::initialize() {
 2   if (_code != NULL) return;
 3   // assertions
 4   //...
 5 
 6   AbstractInterpreter::initialize();
 7 
 8   TemplateTable::initialize();
 9 
10   // generate interpreter
11   { ResourceMark rm;
12     TraceTime timer("Interpreter generation", TraceStartupTime);
13     int code_size = InterpreterCodeSize;
14     NOT_PRODUCT(code_size *= 4;)  // debug uses extra interpreter code space
15     _code = new StubQueue(new InterpreterCodeletInterface, code_size, NULL,
16                           "Interpreter");
17     InterpreterGenerator g(_code);
18     if (PrintInterpreter) print();
19   }
20 
21   // initialize dispatch table
22   _active_table = _normal_table;
23 }

1.AbstractInterpreter是基于汇编模型的解释器的共同基类,定义了解释器和解释器生成器的抽象接口。 
2.模板表TemplateTable保存了各个字节码的模板(目标代码生成函数和参数)。 
TemplateTable的初始化调用def()将所有字节码的目标代码生成函数和参数保存在_template_table或_template_table_wide(wide指令)模板数组中

  //                              interpr. templates// Java spec bytecodes          ubcp|disp|clvm|iswd  in    out   generator      argumentdef(Bytecodes::_nop           , ____|____|____|____, vtos, vtos, nop           ,  _      );def(Bytecodes::_aconst_null   , ____|____|____|____, vtos, atos, aconst_null   ,  _      );def(Bytecodes::_iconst_m1     , ____|____|____|____, vtos, itos, iconst        , -1      );def(Bytecodes::_iconst_0      , ____|____|____|____, vtos, itos, iconst        ,  0      );def(Bytecodes::_iconst_1      , ____|____|____|____, vtos, itos, iconst        ,  1      );def(Bytecodes::_iconst_2      , ____|____|____|____, vtos, itos, iconst        ,  2      );
//...其他字节码的模板定义

其中,def()是查看数组对应项是否为空,若为空则初始化该数组项。

Template* t = is_wide ? template_for_wide(code) : template_for(code);// setup entryt->initialize(flags, in, out, gen, arg);

_template_table或_template_table_wide的数组项就是Template对象,即字节码的模板,Template的结构如下:

class Template VALUE_OBJ_CLASS_SPEC {private:enum Flags {uses_bcp_bit,                                // set if template needs the bcp pointing to bytecodedoes_dispatch_bit,                           // set if template dispatches on its owncalls_vm_bit,                                // set if template calls the vmwide_bit                                     // set if template belongs to a wide instruction
  };typedef void (*generator)(int arg);int       _flags;                  // describes interpreter template properties (bcp unknown)TosState  _tos_in;                 // tos cache state before template executionTosState  _tos_out;                // tos cache state after  template executiongenerator _gen;                    // template code generatorint       _arg;   

_flags为标志,该项的低四位分别标志:

  • uses_bcp_bit,标志需要使用字节码指针(byte code pointer,数值为字节码基址+字节码偏移量)
  • does_dispatch_bit,标志是否在模板范围内进行转发,如跳转类指令会设置该位
  • calls_vm_bit,标志是否需要调用JVM函数
  • wide_bit,标志是否是wide指令(使用附加字节扩展全局变量索引)

_tos_in表示模板执行前的TosState(操作数栈栈顶元素的数据类型,TopOfStack,用来检查模板所声明的输出输入类型是否和该函数一致,以确保栈顶元素被正确使用) 
 _tos_out表示模板执行后的TosState 
 _gen表示模板生成器(函数指针) 
 _arg表示模板生成器参数

3.StubQueue是用来保存生成的本地代码的Stub队列,队列每一个元素对应一个InterpreterCodelet对象,InterpreterCodelet对象继承自抽象基类Stub,包含了字节码对应的本地代码以及一些调试和输出信息。 
其内存结构如下:在对齐至CodeEntryAlignment后,紧接着InterpreterCodelet的就是生成的目标代码。

4.InterpreterGenerator根据虚拟机使用的解释器模型不同分为别CppInterpreterGenerator和TemplateInterpreterGenerator 
根据不同平台的实现,以x86_64平台为例,TemplateInterpreterGenerator定义在/hotspot/src/cpu/x86/vm/templateInterpreter_x86_64.cpp

1 InterpreterGenerator::InterpreterGenerator(StubQueue* code)
2   : TemplateInterpreterGenerator(code) {
3    generate_all(); // down here so it can be "virtual"
4 }

(1).在TemplateInterpreterGenerator的generate_all()中,将生成一系列JVM运行过程中所执行的一些公共代码和所有字节码的InterpreterCodelet

  • error exits:出错退出处理入口
  • 字节码追踪入口(配置了-XX:+TraceBytecodes)
  • 函数返回入口
  • JVMTI的EarlyReturn入口
  • 逆优化调用返回入口
  • native调用返回值处理handlers入口
  • continuation入口
  • safepoint入口
  • 异常处理入口
  • 抛出异常入口
  • 方法入口(native方法和非native方法)
  • 字节码入口

(2).其中,set_entry_points_for_all_bytes()会对所有被定义的字节码生成目标代码并设置对应的入口(这里只考虑is_defined的情况)

 1 void TemplateInterpreterGenerator::set_entry_points_for_all_bytes() {
 2   for (int i = 0; i < DispatchTable::length; i++) {
 3     Bytecodes::Code code = (Bytecodes::Code)i;
 4     if (Bytecodes::is_defined(code)) {
 5       set_entry_points(code);
 6     } else {
 7       //未被实现的字节码(操作码)
 8       set_unimplemented(i);
 9     }
10   }
11 }

(3).set_entry_points()将取出该字节码对应的Template模板,并调用set_short_enrty_points()进行处理,并将入口地址保存在转发表(DispatchTable)_normal_table或_wentry_table(使用wide指令)中

 1 void TemplateInterpreterGenerator::set_entry_points(Bytecodes::Code code) {
 2   CodeletMark cm(_masm, Bytecodes::name(code), code);
 3   // initialize entry points
 4   // ... asserts
 5   address bep = _illegal_bytecode_sequence;
 6   address cep = _illegal_bytecode_sequence;
 7   address sep = _illegal_bytecode_sequence;
 8   address aep = _illegal_bytecode_sequence;
 9   address iep = _illegal_bytecode_sequence;
10   address lep = _illegal_bytecode_sequence;
11   address fep = _illegal_bytecode_sequence;
12   address dep = _illegal_bytecode_sequence;
13   address vep = _unimplemented_bytecode;
14   address wep = _unimplemented_bytecode;
15   // code for short & wide version of bytecode
16   if (Bytecodes::is_defined(code)) {
17     Template* t = TemplateTable::template_for(code);
18     assert(t->is_valid(), "just checking");
19     set_short_entry_points(t, bep, cep, sep, aep, iep, lep, fep, dep, vep);
20   }
21   if (Bytecodes::wide_is_defined(code)) {
22     Template* t = TemplateTable::template_for_wide(code);
23     assert(t->is_valid(), "just checking");
24     set_wide_entry_point(t, wep);
25   }
26   // set entry points
27   EntryPoint entry(bep, cep, sep, aep, iep, lep, fep, dep, vep);
28   Interpreter::_normal_table.set_entry(code, entry);
29   Interpreter::_wentry_point[code] = wep;
30 }

这里以非wide指令为例分析set_short_entry_points()。bep(byte entry point), cep, sep, aep, iep, lep, fep, dep, vep分别为指令执行前栈顶元素状态为byte/boolean、char、short、array/reference(对象引用)、int、long、float、double、void类型时的入口地址。

(4).set_short_entry_points()根据操作数栈栈顶元素类型进行判断,首先byte类型、char类型和short类型都应被当做int类型进行处理,对于非void类型将调用generate_and_dispatch()产生目标代码,这里以iconst_0为例对TOS的处理进行介绍: 
对于iconst,其期望的_tos_in(执行前栈顶元素类型)是void类型(vtos),期望的_tos_out(执行后栈顶元素类型)是int类型(itos)

 1 void TemplateInterpreterGenerator::set_short_entry_points(Template* t, address& bep, address& cep, address& sep, address& aep, address& iep, address& lep, address& fep, address& dep, address& vep) {
 2   assert(t->is_valid(), "template must exist");
 3   switch (t->tos_in()) {
 4     case btos:
 5     case ctos:
 6     case stos:
 7       ShouldNotReachHere();  // btos/ctos/stos should use itos.
 8       break;
 9     case atos: vep = __ pc(); __ pop(atos); aep = __ pc(); generate_and_dispatch(t); break;
10     case itos: vep = __ pc(); __ pop(itos); iep = __ pc(); generate_and_dispatch(t); break;
11     case ltos: vep = __ pc(); __ pop(ltos); lep = __ pc(); generate_and_dispatch(t); break;
12     case ftos: vep = __ pc(); __ pop(ftos); fep = __ pc(); generate_and_dispatch(t); break;
13     case dtos: vep = __ pc(); __ pop(dtos); dep = __ pc(); generate_and_dispatch(t); break;
14     case vtos: set_vtos_entry_points(t, bep, cep, sep, aep, iep, lep, fep, dep, vep);     break;
15     default  : ShouldNotReachHere();                                                 break;
16   }
17 }

其中__定义如下:

# define __ _masm->

即模板解释器的宏汇编器

(5).以期望的栈顶状态为vtos状态为例,分析set_vtos_entry_points()

 1 void TemplateInterpreterGenerator::set_vtos_entry_points(Template* t,
 2                                                          address& bep,
 3                                                          address& cep,
 4                                                          address& sep,
 5                                                          address& aep,
 6                                                          address& iep,
 7                                                          address& lep,
 8                                                          address& fep,
 9                                                          address& dep,
10                                                          address& vep) {
11   assert(t->is_valid() && t->tos_in() == vtos, "illegal template");
12   Label L;
13   aep = __ pc();  __ push_ptr();  __ jmp(L);
14   fep = __ pc();  __ push_f();    __ jmp(L);
15   dep = __ pc();  __ push_d();    __ jmp(L);
16   lep = __ pc();  __ push_l();    __ jmp(L);
17   bep = cep = sep =
18   iep = __ pc();  __ push_i();
19   vep = __ pc();
20   __ bind(L);
21   generate_and_dispatch(t);
22 }

以ftos入口类型为例(vtos即当前字节码的实现不关心栈顶元素的状态),分析该入口的处理指令: 
push_f(): 
  定义在 /hotspot/src/cpu/x86/vm/interp_masm_x86_64.cpp中

1 void InterpreterMacroAssembler::push_f(XMMRegister r) {
2   subptr(rsp, wordSize);
3   movflt(Address(rsp, 0), r);
4 }

其中r的默认值为xmm0,wordSize为机器字长(如64位机器为8字节)

subptr()实际上调用了subq():

1 void MacroAssembler::subptr(Register dst, int32_t imm32) {
2   LP64_ONLY(subq(dst, imm32)) NOT_LP64(subl(dst, imm32));
3 }

subq()的实现如下:

1 void Assembler::subq(Register dst, int32_t imm32) {
2   (void) prefixq_and_encode(dst->encoding());
3   emit_arith(0x81, 0xE8, dst, imm32);
4 }

而emit_arith()将调用emit_byte()/emit_long()写入指令的二进制代码”83 EC 08”(由于8可由8位有符号数表示,第一个字节为0x81 | 0x02,即0x83,rsp的寄存器号为4,第二个字节为0xE8 | 0x04,即0xEC,第三个字节为0x08 & 0xFF,即0x08),该指令即AT&T风格的sub $0x8,%rsp

 1 void Assembler::emit_arith(int op1, int op2, Register dst, int32_t imm32) {
 2   assert(isByte(op1) && isByte(op2), "wrong opcode");
 3   assert((op1 & 0x01) == 1, "should be 32bit operation");
 4   assert((op1 & 0x02) == 0, "sign-extension bit should not be set");
 5   if (is8bit(imm32)) { //iconst_0的操作数为0,即可以用8位二进制数表示
 6     emit_byte(op1 | 0x02); // set sign bit
 7     emit_byte(op2 | encode(dst));
 8     emit_byte(imm32 & 0xFF);
 9   } else {
10     emit_byte(op1);
11     emit_byte(op2 | encode(dst));
12     emit_long(imm32);
13   }
14 }

emit_byte()定义在/hotspot/src/share/vm/asm/assembler.inlilne.hpp中: 
该函数将把该字节复制到_code_pos处

1 inline void AbstractAssembler::emit_byte(int x) {
2   assert(isByte(x), "not a byte");
3   *(unsigned char*)_code_pos = (unsigned char)x;
4   _code_pos += sizeof(unsigned char);
5   sync();
6 }

故subq()向代码缓冲写入了指令sub $0x8,%rsp 
类似地,movflt()向代码缓冲写入了指令 movss %xmm0,(%rsp) 
jmp()向代码缓冲写入了指令jmpq (addr为字节码的本地代码入口)

set_vtos_entry_points()产生的入口部分代码如下:

 1 push %rax        .....(atos entry)
 2 jmpq <addr> 
 3 sub $0x8,%rsp     .....(ftos entry)
 4 movss %xmm0,(%rsp)
 5 jmpq <addr>(addr为字节码的本地代码入口)
 6 sub $0x10,%rsp    .....(dtos entry)
 7 movsd %xmm0,(%rsp)
 8 jmpq <addr>
 9 sub $0x10,%rsp     .....(ltos entry)
10 mov %rax,(%rsp)
11 jmpq <addr>
12 push %rax         ...(itos entry)

set_vtos_entry_points()的最后调用generate_and_dispatch()写入了当前字节码的解释代码和跳转到下一个字节码继续执行的逻辑处理部分

generate_and_dispatch()主要内容如下:

 1 void TemplateInterpreterGenerator::generate_and_dispatch(Template* t, TosState tos_out) {
 2   // ...
 3   // generate template
 4   t->generate(_masm);
 5   // advance
 6   if (t->does_dispatch()) {
 7     //asserts
 8   } else {
 9     // dispatch to next bytecode
10     __ dispatch_epilog(tos_out, step);
11   }
12 }

这里我们以iconst()为目标代码生成器为例,分析generate():

1 void Template::generate(InterpreterMacroAssembler* masm) {
2   // parameter passing
3   TemplateTable::_desc = this;
4   TemplateTable::_masm = masm;
5   // code generation
6   _gen(_arg);
7   masm->flush();
8 }

generate()会调用生成器函数_gen(_arg),该函数根据平台而不同,如x86_64平台下,定义在/hotspot/src/cpu/x86/vm/templateTable_x86_64.cpp中

1 void TemplateTable::iconst(int value) {
2   transition(vtos, itos);
3   if (value == 0) {
4     __ xorl(rax, rax);
5   } else {
6     __ movl(rax, value);
7   }
8 }

我们知道,iconst_i指令是将i压入栈,这里生成器函数iconst()在i为0时,没有直接将0写入rax,而是使用异或运算清零,即向代码缓冲区写入指令”xor %rax, %rax”;在i不为0时,写入指令”mov $0xi, %rax”

当不需要转发时,会调用dispatch_epilog()生成取下一条指令和分派的目标代码:

1 void InterpreterMacroAssembler::dispatch_epilog(TosState state, int step) {
2   dispatch_next(state, step);
3 }

dispatch_next()实现如下:

1 void InterpreterMacroAssembler::dispatch_next(TosState state, int step) {
2   // load next bytecode (load before advancing r13 to prevent AGI)
3   load_unsigned_byte(rbx, Address(r13, step));
4   // advance r13
5   increment(r13, step);
6   dispatch_base(state, Interpreter::dispatch_table(state));
7 }

dispatch_next()首先调用load_unsigned_byte()写入指令”movzbl (%r13),%rbx”,再调用increment()写入指令”inc/add (,)%r13”指令,最后调用dispatch_base()写入”jmp *(%r10,%rbx,8)”。这类似于PC自增一条指令的宽度再继续取值运行的过程。

分析到这里,不禁有一个疑问,_code_pos是哪里?之前说过,StubQueue是用来保存生成的本地代码的Stub队列,队列每一个元素对应一个InterpreterCodelet对象,InterpreterCodelet对象包含了字节码对应的本地代码以及一些调试和输出信息。那么_code_pos是如何和InterpreterCodelet对应的呢?

我们注意到无论是为JVM的各种入口函数,还是为字节码生成本地代码,都会构造一个CodeletMark对象

CodeletMark cm(_masm, Bytecodes::name(code), code);

CodeletMark的构造函数如下:在初始值列表中,调用了StubQueue的request()创建了一个InterpreterCodelet对象,并以该InterpreterCodelet目标代码地址和大小为参数构造了一块CodeBuffer用来存放生成的目标代码。

public:CodeletMark(InterpreterMacroAssembler*& masm,const char* description,Bytecodes::Code bytecode = Bytecodes::_illegal):_clet((InterpreterCodelet*)AbstractInterpreter::code()->request(codelet_size())),_cb(_clet->code_begin(), _clet->code_size()){ // request all space (add some slack for Codelet data)assert (_clet != NULL, "we checked not enough space already");// initialize Codelet attributes_clet->initialize(description, bytecode);// create assembler for code generationmasm  = new InterpreterMacroAssembler(&_cb);_masm = &masm;}

但在此时还未生成目标代码,所以并不知道生成的目标代码有多大,所以这里会向StubQueue申请全部的空闲空间(只留有一点用来对齐空间,注意StubQueue实际上是一片连续的内存空间,所有Stub都在该空间上进行分配) 
随后初始化该InterpreterCodelet的描述部分和对应字节码,并以该CodeBuffer为参数构造了一个编译器对象InterpreterMacroAssembler

分析到这里,就应该明白编译器的_code_pos指的就是生成代码在CodeBuffer中的当前写位值 
还需一提的就是CodeletMark的析构函数,这里确认编译器的生产代码完全写入到CodeBuffer中后,就会调用StubQueue的commit()将占用的空间划分为当前Stub(InterpreterCodelet)所有

~CodeletMark() {// align so printing shows nop's instead of random code at the end (Codelets are aligned)(*_masm)->align(wordSize);// make sure all code is in code buffer(*_masm)->flush();// commit CodeletAbstractInterpreter::code()->commit((*_masm)->code()->pure_insts_size());// make sure nobody can use _masm outside a CodeletMark lifespan*_masm = NULL;}

转载于:https://www.cnblogs.com/iceAeterNa/p/4876924.html

相关文章:

flex--unable to transcode image

很低级的解决啊&#xff0c;把那个.png文件用画图打开后&#xff0c;重新用png格式保存了下就好咧。。。 转载于:https://www.cnblogs.com/avenxia/archive/2012/04/24/2468334.html

svn服务器发生变更,如何切换

参考链接&#xff1a; https://blog.csdn.net/jk110333/article/details/9301283 https://blog.csdn.net/emtit2008/article/details/51498012 svn switch --relocate svn://旧IP地址/项目 svn://新IP地址/项目 如果是windows客户端,直接在工作副本上右键&#xff0c;选择Torto…

泛型java博客园,Java深度历险之Java泛型

Java泛型(generics)是JDK 5中引入的一个新特性&#xff0c;允许在定义类和接口的时候使用类型参数(type parameter)。声明的类型参数在使用时用具体的类型来替换。泛型最主要的应用是在JDK 5中的新集合类框架中。对于泛型概念的引入&#xff0c;开发社区的观点是褒贬不一。从好…

EXCEL-XML 代码相对行列转换绝对

这段时间因为工作的要求&#xff0c;需要在程序中处理xml 格式的excel 文档。但是在期间遇到了一个问题就是Row 和 Cell 的index 属性相对绝对的问题。 之前在bing 找了一下没找到介绍相对转换成绝对的资料。在经过多次的尝试和对比在空白的文档中不同的cell 中输入值后的XML文…

Integer 和 int 比较的特殊之处

2019独角兽企业重金招聘Python工程师标准>>> 第一个例子&#xff1a; 假设我们同时定义 int a 3; int b 3; 编译器先处理int a 3&#xff1b; 首先它会在栈中创建一个变量为a的引用&#xff0c;然后查找栈中是否有3这个值&#xff0c;如果没找到&#xff0c;就…

偷偷mark下一个

java书单thinking in java java战 Effective Java 深入了解JVM虚拟机 java性能优化权威指南 JSR133 Google Guava官方教程版权声明&#xff1a;本文博主原创文章&#xff0c;博客&#xff0c;未经同意不得转载。 转载于:https://www.cnblogs.com/hrhguanli/p/4915607.html

unix odbc php 连接sqlserver,Ubuntu下通过unixODBC连接MS SqlServer2005

一、下载相关软件 unixODBC、freetds(1) Linux系统的ODBC unixODBC-2.2.8.tar.gz (http://www.unixodbc.org)(2) 连接SQLServer或Sybase的驱动 freetds-0.62.4.tar.gz (http://www.freetds.org)二、安装和配置(1) 安装unixODBC# tar vxzf unixODBC-2.2.8.tar.gz# cd un…

Apache htpasswd命令

一、简介 htpasswd是apache的一个工具&#xff0c;该工具主要用于建立和更新存储用户名、密码的文本文件&#xff0c;主要用于对基于http用户的认证。 二、语法 Usage:htpasswd [-cimBdpsDv] [-C cost] passwordfile usernamehtpasswd -b[cmBdpsDv] [-C cost] passwordfile use…

l2-22(重排链表)

题目链接&#xff1a;https://pintia.cn/problem-sets/994805046380707840/problems/994805057860517888 题意&#xff1a;给定链表L1->L2->...->Ln&#xff0c;要求按Ln->L1->Ln-1->L2->...的格式输出。 思路&#xff1a;水模拟&#xff0c;按照要求做就…

深入研究敏捷的成功因素

Scott W. Ambler针对Dr. Dobbs网站上发布的敏捷成功因素进行了一些分析讨论&#xff0c;那些敏捷成功因素来自2011年11月的敏捷现状调查结果。据此文所说&#xff0c;此次调查的两个目标是&#xff1a; 为了探索与敏捷项目成功有关的实施策略&#xff0c;例如对单一敏捷团队的行…

php 多条数据更新数据类型,PHPdoc @param中的两个或多个数据类型

好的,我有这个phpdoc上面的我的类方法/*** this a function that translates the text* param string|boolean $lang if string the string given in the parameter will be the language code that will represent the language desired, if true, this will translate based …

快速构建Spring Cloud工程

spring cloud简介 spring cloud为开发人员提供了快速构建分布式系统的一些工具&#xff0c;包括配置管理、服务发现、断路器、路由、微代理、事件总线、全局锁、决策竞选、分布式会话等等。它运行环境简单&#xff0c;可以在开发人员的电脑上跑。另外说明spring cloud是基于spr…

win10红色警戒黑屏解决

相信很多80&#xff0c;90后的同学们都喜欢在代码之余打打红色警戒 但是苦于win10差劲兼容性&#xff0c;每次下个红色警戒or尤里复仇不是弹框就是黑屏 今天笔者给出一个完美究极解决方案 请注意&#xff1a; 1&#xff01;红色警戒中把Ra2.exe和Game.exe右键兼容性调到Win XP …

html超链接button

1.如果让本页转向新的页面则用&#xff1a; <input typebutton οnclick"window.location.href(连接)"> 2.如果需要打开一个新的页面进行转向&#xff0c;则用&#xff1a; <input typebutton οnclick"window.open(连接)">转载于:https://www…

低版本jdbc连接高版本oracle,转:oracle11g的JDBC连接 URL和之前的版本有一定的区别...

今天安装了oracle11g后&#xff0c;写了JDBC测试程序&#xff0c;一直都连接不上&#xff01;一直找不到原因后来读了一下安装文件中的Readme.txt文档&#xff0c;汗啊&#xff01;这个版本居然把url的访问方式改变了&#xff1a;Some Useful Hints In Using the JDBC Drivers-…

Android studio 获取每次编译apk时的日期

项目中需要获取apk的编译日期&#xff0c;首先肯定是用手动的方式获取&#xff0c;但这样容易遗忘&#xff0c;怎么样通过代码的方式获取呢&#xff1f; 其实android 为我们提供了一个BuildConfig的类&#xff0c;android 每次编译的时候都会自动生成 一次BuildConfig 类&#…

明文存密码成惯例?Facebook 6 亿用户密码可被 2 万员工直接看

近日&#xff0c;外媒发布了一份互联网安全的调研报告&#xff0c;报告中称Facebook曾将6亿用户的账号密码使用明文存储&#xff0c;且可以被Facebook内部员工随意搜索查看。据Facebook方面的消息人士称&#xff0c;纯文本存档的用户密码可追溯到2012年&#xff0c;在这期间有超…

pthreads 的学习

多线程学习参考的网站&#xff1a; http://www.ibm.com/developerworks/cn/linux/l-pthred/ 初探线程——pthread_create http://www.cnblogs.com/huangwei/archive/2010/05/19/1739593.html 转载于:https://www.cnblogs.com/nemo2011/archive/2012/05/02/2479163.html

Oracle不加IP无法登录,Oracle 无法通过IP连接问题

1.安装目录:D:\app\Administrator\product\11.2.0\dbhome_1\NETWORK\ADMIN2.listener.ora(里面的localhost或127.0.0.1改成机器名)# listener.ora Network Configuration File: D:\app\Administrator\product\11.2.0\dbhome_1\NETWORK\ADMIN\listener.ora# Generated by Oracle…

巧用gh-pages分支发布自己的静态项目

大家都知道可以通过github pages 发布自己的静态博客&#xff0c;然后通过 username.github.io 可以访问。例如我的博客可以通过 nqmysb.github.io 访问&#xff0c;不过我的已经绑定域名 https://liaocan.top &#xff0c;所以会直接跳转到域名显示。但是我们通常有很多其他的…

【读书笔记】iOS-网络-解析响应负载

Web Service可以通过多种格式返回结构化数据&#xff0c; 不过大多数时候使用的是XML与JSON。也可以让应用只接收HTML结构的数据。实现了这些Web Service或是接收HTML文档的应用必须能解释这种结构化数据并将其转换为对于应用上下文有意义的对象。 一&#xff0c;XML 使用原生解…

What Are Words(一诺千金)

曲名&#xff1a;What Are Words&#xff08;一诺千金&#xff09;Anywhere you are, I am near Anywhere you go, Ill be there Anytime you whisper my name, youll see How every single promise I keep Cause what kind of guy would I be If I was to leave when you need…

oracle 插入 基准测试,oracle proc 插入操作性能优化实践

场景&#xff1a;student 表中 10万条数据。从 student 表中取出所有数据&#xff0c;插入到 student_his 表中优化方法&#xff1a;1.批量插入(效果明显)2.批量查询(效果不明显)3.批量提交(效果不明显)4.预编译 sql 语句(效果不明显)效果&#xff1a;10万条数据&#xff0c;普…

240个jquery插件

240个jquery插件 http://www.kollermedia.at/archive/2007/11/21/the-ultimate-jquery-plugin-list/File upload Ajax File Upload.jQUploader.Multiple File Upload plugin.jQuery File Style.Styling an input type file.Progress Bar Plugin. Form Validation jQuery Valida…

sql 优化 tips

索引就是排序 outer join笛卡儿积, inner join看情况。 可以用临时表加update的方式把outer join 替换成inner join提高性能。用union代替where中的or 和join(不同表时)join的列有索引&#xff0c;select 中的列能被索引覆盖到&#xff0c;消除执行计划中的lookup(lookup有时会…

第24课 《前端之路,以不变应万变》

今天的内容有些借鉴于业内大佬的内容&#xff0c;由于本人技术实在太渣&#xff0c;几乎没有可以用来演讲的素材。抱歉 大家好&#xff0c;我是来自存勖科技的Rocken。我今天演讲的内容是&#xff1a;前端的未来。大家都知道&#xff0c;前端所依托的基础直到上世纪九十年代才出…

php hasattribute,PHP DOMElement hasAttribute()用法及代码示例

DOMElement::hasAttribute()函数是PHP中的内置函数&#xff0c;用于了解具有特定名称的属性是否作为元素的成员存在。用法:bool DOMElement::hasAttribute( string $name )参数&#xff1a;该函数接受单个参数$name&#xff0c;该参数保存属性的名称。返回值&#xff1a;如果成…

搭建turnserver

参考文件&#xff1a; http://blog.csdn.net/kl222/article/details/20145423 为什么要搭建TURN服务器&#xff1f; 因为我们编写的sip客户端再和南瑞的sip服务器进行通信的时候&#xff0c;中间经过一个安全平台&#xff0c;这个安全平台具有NAT和防火墙功能。RTP和RTCP包传递…

【Android开发】:在任意目录执行NDK编译

2019独角兽企业重金招聘Python工程师标准>>> 文以简单的例子讲述如何在任意目录把自己写的C代码使用NDK提供的交叉编译该工具来编译成Android可使用的静态库/动态库。 1. 准备环境 首先&#xff0c;你得安装了Android的NDK编译工具&#xff0c;假设你的NDK的根目录在…

SurfaceView 间取得焦点

在SurfaceView中我们的onKeyDown虽然重写了view的函数&#xff0c; 但一定需要我们在初始化的时候去声明焦点 //添加这个来取得按健事件this.setFocusable(true);this.setFocusableInTouchMode(true);this.requestFocus();如果这些方法&#xff0c;会造成按键无效&#xff0c;提…