易燃物汇聚 YouTube 有声书与书评
深入理解Java虚拟机:JVM高级特性与最佳实践(第3版) 封面

深入理解Java虚拟机:JVM高级特性与最佳实践(第3版)

周志明

4.3万人在读
49.6万字总字数
¥31.99定价

有声书与视频解读 YouTube视频解读 10

内容简介

内容介绍 这是一部从工作原理和工程实践两个维度深入剖析JVM的著作,是计算机领域公认的经典,繁体版在台湾也颇受欢迎。 自2011年上市以来,前两个版本累计印刷36次,销量超过30万册,两家主要网络书店的评论近90000条,内容上近乎零差评,是原创计算机图书领域不可逾越的丰碑。 第3版在第2版的基础上做了重大修订,内容更丰富、实战性更强:根据新版JDK对内容进行了全方位的修订和升级,围绕新技术和生产实践新增逾10万字,包含近50%的全新内容,并对第2版中含糊、瑕疵和错误内容进行了修正。 全书一共13章,分为五大部分: 第壹部分(第1章)走近Java 系统介绍了Java的技术体系、发展历程、虚拟机家族,以及动手编译JDK,了解这部分内容能对学习JVM提供良好的指引。 第二部分(第2~5章)自动内存管理 详细讲解了Java的内存区域与内存溢出、垃圾收集器与内存分配策略、虚拟机性能监控与故障排除等与自动内存管理相关的内容,以及10余个经典的性能优化案例和优化方法; 第三部分(第6~9章)虚拟机执行子系统 深入分析了虚拟机执行子系统,包括类文件结构、虚拟机类加载机制、虚拟机字节码执行引擎,以及多个类加载及其执行子系统的实战案例; 第四部分(第10~11章)程序编译与代码优化 详细讲解了程序的前、后端编译与优化,包括前端的易用性优化措施,如泛型、主动装箱拆箱、条件编译等的内容的深入分析;以及后端的性能优化措施,如虚拟机的热点探测方法、HotSpot 的即时编译器、提前编译器,以及各种常见的编译期优化技术; 第五部分(第12~13章)高效并发 主要讲解了Java实现高并发的原理,包括Java的内存模型、线程与协程,以及线程安全和锁优化。 全书以实战为导向,通过大量与实际生产环境相结合的案例分析和展示了解决各种Java技术难题的方案和技巧。

读者书评

★★★★★

