Coder Social home page Coder Social logo

jvm_book's Introduction

《深入理解Java虚拟机(第3版)》

广告
凤凰架构:构建可靠的大型分布式系统》:https://github.com/fenixsoft/awesome-fenix

我的一本新书,也是一部以“讨论如何构筑一套可靠的分布式大型软件系统”为主题的免费开源文档,如对您有用,望不吝给个Star


  本工程为《深入理解Java虚拟机(第3版)》书中的样例代码,以方便读者自行测试。部分代码需要在特定的虚拟机版本、参数下运行,在代码前均已“VM Args”的注释进行标注。

  书中的勘误也会在本文中持续更新,读者可通过issues提交新的勘误,如对新版有任何建议,也欢迎以issues或任何其他您方便的形式提出。

勘误列表:

  • Page 120:图3-23应该是【Parallel】,但是印了【Paraller】,差了一个字母

  • Page 219:Java代码在进行Javac编译的时候,并不像C和C++那样有“连接”这一步骤,而是在虚拟机加载Class文件的时候进行动态连接(具体见【第7章】)。
    更正:Java代码在进行Javac编译的时候,并不像C和C++那样有“连接”这一步骤,而是在虚拟机加载Class文件的时候进行动态连接(具体见【第8章】)。

  • Page 232:表6-13,JDK 8中新增的属性,用于支持(编译时加上-parameters参数)将【方法名称】编译进Class文件中,并可运行时获取。此前要获取【方法名称】(典型的如IDE的代码提示)只能通过JavaDoc中得到。
    更正:JDK 8中新增的属性,用于支持(编译时加上-parameters参数)将【方法参数名称】编译进Class文件中,并可运行时获取。此前要获取【方法参数名称】(典型的如IDE的代码提示)只能通过JavaDoc中得到。

以下勘误内容已在第10次重印版(2021-12-14日)修正


  • Page 223:表6-6的描述列中,有三处【CONSTANT_NameAndType】,均应为:【CONSTANT_NameAndType_info】

  • page 279,那就可能造成多个【进程】阻塞
    更正:那就可能造成多个【线程】阻塞

  • Page 295:图8-1中,【操作栈】应为【操作数栈】,此格的英文【Oper and Stack】应为【Operand Stack】(Operand是一个完整单词)

  • Page 464:如【图12-11】所示
    更正:如【图12-7】所示

  • 书背面#148 中有同学反映:有评语是“望其项背”,“望其项背”是追得上赶得上,他这意思就相当于其他书可以追得上和赶得上了。
    请编辑从语文专业角度看看这个issue是否正确。

以下勘误内容已在第9次重印版(2021-9-23日)修正(第8次重影因时间关系未修正任何勘误)


  • 前言VII 为了实现JDK11新增的【嵌套内】
    更正:为了实现JDK11新增的【嵌套类】

  • 目录XIV,Page164 4.3.3 VisualVM:多合 - 故障处理工具中”的【多合 - 】应该是【多合一】
    这个在原稿上是多合一,但印刷后确实怎么看都像是“多合 - ”,请编辑看看。

  • Page 34:其中lib的可选值包括有:【boot-jd】、freetype、cups、x、alsa、libffi、jtreg、libjpeg、giflib、libpng、lcms、zlib
    更正:其中lib的可选值包括有:【boot-jdk】、freetype、cups、x、alsa、libffi、jtreg、libjpeg、giflib、libpng、lcms、zlib

  • Page 38:图1-11 在【NetBeans】中创建HotSpot项目(2)
    更正:图1-11 在【CLion】中创建HotSpot项目(2)

  • Page 124:JDK 9之后使用【-X-log:gc*】
    更正:JDK 9之后使用【-Xlog:gc*】

  • Page 202:如果使用【客户端】模式的虚拟机启动Eclipse将会使用到C2编译器
    更正:如果使用【服务端】模式的虚拟机启动Eclipse将会使用到C2编译器

  • Page 235:常量表部分的输出见代码清单【6-1】,因版面原因这里省略掉
    更正:常量表部分的输出见代码清单【6-2】,因版面原因这里省略掉

  • Page 246:常量池在该索引【出】必须是以下结构之一
    更正:常量池在该索引【处】必须是以下结构之一

  • Page 259:代码部分排版有问题:

    FromTo  Target  Type
       4    10      13   any
      13    16      13   any
    

    应为:

    From    To  Target  Type
       4    10      13   any
      13    16      13   any
    
  • Page 282:代码清单7-9所示为【java.lang.ClassLoader.getClassLoader()】方法的代码片段
    更正:代码清单7-9所示为【java.lang.Class.getClassLoader()】方法的代码片段

  • Page 282:代码清单7-9 【ClassLoader.getClassLoader()】方法的代码片段
    更正:代码清单7-9 【Class.getClassLoader()】方法的代码片段

  • Page 286:在OSGI环境下,类加载器【不再】双亲委派模型推荐的树状结构
    更正:在OSGI环境下,类加载器【不再使用】双亲委派模型推荐的树状结构

  • Page 378:我们就只有使用【JSR-296】中定义的插入式注解处理器API来对Java编译子系统的行为施加影响
    更正:我们就只有使用【JSR-269】中定义的插入式注解处理器API来对Java编译子系统的行为施加影响

