Java对象分配原理

Java对象模型: OOP-Klass模型

在正式探讨JVM对象的创建前,先简单地介绍一下hotspot中实现的Java的对象模型。在JVM中,并没有直接将Java对象映射成C++对象,而是采用了oop-klass模型,主要是不希望每个对象中都包含有一份虚函数表,其中:

  1. OOP(Ordinary Object Point),表示对象的实例信息
  2. Klass,是Java类的在C++中的表示,用来描述Java类的信息

简单地说,一个Java类在JVM中被拆分为了两个部分:数据和描述信息,分别对应OOP和Klass。

在具体的JVM源码中,当加载一个Class时,会创建一个InstanceKlass对象,实例化的对象则对应InstanceOopDesc,其中InstanceKlass存放在元空间,InstanceOopDesc存放在堆中。

对象创建过程

首先先来看InstanceOopDesc的数据结构,InstanceOopDesc继承了OopDesc,数据结构如下

// 此处为了方便阅读,改写了一下代码
class instanceOopDesc : public oopDesc {private:volatile markOop _mark;union _metadata {Klass* _klass;narrowKlass _compressed_klass;} _metadata;
};

其中_metadata指向该对象的InstanceKlass,而_mark中则存储了对象运行时的状态数据,数据结构如下(图中为32位的情况下的数据,64位也大同小异)

32 bits:
--------
hash:25 ------------>| age:4 biased_lock:1 lock:2 (normal object)
JavaThread*:23 epoch:2 age:4 biased_lock:1 lock:2 (biased object)
size:32 ------------------------------------------>| (CMS free block)
PromotedObject*:29 ---------->| promo_bits:3 ----->| (CMS promoted object)

每一行都代表了一种情况,描述了哈希码、GC分代年龄、锁等状态信息,如下:

hash: 哈希码
age: 分代年龄
biased_lock: 偏向锁标识位
lock: 锁状态标识位
JavaThread*: 持有偏向锁的线程ID
epoch: 偏向时间戳

instanceOopDesc其实保存的是对象的头部信息,除了头部信息,对象还有数据,对象数据紧跟着头部后面,图示如下:

 

1. 入口

 

上图截取了一段程序字节码,红线所框对应了Java中new操作的字节码,Java中的new操作对应了字节码的三个操作,本文主要讲述第一个操作(new)。字节码中new操作对应JVM中的InterpreterRuntime::_new,代码如下,

// hotspot/share/interpreter/interpreterRuntime.cpp
IRT_ENTRY(void, InterpreterRuntime::_new(JavaThread* thread, ConstantPool* pool, int index))Klass* k = pool->klass_at(index, CHECK);InstanceKlass* klass = InstanceKlass::cast(k);klass->check_valid_for_instantiation(true, CHECK); // 校验:接口/抽象类/Class不能实例化klass->initialize(CHECK); // 初始化klassoop obj = klass->allocate_instance(CHECK); // 分配实例thread->set_vm_result(obj);
IRT_END
里面主要包含了两个部分:初始化klass和分配实例

2. 初始化klass

// hotspot/share/oops/instanceKlass.cpp
void InstanceKlass::initialize(TRAPS) {if (this->should_be_initialized()) {initialize_impl(CHECK);} else {assert(is_initialized(), "sanity check");}
}

在这里我们继续看initialize_impl()方法