JVM 调优思路 分析系统核心业务 估算每秒请求数 预估核心业务每秒处理了多少请求 结合服务器负载,估算部署了多少台服务器 每台服务器处理了多少请求 估算每秒占用内存 估算每次请求耗时多久 估算每个请求占用内存 估算每秒占用内存 考虑到关联对象再扩大 10-20 倍,考虑到其他业务在扩大 5-10 倍,所以每秒占用内存再 * 20 * 10 分配 JVM 内存 结合机器配置分配内存空间,首先需要给元空间和虚拟机栈预留足够的空间 元空间一般设置几百兆,比如 256 M 线程栈一般设置 512 KB - 1M,例如每秒 500 次请求,那么需要给虚拟机栈预留 500 MB 设置堆内存大小 估算 GC 频率 年轻代 GC 频率 = Eden 内存大小 / 每秒占用内存 一般几分钟或几十分钟触发一次 Young GC,一次 Young GC 耗时几毫秒或者几十毫秒都是正常的 估算 Young GC 后存活对象大小 根据 Survivor 空间大小与 YGC 存活对象大小估算每次进入老年代对象大小 估算 Full GC 频率,一般 Full GC 的频率在一天几次、几天一次、甚至不出现 Full GC 最好,一次 Full GC 耗时几百毫秒都是正确的 内存分配和 Young GC 优化 保证 Survivor 区空间足够,避免 YGC 存活对象太多不能放进 Survivor 区,避免动态年龄判断导致对象快速进入老年代 提高 Survivor 区大小,尽量让 YGC 后存活对象小于 Survivor 区的一半 减少 Young GC 频率,增大内存,增大新生代大小 提升机器配置,增大堆内存,调整新生代与老年代比例,适当增大新生代大小,调整 Eden 区和 Survivor 老年代优化 调整大对象阈值,避免频繁有大对象直接进入老年代(减少分代年龄,避免长期存活对象需要很多次复制后才能进入老年代) 减少 Full GC/OldGC 的频率(根据实际情况适当增大老年代内存,检查是否有内存泄漏让老年代驻留了大量对象导致每次进入少量对象就触发 Full GC) CMS 垃圾回收避免并发失败触发 Full GC(调整 CMS 触发 Old GC 的阈值,这个一般不需要调整) 选择合适的垃圾回收器 普通的系统一般 ParNew + CMS 组合的垃圾回收器 大内存机器选择 G1 垃圾回收器,设置合理的停顿时间 估算瞬时高峰压力的情况 估算瞬时高峰压力时的内存模型 根据实际情况下将正常情况下的估算值再扩大流量,估算高峰压力时的配置 谈谈 JMM 的理解? JMM 是 Java 内存模型,是一种抽象的模型,被定义出来屏蔽各种硬件和操作系统的内存访问差异 JMM 定义了线程和主内存之间的抽象关系:线程之间的共享变量存储在主内存,每个线程都有一个私有的本地内存,本地内存存储了该线程以读/写共享变量的副本 本地内存是 JMM 的一个抽象概念,并不真实存在,它其实涵盖了缓存、写缓冲区、寄存器以及其他的硬件和编译器优化 说垃圾回收算法 复制算法的核心思想就是把内存空间分为两块,每次只使用一块,在垃圾回收时将正在使用的内存中的活跃对象复制到未被使用的内存块中,之后清除正在使用的内存块中的所有对象,两块内存交替使用,从而完成垃圾对象的回收, 复制算法没有标记和清除过程,实现简单,运行高效,内存从一块空间复制到另一块空间可以保证空间的连续性,不会出现内存碎片问题,但是复制算法需要双倍内存空间,而且需要移动对象,这就涉及修改对象引用地址值的问题,另外,对于 G1 这种把内存划分为大量 region 的垃圾收集器,意味着需要维护各个 region 之间对象引用关系,在时空消耗方面不低 如果垃圾对象很少,复制算法效率低 标记清除算法: 从引用根节点开始遍历,标记所有被引用的对象 对堆内存从头到尾进行线性遍历,如果发现某个对象为不可达对象,则将其回收 优点是不需要移动对象,简单容易实现,缺点是用户体验差,有内存碎片,清除对象不是把对象置空,而是把需要清除的对象地址保存在空闲的地址列表中,下次有新对象申请时,判断某块内存空间是否充足,如果充足,新的对象会覆盖标记为垃圾对象,从而实现内存的复用,但是因为可用内存不连续问题,在大对象申请内存时,需要花费更多时间去寻找合适的位置,甚至失败 标记压缩算法:就地压缩内存空间,内存利用率高,不存在内存碎片的问题,但是多次遍历内存空间,算法的执行效率不高 遍历内存空间,标记内存空间的活跃对象 遍历内存空间,计算所有活跃对象压缩后的位置 遍历内存空间,更新所有活跃对象成员变量压缩后的位置 遍历内存空间,移动活跃对象到第二步计算好的位置,由于此时对象内部的成员变量已经更新完成,因此移动对象后的所有引用关系都是正确的 继承时父子类的初始化顺序是怎么样的? 父类---静态变量 父类--静态初始化块 子类--静态变量 子类--静态初始化块 父类--变量 父类--初始化块 父类--构造器 子类--变量 子类---初始化块 子类---构造器 说说类加载双亲委派模型 加载将网络、某个磁盘位置下的类的 class 二进制字节流读进来,在内存中生成一个代表这个类的 Java.lang.Class 对象放入到元空间来实现类的加载,这个阶段我们程序员可以干预,我们可以自定义类加载器实现类的加载 验证:验证 Class 文件的字节流信息包含的信息符合 《Java 虚拟机规范》的全部约束要求,保证虚拟机的安全 准备:准备类变量赋予默认值,int 为 0,long 为 0L,boolean 为 false,引用类型为 null,常量赋予正式值 解析:把符号引用翻译为直接引用 初始化:当我们 new 一个类的对象,访问一个类的静态属性,修改一个类的静态属性,调用一个类的静态方法,用反射 API 对一个类进行调用,初始化当前类,其父类也会被初始化.......这些都会触发类的初始化 使用:使用这个类 卸载: 该类所有的实例都已经被 GC,也就是 JVM 中不存在该 Class 的任何实例 加载该类的 ClassLoader 已经被 GC 该类的 Java.lang.class 对象没有在任何地方被引用,如果不能在任何地方通过反射访问该类的方法 类加载过程被分为三个步骤,5个阶段: 分为加载、验证、准备、解析以及初始化,加载、验证、准备、解析这四个阶段的顺序是确定的,但是解析阶段不一定,为了支持 Java 语言运行时绑定特性,在某些情况下可以在初始化阶段之后再开始 加载阶段分为三步: 通过完全限定名查找定位 .class 文件,并获取其二进制字节流数据 把字节流所代表的静态存储结构转换为运行时数据结构 在堆中间中为其创建一个Class对象,作为程序访问这些数据的入口 验证阶段: 文件格式验证:验证字节流是否符合 Class 文件格式的规范 比如 CA/FE/BA/BE 魔数验证 主次版本号验证 元数据验证:堆字节码描述的信息进行语义分析,以保证其描述的信息符合 Java 语言规范的要求 类是否有父类,除了Object之外,所有的类都应该有父类 类的父类是否继承了不允许被继承的类(被final修饰的类) 如果这个类不是抽象类,是否实现了其父类或接口中要求实现的所有方法 类的字段/方法是否与父类的存在冲突。例如方法参数都一样,返回值却不同 字节码验证:通过数据流和控制流分析,确定程序语义合法且符合逻辑\ 对类的方法体进行校验分析,保证在运行时不会做出危害虚拟机的行为 保证任意时刻操作数栈的数据类型与指令代码序列都能配合工作,不会出现类似于在操作数栈放了一个int类型的数据,读取时却按照long类型加载到本地变量表中的情况 保障任何跳转指令都不会跳转到方法体之外的字节码指令上 符号引用验证:确保后续的解析动作能正确执行 通过字符串描述的全限定名是否能找到对应的类 符号引用中的类、字段、方法的可访问性是否可被当前类访问 准备阶段主要是为类中声明的静态变量分配内存空间,并将其初始化成默认值(零值) 解析阶段主要是把类中对常量池内的符号引用转换为直接引用的过程 初始化步骤中,主要是对类的静态变量赋予正确的初始值,也就是在声明静态变量时指定的初始化值以及静态代码块中的赋值。本质上就是执行类构造器方法<clinit>()的过程。而触发初始化的情况分为六种 ①遇到new/getstatic/putstatic/invokestatic这四条字节码指令时触发 new:使用new关键字创建一个实例对象 getstatic:读取一个静态字段 putstatic:设置一个静态字段 invokestatic:调用一个静态方法 ②对类型进行反射调用,如果类型没有经过初始化,则会触发初始化 ③初始化一个类的时候,发现父类没有初始化,则先触发父类初始化 ④虚拟机启动时,需指定一个要执行的主类(包含main方法的那个类),虚拟机会初始化这个主类(如SpringBoot的启动类) ⑤当使用JDK7中新加入的动态语言支持时,如果一个java.lang.invoke.MethodHandler实例最后的解析结果为REF_getStatic,REF_putStatic,REF_invokeStatic,REF_newInvokeSpecial四种类型的方法句柄,并且这个方法对应的类没有进行初始化,则先触发其初始化 ⑥当一个接口中定了JDK8新加入的默认方法时,如果这个接口的实现类发生了初始化,要先将接口进行初始化 当一个类完整的经过了类加载过程之后,在内存中已经生成了Class对象,同时在Java程序中已经通过它开始创建实例对象使用时,该阶段被称为使用阶段。 而当一个Class对象不再被任何一处位置引用,即不可触及时,Class就会结束生命周期,该类加载的数据也会被卸载 JVM 存在三个默认的类加载器: BootstrapClassLoader 启动类加载器 加载 jre\lib\rt.jar ExtClassLoader 扩展类加载器 jre\lib\ext AppClassLoader 应用程序类加载器 加载用户类路径下的所有类库 AppClassLoader 的父加载器是 ExtClassLoader,ExtClassLoader 的父加载器是 BootstrapClassLoader JVM 在加载一个类时,会调用 AppClassLoader 的 loadClass 方法来加载类 不过在这个方法中,会先使用 EXTClassLoader 的 loadClass 的方法来加载类 同样 ExtClassLoader 的 loadClass 方法中会先使用 BootstrapClassLoader 来加载类 如果 BootstrapClassLoader 加载到类就直接成功 如果 BoostrapClassLoader 没有加载到,那么 ExtClassLoader 就会尝试自己加载类 如果没有加载到,那么则会由 AppClassLoader 来加载这个类 所以,双亲委派指的是,JVM 在加载类时,会委派给 Ext 和 Bootstrap 来进行加载,如果没有加载到才由自己进行加载,双亲委派的好处就是 确保安全,避免 Java 核心类库被修改 避免重复加载 保证类的唯一性,如果自己写了 Java.lang.String 的类去运行,抛出异常。

