第一章:走进Java
第二章:Java内存区域与内存溢出异常
第三章:垃圾收集器与内存分配策略
第四章:虚拟机性能监控与故障处理
第五章:调优案例分析与实战
第六章:类文件结构
第七章:虚拟机类加载机制
第八章:虚拟机字节码执行引
第九章:类加载及其执行子系统的案例与实战
第十章:早期(编译器)优化
第十一章:晚期(运行期)优化
第十二章:Java内存模型与线程
第十三章:线程安全与锁优化
程序计数器、虚拟机栈、本地方法栈 3 个区域随线程生灭(因为是线程私有),栈中的栈帧随着方法的进入和退出而有条不紊地执行着出栈和入栈操作。而 Java 堆和方法区则不一样,一个接口中的多个实现类需要的内存可能不一样,一个方法中的多个分支需要的内存也可能不一样,我们只有在程序处于运行期才知道那些对象会创建,这部分内存的分配和回收都是动态的,垃圾回收期所关注的就是这部分内存。
给对象中添加一个引用计数器,每当有一个地方引用它时计数器值就加1,当引用失效时,计数器值就减1;任何时刻计数器为0的对象就是不可能被使用。
通过一系列称作“GC Roots”的对象作为起始点,向下搜索,搜索走过的路径成为引用链,当一个对象到GC Roots没有任何引用链相连,则证明不可用
可作为GC Roots的对象:
Object obj = new Object();
创建的,只要强引用在就不回收。即使在可达性分析算法中不可达的对象,它们暂时出于“缓刑”阶段,一个对象的真正死亡至少要经历两次标记过程:如果对象在进行中可达性分析后发现没有与 GC Roots 相连接的引用链,那他将会被第一次标记并且进行一次筛选,筛选条件是此对象是否有必要执行 finalize() 方法。当对象没有覆盖 finalize() 方法,或者 finalize() 方法已经被虚拟机调用过,虚拟机将这两种情况都视为“没有必要执行”。
如果这个对象被判定为有必要执行 finalize() 方法,那么这个对象竟会放置在一个叫做 F-Queue 的队列中,并在稍后由一个由虚拟机自动建立的、低优先级的 Finalizer 线程去执行它。这里所谓的“执行”是指虚拟机会出发这个方法,并不承诺或等待他运行结束。finalize() 方法是对象逃脱死亡命运的最后一次机会,稍后 GC 将对 F-Queue 中的对象进行第二次小规模的标记,如果对象要在 finalize() 中成功拯救自己 —— 只要重新与引用链上的任何一个对象简历关联即可。
finalize() 方法只会被系统自动调用一次。
回收废弃常量:判断没有该常量的引用。
回收无用的类:要以下三个条件都满足
算法分为标记和清除两个阶段,首先标出需要回收的对象,标记完成后统一回收
不足:效率,标记和清除效率都不高;空间,产生内存碎片
将内存按容量分为大小相等的两块,每次只使用一块
当一块用完了,将活着的对象复制到另一块上,然后把已使用过的内存清理掉
不足:内存缩小一半,造成内存浪费
解决前一种方法的不足,但是会造成空间利用率低下。因为大多数新生代对象都不会熬过第一次 GC。所以没必要 1 : 1 划分空间。可以分一块较大的 Eden 空间和两块较小的 Survivor 空间,每次使用 Eden 空间和其中一块 Survivor。当回收时,将 Eden 和 Survivor 中还存活的对象一次性复制到另一块 Survivor 上,最后清理 Eden 和 Survivor 空间。大小比例一般是 8 : 1 : 1,每次浪费 10% 的 Survivor 空间。但是这里有一个问题就是如果存活的大于 10% 怎么办?这里采用一种分配担保策略:多出来的对象直接进入老年代。
适合老年代
过程与 ”标记-清除“ 算法一样,不同的是不是直接对可回收对象进行清理,而是让所有存活对象都向一端移动,然后直接清理掉端边界以外的内存
根据存活对象划分几块内存区,一般是分为新生代和老年代。然后根据各个年代的特点制定相应的回收算法
新生代:每次垃圾回收都有大量对象死去,只有少量存活,选用复制算法
比较合理。
老年代:老年代中对象存活率较高、没有额外的空间分配对它进行担保。所以必须使用 标记 —— 清除
或者 标记 —— 整理
算法回收。
新生代收集器,采用复制算法
并行的多线程收集器
目的达到一个可控制的吞吐量
四个步骤
初始标记、重新标记扔“Stop The World”,但是耗时短;
并发标记、并发清理耗时长,但收集器线程可以与用户线程一起工作;
所以,总的来说CMS收集器的内存回收过程是与用户线程一起并发的。
优点:并发收集、低停顿
缺点:对CPU资源敏感;无法处理浮动垃圾;空间碎片
面向服务端应用的垃圾收集器
特点
其他收集器进行收集的范围都是整个新生代或者老年代,而G1把JAVA堆分为多个Region,根据价值大小(回收获得的空间和所需要时间的经验值)回收
如不计算维护 Remembered Set的操作,G1 的步骤为:
两个问题:给对象分配内存,回收分配给对象的内存
大多数情况下,对象在新生代Eden区中分配。当没有足够的Eden时,虚拟机进行一次Minor GC
打印内存回收日志:-XX:PrintGCDetails
大对象:大量连续内存空间的Java对象,如很长的字符串以及数组。经常出现大对象容易导致还有不少空间时就提前触发垃圾收集以获取足够的连续空间
通过设置参数-XX:PretenureSizeThreshold,大于该参数的垃圾收集的时候直接进入老年代,避免在Eden区和两个Survivor进行大量的内存复制
虚拟机为每个对象定义了年龄计数器,如果对象在Eden区出生,每熬过一次Minor GC,年龄 + 1。
年龄阈值-XX:MaxTenuringThreshold
为了适应不同内存状况,虚拟机不是必须要求对象年龄达到年龄阈值才晋升老年代。在Survivor区相同年龄大小的所有对象大小的总和大于Survivor的一半,则年龄大于或等于该年龄的对象直接进入老年代
发生Minor GC之前,虚拟机会先检查老年代最大可用的连续空间是否大于新生代所有对象总空间,如果成立则Minor GC是安全的。不成立则虚拟机会查看 HandlePromotionFailure设置值是否允许担保失败,如果允许,那么会继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小,如果大于,就尝试进行一次 冒险的Minor GC;如果小于或HandlePromotionFailure
设置不允许冒险,则改为 Full GC。