// hotspot/share/oops/instanceKlass.cpp
void InstanceKlass::initialize_impl(TRAPS) {HandleMark hm(THREAD);link_class(CHECK); // 链接classbool wait = false;// Step 1{Handle h_init_lock(THREAD, init_lock());ObjectLocker ol(h_init_lock, THREAD, h_init_lock() != NULL);Thread *self = THREAD;// Step 2while(is_being_initialized() && !is_reentrant_initialization(self)) {wait = true;ol.waitUninterruptibly(CHECK);}// Step 3if (is_being_initialized() && is_reentrant_initialization(self)) {DTRACE_CLASSINIT_PROBE_WAIT(recursive, -1, wait);return;}// Step 4if (is_initialized()) {DTRACE_CLASSINIT_PROBE_WAIT(concurrent, -1, wait);return;}// Step 5if (is_in_error_state()) {DTRACE_CLASSINIT_PROBE_WAIT(erroneous, -1, wait);ResourceMark rm(THREAD);const char* desc = "Could not initialize class ";const char* className = external_name();size_t msglen = strlen(desc) + strlen(className) + 1;char* message = NEW_RESOURCE_ARRAY(char, msglen);if (NULL == message) {// Out of memory: can't create detailed error messageTHROW_MSG(vmSymbols::java_lang_NoClassDefFoundError(), className);} else {jio_snprintf(message, msglen, "%s%s", desc, className);THROW_MSG(vmSymbols::java_lang_NoClassDefFoundError(), message);}}// Step 6set_init_state(being_initialized);set_init_thread(self);}// Step 7if (!is_interface()) {Klass* super_klass = super();if (super_klass != NULL && super_klass->should_be_initialized()) {super_klass->initialize(THREAD);}if (!HAS_PENDING_EXCEPTION && has_nonstatic_concrete_methods()) {initialize_super_interfaces(THREAD);}if (HAS_PENDING_EXCEPTION) {Handle e(THREAD, PENDING_EXCEPTION);CLEAR_PENDING_EXCEPTION;{EXCEPTION_MARK;// Locks object, set state, and notify all waiting threadsset_initialization_state_and_notify(initialization_error, THREAD);CLEAR_PENDING_EXCEPTION;}DTRACE_CLASSINIT_PROBE_WAIT(super__failed, -1, wait);THROW_OOP(e());}}AOTLoader::load_for_klass(this, THREAD);// Step 8{assert(THREAD->is_Java_thread(), "non-JavaThread in initialize_impl");JavaThread* jt = (JavaThread*)THREAD;DTRACE_CLASSINIT_PROBE_WAIT(clinit, -1, wait);PerfClassTraceTime timer(ClassLoader::perf_class_init_time(),ClassLoader::perf_class_init_selftime(),ClassLoader::perf_classes_inited(),jt->get_thread_stat()->perf_recursion_counts_addr(),jt->get_thread_stat()->perf_timers_addr(),PerfClassTraceTime::CLASS_CLINIT);call_class_initializer(THREAD);}// Step 9if (!HAS_PENDING_EXCEPTION) {set_initialization_state_and_notify(fully_initialized, CHECK);{debug_only(vtable().verify(tty, true);)}}else {// Step 10 and 11Handle e(THREAD, PENDING_EXCEPTION);CLEAR_PENDING_EXCEPTION;JvmtiExport::clear_detected_exception((JavaThread*)THREAD);{EXCEPTION_MARK;set_initialization_state_and_notify(initialization_error, THREAD);CLEAR_PENDING_EXCEPTION;JvmtiExport::clear_detected_exception((JavaThread*)THREAD);}DTRACE_CLASSINIT_PROBE_WAIT(error, -1, wait);if (e->is_a(SystemDictionary::Error_klass())) {THROW_OOP(e());} else {JavaCallArguments args(e);THROW_ARG(vmSymbols::java_lang_ExceptionInInitializerError(),vmSymbols::throwable_void_signature(),&args);}}DTRACE_CLASSINIT_PROBE_WAIT(end, -1, wait);
}

2.1 链接

