博客首页
每日早报
论坛首页
科普
量子力学
中文文档
logstash 中文文档
Framework Repositories
vue3.0文档
发布
发布文章
创建话题
创建板块
发布帖子
登录
注册
博客首页
每日早报
论坛首页
科普
量子力学
中文文档
logstash 中文文档
Framework Repositories
vue3.0文档
登录
注册
找回密码
3.4W+
381
1
更多资料
搜索内容
mysticalycc
管理员
管理员
超级版主
简短的个人签名
关注
私信
文章
126
收藏
2
评论
14
板块
2
帖子
5
粉丝
1
7个月前
因为OutOfMemoryError是可以catch的。catch之后吞掉的话程序还能试着继续运行。 例如说以前见过的一个案例是:一个Java服务器端应用,有段代码没写对导致有一个线程在疯狂创建大数组对象——直到OOM。 这个线程注册的uncaught exception handler捕获到了这个异常,记录了日志,然后就把这个异常吞掉了。 这样还能继续正常跑下去是因为:只是一个创建很大的数组对象的请求失败了而已,而出错的那个方法由于异常处理已经被退出了,程序的其它部分并没有受影响。
评论于:
JAVA发生OOM后还能运行么?
8个月前
作者:RednaxelaFX 链接:https://www.zhihu.com/question/35777031/answer/64575683 来源:知乎 太长不读(TL;DR)版定性分析: 对于解释器来说,解释器开销主要来自解释器循环(fetch-decode/dispatch-execute循环)中的fetch与decode/dispatch,反而真正用于执行程序逻辑的execute部分并不是大头。每条指令都要经历一轮FDX循环。因而减少指令条数可以导致F与D的开销减少,于是就提升了解释器速度。 基于栈与基于寄存器的指令集,用在解释器里,笼统说有以下对比: 从源码生成代码的难度:基于栈 < 基于寄存器,不过差别不是特别大 表示同样程序逻辑的代码大小(code size):基于栈 基于寄存器 简易实现中数据移动次数(data movement count):基于栈 > 基于寄存器;不过值得一提的是实现时通过栈顶缓存(top-of-stack caching)可以大幅降低基于栈的解释器的数据移动开销,可以让这部分开销跟基于寄存器的在同等水平。请参考另一个回答:寄存器分配问题? - RednaxelaFX 的回答 采用同等优化程度的解释器速度:基于栈 < 基于寄存器 交由同等优化程度的JIT编译器编译后生成的代码速度:基于栈 === 基于寄存器 因而,笼统说可以有以下结论:要追求 尽量实现简单:选择基于栈 传输代码的大小尽量小:选择基于栈 纯解释执行的解释器的速度:选择基于寄存器 带有JIT编译器的执行引擎的速度:随便,两者一样;对简易JIT编译器而言基于栈的指令集可能反而更便于生成更快的代码,而对比较优化的JIT编译器而言输入是基于栈还是基于寄存器都无所谓,经过parse之后就变得完全一样了。 =========================================== JVM的选择 JVM当初设计的时候非常重视代码传输和存储的开销,因为假定的应用场景是诸如手持设备(PDA)、机顶盒之类的嵌入式应用,所以要代码尽量小;外加基于栈的实现更简单(无论是在源码编译器的一侧还是在虚拟机的一侧),而且主要设计者James Gosling的个人经验上也对这种做法非常熟悉(例如他之前实现过PostScript的虚拟机,也是基于栈的指令集),所以就选择了基于栈。 回头看,这个决定也还算OK,可惜的是基于栈的设计并没有让Java的代码传输大小减小多少。 这是因为:Java代码是以Class文件为单位来传输与存储的。Java从设计之初就非要支持分离编译(separate compilation)与按需动态类加载(on-demand dynamic class loading),导致Java的Class文件必须独立的(self-contained)——每个Class文件必须自己携带自己的常量池,其主要信息是字符串与若干其它常量的值,以及用于符号链接的符号引用信息(symbolic reference)。 如果大家关注过Class文件的内容的话,会知道其实通常Class文件里表示程序逻辑的代码部分——“字节码”——只占Class文件大小的小头;而大头都被常量池占了。而且多个Class文件的常量池内容之间常常有重叠,所以当程序涉及多个Class文件时,就容易有冗余信息,不利于减少传输/存储代码的大小。 大家或许还记得Google在Google I/O 2008的 Dalvik VM Internals 演讲里,Dan得意的介绍到Dalvik的Dex格式在未压缩的情况下都比压缩了的JAR文件还小么? (下面数据引用自演示稿第22页) common system libraries (U) 21445320 — 100% (J) 10662048 — 50% (D) 10311972 — 48% web browser app (U) 470312 — 100% (J) 232065 — 49% (D) 209248 — 44% alarm clock app (U) 119200 — 100% (J) 61658 — 52% (D) 53020 — 44% (U) uncompressed jar file (J) compressed jar file (D) uncompressed dex file Dan准确的介绍了Dex体积更小的原因:一个Dex相当于一个或多个JAR包,里面可以包含多个Class文件对应的内容。一个Dex文件里的所有Class都共享同一个常量池,因而不会像Class文件那样在多个常量池之间有冗余。这样Dex文件就等同于在元数据层面上对JAR文件做了压缩,所以前者比后者更小。 但是很明显后来有不少群众不明就里,以为Dalvik的Dex文件更小是跟基于寄存器的选择相关的——其实正好相反,在字节码部分,Dalvik的字节码其实比JVM的字节码更大。只要仔细读了同一个演示稿就会看到 (第37页) The Register Machine 30% fewer instructions 35% fewer code units 35% *more* bytes in the instruction stream but we get to consume two at a time 而其实Java世界里早就发现了这个问题,并且有一个传输/存储格式跟Dex文件应用了类似的压缩思路: pack200 格式。它也是把JAR包里的所有Class文件的常量池汇总为一个,以此压缩掉其中的冗余。以pack200格式打包的Java应用就不会比用Dex格式打包大了。 之前在Oracle的HotSpot JVM组工作时,跟同事们讨论一些与此相关的话题,大家的共识是如果JVM的指令集是在20年后的今天设计的话,很有可能也会跟现在的潮流相似,基于寄存器。不过就算保持现在这样用基于栈的指令集也挺好。 目前Class文件/JAR文件的真正让大家头疼的问题并不是基于栈还是基于寄存器的设计,而是别的地方,例如: Class文件方面: 各种人为的大小限制都跟不上时代了,例如每个方法的字节码最多65535字节; 要生成StackMapTable太闹心; 常量池的组织方式不便于直接从文件映射到内存然后高效执行;可以有更高效的组织方式。 JAR文件方面: 如前文提的,多个Class文件之间的常量池冗余; 缺少带有强语义的描述模块的信息; 等等… 一切都有待未来版本的Java继续改进。 =========================================== Lua的选择 官方版Lua的设计可以从经典文章 The Implementation of Lua 5.0 一窥究竟。 它提到: For ten years (since 1993, when Lua was first released), Lua used a stack-based virtual machine, in various incarnations. Since 2003, with the release of Lua 5.0, Lua uses a register-based virtual machine. 也就是说Lua 5.0之前的Lua其实是用基于栈的指令集,到5.0才改为用基于寄存器的。 接下来: Register-based code avoids several “push” and “pop” instructions that stack-based code needs to move values around the stack. Those instructions are particularly expensive in Lua, because they involve the copy of a tagged value, as discussed in Section 3. So, the register architecture both avoids excessive copying of values and reduces the total number of instructions per function. Davis et al. [6] argue in defense of register-based virtual machines and provide hard data on the improvement of Java bytecode. Some authors also defend register-based virtual machines based on their suitability for on-the-fly compilation (see [24], for instance). 所以Lua 5.0开始改为选用基于寄存器的指令集设计,主要是出于 (1) 减少数据移动次数,降低由数据移动带来的拷贝开销,和 (2) 减少虚拟指令条数 这两点考虑。 从纯解释器的角度看,这两点考虑是非常合理的。 不过如果官方版Lua有JIT编译器的话,它就没必要这么做了——基于栈和基于寄存器的指令集只要经过合理的编译,得到的结果会是一模一样的。 至于LuaJIT, LuaJIT 1.x的字节码设计源于Lua 5.1.x系列,因而也是基于寄存器的; LuaJIT 2.x系列的字节码虽然重新设计了,但应该还是受到之前设计的影响而继续采用了基于寄存器的设计。像Mike Pall那种想榨干一切性能的思路,即便有优化的JIT编译器,也还是想让解释器尽量快的心情也是可以理解的。
评论于:
栈式虚拟机和寄存器式虚拟机?
8个月前
寄存器线程私有的
评论于:
volatile单核的情况下能保证线程安全么
8个月前
volatile:从最终[汇编语言]从面来看,volatile使得每次将i进行了修改之后,增加了一个内存屏障lock addl $0x0,(%rsp)保证修改的值必须刷新到[主内存]才能进行[内存屏障]后续的指令操作。但是内存屏障之前的指令并不是原子的 代码例子 [代码] 指令“lock; addl $0,0(%%esp)”表示加锁,把0加到栈顶的[内存单元],该指令操作本身无意义,但这些指令起到内存屏障的作用,让前面的指令执行完成。具有XMM2特征的CPU已有内存屏障指令,就直接使用该指令 volatile方式的[i++],总共是四个步骤: i++实际为load、Increment、store、Memory Barriers 四个操作。 内存屏障是[线程安全]的,但是内存屏障之前的指令并不是.在某一时刻线程1将i的值load取出来,放置到[cpu缓存]中,然后再将此值放置到[寄存器]()A中,然后A中的值自增1(寄存器A中保存的是[中间值],没有直接修改i,因此其他线程并不会获取到这个自增1的值)。如果在此时[线程2]也执行同样的操作,获取值i==10,自增1变为11,然后马上刷入主内存。此时由于线程2修改了i的值,实时的线程1中的i==10的值缓存失效,重新从主内存中读取,变为11。接下来[线程1]恢复。将自增过后的A[寄存器值]11赋值给cpu缓存i。这样就出现了线程安全问题。 [克里斯泡] 既然他从主存中读取了最新的11那什么不会把这个值放到寄存器中加一呢,而是把之前的值直接写会主存? [千帆] 这个涉及到MESI缓存一致性协议,为了保证不同缓存的一致性,一旦某个线程执行了修改(Modify)操作,会立刻使其他层级的缓存失效(invaild),然后立刻从主存中读取最新值~协议就是这样规定的 --- [明明] 寄存器里面的11已经是用缓存中的10自加后的结果了,然后才是缓存中的10失效,重新从内存读取11,这个重新读取的11则会进行下一次自加。不知道我这样的理解是否正确。 --- [明明] 不会自加的,自加这个操作已经完成了,只是cpu缓存的值会更新 --- [明明] 我知道你的疑问在哪里。作者的意思是,一个线程会有3个操作内存:1主存2缓存3寄存器 只有从缓存读到寄存器这一步才会检查10是否无效。一但读入寄存器,就不会进行检查了,即时已经被无效。 --- 突然就懂了! --- [千帆] 你好,我问下,这里说的会立刻使其他层级的缓存失效(invaild),然后立刻从主存中读取最新值,然后会再进行+1计算,写入主存吗,还是只是读取内存的值,不再进行计算了呢 --- [田林轩] 立刻从主存中读取最新值不再进行计算了! --- [黄花小伙子] cpu执行编译后的代码/指令也是自上而下执行有个固定顺序,线程1进行自增完成后发现别的线程已经修改且刷新了值,线程1这时候只能从主存中获取最新值,然后继续后面的回写操作,不可能获取最新值后把这段自增的代码再跑一边的,因为这样从程序逻辑上会有更大问题,比如100个线程一起自增,线程1一直很倒霉,它每次自增完后都发现其他线程刚更新了数据,它就只能重新获取最新数据然后重新跑,在其他线程停止自增之前它永远都结束不了运行 --- [田林轩] 寄存器的值应该也会更新的,不过不影响,如果i已经被一个线程读到栈顶的话(栈是线程私有的),i在寄存器中的值发生变化也无所谓,因为自增操作是在栈上执行完然后再写回寄存器到缓存到内存。栈上的操作不会被改变。
评论于:
volatile为什么不能保证原子性?
9个月前
就是我写的
评论于:
【性能测试篇】你现在用的SIMPLEDATEFORMAT类性能最差!
1年前
嘿嘿嘿
评论于:
下面哪谢书是你看过的
1年前
啥破玩意
评论于:
下面哪谢书是你看过的
1年前
1
评论于:
链路追踪实现原理
3年前
( ఠൠఠ )ノ
[图片]
评论于:
基础
3年前
牛逼
[图片]
评论于:
Java8思维导图
加载更多
发布文章
创建话题
创建板块
发布帖子
登录
没有帐号?立即注册
用户名或邮箱
登录密码
记住登录
找回密码
登录
注册
已有帐号,立即登录
设置用户名
设置密码
重复密码
注册
[图片]