深入解析JVM字节码解释器执行流程(OpenJDK 17源码实现)
一、核心流程概述
JVM解释器的核心任务是将Java字节码逐条翻译为本地机器指令并执行。其执行流程可分为以下关键阶段:
-
方法调用入口构建:生成栈帧、处理参数、同步锁等。
-
字节码分派(Dispatch):根据字节码跳转到对应处理逻辑。
-
操作数栈与局部变量管理:维护方法执行上下文。
-
方法返回与栈帧销毁:处理返回值、恢复调用者栈帧。
以下结合OpenJDK 17源码详细分析各环节实现。
二、方法调用入口构建
1. generate_call_stub
:调用桩(Call Stub)生成
这是从本地代码(如JNI)调用Java方法的核心入口,负责设置调用环境:
address generate_call_stub(address& return_address) {// 保存调用者寄存器状态(rbx, r12-r15等)__ enter();__ subptr(rsp, -rsp_after_call_off * wordSize);// 处理参数传递(Windows与Linux差异)__ movptr(parameters, c_rarg5); // 参数指针__ movptr(entry_point, c_rarg4); // 方法入口地址// 保存线程上下文到栈帧__ movptr(rbx_save, rbx);__ movptr(r12_save, r12);// ... 其他寄存器保存// 加载线程对象并检查异常__ movptr(r15_thread, thread);__ reinit_heapbase(); // 重置堆基址// 压入参数到栈(通过循环逐个压入)Label loop;__ movptr(c_rarg2, parameters);__ movl(c_rarg1, c_rarg3); // 参数计数器__ BIND(loop);__ movptr(rax, Address(c_rarg2, 0));__ push(rax); // 参数入栈__ jcc(Assembler::notZero, loop);// 调用Java方法(entry_point为方法入口)__ call(c_rarg1); // c_rarg1=entry_pointreturn_address = __ pc();// 处理返回值(根据类型存储到指定地址)__ movptr(c_rarg0, result);Label is_long, is_float, exit;__ cmpl(c_rarg1, T_OBJECT);__ jcc(Assembler::equal, is_long);// ... 其他类型处理
关键点:
-
寄存器保存与恢复:通过
movptr
保存调用者寄存器,调用结束后恢复。 -
参数传递:根据ABI规范处理不同平台(如Windows使用
rcx, rdx, r8, r9
传递前4参数)。 -
异常检查:通过
cmpptr
验证线程是否有未处理异常。
三、字节码分派机制
1. 分发表(Dispatch Table)
每个字节码对应一个处理函数地址,按栈顶状态(TosState
)分类:
void InterpreterMacroAssembler::dispatch_base(TosState state, address* table) {// 验证栈帧完整性(调试模式)if (VerifyActivationFrameSize) { /* ... */ }// 安全点轮询:检查是否需要进入安全点if (generate_poll) {testb(Address(r15_thread, JavaThread::polling_word_offset()), poll_bit());jccb(Assembler::zero, no_safepoint);lea(rscratch1, ExternalAddress(safepoint_table));}// 跳转到分发表项lea(rscratch1, ExternalAddress(table));jmp(Address(rscratch1, rbx, Address::times_8)); // rbx=字节码值 }
分发表结构:
// 每个TosState对应一个分发表 address* Interpreter::dispatch_table(TosState state) {switch(state) {case vtos: return _active_table[vtos];case itos: return _active_table[itos];// ... 其他状态} }
2. dispatch_next
:逐条分派字节码
void InterpreterMacroAssembler::dispatch_next(TosState state, int step) {load_unsigned_byte(rbx, Address(_bcp_register, step)); // 加载下一字节码increment(_bcp_register, step); // 移动字节码指针dispatch_base(state, Interpreter::dispatch_table(state)); // 分派 }
核心逻辑:
-
加载字节码:从
_bcp_register
(字节码指针)读取下一条指令。 -
指针前进:
increment
调整指针位置。 -
分派跳转:通过
dispatch_base
跳转到对应处理函数。
四、方法调用与虚方法分派
1. invokevirtual
实现
void TemplateTable::invokevirtual(int byte_no) {prepare_invoke(byte_no, rbx, noreg, rcx, rdx); // 准备调用参数invokevirtual_helper(rbx, rcx, rdx); // 实际分派逻辑 }void invokevirtual_helper(Register index, Register recv, Register flags) {// 检查是否为final方法__ testl(rax, (1 << ConstantPoolCacheEntry::is_vfinal_shift));__ jcc(Assembler::zero, notFinal);// Final方法直接调用(无虚表查找)__ jump_from_interpreted(method, rax);__ bind(notFinal);// 虚方法查找:通过接收者klass查找vtable__ load_klass(rax, recv); // 加载接收者类__ lookup_virtual_method(rax, index, method);// 查找虚方法__ jump_from_interpreted(method, rdx); // 跳转执行 }
虚方法查找逻辑:
void MacroAssembler::lookup_virtual_method(Register klass, Register index, Register method) {// 计算vtable条目地址Address vtable_entry_addr(klass, index, Address::times_ptr, base + vtableEntry::method_offset_in_bytes());movptr(method, vtable_entry_addr); // 加载Method* }
2. 同步方法处理
在方法入口处理同步锁:
address TemplateInterpreterGenerator::generate_normal_entry(bool synchronized) {if (synchronized) {lock_method(); // 生成monitorenter指令}// ... 后续执行逻辑 }void lock_method() {__ movptr(rax, Address(rbx, Method::const_offset()));__ movl(rax, Address(rax, ConstMethod::access_flags_offset()));__ testl(rax, JVM_ACC_SYNCHRONIZED);__ jcc(Assembler::zero, done);// 生成锁获取代码(monitorenter) }
五、栈帧构建与上下文管理
1. 栈帧初始化
在generate_normal_entry
中构建解释器栈帧:
// 分配局部变量空间 __ load_unsigned_short(rdx, size_of_locals); __ subl(rdx, rcx); // 计算额外局部变量数 Label loop; __ bind(loop); __ push((int)NULL_WORD); // 初始化局部变量为NULL __ decrementl(rdx); __ jcc(Assembler::greater, loop);// 构建固定帧头(包含返回地址、方法指针等) generate_fixed_frame(false);
固定帧结构:
| Return Address | | Method* | | Constant Pool | | Local Variables | | Operand Stack | | Monitor Block |
2. 调试支持(新增的自定义逻辑)
在方法入口插入调试信息打印(用户自定义代码):
// 保存寄存器状态 __ push(rax); __ push(rcx); // ... 保存其他寄存器// 调用调试函数打印方法名 __ movptr(rdi, rbx); // Method*作为参数 __ call(CAST_FROM_FN_PTR(address, print_debug_info));// 恢复寄存器 __ pop(r15); __ pop(r11); // ... 恢复其他寄存器
作用:通过os::write
系统调用输出调试信息(如打印"yym"标识),便于跟踪方法调用链。
六、性能优化与安全机制
1. 调用计数器与JIT编译
Label invocation_counter_overflow; generate_counter_incr(&invocation_counter_overflow); // 递增计数器// 计数器溢出触发编译 __ bind(invocation_counter_overflow); generate_counter_overflow(continue_after_compile);
逻辑:当方法调用次数超过阈值,调用InterpreterRuntime::frequency_counter_overflow
触发JIT编译。
2. 安全点轮询
在分派前插入安全点检查:
testb(Address(r15_thread, JavaThread::polling_word_offset()), poll_bit()); jccb(Assembler::zero, no_safepoint); lea(rscratch1, ExternalAddress(safepoint_table)); // 跳转到安全点处理
作用:确保在GC或Deoptimization时,线程能及时响应。
七、总结
OpenJDK解释器的执行流程通过高度优化的汇编代码实现,核心特点包括:
-
高效分派:通过分发表实现字节码快速跳转。
-
栈帧精细管理:严格处理局部变量、操作数栈和监视器锁。
-
与JIT协作:通过调用计数器触发编译优化。
-
安全机制:集成安全点、异常检查等关键功能。
新增的调试代码(如"yym"打印)展示了如何在解释器关键路径插入定制逻辑,为开发者提供灵活的调试能力。
##源码
address generate_call_stub(address& return_address) {assert((int)frame::entry_frame_after_call_words == -(int)rsp_after_call_off + 1 &&(int)frame::entry_frame_call_wrapper_offset == (int)call_wrapper_off,"adjust this code");StubCodeMark mark(this, "StubRoutines", "call_stub");address start = __ pc();// same as in generate_catch_exception()!const Address rsp_after_call(rbp, rsp_after_call_off * wordSize);const Address call_wrapper (rbp, call_wrapper_off * wordSize);const Address result (rbp, result_off * wordSize);const Address result_type (rbp, result_type_off * wordSize);const Address method (rbp, method_off * wordSize);const Address entry_point (rbp, entry_point_off * wordSize);const Address parameters (rbp, parameters_off * wordSize);const Address parameter_size(rbp, parameter_size_off * wordSize);// same as in generate_catch_exception()!const Address thread (rbp, thread_off * wordSize);const Address r15_save(rbp, r15_off * wordSize);const Address r14_save(rbp, r14_off * wordSize);const Address r13_save(rbp, r13_off * wordSize);const Address r12_save(rbp, r12_off * wordSize);const Address rbx_save(rbp, rbx_off * wordSize);// stub code__ enter();__ subptr(rsp, -rsp_after_call_off * wordSize);// save register parameters
#ifndef _WIN64__ movptr(parameters, c_rarg5); // parameters__ movptr(entry_point, c_rarg4); // entry_point
#endif__ movptr(method, c_rarg3); // method__ movl(result_type, c_rarg2); // result type__ movptr(result, c_rarg1); // result__ movptr(call_wrapper, c_rarg0); // call wrapper// save regs belonging to calling function__ movptr(rbx_save, rbx);__ movptr(r12_save, r12);__ movptr(r13_save, r13);__ movptr(r14_save, r14);__ movptr(r15_save, r15);#ifdef _WIN64int last_reg = 15;if (UseAVX > 2) {last_reg = 31;}if (VM_Version::supports_evex()) {for (int i = xmm_save_first; i <= last_reg; i++) {__ vextractf32x4(xmm_save(i), as_XMMRegister(i), 0);}} else {for (int i = xmm_save_first; i <= last_reg; i++) {__ movdqu(xmm_save(i), as_XMMRegister(i));}}const Address rdi_save(rbp, rdi_off * wordSize);const Address rsi_save(rbp, rsi_off * wordSize);__ movptr(rsi_save, rsi);__ movptr(rdi_save, rdi);
#elseconst Address mxcsr_save(rbp, mxcsr_off * wordSize);{Label skip_ldmx;__ stmxcsr(mxcsr_save);__ movl(rax, mxcsr_save);__ andl(rax, MXCSR_MASK); // Only check control and mask bitsExternalAddress mxcsr_std(StubRoutines::x86::addr_mxcsr_std());__ cmp32(rax, mxcsr_std);__ jcc(Assembler::equal, skip_ldmx);__ ldmxcsr(mxcsr_std);__ bind(skip_ldmx);}
#endif// Load up thread register__ movptr(r15_thread, thread);__ reinit_heapbase();#ifdef ASSERT// make sure we have no pending exceptions{Label L;__ cmpptr(Address(r15_thread, Thread::pending_exception_offset()), (int32_t)NULL_WORD);__ jcc(Assembler::equal, L);__ stop("StubRoutines::call_stub: entered with pending exception");__ bind(L);}
#endif// pass parameters if anyBLOCK_COMMENT("pass parameters if any");Label parameters_done;__ movl(c_rarg3, parameter_size);__ testl(c_rarg3, c_rarg3);__ jcc(Assembler::zero, parameters_done);Label loop;__ movptr(c_rarg2, parameters); // parameter pointer__ movl(c_rarg1, c_rarg3); // parameter counter is in c_rarg1__ BIND(loop);__ movptr(rax, Address(c_rarg2, 0));// get parameter__ addptr(c_rarg2, wordSize); // advance to next parameter__ decrementl(c_rarg1); // decrement counter__ push(rax); // pass parameter__ jcc(Assembler::notZero, loop);// call Java function__ BIND(parameters_done);__ movptr(rbx, method); // get Method*//yym-gaizao// #ifdef DEBUG_PRINT_METHOD_NAME// 打印 "yym" 字符串{__ push(rax); // 保存寄存器__ push(rdi);__ push(rsi);__ push(rdx);__ movl(rax, 1); // syscall number for sys_write (1)__ movl(rdi, 1); // fd = stdout (1)__ lea(rsi, ExternalAddress((address)"yym\n")); // 字符串地址__ movl(rdx, 4); // length = 4 ("yym\n")__ call(RuntimeAddress(CAST_FROM_FN_PTR(address, os::write))); // 调用系统调用__ pop(rdx); // 恢复寄存器__ pop(rsi);__ pop(rdi);__ pop(rax);}// #endif__ movptr(c_rarg1, entry_point); // get entry_point__ mov(r13, rsp); // set sender spBLOCK_COMMENT("call Java function");__ call(c_rarg1);BLOCK_COMMENT("call_stub_return_address:");return_address = __ pc();// store result depending on type (everything that is not// T_OBJECT, T_LONG, T_FLOAT or T_DOUBLE is treated as T_INT)__ movptr(c_rarg0, result);Label is_long, is_float, is_double, exit;__ movl(c_rarg1, result_type);__ cmpl(c_rarg1, T_OBJECT);__ jcc(Assembler::equal, is_long);__ cmpl(c_rarg1, T_LONG);__ jcc(Assembler::equal, is_long);__ cmpl(c_rarg1, T_FLOAT);__ jcc(Assembler::equal, is_float);__ cmpl(c_rarg1, T_DOUBLE);__ jcc(Assembler::equal, is_double);// handle T_INT case__ movl(Address(c_rarg0, 0), rax);__ BIND(exit);// pop parameters__ lea(rsp, rsp_after_call);#ifdef ASSERT// verify that threads correspond{Label L1, L2, L3;__ cmpptr(r15_thread, thread);__ jcc(Assembler::equal, L1);__ stop("StubRoutines::call_stub: r15_thread is corrupted");__ bind(L1);__ get_thread(rbx);__ cmpptr(r15_thread, thread);__ jcc(Assembler::equal, L2);__ stop("StubRoutines::call_stub: r15_thread is modified by call");__ bind(L2);__ cmpptr(r15_thread, rbx);__ jcc(Assembler::equal, L3);__ stop("StubRoutines::call_stub: threads must correspond");__ bind(L3);}
#endif// restore regs belonging to calling function
#ifdef _WIN64// emit the restores for xmm regsif (VM_Version::supports_evex()) {for (int i = xmm_save_first; i <= last_reg; i++) {__ vinsertf32x4(as_XMMRegister(i), as_XMMRegister(i), xmm_save(i), 0);}} else {for (int i = xmm_save_first; i <= last_reg; i++) {__ movdqu(as_XMMRegister(i), xmm_save(i));}}
#endif__ movptr(r15, r15_save);__ movptr(r14, r14_save);__ movptr(r13, r13_save);__ movptr(r12, r12_save);__ movptr(rbx, rbx_save);#ifdef _WIN64__ movptr(rdi, rdi_save);__ movptr(rsi, rsi_save);
#else__ ldmxcsr(mxcsr_save);
#endif// restore rsp__ addptr(rsp, -rsp_after_call_off * wordSize);// return__ vzeroupper();__ pop(rbp);__ ret(0);// handle return types different from T_INT__ BIND(is_long);__ movq(Address(c_rarg0, 0), rax);__ jmp(exit);__ BIND(is_float);__ movflt(Address(c_rarg0, 0), xmm0);__ jmp(exit);__ BIND(is_double);__ movdbl(Address(c_rarg0, 0), xmm0);__ jmp(exit);return start;}void TemplateTable::invokevirtual_helper(Register index,Register recv,Register flags) {// Uses temporary registers rax, rdxassert_different_registers(index, recv, rax, rdx);assert(index == rbx, "");assert(recv == rcx, "");// Test for an invoke of a final methodLabel notFinal;__ movl(rax, flags);__ andl(rax, (1 << ConstantPoolCacheEntry::is_vfinal_shift));__ jcc(Assembler::zero, notFinal);const Register method = index; // method must be rbxassert(method == rbx,"Method* must be rbx for interpreter calling convention");// do the call - the index is actually the method to call// that is, f2 is a vtable index if !is_vfinal, else f2 is a Method*// It's final, need a null check here!__ null_check(recv);// profile this call__ profile_final_call(rax);__ profile_arguments_type(rax, method, rbcp, true);__ jump_from_interpreted(method, rax);__ bind(notFinal);// get receiver klass__ null_check(recv, oopDesc::klass_offset_in_bytes());Register tmp_load_klass = LP64_ONLY(rscratch1) NOT_LP64(noreg);__ load_klass(rax, recv, tmp_load_klass);// profile this call__ profile_virtual_call(rax, rlocals, rdx);// get target Method* & entry point__ lookup_virtual_method(rax, index, method);__ profile_arguments_type(rdx, method, rbcp, true);__ jump_from_interpreted(method, rdx);
}// virtual method calling
void MacroAssembler::lookup_virtual_method(Register recv_klass,RegisterOrConstant vtable_index,Register method_result) {const int base = in_bytes(Klass::vtable_start_offset());assert(vtableEntry::size() * wordSize == wordSize, "else adjust the scaling in the code below");Address vtable_entry_addr(recv_klass,vtable_index, Address::times_ptr,base + vtableEntry::method_offset_in_bytes());movptr(method_result, vtable_entry_addr);
}// Jump to from_interpreted entry of a call unless single stepping is possible
// in this thread in which case we must call the i2i entry
void InterpreterMacroAssembler::jump_from_interpreted(Register method, Register temp) {prepare_to_jump_from_interpreted();if (JvmtiExport::can_post_interpreter_events()) {Label run_compiled_code;// JVMTI events, such as single-stepping, are implemented partly by avoiding running// compiled code in threads for which the event is enabled. Check here for// interp_only_mode if these events CAN be enabled.// interp_only is an int, on little endian it is sufficient to test the byte only// Is a cmpl faster?LP64_ONLY(temp = r15_thread;)NOT_LP64(get_thread(temp);)cmpb(Address(temp, JavaThread::interp_only_mode_offset()), 0);jccb(Assembler::zero, run_compiled_code);jmp(Address(method, Method::interpreter_entry_offset()));bind(run_compiled_code);}jmp(Address(method, Method::from_interpreted_offset()));
}void TemplateTable::invokevirtual(int byte_no) {transition(vtos, vtos);assert(byte_no == f2_byte, "use this argument");prepare_invoke(byte_no,rbx, // method or vtable indexnoreg, // unused itable indexrcx, rdx); // recv, flags// rbx: index// rcx: receiver// rdx: flagsinvokevirtual_helper(rbx, rcx, rdx);
}void TemplateTable::prepare_invoke(int byte_no,Register method, // linked method (or i-klass)Register index, // itable index, MethodType, etc.Register recv, // if caller wants to see itRegister flags // if caller wants to test it) {// determine flagsconst Bytecodes::Code code = bytecode();const bool is_invokeinterface = code == Bytecodes::_invokeinterface;const bool is_invokedynamic = code == Bytecodes::_invokedynamic;const bool is_invokehandle = code == Bytecodes::_invokehandle;const bool is_invokevirtual = code == Bytecodes::_invokevirtual;const bool is_invokespecial = code == Bytecodes::_invokespecial;const bool load_receiver = (recv != noreg);const bool save_flags = (flags != noreg);assert(load_receiver == (code != Bytecodes::_invokestatic && code != Bytecodes::_invokedynamic), "");assert(save_flags == (is_invokeinterface || is_invokevirtual), "need flags for vfinal");assert(flags == noreg || flags == rdx, "");assert(recv == noreg || recv == rcx, "");// setup registers & access constant pool cacheif (recv == noreg) recv = rcx;if (flags == noreg) flags = rdx;assert_different_registers(method, index, recv, flags);// save 'interpreter return address'__ save_bcp();load_invoke_cp_cache_entry(byte_no, method, index, flags, is_invokevirtual, false, is_invokedynamic);// maybe push appendix to arguments (just before return address)if (is_invokedynamic || is_invokehandle) {Label L_no_push;__ testl(flags, (1 << ConstantPoolCacheEntry::has_appendix_shift));__ jcc(Assembler::zero, L_no_push);// Push the appendix as a trailing parameter.// This must be done before we get the receiver,// since the parameter_size includes it.__ push(rbx);__ mov(rbx, index);__ load_resolved_reference_at_index(index, rbx);__ pop(rbx);__ push(index); // push appendix (MethodType, CallSite, etc.)__ bind(L_no_push);}void TemplateTable::resolve_cache_and_index(int byte_no,Register cache,Register index,size_t index_size) {const Register temp = rbx;assert_different_registers(cache, index, temp);Label L_clinit_barrier_slow;Label resolved;Bytecodes::Code code = bytecode();switch (code) {case Bytecodes::_nofast_getfield: code = Bytecodes::_getfield; break;case Bytecodes::_nofast_putfield: code = Bytecodes::_putfield; break;default: break;}assert(byte_no == f1_byte || byte_no == f2_byte, "byte_no out of range");__ get_cache_and_index_and_bytecode_at_bcp(cache, index, temp, byte_no, 1, index_size);__ cmpl(temp, code); // have we resolved this bytecode?__ jcc(Assembler::equal, resolved);// resolve first time through// Class initialization barrier slow path lands here as well.__ bind(L_clinit_barrier_slow);// std::cout << "@@@@yym%%%%" << "method begin" << "----begin" << std::endl;address entry = CAST_FROM_FN_PTR(address, InterpreterRuntime::resolve_from_cache);__ movl(temp, code);__ call_VM(noreg, entry, temp);// std::cout << "@@@@yym%%%%" << "method end" << "----end" << std::endl;// Update registers with resolved info__ get_cache_and_index_at_bcp(cache, index, 1, index_size);__ bind(resolved);// Class initialization barrier for static methodsif (VM_Version::supports_fast_class_init_checks() && bytecode() == Bytecodes::_invokestatic) {const Register method = temp;const Register klass = temp;const Register thread = LP64_ONLY(r15_thread) NOT_LP64(noreg);assert(thread != noreg, "x86_32 not supported");__ load_resolved_method_at_index(byte_no, method, cache, index);__ load_method_holder(klass, method);__ clinit_barrier(klass, thread, NULL /*L_fast_path*/, &L_clinit_barrier_slow);}
}void TemplateTable::load_invoke_cp_cache_entry(int byte_no,Register method,Register itable_index,Register flags,bool is_invokevirtual,bool is_invokevfinal, /*unused*/bool is_invokedynamic) {// setup registersconst Register cache = rcx;const Register index = rdx;assert_different_registers(method, flags);assert_different_registers(method, cache, index);assert_different_registers(itable_index, flags);assert_different_registers(itable_index, cache, index);// determine constant pool cache field offsetsassert(is_invokevirtual == (byte_no == f2_byte), "is_invokevirtual flag redundant");const int flags_offset = in_bytes(ConstantPoolCache::base_offset() +ConstantPoolCacheEntry::flags_offset());// access constant pool cache fieldsconst int index_offset = in_bytes(ConstantPoolCache::base_offset() +ConstantPoolCacheEntry::f2_offset());size_t index_size = (is_invokedynamic ? sizeof(u4) : sizeof(u2));resolve_cache_and_index(byte_no, cache, index, index_size);__ load_resolved_method_at_index(byte_no, method, cache, index);if (itable_index != noreg) {// pick up itable or appendix index from f2 also:__ movptr(itable_index, Address(cache, index, Address::times_ptr, index_offset));}__ movl(flags, Address(cache, index, Address::times_ptr, flags_offset));
}void TemplateTable::prepare_invoke(int byte_no,Register method, // linked method (or i-klass)Register index, // itable index, MethodType, etc.Register recv, // if caller wants to see itRegister flags // if caller wants to test it) {// determine flagsconst Bytecodes::Code code = bytecode();const bool is_invokeinterface = code == Bytecodes::_invokeinterface;const bool is_invokedynamic = code == Bytecodes::_invokedynamic;const bool is_invokehandle = code == Bytecodes::_invokehandle;const bool is_invokevirtual = code == Bytecodes::_invokevirtual;const bool is_invokespecial = code == Bytecodes::_invokespecial;const bool load_receiver = (recv != noreg);const bool save_flags = (flags != noreg);assert(load_receiver == (code != Bytecodes::_invokestatic && code != Bytecodes::_invokedynamic), "");assert(save_flags == (is_invokeinterface || is_invokevirtual), "need flags for vfinal");assert(flags == noreg || flags == rdx, "");assert(recv == noreg || recv == rcx, "");// setup registers & access constant pool cacheif (recv == noreg) recv = rcx;if (flags == noreg) flags = rdx;assert_different_registers(method, index, recv, flags);// save 'interpreter return address'__ save_bcp();load_invoke_cp_cache_entry(byte_no, method, index, flags, is_invokevirtual, false, is_invokedynamic);// maybe push appendix to arguments (just before return address)if (is_invokedynamic || is_invokehandle) {Label L_no_push;__ testl(flags, (1 << ConstantPoolCacheEntry::has_appendix_shift));__ jcc(Assembler::zero, L_no_push);// Push the appendix as a trailing parameter.// This must be done before we get the receiver,// since the parameter_size includes it.__ push(rbx);__ mov(rbx, index);__ load_resolved_reference_at_index(index, rbx);__ pop(rbx);__ push(index); // push appendix (MethodType, CallSite, etc.)__ bind(L_no_push);}// load receiver if needed (after appendix is pushed so parameter size is correct)// Note: no return address pushed yetif (load_receiver) {__ movl(recv, flags);__ andl(recv, ConstantPoolCacheEntry::parameter_size_mask);const int no_return_pc_pushed_yet = -1; // argument slot correction before we push return addressconst int receiver_is_at_end = -1; // back off one slot to get receiverAddress recv_addr = __ argument_address(recv, no_return_pc_pushed_yet + receiver_is_at_end);__ movptr(recv, recv_addr);__ verify_oop(recv);}if (save_flags) {__ movl(rbcp, flags);}// compute return type__ shrl(flags, ConstantPoolCacheEntry::tos_state_shift);// Make sure we don't need to mask flags after the above shiftConstantPoolCacheEntry::verify_tos_state_shift();// load return address{const address table_addr = (address) Interpreter::invoke_return_entry_table_for(code);ExternalAddress table(table_addr);LP64_ONLY(__ lea(rscratch1, table));LP64_ONLY(__ movptr(flags, Address(rscratch1, flags, Address::times_ptr)));NOT_LP64(__ movptr(flags, ArrayAddress(table, Address(noreg, flags, Address::times_ptr))));}// push return address__ push(flags);// Restore flags value from the constant pool cache, and restore rsi// for later null checks. r13 is the bytecode pointerif (save_flags) {__ movl(flags, rbcp);__ restore_bcp();}
}//
// Generic interpreted method entry to (asm) interpreter
//
address TemplateInterpreterGenerator::generate_normal_entry(bool synchronized) {// determine code generation flagsbool inc_counter = UseCompiler || CountCompiledCalls || LogTouchedMethods;// ebx: Method*// rbcp: sender spaddress entry_point = __ pc();const Address constMethod(rbx, Method::const_offset());const Address access_flags(rbx, Method::access_flags_offset());const Address size_of_parameters(rdx,ConstMethod::size_of_parameters_offset());const Address size_of_locals(rdx, ConstMethod::size_of_locals_offset());// get parameter size (always needed)__ movptr(rdx, constMethod);__ load_unsigned_short(rcx, size_of_parameters);// rbx: Method*// rcx: size of parameters// rbcp: sender_sp (could differ from sp+wordSize if we were called via c2i )__ load_unsigned_short(rdx, size_of_locals); // get size of locals in words__ subl(rdx, rcx); // rdx = no. of additional locals// YYY
// __ incrementl(rdx);
// __ andl(rdx, -2);// see if we've got enough room on the stack for locals plus overhead.generate_stack_overflow_check();//yym-gaizao// #ifdef DEBUG_PRINT_METHOD_NAME// ---yym--- 打印代码移动到堆栈检查之后{// 保存寄存器状态__ push(rax);__ push(rcx);__ push(rdx);__ push(rdi);__ push(rsi);__ push(r8);__ push(r9);__ push(r10);__ push(r11);NOT_LP64(__ get_thread(r15_thread));__ push(r15);// 准备调用参数__ movptr(rdi, rbx); // Method* 作为第一个参数__ lea(rsi, Address(rsp, 14*wordSize)); // 原始rsp地址__ mov(r8, rcx); // 参数大小__ mov(r9, rdx); // 本地变量大小// 调用调试信息输出函数__ call(RuntimeAddress(CAST_FROM_FN_PTR(address, ::TemplateInterpreterGenerator::print_debug_info)));// 恢复寄存器__ pop(r15);NOT_LP64(__ restore_thread(r15_thread));__ pop(r11);__ pop(r10);__ pop(r9);__ pop(r8);__ pop(rsi);__ pop(rdi);__ pop(rdx);__ pop(rcx);__ pop(rax);}// #endif// get return address__ pop(rax);// compute beginning of parameters__ lea(rlocals, Address(rsp, rcx, Interpreter::stackElementScale(), -wordSize));// rdx - # of additional locals// allocate space for locals// explicitly initialize locals{Label exit, loop;__ testl(rdx, rdx);__ jcc(Assembler::lessEqual, exit); // do nothing if rdx <= 0__ bind(loop);__ push((int) NULL_WORD); // initialize local variables__ decrementl(rdx); // until everything initialized__ jcc(Assembler::greater, loop);__ bind(exit);}// initialize fixed part of activation framegenerate_fixed_frame(false);// make sure method is not native & not abstract
#ifdef ASSERT__ movl(rax, access_flags);{Label L;__ testl(rax, JVM_ACC_NATIVE);__ jcc(Assembler::zero, L);__ stop("tried to execute native method as non-native");__ bind(L);}{Label L;__ testl(rax, JVM_ACC_ABSTRACT);__ jcc(Assembler::zero, L);__ stop("tried to execute abstract method in interpreter");__ bind(L);}
#endif// Since at this point in the method invocation the exception// handler would try to exit the monitor of synchronized methods// which hasn't been entered yet, we set the thread local variable// _do_not_unlock_if_synchronized to true. The remove_activation// will check this flag.const Register thread = NOT_LP64(rax) LP64_ONLY(r15_thread);NOT_LP64(__ get_thread(thread));const Address do_not_unlock_if_synchronized(thread,in_bytes(JavaThread::do_not_unlock_if_synchronized_offset()));__ movbool(do_not_unlock_if_synchronized, true);__ profile_parameters_type(rax, rcx, rdx);// increment invocation count & check for overflowLabel invocation_counter_overflow;if (inc_counter) {generate_counter_incr(&invocation_counter_overflow);}Label continue_after_compile;__ bind(continue_after_compile);// check for synchronized interpreted methodsbang_stack_shadow_pages(false);// reset the _do_not_unlock_if_synchronized flagNOT_LP64(__ get_thread(thread));__ movbool(do_not_unlock_if_synchronized, false);// check for synchronized methods// Must happen AFTER invocation_counter check and stack overflow check,// so method is not locked if overflows.if (synchronized) {// Allocate monitor and lock methodlock_method();} else {// no synchronization necessary
#ifdef ASSERT{Label L;__ movl(rax, access_flags);__ testl(rax, JVM_ACC_SYNCHRONIZED);__ jcc(Assembler::zero, L);__ stop("method needs synchronization");__ bind(L);}
#endif}// start execution
#ifdef ASSERT{Label L;const Address monitor_block_top (rbp,frame::interpreter_frame_monitor_block_top_offset * wordSize);__ movptr(rax, monitor_block_top);__ cmpptr(rax, rsp);__ jcc(Assembler::equal, L);__ stop("broken stack frame setup in interpreter");__ bind(L);}
#endif// jvmti support__ notify_method_entry();__ dispatch_next(vtos);// invocation counter overflowif (inc_counter) {// Handle overflow of counter and compile method__ bind(invocation_counter_overflow);generate_counter_overflow(continue_after_compile);}return entry_point;
}void InterpreterMacroAssembler::dispatch_next(TosState state, int step, bool generate_poll) {// load next bytecode (load before advancing _bcp_register to prevent AGI)load_unsigned_byte(rbx, Address(_bcp_register, step));// advance _bcp_registerincrement(_bcp_register, step);dispatch_base(state, Interpreter::dispatch_table(state), true, generate_poll);
}void InterpreterMacroAssembler::dispatch_base(TosState state,address* table,bool verifyoop,bool generate_poll) {verify_FPU(1, state);if (VerifyActivationFrameSize) {Label L;mov(rcx, rbp);subptr(rcx, rsp);int32_t min_frame_size =(frame::link_offset - frame::interpreter_frame_initial_sp_offset) *wordSize;cmpptr(rcx, (int32_t)min_frame_size);jcc(Assembler::greaterEqual, L);stop("broken stack frame");bind(L);}if (verifyoop) {interp_verify_oop(rax, state);}address* const safepoint_table = Interpreter::safept_table(state);
#ifdef _LP64Label no_safepoint, dispatch;if (table != safepoint_table && generate_poll) {NOT_PRODUCT(block_comment("Thread-local Safepoint poll"));testb(Address(r15_thread, JavaThread::polling_word_offset()), SafepointMechanism::poll_bit());jccb(Assembler::zero, no_safepoint);lea(rscratch1, ExternalAddress((address)safepoint_table));jmpb(dispatch);}bind(no_safepoint);lea(rscratch1, ExternalAddress((address)table));bind(dispatch);jmp(Address(rscratch1, rbx, Address::times_8));#elseAddress index(noreg, rbx, Address::times_ptr);if (table != safepoint_table && generate_poll) {NOT_PRODUCT(block_comment("Thread-local Safepoint poll"));Label no_safepoint;const Register thread = rcx;get_thread(thread);testb(Address(thread, JavaThread::polling_word_offset()), SafepointMechanism::poll_bit());jccb(Assembler::zero, no_safepoint);ArrayAddress dispatch_addr(ExternalAddress((address)safepoint_table), index);jump(dispatch_addr);bind(no_safepoint);}{ArrayAddress dispatch_addr(ExternalAddress((address)table), index);jump(dispatch_addr);}
#endif // _LP64
}
相关文章:
深入解析JVM字节码解释器执行流程(OpenJDK 17源码实现)
一、核心流程概述 JVM解释器的核心任务是将Java字节码逐条翻译为本地机器指令并执行。其执行流程可分为以下关键阶段: 方法调用入口构建:生成栈帧、处理参数、同步锁等。 字节码分派(Dispatch):根据字节码跳转到对应…...
【HCIA】BFD
前言 前面我们介绍了浮动路由以及出口路由器的默认路由配置,可如此配置会存在隐患,就是出口路由器直连的网络设备并不是运营商的路由器,而是交换机。此时我们就需要感知路由器的存活状态,这就需要用到 BFD(Bidirectio…...
vue使用路由技术实现登录成功后跳转到首页
文章目录 一、概述二、使用步骤安装vue-router在src/router/index.js中创建路由器,并导出在vue应用实例中使用router声明router-view标签,展示组件内容 三、配置登录成功后跳转首页四、参考资料 一、概述 路由,决定从起点到终点的路径的进程…...
用户模块 - IP归属地框架吞吐测试
一、引言 在很多用户系统中,我们常常需要知道一个IP地址来自哪里,比如判断一个用户是否来自国内、识别异常登录等。而实现这个功能,通常会使用一个“IP归属地解析框架”,它可以根据IP地址返回国家、省份、城市等信息。 不过&#…...
生活实用小工具-手机号归属地查询
一、接口定义 手机号码归属地接口(又称手机号查询API)是一种通过输入手机号码,快速返回其归属地信息(如省份、城市、运营商、区号等)的应用程序接口。其数据基础来源于运营商(移动、联通、电信)…...
鸿蒙-5.1.0-release源码下载
源码获取 前提条件 注册码云gitee帐号。注册码云SSH公钥,请参考码云帮助中心。安装git客户端和git-lfs并配置用户信息。 git config --global user.name "yourname" # 这得和gitee的账号对的上 git config --global user.email "your-email-ad…...
2020年下半年试题三:论云原生架构及其应用
论文库链接:系统架构设计师论文 论文题目 近年来,随着数字化转型不断深入,科技创新与业务发展不断融合,各行各业正在从大工业时代的固化范式进化成面向创新型组织与灵活型业务的崭新模式。在这一背景下,以容器盒微服务…...
Flutter到HarmonyOS Next 的跨越:memory_info库的鸿蒙适配之旅
Flutter到鸿蒙的跨越:memory_info库的鸿蒙适配之旅 本项目作者:kirk/坚果 您可以使用这个Flutter插件来更改应用程序图标上的角标 作者仓库:https://github.com/MrOlolo/memory_info/tree/master/memory_info 在数字化浪潮的推动下&#…...
昆士兰科技大学无人机自主导航探索新框架!UAVNav:GNSS拒止与视觉受限环境中的无人机导航与目标检测
作者: Sebastien Boiteau, Fernando Vanegas, Felipe Gonzalez 单位:昆士兰科技大学电气工程与机器人学院,昆士兰科技大学机器人中心 论文标题:Framework for Autonomous UAV Navigation and Target Detection in Global-Naviga…...
uniapp设置 overflow:auto;右边不显示滚动条的问题
设置了overflow:auto;或者其它overflow的属性不显示滚动条是因为在uniapp中默认隐藏了滚动条 解决方法: //强制显示滚动条 ::-webkit-scrollbar {width: 8px !important;background: #ccc !important;display: block !important;}//设置滚动条颜色.cu-…...
基于SIP协议的VOIP话机认证注册流程分析与抓包验证
话机的认证注册报文怎么看? 在SIP协议中,当VOIP话机首次启动的时候,他会向SIP服务器发送一个Register请求来注册自己的信息地址,,告诉服务器 话机当前在线话机的IP地址和端口是什么话机希望接收通话的联系方式 认证注…...
JS,ES,TS三者什么区别
Java Script(JS)、ECMAScript(ES)、TypeScript(TS) 的核心区别与关联的详细解析,结合技术背景、设计目标及应用场景展开说明: 一、核心定义与关系 JavaScript(JS) 定义:一种动态类型、基于原型的脚本语言,由 Netscape 公司于 1995 年首次开发,用于网页交互功能。角…...
深度理解指针(2)
🎁个人主页:工藤新一 🔍系列专栏:C面向对象(类和对象篇) 🌟心中的天空之城,终会照亮我前方的路 🎉欢迎大家点赞👍评论📝收藏⭐文章 深入理解指…...
笔记本/台式机加装PCIe 5.0固态硬盘兼容性与安装方法详解 —— 金士顿Kingston FURY Renegade G5装机指南
在2025年,存储设备市场迎来了革命性的升级浪潮。作为最高性能PCIe 5.0固态硬盘的代表,Kingston FURY Renegade G5 PCIe 5.0 NVMe M.2 固态硬盘不仅刷新了读写速度新高,更以优异的能耗和温控表现成为高端PC、游戏本和工作站升级的“定心丸”。…...
使用libUSB-win32的简单读写例程参考
USB上位机程序的编写,函数的调用过程. 调用 void usb_init(void); 进行初始化 调用usb_find_busses、usb_find_devices和usb_get_busses这三个函数,获得已找到的USB总线序列;然后通过链表遍历所有的USB设备,根据已知的要打开USB设…...
Tailwind CSS 实战教程:从入门到精通
Tailwind CSS 实战教程:从入门到精通 前言 在Web开发的世界里,CSS框架层出不穷。从早期的Bootstrap到现在的Tailwind CSS,前端开发者们总是在寻找更高效、更灵活的样式解决方案。今天,我们就来深入探讨这个被称为"实用优先…...
【IC】如何获取良好的翻转数据来改进dynamic IR drop分析?
动态电压降分析是一个复杂的过程。为了成功执行适当的分析,需要组合多个输入文件和不同的配置设置。 切换场景是任何动态压降分析的关键。设计中的所有门电路和实例不会同时处于活动状态。此外,对于更复杂的单元,可能的切换模式会非常多。这…...
WebGL知识框架
一、WebGL 基础概念 1. WebGL 简介 是什么? 基于 OpenGL ES 的浏览器 3D 图形 API,直接操作 GPU 渲染。 核心特点 底层、高性能、需手动控制渲染管线。 依赖 JavaScript 和 GLSL(着色器语言)。 与 Three.js 的关系 Three.js…...
集成 ONLYOFFICE 与 AI 插件,为您的服务带来智能文档编辑器
在数字化办公浪潮中,文档处理效率对企业发展具有关键意义。但许多办公平台仅支持基础流程,查阅、批注和修改需借助外部工具,增加了操作复杂性和沟通成本。本文将为开发者介绍如何集成 ONLYOFFICE 文档并利用其中的 AI 插件,智能处…...
Simulink模型回调
Simulink 模型回调函数是一种特殊的 MATLAB 函数,可在模型生命周期的特定阶段自动执行。它们允许用户自定义模型行为、执行初始化任务、验证参数或记录数据。以下是各回调函数的详细说明: 1. PreLoadFcn 触发时机:Simulink 模型加载到内存之…...
网络协议分析 实验五 UDP-IPv6-DNS
文章目录 实验5.1 UDP(User Datagram Protocol)练习二 UDP单播通信练习三 利用仿真编辑器编辑UDP数据包,利用工具接收练习四 UDP受限广播通信练习六 利用仿真编辑器编辑IPV6的UDP数据包并发送实验5.2 DNS(Domain Name System)练习二 仿真编辑DNS查询报文(…...
共享代理IP vs 动态IP:企业级业务场景的选型深度解析
在数字化转型加速的今天,IP地址管理已成为企业网络架构中的核心命题。无论是跨境电商的多账号运营、大数据采集的精准度保障,还是网络安全的纵深防御,IP解决方案的选择直接关系到业务效能与合规风险。本文将从技术底层逻辑出发,结…...
鸿蒙OSUniApp制作一个小巧的图片浏览器#三方框架 #Uniapp
利用UniApp制作一个小巧的图片浏览器 最近接了个需求,要求做一个轻量级的图片浏览工具,考虑到多端适配的问题,果断选择了UniApp作为开发框架。本文记录了我从0到1的开发过程,希望能给有类似需求的小伙伴一些参考。 前言 移动互联…...
Java并发编程面试题:并发工具类(10题)
🧑 博主简介:CSDN博客专家,历代文学网(PC端可以访问:https://literature.sinhy.com/#/?__c1000,移动端可微信小程序搜索“历代文学”)总架构师,15年工作经验,精通Java编…...
【Oracle专栏】扩容导致数据文件 dbf 丢失,实操
Oracle相关文档,希望互相学习,共同进步 风123456789~-CSDN博客 1.背景 同事检查扩容情况,发现客户扩容后数据盘后,盘中原有文件丢失,再检查发现数据库没有启动。通过检查发现数据盘中丢失的是oracle的 dbf 表空间文件。数据库无法启动。 检查情况:1)没有rman备份 …...
【Linux】Linux 的管道与重定向的理解
目录 一、了解Linux目录配置标准FHS 二、Linux数据重定向的理解与操作 2.1基本背景 2.2重定向的理解 2.3Linux管道命令的理解与操作 三、Linux 环境变量与PATH 3.1环境变量PATH 一、了解Linux目录配置标准FHS FHS本质:是一套规定Linux目录结构,软…...
【交互 / 差分约束】
题目 代码 #include <bits/stdc.h> using namespace std; using ll long long;const int N 10510; const int M 200 * 500 10; int h[N], ne[M], e[M], w[M], idx; ll d[N]; int n, m; bool st[N]; int cnt[N];void add(int a, int b, int c) {w[idx] c, e[idx] b…...
1. Go 语言环境安装
👑 博主简介:高级开发工程师 👣 出没地点:北京 💊 人生目标:自由 ——————————————————————————————————————————— 版权声明:本文为原创文章…...
灰度图像和RGB图像在数据大小和编码处理方式差别
技术背景 好多开发者对灰度图像和RGB图像有些认知差异,今天我们大概介绍下二者差别。灰度图像(Grayscale Image)和RGB图像在编码处理时,数据大小和处理方式的差别主要体现在以下几个方面: 1. 通道数差异 图像类型通道…...
Lord Of The Root: 1.0.1通关
Lord Of The Root: 1.0.1 来自 <Lord Of The Root: 1.0.1 ~ VulnHub> 1,将两台虚拟机网络连接都改为NAT模式 2,攻击机上做namp局域网扫描发现靶机 nmap -sn 192.168.23.0/24 那么攻击机IP为192.168.23.182,靶场IP192.168.23.247 3&…...
虚拟机安装CentOS7网络问题
虚拟机安装CentOS7网络问题 1. 存在的问题1.1 CentOS7详细信息 2. 解决问题3.Windows下配置桥接模式 1. 存在的问题 虽然已经成功在虚拟机上安装了CentOS7,但是依旧不能上网。 1.1 CentOS7详细信息 [fanzhencentos01 ~]$ hostnamectlStatic hostname: centos01Ic…...
AI-02a5a6.神经网络-与学习相关的技巧-批量归一化
批量归一化 Batch Normalization 设置合适的权重初始值,则各层的激活值分布会有适当的广度,从而可以顺利的进行学习。那么,更进一步,强制性的调整激活值的分布,是的各层拥有适当的广度呢?批量归一化&#…...
matlab实现蚁群算法解决公交车路径规划问题
使用蚁群算法解决公交车路径规划问题的MATLAB代码实现,包含详细注释和仿真流程。该算法以站点间行驶时间或距离为优化目标,寻找最优公交路线。 1. 问题建模与参数设置 1.1 输入数据 站点坐标:假设有20个公交站点,随机生成位置。…...
Agent Builder API - Agent Smith 扩展的后端服务(开源代码)
一、软件介绍 文末提供程序和源码下载 Agent Builder API - Agent Smith 扩展的后端服务(开源代码)手动设置:在本地计算机中克隆此存储库并启动 python FAST API 服务器。(可选)安装并设置 Mongo DB。Dev Container…...
C++类和对象之相关特性
C 一.类型转换隐式类型转换 二.static成员一、static成员变量二、static成员函数三、static成员的存储位置四、总结 三.友元一、友元函数二、友元类三、友元成员函数四、总结 四.内部类五.匿名对象六.new 一.类型转换 在C中,类类型转换是指将一个类的对象转换为另一…...
容器编排的革命:Kubernetes如何引领IT的云原生时代
文章目录 Kubernetes的本质:容器世界的智能指挥家Kubernetes的核心功能包括: Kubernetes的演进:从谷歌的实验到全球标准核心技术:Kubernetes的基石与生态1. Pod与容器:最小调度单位2. Deployment:无状态应用…...
2025视频协作工具全景解析:技术跃迁与场景重构
一、技术演进:从功能工具到智能生态 2025年视频协作软件的核心竞争力已从基础功能升级为技术生态的构建。以分秒帧为例,其音视频生产协作系统,可帮助创作者在云端构建工作流,让跨地域、跨终端、跨团队的协作组可以在统一的安全平台上管理所有媒体资源、任务、反馈信息,从而更高…...
保持视频二维码不变,更新视频的内容
视频替换功能允许用户在保持视频二维码、观看地址和调用代码不变的情况下更新视频内容,从而节省重新印刷物料的成本。这一功能适用于多种场景,如营销宣传、产品操作手册、设备说明书等,当视频内容需要修改或更新时,用户只需上传新…...
Linux常用命令40——alias设置命令别名
在使用Linux或macOS日常开发中,熟悉一些基本的命令有助于提高工作效率,alias命令来自英文单词alias,中文译为“别名”,其功能是设置命令别名信息。我们可以使用alias将一些较长的命令进行简写,往往几十个字符的命令会变…...
Java大师成长计划之第22天:Spring Cloud微服务架构
📢 友情提示: 本文由银河易创AI(https://ai.eaigx.com)平台gpt-4o-mini模型辅助创作完成,旨在提供灵感参考与技术分享,文中关键数据、代码与结论建议通过官方渠道验证。 随着企业应用的不断扩展,…...
为什么go语言中返回的指针类型,不需要用*取值(解引用),就可以直接赋值呢?
Go 中返回的是指针,但你却能直接用“.”访问字段,看起来像是“没有解引用”,其实是 Go 帮你自动处理了“指针解引用”的语法糖。 在 Go 中,如果你有一个结构体指针(例如 *FileMeta),你可以直接…...
Java生成可控的Word表格功能开发
在日常办公自动化与系统集成场景中,生成结构化的Word文档已成为一种刚性需求,尤其是带有格式规范、内容动态填充的Word表格(Table)。本文将围绕如何利用Java开发一个可控的Word表格生成功能模块展开,涵盖技术选型、代码实现、边界控制与常见问题处理等方面,帮助开发者快速…...
OpenCV CUDA 模块中用于在 GPU 上计算矩阵中每个元素的绝对值或复数的模函数abs()
操作系统:ubuntu22.04 OpenCV版本:OpenCV4.9 IDE:Visual Studio Code 编程语言:C11 算法描述 void cv::cuda::abs(InputArray src, OutputArray dst, Stream &stream Stream::Null()) 是 OpenCV 的 CUDA 模块中的一个函数,…...
hadoop知识点
(一)复制和移动 1.复制文件 格式:cp源文件 目标文件 示例:把filel.txt复制一份得到file2.txt 2.复制目录 格式:cp-r源文件夹 目标文件夹 示例:把目标dir1复制一份得到dir2 3.重命名和移动 格式:…...
最短路与拓扑(2)
1、信使 #include<bits/stdc.h> using namespace std; const int N105; int n,m; int g[N][N]; int dist[N]; bool st[N]; const int INF0x3f3f3f3f;int dij(){memset(dist,0x3f,sizeof dist);dist[1]0;for(int i1;i<n;i){int t0;for(int j1;j<n;j){if(!st[j]&…...
LLaMA-Factory 微调 Qwen2-7B-Instruct
一、系统环境 使用的 autoDL 算力平台 1、下载基座模型 pip install -U huggingface_hub export HF_ENDPOINThttps://hf-mirror.com # (可选)配置 hf 国内镜像站huggingface-cli download --resume-download shenzhi-wang/Llama3-8B-Chinese-Chat -…...
濒危仙草的重生叙事:九仙尊米斛花节如何以雅集重构中医药文化IP
五月的霍山深处,层峦叠翠之间,中华仙草霍山米斛迎来一年一度的花期。九仙尊以“斛韵雅集,春野茶会”为主题,举办为期半月的米斛花文化节,融合中医药文化、东方美学与自然体验,打造一场跨越古今的沉浸式文化盛宴。活动涵盖古琴雅集、书法创作、茶道冥想、诗歌吟诵、民族歌舞等多…...
Pomelo知识框架
一、Pomelo 基础概念 Pomelo 简介 定位:分布式游戏服务器框架(网易开源)。 特点:高并发、可扩展、多进程架构、支持多种通信协议(WebSocket、TCP等)。 适用场景:MMO RPG、实时对战、社交游戏等…...
歌词滚动效果
<!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><!-- 设置标签页图标 --><link rel"shortcut icon&…...
python如何合并excel单元格
在Python中合并Excel单元格,常用openpyxl库实现。以下是详细步骤和示例代码: 方法一:使用 openpyxl 库 步骤说明: 安装库: pip install openpyxl导入库并加载文件: from openpyxl import load_workbook# …...