// hotspot/share/oops/instanceKlass.cpp
bool InstanceKlass::link_class_impl(bool throw_verifyerror, TRAPS) {if (is_linked()) {return true;}assert(THREAD->is_Java_thread(), "non-JavaThread in link_class_impl");JavaThread* jt = (JavaThread*)THREAD;// 先链接父类Klass* super_klass = super();if (super_klass != NULL) {if (super_klass->is_interface()) {ResourceMark rm(THREAD);Exceptions::fthrow(THREAD_AND_LOCATION,vmSymbols::java_lang_IncompatibleClassChangeError(),"class %s has interface %s as super class",external_name(),super_klass->external_name());return false;}InstanceKlass* ik_super = InstanceKlass::cast(super_klass);ik_super->link_class_impl(throw_verifyerror, CHECK_false);}// 链接该类的所有借口Array<Klass*>* interfaces = local_interfaces();int num_interfaces = interfaces->length();for (int index = 0; index < num_interfaces; index++) {InstanceKlass* interk = InstanceKlass::cast(interfaces->at(index));interk->link_class_impl(throw_verifyerror, CHECK_false);}if (is_linked()) {return true;}// 验证 & 重写{HandleMark hm(THREAD);Handle h_init_lock(THREAD, init_lock());ObjectLocker ol(h_init_lock, THREAD, h_init_lock() != NULL);if (!is_linked()) {if (!is_rewritten()) {{bool verify_ok = verify_code(throw_verifyerror, THREAD);if (!verify_ok) {return false;}}if (is_linked()) {return true;}// 重写类rewrite_class(CHECK_false);} else if (is_shared()) {SystemDictionaryShared::check_verification_constraints(this, CHECK_false);}// 重写完成后链接方法link_methods(CHECK_false);// 初始化vtable和itableClassLoaderData * loader_data = class_loader_data();if (!(is_shared() &&loader_data->is_the_null_class_loader_data())) {ResourceMark rm(THREAD);vtable().initialize_vtable(true, CHECK_false);itable().initialize_itable(true, CHECK_false);}// 将类的状态标记为已链接set_init_state(linked);if (JvmtiExport::should_post_class_prepare()) {Thread *thread = THREAD;assert(thread->is_Java_thread(), "thread->is_Java_thread()");JvmtiExport::post_class_prepare((JavaThread *) thread, this);}}}return true;
}

class链接的过程就是这样,主要步骤总结如下:

  1. 链接父类和实现的接口
  2. 重写类
  3. 初始化vtable和itable
  4. 将类的状态标记为已链接

关于重写类和初始化vtable、itable的内容有空新开一章,本文就不描述具体细节了。

2.2 初始化过程

这段初始化klass步骤在JVM规范中有详细描述,假设当前类(接口)为C,它持有一个独有的初始化锁LC

  1. 同步锁LC,防止并发导致多次初始化
  2. 如果有其他线程正在初始化C,就释放LC并阻塞当前线程直到那个线程完成初始化
  3. 如果是执行初始化的是当前线程,则表明是递归请求,释放LC并正常完成初始化
  4. 如果C已经被初始化了,则释放LC并正常完成初始化
  5. 如果C的对象处于一个错误状态,则释放LC并抛出NoClassDefFoundError异常
  6. 记录C正在被当前线程初始化并释放LC,初始化类中所有final static字段
  7. 如果C是一个类,初始化其父类和接口
  8. 判断C是否打开断言
  9. 执行类(接口)的初始化方法
  10. 标记C已经完全初始化,并唤醒所有的等待线程
  11. 如果初始化失败,则抛出异常,并将C标记为错误,同时唤醒所有的等待线程

上文为JVM11规范中的步骤,实际中可以看到hotspot在实现时和规范所写略有偏差,但基本差不多。

3. 分配实例

// hotspot/share/oops/instanceKlass.cpp
instanceOop InstanceKlass::allocate_instance(TRAPS) {bool has_finalizer_flag = has_finalizer(); // 是否存在非空finalize()方法int size = size_helper(); // 类的大小instanceOop i;i = (instanceOop)Universe::heap()->obj_allocate(this, size, CHECK_NULL); // 分配对象if (has_finalizer_flag && !RegisterFinalizersAtInit) {i = register_finalizer(i, CHECK_NULL);}return i;
}
在这里我们比较关注的是堆空间分配对象环节,