以下勘误内容已在第7次重印版(2021-1-11日)修正


  • #7-1 Page 99:作为CMS收集器的替代者和继承人,设计者们希望做出一款能够建立起“【停顿时间模型】”(Pause Prediction Model)的收集器,【停顿时间模型】的意思是能够支持指定在一个长度为M毫秒的时间片段内
    全书术语统一为:作为CMS收集器的替代者和继承人,设计者们希望做出一款能够建立起“【停顿预测模型】”(Pause Prediction Model)的收集器,【停顿预测模型】的意思是能够支持指定在一个长度为M毫秒的时间片段内

  • #7-1 Page 85 & 87:两处代码部分:
    CARD_TABLE [this address >> 9] = 0;if (CARD_TABLE [this address >> 9] != 0) CARD_TABLE [this address >> 9] = 0;
    由于书中文字是以“1标志变脏,0标志未变脏”来描述的,代码中应该统一起来,因此修改为:
    CARD_TABLE [this address >> 9] = 1;if (CARD_TABLE [this address >> 9] != 1) CARD_TABLE [this address >> 9] = 1;

  • #7-2 Page 108:我们再来聊一下Shenandoah用以支持【并行整理】的核心概念
    更正:我们再来聊一下Shenandoah用以支持【并发整理】的核心概念

  • #7-3 Page 128:表3-4,最后一行“ParallelGCThreads”的重复了,请编辑删除掉此行,另外这个表格中有两个参数是以“=n”结尾的,为了格式统一,请将“=n”删除掉,仅保留参数名称即可。

  • #7-4 Page 182:服务器的硬件为四路【志强】处理器
    更正:服务器的硬件为四路【至强】处理器

  • #7-5 Page 230:脚注:Java代码的方法特征签名只包括了方法名称、【参数顺序及参数类型】
    更正:Java代码的方法特征签名只包括了方法名称、【参数数量、参数顺序及参数类型】

  • #7-6 Page 268:加载阶段既可以使用Java虚拟机里内置的【引导类加载器】来完成
    全书术语统一为:加载阶段既可以使用Java虚拟机里内置的【启动类加载器】来完成

  • #7-7 Page 268:Java虚拟机将会把数组C标记为与【引导类加载器】关联。
    全书术语统一为:Java虚拟机将会把数组C标记为与【启动类加载器】关联。

  • #7-8 Page 282:如果需要把加载请求委派给【引导类加载器】去处理
    全书术语统一为:如果需要把加载请求委派给【启动类加载器】去处理

  • #7-9 Page 282:其中的注释和代码实现都明确地说明了以null值来代表【引导类加载器】的约定规则
    全书术语统一为:其中的注释和代码实现都明确地说明了以null值来代表【启动类加载器】的约定规则

  • #7-10 Page 448:这种操作相当于对缓存中的变量做了一次前面介绍Java内存【模式】中所说的“store和write”操作
    更正:这种操作相当于对缓存中的变量做了一次前面介绍Java内存【模型】中所说的“store和write”操作

  • #7-11 Page 479:如适应性自旋(Adaptive Spinning)、锁削除(Lock Elimination)、【锁膨胀】(Lock Coarsening)、轻量级锁(Lightweight Locking)、偏向锁(Biased Locking)等等
    更正:如适应性自旋(Adaptive Spinning)、锁削除(Lock Elimination)、【锁粗化】(Lock Coarsening)、轻量级锁(Lightweight Locking)、偏向锁(Biased Locking)等等

  • #7-12 Page 483:【同时】使用CAS操作把获取到这个锁的线程的ID记录在对象的Mark Word之中
    更正:【并】使用CAS操作把获取到这个锁的线程的ID记录在对象的Mark Word之中