★★★★★

# 你理解了JVM的运行机制有啥用 ## 定论 定论:这本书常读常新,推荐作为Java入门的枕边书,作为科学工程,知其然当然也要知其所以然.读<out of gobi>时作者说中医持续近千年,结构相对稳定,因其观其然,而西医近百年把医学提高到可以去病延命的地步是其探究至分子原子的微观原理所推动,探究原理才是思维和认识进步的根本途径,所谓触类旁通.知道原理的你和不知的你是两个人. ## 以图记忆 读完这本书,需要在脑海里留存几张图: - JVM运行时数据区构成图 - Java垃圾回收器线程运行图 - Java垃圾回收器搭配图 - 样例Eclipse启动和编译调优heapdump变化图 - class文件构成图 - class文件加载流程图 - 类加载的双亲委派和Tomcat加载模型 - 类编译流程(c1&C2) - 解释执行器与编译执行器运行流程图 - JMM内存模型 - Java的线程模型 - java synchronize锁的升级流程 ## key words 1. 运行时数据区关心分配.分配注意Tlab和栈上分配,配合不同的垃圾收集器有FreeList分配和bump the poniter. 2. 运行时数据区回收,主要集中在垃圾回收器的实现,关键如下 - 分代收集: - 新生代 - eden+from+to - yGC的区域,表现为短期存活的对象(未超过大对象阈值)在此处分配 - from to区域的切换 - 老年代 - 新生代超过markword中age阈值 - 创建时超过新生代最大阈值 - 跨代引用减少扫描 - memory set -> 卡表 -> 写屏障 - 矩阵 -> regions[n][m] == 1,此种标记为n引用m - 并发+并行收集:并发是多个GC线程,并行是与用户线程并行 - 并行(concurrent) -> 垃圾收集期间,应用程序暂定;并发(parallel),GC和UT一起 - serial(old) -> 串行(客户端模式下默认) - parallel(scavenge/old) -> 并行 - CMS(parNew已被内化为其默认新生代) -> 并发(初始标记+重新标记 stw) - G1 -> 细化region+cosplay,基于成本考虑的collection set收集 - 收集算法 - 标记复制 -> 针对新生代 - 标记整理 -> 有压缩过程,移动对象就会慢 - 标记清除 -> CMS,清除不整理产生碎片,需要一次整理 3. class文件加载与初始化 - 加载:全路径载入成二进制流+静态结构变成运行时数据结构+class对象在方法区生成class对象,作为访问该类的入口 - 处理 - 校验 -> 格式 - 准备 -> 0值 - 链接 -> 符号引用替换为直接引用 - 初始化,调用<clinit>,对已经放入方法区的class对象进行类属性的赋值 - 使用,new对象将会调用<init>使用构造器对实例进行构造 - 卸载 -> 类加载器+class都不再使用时可卸载 4. 类的编译和执行 - 编译 - 前端编译器 -> java 2 class - C1编译器 -> 客户端编译器 -> 空值检查优化 - C2编译器 -> 服务端编译器 -> 应用大部分优化(内联/逃逸分析后的栈上分配/标量替换/公共字表达式消除等) - 执行 - 编译执行 -> 快,激进 - 解释执行 -> 加载内容少,快,作为逃生回退方法 - 转换阈值 - 调用计数器 - 回边计数器 - 衰减 5. JMM - Java内存模型,与运行时数据区关系不大 - 其目的是指明对内存的操作:符合happen-before是有序的;其余是不可知的 - happen-before原则 - 创建先于使用 - 创建先于终止 - synchronize/volatile先写后用等 - 控制流 6. 高并发 - 线程安全的分类 - 线程安全的实现 - 互斥加锁 - 非阻塞同步 -> CAS - 线程私有 -> threadLocal ## 回想 关键不在上面的骨架,很多内容摘出来还是要细细深入,咀嚼几遍才能消化的了,另外书中的内容较全就不会样样深入,好在有样例是不错的思路.题外话,人生有很多机会走新路,我第一次拿到这本书是在18年,而今年才第一次读完,更觉得人生有命数,说是缘分,实则是心性不够,还未开悟.只是未来不知何往,读书总归有益,聊以自慰的是7年后的我好过当时的自己,也好过昨天的自己,吃新东西才能成新人,走新路才能赏新景.