3.1 堆空间分配对象

代码如下:

// hotspot/share/gc/shared/memAllocator.cpp
oop MemAllocator::allocate() const {oop obj = NULL;{Allocation allocation(*this, &obj);HeapWord* mem = mem_allocate(allocation);if (mem != NULL) {obj = initialize(mem);}}return obj;
}
很容易可以看到,此处的主流程分为两个部分,内存分配和初始化。

3.1.1 内存分配

直接打开代码,如下:

// hotspot/share/gc/shared/memAllocator.cpp
HeapWord* MemAllocator::mem_allocate(Allocation& allocation) const {if (UseTLAB) {HeapWord* result = allocate_inside_tlab(allocation);if (result != NULL) {return result;}}return allocate_outside_tlab(allocation);
}
在这段代码中,我们可以看到一个很耳熟的东西——TLAB(ThreadLocalAllocBuffer),默认情况下TLAB是打开状态,而且其对Java性能提升非常显著。首先,先简单介绍一下TLAB的概念,

因为JVM堆空间是所有线程共享的,因此分配一个对象时会锁住整个堆,这样效率就会比较低下。因此JVM在eden区分配了一块空间作为线程的私有缓冲区,这个缓冲区称为TLAB。不同线程不共享TLAB,因此在TLAB中分配对象时是无需上锁的,从而可以快速分配。

在这段代码中,内存分配划分为了两个部分——TLAB内分配和TLAB外分配。

a. TLAB内分配

我们先来看看TLAB内分配的过程,

// hotspot/share/gc/shared/memAllocator.cpp
HeapWord* MemAllocator::allocate_inside_tlab(Allocation& allocation) const {HeapWord* mem = _thread->tlab().allocate(_word_size);if (mem != NULL) {return mem;}return allocate_inside_tlab_slow(allocation);
}

同样的在TLAB的分配的过程中,也被拆成了两种情况,一种是直接使用线程现有的TLAB来进行分配,代码如下,在下面的这段代码中,我们可以看到TLAB的分配就只是简单地将top指针向上增加了size大小,并且将原先top的位置分配给了obj,因此分配效率可以说是极速了。(事实上,TLAB就是通过start、top、end等指针标记了TLAB的存储信息以及分配空间)

// hotspot/share/gc/shared/threadLocalAllocBuffer.inline.hpp
inline HeapWord* ThreadLocalAllocBuffer::allocate(size_t size) {invariants(); // 校验TLAB是否合法HeapWord* obj = top();if (pointer_delta(end(), obj) >= size) {set_top(obj + size);invariants();return obj;}return NULL;
}

接下来我们来看看TLAB内的慢分配,

// hotspot/share/gc/shared/memAllocator.cpp
HeapWord* MemAllocator::allocate_inside_tlab_slow(Allocation& allocation) const {HeapWord* mem = NULL;ThreadLocalAllocBuffer& tlab = _thread->tlab();if (ThreadHeapSampler::enabled()) {tlab.set_back_allocation_end();mem = tlab.allocate(_word_size);if (mem != NULL) {allocation._tlab_end_reset_for_sample = true;return mem;}}// 如果TLAB的剩余空间大于阈值,则保留TLAB,这样就会进入TLAB外分配。在这里,每次TLAB分配失败,该TLAB都会调大该阈值,以防线程重复分配同样大小的对象if (tlab.free() > tlab.refill_waste_limit()) {tlab.record_slow_allocation(_word_size);return NULL;}// 计算一个新的TLAB的大小,公式=min{可用空间,期待空间+对象占据空间,最大TLAB空间}size_t new_tlab_size = tlab.compute_size(_word_size);// 清理原先的TLAB。会将剩余的未使用空间填充进一个假数组,创造EDEN连续的假象,并且将start、end、top等指针全部置为空tlab.clear_before_allocation();if (new_tlab_size == 0) {return NULL;}// 创建一个新的TLAB,空间可能在min_tlab_size到new_tlab_size之间size_t min_tlab_size = ThreadLocalAllocBuffer::compute_min_size(_word_size);mem = _heap->allocate_new_tlab(min_tlab_size, new_tlab_size, &allocation._allocated_tlab_size);if (mem == NULL) {return NULL;}// 将分配的空间数据全部清0if (ZeroTLAB) {Copy::zero_to_words(mem, allocation._allocated_tlab_size);}// 将mem位置分配word_size大小给objtlab.fill(mem, mem + _word_size, allocation._allocated_tlab_size);return mem;
}