以下勘误内容已在第6次重印版(2020-11-5日)修正


  • #6-1 Page 46:到了JDK 7的HotSpot,已经把原本放在永久代的字符串常量池、静态变量等【移出】
    更正:到了JDK 7的HotSpot,已经把原本放在永久代的字符串常量池、静态变量等【移至Java堆中,在4.3.1节会通过实验验证这一点】

  • #6-2 Page 70:在虚拟机栈(栈帧中的本地变量表)中引用的对象,譬如【各个线程被调用的方法堆栈中使用到的参数、局部变量、临时变量等】
    有同学反应太过拗口,修改为:在虚拟机栈(栈帧中的本地变量表)中的引用的对象,譬如【当前正在运行的方法所使用到的参数、局部变量、临时变量等】

  • #6-3 Page 94:-XX:GCTimeRatio参数的值则应当是一个大于0小于100的整数,也就是垃圾收集时间占总时间的比率,这个参数设定为N的话,表示用户代码执行时间与总执行时间之比为N:N+1。譬如把此参数设置为19,那允许的最大垃圾收集时间就占总时间的5%(即1 /(1+19)),默认值为99,就是允许最大1%(即1 /(1+99))的垃圾收集时间。
    整一段修正为:-XX:GCTimeRatio参数的值应为设置为一个正整数,表示用户期望虚拟机消耗在GC上的时间不超过程序运行时间的1/(1+N)。默认值为99,含义是尽可能保证应用程序执行的时间为收集器执行时间的99倍,也即是收集器的时间消耗不超过总运行时间的1%。

  • #6-4 Page 243:表6-26,第6行:ACC_INTERFACE 【0x0020】
    更正:ACC_INTERFACE 【0x0200】

  • #6-5 Page 256:如果v在目标类型T(int或long)的表示范围之【类】
    更正:如果v在目标类型T(int或long)的表示范围之【内】

  • #6-6 Page 274:接着由虚拟机生成一个代表该数组维度和元素的【数组对象】。
    更正:接着由虚拟机生成一个代表该数组维度和元素的【数组类型】。

  • #6-7 Page 317:本例中为一项【CONSTANT_InterfaceMethodref_info】常量
    更正:本例中为一项【CONSTANT_Methodref_info】常量

  • #6-8 Page 331:图8-6 执行偏移地址为【1】的指令的情况
    更正:图8-6 执行偏移地址为【2】的指令的情况

  • #6-9 Page 385:代码清单10-18 第13行需修改方法名称,以符合输出结果:
    protected void BADLY_NAMED_CODE() { 修改为 protected void Test() {

  • #6-10 Page 392:脚注:还有一个不太上台面但其实是Java虚拟机必须支持循环体触发编译的【理由,是】诸多跑分软件的测试用例通常都属于第二种,如果不去支持跑分会显得成绩很不好看。
    更正为:还有一个不太上台面但其实是Java虚拟机必须支持循环体触发编译的【理由是:】诸多跑分软件的测试用例通常都属于第二种,如果不去支持跑分会显得成绩很不好看。

  • #6-11 Page 419:p.y = 42
    更正:p.y = 42【;】

  • #6-12 Page 420:int py = 42
    更正:int py = 42【;】

  • #6-13 Page 431:与【图11-11】和【图11-12】所示相比,虽然没有了箭头
    更正:与【图11-13】和【图11-14】所示相比,虽然没有了箭头

  • #6-14 Page 443:Java内存模型的基础设计并未改变,即使是【这四操作种】对普通用户阅读使用起来仍然是并不方便。
    更正:Java内存模型的基础设计并未改变,即使是【这四种操作】对普通用户阅读使用起来仍然是并不方便。

  • #6-15 Page 467:在【第10章】里我们讲解“final关键字带来的可见性”时曾经提到过这一点
    更正:在【第12章】里我们讲解“final关键字带来的可见性”时曾经提到过这一点

  • #6-16 Page 472:在【第10章】中我们知道了主流Java虚拟机实现中,Java的线程是映射到操作系统的原生内核线程之上的
    更正:在【第12章】中我们知道了主流Java虚拟机实现中,Java的线程是映射到操作系统的原生内核线程之上的

  • #6-17 Page 480:锁削除是指虚拟机即时编译器在运行时,对一些代码中要求同步,但是被检测到不可能存在共享数据竞争的锁进行削除。
    这句话有读者反应不通顺,整句修改为:锁消除是指虚拟机即时编译器在运行时检测到某段需要同步的代码根本不可能存在共享数据竞争而实施的一种对锁进行消除的优化策略。

以下勘误内容已在第5次重印版(2020-9-11日)修正


  • #5-1 Page 51:以下代码清单2-2为HotSpot虚拟机代表Mark Word中的代码(【markOop.cpp】)注释片段
    更正:以下代码清单2-2为HotSpot虚拟机代表Mark Word中的代码(【markOop.hpp】)注释片段。另,下面代码清单2-2也应更正为“代码清单2-2 【markOop.hpp】片段”

  • #5-2 Page 72:要真正宣告一个对象死亡,【至少要】经历两次标记过程
    更正:要真正宣告一个对象死亡,【最多会】经历两次标记过程

  • #5-3 Page 78:图3-2、图3-3,原稿中第2行2列、3行2列、3行4列都是“可回收”对象的颜色,排版重画的时候颜色搞错了,麻烦编辑下次印刷中修改过来。

  • #5-4 Page 79:发生垃圾【搜集】时,将Eden和Survivor中仍然存活的对象一次性过拷贝到另外一块Survivor空间上
    更正:发生垃圾【收集】时,将Eden和Survivor中仍然存活的对象一次性过拷贝到另外一块Survivor空间上

  • #5-5 Page 94:-XX:GCTimeRatio参数的值则应当是一个大于0小于100的整数,也就是垃圾收集时间占总时间的比率,【相当于是吞吐量的倒数】。
    更正:-XX:GCTimeRatio参数的值则应当是一个大于0小于100的整数,也就是垃圾收集时间占总时间的比率,【这个参数设定为N的话,表示用户代码执行时间与总执行时间之比为N:N+1】

  • #5-6 Page 95:Parallel Old是Parallel Scavenge收集器的老年代版本,支持多线程【并发收集】
    更正:Parallel Old是Parallel Scavenge收集器的老年代版本,支持多线程【并行收集】

  • #5-7 Page 109:一旦用户程序访问到归属于旧对象的内存空间就会产生自陷【中段】
    更正:一旦用户程序访问到归属于旧对象的内存空间就会产生自陷【中断】

  • #5-8 Page 128:JDK 9之前虚拟机运行在Server模式下的默认值,打开此开关后,使用【Parallel Scavenge + Serial Old(PS MarkSweep)的】收集器组合进行内存回收
    更正:JDK 9之前虚拟机运行在Server模式下的默认值,打开此开关后,使用【Parallel】收集器组合进行内存回收

  • #5-9 Page 215:Class文件是一组以【8个字节】为基础单位的二进制流,各个数据项目严格按照顺序紧凑地排列在文件之中,中间没有添加任何分隔符,这使得整个Class文件中存储的内容几乎全部是程序运行的必要数据,没有空隙存在。当遇到需要占用【8个字节】以上空间的数据项时,则会按照高位在前 的方式分割成若干个【8个字节】进行存储。
    更正:Class文件是一组以【字节】为基础单位的二进制流,各个数据项目严格按照顺序紧凑地排列在文件之中,中间没有添加任何分隔符,这使得整个Class文件中存储的内容几乎全部是程序运行的必要数据,没有空隙存在。当遇到需要占用【单个字节】以上空间的数据项时,则会按照高位在前 的方式分割成若干个【字节】进行存储。

  • #5-10 Page 259:3 monitorenter // 以【栈定】元素(即f)作为锁,开始同步
    更正:3 monitorenter // 以【栈顶】元素(即f)作为锁,开始同步

  • #5-11 Page 265:上述代码运行之后,只会输出“SuperClass init!”
    更正:上述代码运行之后,【除value的值外,】只会输出“SuperClass init!”

  • #5-12 Page 280:成为了Java技术体系中一块重要的基石,可谓是【失之桑榆,收之东隅】。
    更正:成为了Java技术体系中一块重要的基石,可谓是【失之东隅,收之桑榆】。

  • #5-13 Page 307:'a'除了可以代表一个【字符串】
    更正:'a'除了可以代表一个【字符】

  • #5-14 Page 392:脚注:是诸多跑分软件的测试【用力】通常都属于第二种,如果不去支持跑分会显得成绩很不好看。
    更正:是诸多跑分软件的测试【用例】通常都属于第二种,如果不去支持跑分会显得成绩很不好看。

  • #5-15 Page 432:在Outline视图中找到创建理想图的方法是【greateGraph()】
    更正:在Outline视图中找到创建理想图的方法是【createGraph()】(这个笔误在432页一共有3处)

  • #5-16 Page 472:脚注2:由于本书的主题是Java虚拟机【和】不是Java并发编程
    更正:由于本书的主题是Java虚拟机【而】不是Java并发编程

  • #5-17 Page 480:对一些代码要求同步,但是【对被检测】到不可能存在共享数据竞争的锁进行消除
    更正:对一些代码要求同步,但是【虚拟机又检测】到不可能存在共享数据竞争的锁进行消除

以下勘误内容已在第4次重印版(2020-6-10日)修正


  • #4-1 Page 25:即不能动态加载其他【编译器】不可知的代码和类库
    更正:即不能动态加载其他【编译期】不可知的代码和类库

  • #4-2 Page 30:JDK 7则【还完全】处于研发状态的半成品。
    更正:JDK 7则【完全是】处于研发状态的半成品。

  • #4-3 Page 38:由于JDK12已EOL,RedHat的GitHub上目前只维护8、11两个LTS和13这个正在半年支持期内的JDK版本,JDK12的Repo已经关闭(但从Git的History中挖掘出来是很容易的),为了便于读者获取 CMakeLists.txt,我上传了一份到本仓库中。此文件版权归原作者所有。(编辑在更新勘误时可跳过此条)

  • #4-4 Page 48:Java是一门面向对象的编程语言,Java程序运行过程中【无时无刻都】有对象被创建出来
    更正:Java是一门面向对象的编程语言,Java程序运行过程中【每时每刻都】有对象被创建出来

  • #4-5 Page 58-60:代码清单2-4、2-5的输出,在代码中定义的方法名是“stackLeak()”,在输出日志中看到的堆栈的方法名是“leak()”。这个是代码粘贴后整理编辑过方法名,不影响意思表达。(方法名在日志、代码中出现次数较多,编辑处理这条勘误时请单独联系我提供更正详情)

  • #4-6 Page 80:HotSpot虚拟机里面关注吞吐量的Parallel 【Scavenge】收集器是基于标记-整理算法的。
    更正:HotSpot虚拟机里面关注吞吐量的Parallel 【Old】收集器是基于标记-整理算法的。

  • #4-7 Page 97:并发回收时垃圾收集线程【只占用不超过25%】的处理器运算资源,并且会随着处理器核心数量的增加而下降。
    更正:并发回收时垃圾收集线程【占用不少于25%】的处理器运算资源,并且会随着处理器核心数量的增加而下降。

  • #4-8 Page 105:图3-14中第3行,G1收集器的Compact阶段应该为【浅色】,这个我交上去的原图是正确的,但编辑重新画图时候涂层了深色。请编辑更新新版的时候注意一下。

  • #4-9 Page 106:在回收时通过这张表格就可以得出哪些Region之间产生了【跨代引用】。
    更正:在回收时通过这张表格就可以得出哪些Region之间产生了【跨Region的引用】。

  • #4-10 Page 107:图3-15中,两个X的位置打错了,应该在5行3列、3行1列处打X

  • #4-11 Page 106:降低了处理跨代指针时的记忆集维护消耗,也降低了伪共享问题(【见3.4.4节】)的发生概率。
    更正:降低了处理跨代指针时的记忆集维护消耗,也降低了伪共享问题(【见3.4.5节】)的发生概率。

  • #4-12 Page 134:如果在Survivor空间中【相同年龄】所有对象大小的总和大于Survivor空间的一半
    更正:如果在Survivor空间中【低或等于某个年龄的】所有对象大小的总和大于Survivor空间的一半

  • #4-13 Page 134:满足【同年】对象达到Survivor空间的一半规则
    更正:满足【低于或等于某年龄的】对象达到Survivor空间的一半规则

  • #4-14 Page 162-163:代码中类名“SynAddRunalbe”中英文“Runalbe”为拼写错误,应为“SynAddRunnable”,代码中一共有4处,图片中有1处。

  • #4.15 Page 239:Exceptions属性的作用是列举出方法中可能抛出的受查异常(Checked 【Excepitons】)
    更正:Exceptions属性的作用是列举出方法中可能抛出的受查异常(Checked 【Exceptions】)

  • #4.16 Page 265:创建动作由字节码指令【newarray】触发
    更正:创建动作由字节码指令【anewarray】触发

  • #4-17 Page 272:【JDK 7及之前】,HotSpot使用永久代来实现方法区时,实现是完全符合这种逻辑概念的;【而在JDK 8及之后】,类变量则会随着Class对象一起存放在Java堆中
    更正:【JDK 7之前】,HotSpot使用永久代来实现方法区时,实现是完全符合这种逻辑概念的;【而在JDK 7及之后】,类变量则会随着Class对象一起存放在Java堆中

  • #4-18 Page 274:被访问类C是public的,不与访问类D处于同一个模块,但是被访问类C的模块允许【被访问类D】的模块进行访问。
    更正:被访问类C是public的,不与访问类D处于同一个模块,但是被访问类C的模块允许【访问类D】的模块进行访问。

  • #4-19 Page 312:运行结果的第3行:This 【gay】 has $2
    更正:This 【guy】 has $2

  • #4-20 Page 314:脚注三最后一行:具体可【常见】第11章关于方法内联的内容。
    更正:具体可【参见】第11章关于方法内联的内容。

  • #4-21 Page 461:图12-6,New->Running,Running->Terminated这两个是单向箭头,交给编辑的原稿上也还是单向的,应该是排版修图时搞错变成双向了,请编辑同学修正过来。

  • #4-22 Page 462:【以前处理一个请求可以允许花费很长时间在单体应用中】,具有这种线程切换成本也是无伤大雅的
    语句不通,更正为:【在以前的单体应用中,处理一个请求可以允许花费很长时间】,具有这种线程切换成本也是无伤大雅的

  • #4-23 Page 462:现实的需求在迫使Java去研究新的解决方案,【同】大家又怀念起以前绿色线程的种种好处
    更正为:现实的需求在迫使Java去研究新的解决方案,【此时】大家又怀念起以前绿色线程的种种好处

以下勘误内容已在第3次重印版(2020-2-20日)修正


  • #3-1 Page 27:到了JDK 10,HotSpot又重构了Java虚拟机的垃圾收集器接口 (Java Virtual Machine 【Compiler】 Interface)
    更正:到了JDK 10,HotSpot又重构了Java虚拟机的垃圾收集器接口 (Java Virtual Machine 【Garbage】 Interface)

  • #3-2 Page 37:譬如【Eclipst】 CDT或者NetBeans来进行的话
    更正:譬如【Eclipse】 CDT或者NetBeans来进行的话

  • #3-3 Page 110:对象的读取、写入、对象的比较、【为对象哈希值计算】、用对象加锁等等
    更正:对象的读取、写入、对象的比较、【为对象计算哈希值】、用对象加锁等等

  • #3-4 Page 113:图3-19中,【Larage】为笔误,应为【Large】

  • #3-5 Page 238:13: iload_1 后的注释应该是字节码第14行的,即

    13: iload_1 // 保存x到returnValue中,此时x=2
    14: istore 4

    改为:

    13: iload_1
    14: istore 4 // 保存x到returnValue中,此时x=2

  • #3-6 Page 254:算术指令用于对【两个操作数栈上的值】进行某种特定运算
    对语序修改以避免歧义:算术指令用于对【操作数栈上的两个值】进行某种特定运算

  • #3-7 Page 259:13 astore_3 后注释【Taget】为笔误,应为【Target】

  • #3-8 Page 265/266:在266页正文中出现两次注释一,其中第一个注释是265页才对,应该是排版问题,请编辑再版时注意。

  • #3-9 Page 278:代码清单7-5,第一行注释:给变量【复制】可以正常编译通过
    更正: 给变量【赋值】可以正常编译通过

  • #3-10 Page 278:代码清单7-5,第二行注释:这句编译器会提示“非法【向前】引用”
    更正: 这句编译器会提示“非法【前向】引用”

  • #3-11 Page 290:用在IntelliJ 【IDE】、Eclipse这些IDE上做HotSwap……
    更正: 用在IntelliJ 【IDEA】、Eclipse这些IDE上做HotSwap……

  • #3-12 Page 312:代码实例中出现三处【gay】,譬如Father gay = new Son(); 均应为【guy】,这个不影响代码运行,只是不太雅观。

  • #3-13 Page 317:产生这种差别产生的根本原因是Java语言在编译期间【却】已将println(String)方法完整的符号引用。
    更正:产生这种差别产生的根本原因是Java语言在编译期间【就】已将println(String)方法完整的符号引用。

  • #3-14 Page 322:由于注解中John Rose博客文章中的代码托管网站Kenai.com已经关闭,为了便于读者获取INDY工具,我上传了一份到本仓库中(源码,在src/indify目录)。此文件版权归原作者John Rose所有。(编辑在更新勘误时可跳过此条)

  • #3-15 Page 348:通常解决【类】问题有以下几种途径
    更正:通常解决【此类】问题有以下几种途径

  • #3-16 Page 372:譬如将【代码清单10-2】稍微修改一下,变成下面代码清单10-7这个样子
    更正:譬如将【代码清单10-4】稍微修改一下,变成下面代码清单10-7这个样子

  • #3-17 Page 396:【图11-2】和【图11-3】都仅仅是描述了客户端模式虚拟机的即时编译方式
    更正:【图11-3】和【图11-4】都仅仅是描述了客户端模式虚拟机的即时编译方式

以下勘误内容已在第2次重印版(2019-12-27日)修正


  • #2.1 前言部分:读者反馈信箱:[email protected]
    更正:由于这个信箱由于一直只收未发,刚印刷后收到Google的通知此账号已自动作废。而且根据Google规定,作废后无法注册同名邮箱。下次重印将修改为本工程地址:https://github.com/fenixsoft/jvm_book。

  • #2-2 Page 9:支持HTTP 2客户【单】API等91个JEP
    更正:支持HTTP 2客户【端】API等91个JEP

  • #2-3 Page 64:在【代码清单2-8】里笔者借助了CGLib……
    更正:在【代码清单2-9】里笔者借助了CGLib……

  • #2.4 Page 368: 【ArrayList<int>】与ArrayList<String>其实是同一个类型
    更正:【ArrayList<Integer>】与ArrayList<String>其实是同一个类型

jvm_book's People

Contributors

fenixsoft avatar moqimoqidea avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

jvm_book's Issues

Page105 图3-14标识错误

图3-14的G1收集器 FinishMark阶段没有标识出来,此外Compact是非并发的应为浅色
此处为作者原图
3-14.png

若干疑问处

Page238 :
13: iload_1 后的注释是否应该是字节码第14行的
Page259:
13 astore_3 后注释Taget 是否为Target
Page265/266:
作者注释1在266页出现两次,应该是排版问题
Page317:
第五段第一句,Java语言在编译期间【却】已将。。。并作为。。。中的却是否改为【就】已将。。。并作为。。。更顺畅
Page312:
代码实例中Father gay = new Son(); 中的gay 只是皮一下哈?

若干疑问处2

Page254 :
算术指令用于对两个操作数栈上的值进行。。。 此处是否改为【对操作栈上的两个值进行】略为不容易歧义,初读此处不了解操作栈,根据后文内容发现自己前文理解错误。
Page322:
作者注释的INDY链接博文还存在,但托管工具代码的Kenai.com站点已关闭,已无法获取相关工具;作者本地还有备份的话方便在仓库**享一下?
Page348:
9.3节 第一段最后一句,【通常解决类问题有以下几种途径:】类字前是否欠缺【该】、【这】、【此】等字眼
Page372:
正文第二段第二行:譬如将代码清单【10-2】稍微修改,此处的10-2 实为10-4
Page396:
正文第二段第一句中:图11-2 和图11-3 应该是 图11-3 和图11-4

勘误:7.3.5 小节

在 7.3.5 小节中(初始化),有一个例子用来证明父类静态代码块要先于子类中运行中提到:

由于父类的<clinit>()方法先执行,也就意味着父类中定义的静态语句块要优先与子类的变量赋值,如代码清单7-6中,字段B的值将会是2而不是1

这里应该是:字段B的值将会是2而不是0。
因为如果子类变量的赋值(<clinit>方法)先执行的话,此时父类的静态代码块 (<clinit>() 方法)还未执行,字段 A 的值应该还为 0 ?
感觉代码清单的例子来证明不太好,字段 A 的赋值有点多余...,反正最后都是收集成 <clinit> 方法,putstatic 两次。删掉 A 字段的赋值会清晰很多,换成

static class Parent{
    public static int A;
    static {
        A = 2;
    }
}

P72 3.2.4 生存还是死亡?

你好周老师,我在阅读 3.2.4 生存还是死亡 章节时,遇到了一个问题,您在第二行中说:“要真正宣告一个对象死亡,至少要经历两次标记过程”,第一次标记过程我能理解,但是您在书中说,进行第一次筛选的时候,会判断是否 “有必要执行 finalize() ”,第二次标记会发生在 finalize() 方法执行后,取决于是否抢救。那如果 “没有必要执行” 还有被抢救过的对象,第二次标记会发生在什么时候呢。还是说,“没有必要执行 finalize() 的对象 ”直接就会被 gc。

3.2.1 引用计数法

书本上说这段代码能说明 JVM 使用非引用计数法,我觉得不能简单地就此判断,因为 JVM 从逻辑上不能排除使用复杂引用计数法。至少应该换一种说法:证明 JVM 能够解决循环引用进行垃圾回收的案例:

public class ReferenceCountingGC {

    public Object instance =null;
    private static final int _1MB = 1024*1024;

    private byte[] bigSize = new byte[2*_1MB];

    public static void testGC(){
        ReferenceCountingGC objA = new ReferenceCountingGC();
        ReferenceCountingGC objB = new ReferenceCountingGC();
        objA.instance=objB;
        objB.instance=objA;

        objA=null;
        objB=null;

        //手动在这里进行垃圾回收
        System.gc();
    }

    public static void main(String[] args) {
        ReferenceCountingGC.testGC();
    }
}

为了查看 GC 日志,需要使用参数:

-XX:+PrintGCDetails

上述代码运行结果为:

[GC (System.gc()) [PSYoungGen: 6717K->560K(76288K)] 6717K->568K(251392K), 0.0016215 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] 
[Full GC (System.gc()) [PSYoungGen: 560K->0K(76288K)] [ParOldGen: 8K->377K(175104K)] 568K->377K(251392K), [Metaspace: 2964K->2964K(1056768K)], 0.0047709 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] 
Heap
 PSYoungGen      total 76288K, used 1966K [0x000000076ab00000, 0x0000000770000000, 0x00000007c0000000)
  eden space 65536K, 3% used [0x000000076ab00000,0x000000076aceb9e8,0x000000076eb00000)
  from space 10752K, 0% used [0x000000076eb00000,0x000000076eb00000,0x000000076f580000)
  to   space 10752K, 0% used [0x000000076f580000,0x000000076f580000,0x0000000770000000)
 ParOldGen       total 175104K, used 377K [0x00000006c0000000, 0x00000006cab00000, 0x000000076ab00000)
  object space 175104K, 0% used [0x00000006c0000000,0x00000006c005e758,0x00000006cab00000)
 Metaspace       used 2974K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 328K, capacity 388K, committed 512K, reserved 1048576K

猜想:如果将以下两句注释掉,那么将导致垃圾回收不会处理这两个对象,那么垃圾回收之后的占用内存应当会大一点。

objA=null;
objB=null;

运行结果为:

[GC (System.gc()) [PSYoungGen: 6717K->4624K(76288K)] 6717K->4632K(251392K), 0.0028630 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] 
[Full GC (System.gc()) [PSYoungGen: 4624K->0K(76288K)] [ParOldGen: 8K->4475K(175104K)] 4632K->4475K(251392K), [Metaspace: 2967K->2967K(1056768K)], 0.0054821 secs] [Times: user=0.01 sys=0.01, real=0.00 secs] 
Heap
 PSYoungGen      total 76288K, used 3277K [0x000000076ab00000, 0x0000000770000000, 0x00000007c0000000)
  eden space 65536K, 5% used [0x000000076ab00000,0x000000076ae334d8,0x000000076eb00000)
  from space 10752K, 0% used [0x000000076eb00000,0x000000076eb00000,0x000000076f580000)
  to   space 10752K, 0% used [0x000000076f580000,0x000000076f580000,0x0000000770000000)
 ParOldGen       total 175104K, used 4475K [0x00000006c0000000, 0x00000006cab00000, 0x000000076ab00000)
  object space 175104K, 2% used [0x00000006c0000000,0x00000006c045ef98,0x00000006cab00000)
 Metaspace       used 3007K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 328K, capacity 388K, committed 512K, reserved 1048576K

证明了猜想。

勘误:第7章节类加载

第 7 章类加载的 7.3.3 小节(准备阶段)中提到

在 JDK 8 及之后,类变量则会随着 Class 对象一起存放在 Java 堆中

实际上是从 JDK7 开始,类变量就和 Class 对象一起存放在 Java 堆中了。

R 大之前在知乎提到过
https://www.zhihu.com/question/50258991

第二版重印已经修正的在印刷版第三版又重现。

Page 27:到了JDK 10,HotSpot又重构了Java虚拟机的垃圾收集器接口 (Java Virtual Machine 【Compiler】 Interface)
更正:到了JDK 10,HotSpot又重构了Java虚拟机的垃圾收集器接口 (Java Virtual Machine 【Garbage】 Interface)
Page 37:譬如【Eclipst】 CDT或者NetBeans来进行的话
更正:譬如【Eclipse】 CDT或者NetBeans来进行的话

以上两点在印刷版第三版并未更正

关于 volatile 的疑问

您好,最近在看 volatile 这一节,有以下疑问:

书中P646页关于volatile的可见性,提到会在volatile的变量写操作后增加一条lock指令,来保证它对于其它处理器是立刻可见的。

可是如果是多线程的话,会出现刚执行完一个写操作,还没执行lock指令时就进行线程切换的情况吗?此时volatile不就是非立刻可见了?

还有就是多核的情况下,会出现两个线程同时对该变量进行写又同时lock的情况吗?此时变量的值会是什么?

另外,volatile 会禁止指令重排,这个是JVM层面的指令重排还是CPU层面的乱序执行啊?

P89: Wilson的证明

周老师您好,关于Wilson证明的引用可以发一下吗?想了解。

书中95页关于Parallel Old收集器的问题

书中95页关于Parallel Old收集器的支持多线程并发收集的特点,其中运行示意图中只有GC线程工作,并没有与用户线程同时工作,根据您在书中93页关于并行和并发的解释,这里Parallel Old收集器的特点是否应该改为支持并行收集,而非并发收集。谢谢您!

2.4.4 本机直接内存溢出例子无法复现

《深入理解Java虚拟机 第三版》中2.4.4 本机直接内存溢出例子无法复现。

/**
 * VM Args:-Xmx20M -XX:MaxDirectMemorySize=10M
 */
public class DirectMemoryOOM {
    private static final int _1MB = 1024*1024;

    public static void main(String[] args) throws IllegalAccessException {
        Field unsafeField = Unsafe.class.getDeclaredFields()[0];
        unsafeField.setAccessible(true);
        Unsafe unsafe = (Unsafe) unsafeField.get(null);
        while (true){
            unsafe.allocateMemory(_1MB);
        }
    }
}

系统版本:macOS 10.13.4
JDK:1.8.0_172, 11.0.5, 13.0.1

第3章3.5.6CMS收集器有些疑问

疑问一:当出现“Concurrent Mode Failure”失败时,导致另一次完全“Stop The World”的Full GC的产生。这时候虚拟机不得不启动后备预案:冻结用户线程执行,临时启用Serial Old收集器来重新进行老年代的垃圾收集。

1.书中77页的“注意”中统一定义:整堆收集(Full GC)。这里启用Serial Old收集,也只是收集老年代,不应该Full GC吧?
2.猜想:启用Serial Old收集,也会触发新生代的收集?
3.书中77页的“注意”中统一定义:老年代收集的内容中指出“目前只有CMS收集器会有单独收集老年代的行为”。这句好像印证了上面的猜想:启用Serial Old收集,也会触发新生代的收集。
4.如果猜想正确,不明白为什么是这样,因为新生代的收集对老年代似乎启不到作用。

疑问二:CMS基于“标记-清除”算法收集时,由于碎片过多,导致无法给大对象分配,而不得不提前触发一次Full GC。

1.这里的提前触发,使用的应该还是CMS收集器,“目前只有CMS收集器会有单独收集老年代的行为”,这里的Full GC表述是不是有误?应该是老年代收集(Major GC)。
2.但参数:-XX:CMSFullGCsBeforeCompaction中的FullGCs也是把对老年代的收集称为Full GC,那这样又与77页的Full GC定义冲突。

感谢你在百忙之中抽出时间,期待你的回复。

谢谢,祝一切顺利~

在Issues中一般用于回复书中错误、勘误的提问

对于指出书中错误的问题一般都会即时回复。如问题是对于书中知识点有疑惑,或者关于虚拟机的其他提问,希望能到知乎、stackoverflow等公共平台提出,将问题交流变为多对多的(有其他相同疑问的朋友也能看见,有能其他能够回答的朋友也能回答)。我在上面见到问题,能力所及的也会回答。目的只是为了更有效率,并非怠慢读者。感谢您的支持。

Page99 第二个段落的疑惑

”停顿时间模型“,时间片段M毫秒和垃圾收集时间不超过N毫秒,M和N之间是否有什么关系?
最后一句话,“这几乎已经是实时Java(RTSJ)的中软实时垃圾收集器特征了“是否有问题?

Page 78: 图 3-2 和 图 3-3 的问题

Page 78: 图 3-2 回收后状态 2行2列和3行2列 这两格在书中是白色的未使用, 是否应该改为存活对象的颜色?
图 3-3 回收后状态 2行6、7、8列 这三格在书中是白色的未使用, 是否应该改为存活对象的颜色?

勘误:48页语法错误

2.3.1 第一行“Java程序运行过程中无时无刻都有对象被创建出来。”应为“每时每刻都有对象被创建出来。”

问题

老师您好,我想问一下,JDK8之后运行时常量池是在堆中还是方法区中?我知道字符串常量池是在堆中的,运行时常量池和字符串常量池到底是什么关系呢?

Page:130 3.8.1 对象优先在Eden分配

由于jdk8默认使用Parallel Scavenge + Parallel Old,会导致4M的数组直接进入老年代而不产生Minor GC

建议这部分可以添加说明下,并在3.8.2中添加使用Parallel Scavenge + Parallel Old作为垃圾收集器时大对象直接进入老年代的条件

Page265: 语句"只会输出'SuperClass init!'"不够准确

原句为

上述代码运行之后,只会输出"SuperClass init",而不会输出"SubClass init!"

但是实际结果应当是也输出了SuperClass的vlaue值---123.虽然明白作者大大本意是对比,但是个人以为可以表述更准确一点.比如:

上述代码运行之后,除了SuperClass的静态字段value值,只会输出"SuperClass init",而不会输出"SubClass init!"

谢谢!

Parallel Scavenge 是标记复制还是标记整理

作者你好,书中P80页倒数第4行提到:”HopSpot 虚拟机里面关注吞吐量的 Parallel Scavenge 收集器是基于标记-整理算法的“。

而P93页3.5.3节提到:”Parallel Scavenge 收集器也是一款新生代收集器,它同样是基于标记-复制算法实现的收集器“。

所以它到底是标记复制还是标记整理呢?

关于JDK9之前的默认垃圾收集器

老师,您好!
我在Page 128看到参数UseParallelGC的描述:JDK9之前虚拟机运行在Server模式下的默认值,打开此开关后,使用Parallel Scavenge + Serial Old(PS MarkSweep)的收集器组合进行内存回收,
在第2版附录C Page 420的参数说明中也是UseParallelGC默认开启,UseParallelOldGC默认关闭

在JDK1.8环境下用java -XX:+PrintCommandLineFlags打印的结果:
image
但是在用java -XX:+PrintFlagsFinal打印出的运行时最终参数中UseParallelOldGC也是开启的:
image
用java -XX:+PrintGCDetails打印的GC日志中:
image
PSYoungGen说明新生代使用的应该是Parallel Scavenge收集器,这个没有疑问,可接下来的ParOldGen说明老年代使用的应该是Parallel Old收集器,所以JDK9之前虚拟机运行在Server模式下的默认收集器组合究竟是哪一个,是Parallel Scavenge + Serial Old 还是 Parallel Scavenge + Parallel Old ?希望老师能解答我的这个疑惑,谢谢!

Page 64 代码2-9 内存溢出异常的问题/疑惑

书中写到在JDK 7中的运行结果会有 java.lang.OutOfMemoryError: PermGen space,但我在windows和macOS下的JDK 1.7.0_80 都只能得到简单的"Exception: java.lang.OutOfMemoryError thrown from the UncaughtExceptionHandler in thread "main"" 这样的提示,如果使用JDK 6的话倒是有PermGen space的错误信息了。想问下JDK 7没有给出错误调用栈和“PermGen space“信息是和JDK7的具体小版本有关系呢,还是我的理解/操作有误?

JDK6 中可以

JDK7 中不行

勘误:3.2.5 小节

P75
-XX:+TraceClassLoading、-XX:+TraceClassUnLoading 查看类加载和卸载信息
此处的 -XX:+TraceClassUnLoading 是否应该改为 -XX:+TraceClassUnloading

p456 12.4 java与线程

p456在介绍2.用户线程实现时,书中这样写道

这种进程与用户线程之间 1:N 的关系称为一对多的线程模型

这里是不是应该是 (内核)线程和用户线程之间的 1:N,这样才和之前1. 内核线程实现一节中提到的

轻量级进程与内核进程之间1:1的关系

逻辑上一致,1:1, 1:N, N:M 三种关系

感谢。

勘误:P109

原文:一旦用户程序访问到归属于旧对象的内存空间就会产生自陷中段,进入预设好的异常处理器中
应为:一旦用户程序访问到归属于旧对象的内存空间就会产生自陷中断,进入预设好的异常处理器中

关于书中P78 图3-3的问题

之前的勘误中提到图2-3,3-3的第2行2列、3行2列都是“可回收”对象的颜色,
请问图3-3“回收前状态”的第3行第4列是否也应该是“可回收”对象的颜色呢?因为回收后存活区域有5个,回收前有6个。

在豆瓣购买的电子版,勘误并没有更正

例如,代码清单7-5:

public class Test {
    static {
        i = 0;  //  给变量复制可以正常编译通过
        System.out.print(i);  // 这句编译器会提示“非法向前引用”
    }
    static int i = 1;
}

应该更正为:

public class Test {
    static {
        i = 0;  //  给变量赋值可以正常编译通过
        System.out.print(i);  // 这句编译器会提示“非法前向引用”
    }
    static int i = 1;
}

脚注问题

265页的脚注提示的问题应该在266页上面,当然如果和266页的脚注并列的话,266页脚注提示的正文就要挪到267页了,真是一个吊诡的问题。

P134-3.8.4动态对象年龄判定的解释有误

原文:如果在Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代,无须等到-XX:MaxTenuringThreshold中要求的年龄

这段话不知道是基于哪个版本的JVM得出的结论,
实际代码:

uint ageTable::compute_tenuring_threshold(size_t survivor_capacity) {
    //survivor_capacity是survivor空间的大小
  size_t desired_survivor_size = (size_t)((((double) survivor_capacity)*TargetSurvivorRatio)/100);
  size_t total = 0;
  uint age = 1;
  while (age < table_size) {
    total += sizes[age];//sizes数组是每个年龄段对象大小
    if (total > desired_survivor_size) break;
    age++;
  }
  uint result = age < MaxTenuringThreshold ? age : MaxTenuringThreshold;
    ...
}

得出的结论是:
从最小的年龄对象大小开始累加,当达到n大小时如果总和大于TargetSurvivorRatio设定的阀值那么大于等于n年龄的对象就会晋升到老年代。
而不是:
相同的某一个年龄的累加大小

P274类或接口的解析

P274,类或接口的解析,在说明D拥有C的访问权限的三条规则时,第二条:
“被访问类C是public的,不与访问类D处于同一个模块,但是被访问类C的模块允许被访问类D的模块进行访问"
最后一句话D应该是访问类吧,也许多了一个字。

第3版第2次印刷中Page 97,Line 6 中CMS收集器垃圾收集线程占比不超过25%有误

第3版 原文:

CMS默认启动的回收线程数是(处理器核心数量 + 3)/ 4,也就是说,如果处理器核心数在四个或以上,并发回收时垃圾收集线程只占用不超过25%的处理器运算资源,并且会随着处理器核心数量的增加而下降。

issue描述:

原文中说CMS在默认情况下垃圾收集线程占用处理器运算资源不超过25%,可是经过我的计算,应该是不少于25%。

例如:
处理器14核心的情况 回收线程数 = (14 +3)/ 4 = 4 , 回收线程占比 = 回收线程数/总线程数 =4 / 14 > 25% ,所以可以看出原文中的不超过25%有误,经过计算应当是不少于25%

关于:”代码清单7-5 非法前向引用变量"中的代码注释错误

public class Test{
  static{
    int i =0; //给变量复制可以正常编译通过
    System.out.print(i);//这句编译器会提示”非法向前引用“
  }
  static int i =1;
}

第一句注释:“复制”应当为“赋值”。
并且代码段称为“非法前向引用变量”,注释中应当也使用非法前向引用?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.