★★★★★

读了 20% 忍不住先来写一下评价:书内容的覆盖面和作者的知识底蕴都是毋庸置疑的。但是几乎不对内容进行列表汇总和新老内容混写是难以接受的。 - 不列表汇总:比如一个东西的优缺点,正常来说应该我们分离出两个列表,让用户清晰看到,然后再根据金字塔理论展开讲解,最后读者只需要记录列表标题即可。但是作者全是混在一起去讲的,需要读者从中提取。 - 新老内容混写:已经过时的东西,前面一顿说优缺点,后面一看整个东西都已经 deprecated 或者被移除了,大到整个特性,小到小的开关。这让读者想记住哪些是目前最新可用的变得非常困难。我倾向这个发展过程完全已经作为一个附录放在最后,让感兴趣的读者去了解才是。而不是放在一起混淆视听。 更新:读完了(跳过了中间几章以后按需再读),被作者的 JVM 知识深度所折服,虽然由于上述原因读起来有些乏力,但瑕不掩瑜,评价从一般改为推荐。

★★★★★

比较优秀的一本书,作者的语言功底不错,读起来基本不会因为表达含糊不清而看不懂。 本书从原理方面全链条讲解了Java是编译成字节码的流程、java class文件有哪些内容、jvm的运行时内存组成、jvm是怎么解释class文件的、如何提高java字节码的运行效率、垃圾回收、jvm调优等内容。 由于是原理类书籍,可能是考虑到不同jvm底层实现不同,本书并不涉及jvm是如何在C++层面处理诸如运行时常量池、堆栈这些底层数据结构的。因此,本书其实是一本偏入门的书籍:正如学习windows/Linux实现首先要学习操作系统原理,本书也为学习虚拟机具体实现打下基础。如果想要进一步学习jvm的读者,需要找具体虚拟机源码剖析类书籍进行学习。