b. TLAB外分配

// hotspot/share/gc/shared/memAllocator.cpp
HeapWord* MemAllocator::allocate_outside_tlab(Allocation& allocation) const {allocation._allocated_outside_tlab = true;HeapWord* mem = _heap->mem_allocate(_word_size, &allocation._overhead_limit_exceeded);if (mem == NULL) {return mem;}NOT_PRODUCT(_heap->check_for_non_bad_heap_word_value(mem, _word_size));size_t size_in_bytes = _word_size * HeapWordSize;_thread->incr_allocated_bytes(size_in_bytes);return mem;
}
这里的核心关注点只有一个——堆内存分配,此处以openjdk11的默认GC——G1为例,看一看分配的过程。
// hotspot/share/gc/g1/g1CollectedHeap.cpp
HeapWord* G1CollectedHeap::mem_allocate(size_t word_size,bool* gc_overhead_limit_was_exceeded) {assert_heap_not_locked_and_not_at_safepoint();if (is_humongous(word_size)) {return attempt_allocation_humongous(word_size);}size_t dummy = 0;return attempt_allocation(word_size, word_size, &dummy);
}

在G1中,对象的分配分为了两种形式:大对象分配、普通分配。由于代码比较长,简单描述大对象的分配过程如下:

  1. 检查是否需要GC,如需要则触发GC,因为大对象消耗堆的速度非常快
  2. 计算大对象需要占据多少区块,尝试分配连续的空闲区块
  3. 如果没有足够的连续空间,找到一块包含空闲和使用中的连续区块,尝试扩展
  4. 尝试GC,如果失败达到阈值则分配失败,进行下一步的普通分配

接下来的普通分配过程较为复杂,本文就不再深入探究了。

3.1.2 初始化对象

代码如下

// hotspot/share/gc/shared/memAllocator.cpp
oop ObjAllocator::initialize(HeapWord* mem) const {mem_clear(mem);return finish(mem);
}其中mem_clear()方法比较简单,就是将对象除头部以外的数据全部置为0,代码如下,
// hotspot/share/gc/shared/memAllocator.cpp
void MemAllocator::mem_clear(HeapWord* mem) const {const size_t hs = oopDesc::header_size();oopDesc::set_klass_gap(mem, 0);Copy::fill_to_aligned_words(mem + hs, _word_size - hs);
}

接下来看看finish()函数,

// hotspot/share/gc/shared/memAllocator.cpp
oop MemAllocator::finish(HeapWord* mem) const {assert(mem != NULL, "NULL object pointer");if (UseBiasedLocking) {oopDesc::set_mark_raw(mem, _klass->prototype_header());} else {oopDesc::set_mark_raw(mem, markOopDesc::prototype());}oopDesc::release_set_klass(mem, _klass);return oop(mem);
}

还记得对象头中有两个属性mark和metadata吗?finish()方法就是设置对象的头部数据。

3.2 注册finalize()方法

由于平时几乎很少用到finalize(),且内部逻辑比较复杂,因此本文暂时不探究finalize的注册机制。

4. 整体流程

整个JVM对象分配的整体流程大致如下,

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/439818.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【HihoCoder - 1831】80 Days(尺取 或 线段树)

