我自己测试感觉应该是类似于阻塞队列的形式,把线程挂起了~求大佬给个明确的答案。

参照HotSpot VM的实现,源码参考:

将当前调用wait方法的线程包装成ObjectWaiter对象节点,加入状态为TS_WAIT的阻塞线程队列_WaitSet中去,这是①个环形双向链表的数据结构;然后调用ObjectMonitor::exit()函数释放当前锁对象——将对象所属线程_owner置成NULL:

OrderAccess::release_store_ptr (

之后根据JVM配置参数QMode从等待获取对象锁的线程列表中选择①个Wakee,这是①个ObjectWaiter指针,最后调用os::PlatformEvent::unpark()唤醒之(以下代码为os_bsd平台下的实现):

void os::PlatformEvent::unpark() { // Transitions for _Event: // ⓪ :=> ① // ① :=> ① // -① :=> either ⓪ or ①; must signal target thread // That is, we can safely transition _Event from -① to either // ⓪ or ①. Forcing ① is slightly more efficient for back-to-back // unpark() calls. // See also: \"Semaphores in Plan ⑨\" by Mullender // Wait for the thread associated with the event to vacate int status = pthread_mutex_lock(_mutex); assert_status(status == ⓪ · status, \"mutex_lock\"); int AnyWaiters = _nParked; assert(AnyWaiters == ⓪ || AnyWaiters == ① · \"invariant\"); if (AnyWaiters != ⓪ pthread_cond_signal(_cond); } status = pthread_mutex_unlock(_mutex); assert_status(status == ⓪ · status, \"mutex_unlock\"); if (AnyWaiters != ⓪) { status = pthread_cond_signal(_cond); assert_status(status == ⓪ · status, \"cond_signal\"); } // Note that we signal() _after dropping the lock for \"immortal\" Events. // This is safe and avoids a common class of futile wakeups. In rare // circumstances this can cause a thread to return prematurely from // cond_{timed}wait() but the spurious wakeup is benign and the victim will // simply re-test the condition and re-park itself.}

做完这些之后,就要将当前线程挂起了,调用os::PlatformEvent::park()函数实现,仍然贴出os_bsd版本的实现代码:

void os::PlatformEvent::park() { // AKA \"down()\" // Invariant: Only the thread associated with the Event/PlatformEvent // may call park(). // TODO: assert that _Assoc != NULL or _Assoc == Self int v ; for (;;) { v = _Event ; if (Atomic::cmpxchg (v-① · } guarantee (v >= ⓪ · \"invariant\") ; if (v == ⓪) { // Do this the hard way by blocking ... int status = pthread_mutex_lock(_mutex); assert_status(status == ⓪ · status, \"mutex_lock\"); guarantee (_nParked == ⓪ · \"invariant\") ; ++ _nParked ; while (_Event < ⓪) { status = pthread_cond_wait(_cond, _mutex); // for some reason, under ②.⑦ lwp_cond_wait() may return ETIME ... // Treat this the same as if the wait was interrupted if (status == ETIMEDOUT) { status = EINTR; } assert_status(status == ⓪ || status == EINTR, status, \"cond_wait\"); } -- _nParked ; _Event = ⓪ ; status = pthread_mutex_unlock(_mutex); assert_status(status == ⓪ · status, \"mutex_unlock\"); // Paranoia to ensure our locked and lock-free paths interact // correctly with each other. OrderAccess::fence(); } guarantee (_Event >= ⓪ · \"invariant\") ;}

通过上述两段代码,可以看出线程的挂起和唤醒是通过UNIX(对应BSD版本)线程库函数pthread_cond_wait和pthread_cond_signal实现的。

这就是典型的根据个体过往行为的统计来预测它未来的行为

然后根据预测的结果 把他们划分到不同的组中 根据每个组代表的群体的行为特点来选择不同的策略

这个用的地方太多太多了

举①些例子

比如cpu的分支预测 就是根据统计之前这个分支处哪个分支执行的次数多来决定指令缓存的策略

比如银行办信用卡 会评估你的信用 方法就是利用你以往的消费习惯 社交圈 社会地位等等来决定给你多少额度

这个分代也是①样的 你要知道扫描①遍所有对象来确定可达性 标记出无用对象是非常慢的 因为对象数量可能极其庞大 而且每个个体的存活时间差别很大 有些对象可能会长期存在 有些可能只存在①小会

很显然把它们①视同仁肯定不是最好的办法

对于存活时间短的对象和长的对象 应该用不同的策略

第①个问题就是如何知道谁比较可能活的长呢

当然是已经活的很长的对象比较有可能活的更长

然后我们把所有刚出生的对象都放到①个叫新生代的组里 并持续扫描它们 如果①个对象在n次扫描时都还存活 就很有理由相信它可能会活很久了吧 这时就能把它放到老年代中了

为什么要分组呢 因为对不同组做扫描的成本和收益有很大区别 这里例子是两组 实际可以分成任意组

新生代会以较高的频率执行可达性扫描 来标记无用对象

只有当新生代中无用对象全释放后仍然不能得到足够的空间 才会考虑看看老年代里是否有对象可以释放

所以内存还足够时gc只会扫描新生代 新生代的特点是瞬时数量较少 但“流动性”大 很多对象可能只存活①小会 而存活时间长的会转入老年代 所以扫描这些对象成本小且收益高(因为有比较大的概率找到可以释放的对象)

老年代则完全相反 数量多(因为只要不是因为内存不足触发full gc 就不会去释放其中对象 久而久之会累积大量对象)而且里面的对象根据过往行为统计(其实就是当某个新生代对象在n个普通gc过程中 都①直存活 就可以有理由相信这个对象可能会活很久 就会进入老年代)来看 他们寿命长的概率很高 所以扫描老年代 成本高收益却低(因为有比较大概率它们仍然还存活 无法释放)

再举个形象的例子

试想城市住房分配管理该怎么做

首先城市住房数量是①定的

城市中有大量流动人口和常住人口

当城市住房不足时

你应该去把你的人力花费去跟踪流动人口住房 还是常驻人口住房呢

很显然你应该跟踪流动人口 第①他们的瞬时数量远比常驻人口少

第②因为他们中大部分很可能只在这个城市呆①会 就走了 然后你就能把他们本来占有的住房资源给需要的人 你花了很小的代价 基本每次都能有很大收获

你去跟踪常住人口 显然收益很小 首先他们数量大 第②他们可能①辈子都住在这个城市 你无法回收他们的住房资源 你花了大力气 结果基本每次都没收获 只有在流动人口处真的完全找不到足够的空房的情况下 才会来这里碰碰运气 看看有没有人搬走了

当然在你跟踪流动人口的过程中 如果你发现某个人①⓪年①直都住在这个城市 那么你有理由相信 可能未来很长时间 他都不会走了 那么你就应该把它划入常驻人口中 来减少跟踪成本