★★★★★

囫囵吞枣地看完了,前面讲述关于“Hotspot虚拟机”、“GC垃圾收集器”和“JVM检测指标工具”模块基本上都能有所收获,毕竟校招时八股天天背,但后面深入到“类”以及更深入的内容时,整个人就开始犯迷糊,根本看不懂。 可能我现在这个水平还不够,等后面造诣深之后再重新翻看。

★★★★★

去年看了第二版的,没坚持看完,今年边画思维导图边看,终于坚持看完了,有了一个整体印象。 内存区域介绍的很细致,垃圾收集部分详细讲了最新的几个垃圾收集器,后续如果升级JDK版本可以试用下;新加的JVM调优的案例分析,很不错,可以开拓大家的思路,将前面学的东西应用起来;类加载部分是第一次读,有些吃力,后续多读几遍应该也是可以理解的;JAVA线程部分讲的很细致,之前学习NIO模糊的地方突然豁然开朗了;线程安全部分讲的较少,但基础知识点都有罗列,如果想详细学习可以看一些进阶的书。 总之,很不错的一本书,但这本真的挺厚的,能坚持看完需要一些基础和毅力。

★★★☆☆

读完这本书其实感觉并没有记住多少东西,只不过是对gc,类加载流程有了更好清晰的认识罢了,特别是内存屏障和卡表这块的,实践部分认识到了访问者模式,也许在某一天,我会因为某个参数某行代码想起书中的某些内容.

★★★★★

书中兼顾理论原理与工程实践,搭配大量案例、代码和调优实战,把晦涩的虚拟机底层知识讲得条理清晰,是 Java 后端开发进阶、面试准备、线上性能问题排查的核心参考书。

★★★★★

内容太过深奥,实属罕见教材,鄙人拜读,感慨万千,阅读了前面两章,内容均是专业级描述,故很难想象。有实战也有理论描述,是一本好书。

★★★☆☆

号称重写50的内容,看完感觉比较失望,能重写20%就不错了,书的内容还是不错的,可惜实验用例还在比较5 6 7 ,后面的优化基本还是在谈6,不希望你升级到JDK11,至少升级到JDK8吧

书籍信息

作者周志明
出版社机械工业出版社
出版时间2021-05-16
ISBN9787111641247
分类计算机-编程设计
格式EPUB
完结状态连载中
章节数295