题干&#xff1a; 80 Days is an interesting game based on Jules Vernes science fiction "Around the World in Eighty Days". In this game, you have to manage the limited money and time. Now we simplified the game as below: There are n cities on a …

动手学PaddlePaddle(4):MNIST(手写数字识别)

本次练习将使用 PaddlePaddle 来实现三种不同的分类器&#xff0c;用于识别手写数字。三种分类器所实现的模型分别为 Softmax 回归、多层感知器、卷积神经网络。 您将学会 实现一个基于Softmax回归的分类器&#xff0c;用于识别手写数字 实现一个基于多层感知器的分类器&#…

深入探究JVM | klass-oop对象模型研究

├─agent Serviceability Agent的客户端实现 ├─make 用来build出HotSpot的各种配置文件 ├─src HotSpot VM的源代码 │ ├─cpu CPU相关代码&#xff0…

动手学PaddlePaddle(5):迁移学习

本次练习&#xff0c;用迁移学习思想&#xff0c;结合paddle框架&#xff0c;来实现图像的分类。 相关理论&#xff1a; 1. 原有模型作为一个特征提取器: 使用一个用ImageNet数据集提前训练&#xff08;pre-trained)好的CNN&#xff0c;再除去最后一层全连接层(fully-connecte…

Apollo进阶课程㉓丨Apollo规划技术详解——Motion Planning with Environment

原文链接&#xff1a;进阶课程㉓丨Apollo规划技术详解——Motion Planning with Environment 当行为层决定要在当前环境中执行的驾驶行为时&#xff0c;其可以是例如巡航-车道&#xff0c;改变车道或右转&#xff0c;所选择的行为必须被转换成路径或轨迹&#xff0c;可由低级反…

Java对象模型-oop和klass

oop-klass模型 Hotspot 虚拟机在内部使用两组类来表示Java的对象和类。 oop(ordinary object pointer)&#xff0c;用来描述对象实例信息。klass&#xff0c;用来描述 Java 类&#xff0c;是虚拟机内部Java类型结构的对等体 。 JVM内部定义了各种oop-klass&#xff0c;在JV…

【2019南昌邀请赛现场赛 - J】Prefix(STLmap,思维)

题干&#xff1a; yah has n strings <s1​,⋯,sn​>, and he generates a sequence P by two steps: P<s1​,⋯,sn​> Replace each si​ with all prefixes of itself. An example is: the n strings are < aab,ab > first, P < aab,ab > then, P…

Apollo进阶课程㉔丨Apollo 规划技术详解——Motion Planning Environment

原文链接&#xff1a;进阶课程㉔丨Apollo 规划技术详解——Motion Planning Environment 自动驾驶汽车核心技术包括环境感知、行为决策、运动规划与控制等方面。其中&#xff0c;行为决策系统、运动规划与控制系统作为无人驾驶汽车的“大脑”&#xff0c;决定了其在不同交通驾…

JAVA类 与类文件

在一个.java文件中可以有多个同级类, 其修饰符只可以public&#xff0f;abstract&#xff0f;final&#xff0f;和无修饰符1.public修饰的只能有一个,且必须要与文件名相同: 因为jvm虚拟机为了提高查找类的速度&#xff0c;使用import语句导入的时候&#xff0c;只会导入对应空…

【2019南昌邀请赛现场赛 - G】Winner(建图,tarjan缩点 或 贪心)

题目大意&#xff1a; n个人参加竞技比赛。比赛由三种模式a,b,c&#xff0c;每个人在每种模式下有对应的权值a[i]b[i]c[i]。举行n−1场比赛&#xff0c;每场比赛主办方可以选择两个人决斗&#xff0c;能力值低的人淘汰。这样保证n-1场比赛过后&#xff0c;只会有一个winner。q…

一步步编写操作系统 26 打开A20地址线

打开A20地址线 还记得实模式下的wrap-around吗&#xff1f;也就是地址回绕。咱们一起来复习一下。实模式下内存访问是采取“段基址:段内偏移地址”的形式&#xff0c;段基址要乘以16后再加上段内偏移地址。实模式下寄存器都是16位的&#xff0c;如果段基址和段内偏移地址都为1…

【HDU - 6567】Cotree(树形dp,思维)

题干&#xff1a; Avin has two trees which are not connected. He asks you to add an edge between them to make them connected while minimizing the function ∑ni1∑nji1dis(i,j)∑i1n∑ji1ndis(i,j), where dis(i,j)dis(i,j) represents the number of edges of the …

一步步编写操作系统 27 处理器微架构之流水线简介

了解处理器内部硬件架构&#xff0c;有助于理解软件运行原理&#xff0c;因为这两者本身相辅相成&#xff0c;相互依存。就像枪和狙击手&#xff0c;枪的操作和外形设计都是要根据人体工学&#xff0c;让人不仅操作容易&#xff0c;而且携带也要轻便&#xff0c;做到能随时射出…

【2019icpc南京站网络赛 - F】Greedy Sequence(思维,贪心构造,STLset)

题干&#xff1a; Youre given a permutation aa of length nn (1 \le n \le 10^51≤n≤105). For each i \in [1,n]i∈[1,n], construct a sequence s_isi​ by the following rules: s_i[1]isi​[1]i;The length of s_isi​ is nn, and for each j \in [2, n]j∈[2,n], s_…

一步步编写操作系统 28 cpu乱序执行

乱序执行(乱序执行译作异步执行更贴切)&#xff0c;是指在cpu中运行的指令并不按照代码中的顺序执行&#xff0c;而是按照一定的策略打乱顺序执行&#xff0c;也许后面的指令先执行&#xff0c;当然&#xff0c;得保证指令之间不具备相关性。 举个简单的例子&#xff0c;比如如…

【POJ - 1741】Tree(树分治,容斥,点分治,模板题)

题干&#xff1a; Give a tree with n vertices,each edge has a length(positive integer less than 1001). Define dist(u,v)The min distance between node u and v. Give an integer k,for every pair (u,v) of vertices is called valid if and only if dist(u,v) not e…

Apollo进阶课程㉚丨Apollo ROS背景介绍

原文链接&#xff1a;进阶课程㉚丨Apollo ROS背景介绍 ROS是机器人学习和无人车学习最好Linux平台软件&#xff0c;资源丰厚。无人车的规划、控制算法通常运行在Linux系统上&#xff0c;各个模块通常使用ROS进行连接。 上周阿波君为大家详细介绍了「进阶课程㉙Apollo控制技术详…

一步步编写操作系统 29 cpu缓存简介

缓存是20世纪最大的发明&#xff0c;其原理用一些存取速度较快的存储设备做为数据缓冲区&#xff0c;避免频繁访问速度较慢的低速存储设备&#xff0c;归根结底的原因是&#xff0c;低速存储设备是整个系统的瓶颈&#xff0c;缓存用来缓解“瓶颈设备”的压力。 之前介绍实模式…

【CodeForces - 1096D】Easy Problem(dp,思维)

题目大意&#xff1a; 现在有一个由小写字母组成的字符串&#xff0c;去掉这个字符串的第i个位置的字符会有ai的代价。你的任务是去掉这个字符串中的一些字符使得该字符串中不包含子序列hard&#xff0c;且去掉字符的代价之和尽可能小。 输入 第一行一个整数n表示字符串的长…

一步步编写操作系统 30 cpu的分支预测简介

人在道路的分岔口时要预测哪条路能够到达目的地&#xff0c;面对众多选择时&#xff0c;计算机也一样要抉择&#xff0c;毕竟计算机的运行方式是以人的思路来设计的&#xff0c;计算机中的抉择其实就是人在抉择。 cpu中的指令是在流水线上执行。分支预测&#xff0c;是指当处理…