最美的不是下雨天,是曾与你躲过雨的屋檐————《不能说的秘密》
JVM 双亲委派机制
好处:防止内存中出现了多份相同的字节码
当一个.class 文件要被加载进 JVM 的时候
- AppClassLoader 首先不会自己去尝试加载这个类,而是把类加载请求委派给父类加载器 ExtClassLoader 去完成
- 当 ExtClassLoader 加载一个 class 时,它首先也不会自己去尝试加载这个类,而是把类加载请求委派给 BootStrapClassLoader 去完成
- 如果 BootStrapClassLoader 加载失败,会使用 ExtClassLoader 来尝试加载;若 ExtClassLoader 也加载失败,则会使用 AppClassLoader 来加载,若 AppClassLoader 还是加载失败,会抛出异常 ClassNotFoundException
当成功加载好某个类时,会将这个类缓存起来,下次使用时直接使用缓存,不会再次加载
类加载过程
加载器加载到 jvm 中,步骤:
- 加载, 通过类名查找并加载该类的二进制流,在堆中生成该类的 class 类对象
加载阶段和连接阶段的部分内容是交叉进行的,加载阶段尚未结束,连接阶段可能就已经开始了
-
连接
a. 验证,文件格式验证→元数据验证→字节码验证→符号引用验证
b. 准备,为类变量分配内存并设置类变量初始值的阶段,这里指的事类的静态变量,不包括实例变量
c. 解析,将常量池的符号引用代替为直接引用JIT 即时编辑器
JVM 解析过程:
对热点代码进行重新编译优化,生成机器代码,让 CPU 直接执行,非热点代码直接解析
热点代码: 多次调用的方法,多次执行的循环体
使用 热点探测 来检测是否为热点代码:采样和计数器,jvm 使用的是计数器,当计数器超过阈值溢出了,就会触发 JIT 编译 - 初始化
图片来源:https://segmentfault.com/a/11…
JVM 内存结构
基于 JDK1.8
- 堆:线程共享,内存中最大的一块,存放对象实例,基本所有对象实例以及数组都在这里分配内存,是垃圾收集器的主要区域,现在收集器基本采用分代垃圾收集算法,所有堆又分为新生代和老年代
- 虚拟机栈:线程私有,生命周期和线程相同,描述的是 Java 方法执行的内存结构,每次方法调用的数据通过栈传递,所以每个方法被执行时都会同时创建一个栈帧
- 本地方法栈:线程私有,与虚拟机栈类似,区别是虚拟机栈为 Java 方法执行服务,本机方法栈为虚拟机使用到的 Native 方法服务
- 程序计数器:线程私有,是当前线程所执行的字节码的行号指示器,因此每条线程都有一个独立的程序计数器
- 方法区:线程共享的内存区域,用于储存被虚拟机加载的类信息、常量、静态变量等数据
常量池:
主要针对 String
https://tech.meituan.com/2014…
JVM 垃圾回收——GC
JVM 垃圾回收主要针对的是内存结构中的堆
JVM 回收的是 "垃圾",即是程序不再使用了,不再需要了,因此回收的第一步需要判断哪些是垃圾,即对象死亡
常用有:** 引用计数法 ** 和 ** 可达性分析算法 **
引用计数法:给对象中添加一个引用计数器,每当有一个地方引用它,计数器就加 1;当引用失效,计数器就减 1;任何时候计数器为 0 的对象就是不可能再被使用的
可达性分析算法:主流的 JVM 采用的是这种方式,这个算法的基本思想就是通过一系列的称为“GC Roots”的对象作为起点,从这些节点开始向下搜索,节点所走过的路径称为引用链,当一个对象到 GC Roots 没有任何引用链相连的话,则证明此对象是不可用的。能够做 GC Roots 的有:
- 虚拟机栈中引用的对象(局部变量)
- 方法去中类静态属性引用的对象(static 对象实例)
- 方法区中常量引用的对象(常量实例)
- 本地方法引用的对象
引用:强引用、软引用、弱引用和虚引用
- 强引用:最普遍的引用,如 Object object = new Object()就是强引用,即使内存不足也不会回收这类引用,抛出 OutOfMemory 的异常
- 软引用:软引用引用的对象,在垃圾回收时,如果发现内存资源不足,即使被引用了依然会被回收,但是在内存资源充足的情况下是不会回收的
- 弱引用:弱引用引用的对象,在垃圾回收时,不管内存资源充不充足,都会被回收
- 虚引用:形同虚设的引用,在任何时候都可能被垃圾回收
不可达的对象并非一定回收
如果一个不可达的对象覆盖了 finalize 方法,并且这个方法从来没被执行过,垃圾回收器就不会马上回收,而是放在一个队列,进行二次标记,但在下一次回收前,重新建立引用,便不会被回收。
** 不推荐在 finalize 方法中去释放资源,因为它什么时候会被调用是不确定的。**
垃圾回收算法
- 标记 - 清除算法
- 复制算法
- 标记 - 整理算法
- 分代收集算法
分代收集算法
将 java 堆分为新生代和老年代,根据各个年代的特点选择合适的垃圾收集算法
- 新生代(Minor GC):复制算法,所以分为 Eden 区、Survivor1、Survivor2 三个区
- 老年代(Full GC):标记 - 清除算法 或标记 - 整理算法
垃圾收集器
算法只是方法论,收集器才是干活的
- Serial 收集器
- ParNew 收集器
- Parallel Scavenge 收集器
- Serial Old 收集器
- Parallel Old 收集器
- CMS 收集器
- G1 收集器
根据具体应用场景选择适合自己的垃圾收集器
什么情况下触发垃圾回收
- Minor GC:当新对象生成,并且在 Eden 申请空间失败时,就会触发 Minor GC,对 Eden 区域进行 GC,清除非存活对象,并且把尚且存活的对象移动到 Survivor 区。然后整理 Survivor 的两个区。这种方式的 GC 是对年轻代的 Eden 区进行,不会影响到年老代。因为大部分对象都是从 Eden 区开始的,同时 Eden 区不会分配的很大,所以 Eden 区的 GC 会频繁进行。因而,一般在这里需要使用速度快、效率高的算法,使 Eden 去能尽快空闲出来。
-
Full GC:对整个堆进行整理,所以比 Minor GC 要慢,因此应该尽可能减少 Full GC 的次数。触发 Full GC 的原因有几个:
- 老年代被写满
- 持久带被写满
- System.gc()被显示调用
- 上一次 GC 之后 Heap 的各域分配策略动态变化
JVM 参数与调优
在对 JVM 调优的过程中,很大一部分工作就是对于 FullGC 的调节
-
年轻代:
- 响应时间优先的应用:尽可能设大,直到接近系统的最低响应时间限制(根据实际情况选择)。在此种情况下,年轻代收集发生的频率也是最小的。同时,减少到达年老代的对象。
- 吞吐量优先的应用:尽可能的设置大,可能到达 Gbit 的程度。因为对响应时间没有要求,垃圾收集可以并行进行,一般适合 8CPU 以上的应用。
-
老年代:
-
响应时间优先的应用:年老代使用并发收集器,所以其大小需要小心设置,一般要考虑并发会话率和会话持续时间等一些参数。如果堆设置小了,可以会造成内存碎片、高回收频率以及应用暂停而使用传统的标记清除方式;如果堆大了,则需要较长的收集时间。最优化的方案,一般需要参考以下数据获得:
- 并发垃圾收集信息
- 持久代并发收集次数
- 传统 GC 信息
- 花在年轻代和年老代回收上的时间比例
- 减少年轻代和年老代花费的时间,一般会提高应用的效率
- 吞吐量优先的应用
一般吞吐量优先的应用都有一个很大的年轻代和一个较小的年老代。原因是,这样可以尽可能回收掉大部分短期对象,减少中期的对象,而年老代尽存放长期存活对象。
-
** 较小堆引起的碎片问题 **
因为年老代的并发收集器使用标记、清除算法,所以不会对堆进行压缩。当收集器回收时,他会把相邻的空间进行合并,这样可以分配给较大的对象。但是,当堆空间较小时,运行一段时间以后,就会出现“碎片”,如果并发收集器找不到足够的空间,那么并发收集器将会停止,然后使用传统的标记、清除方式进行回收。如果出现“碎片”,可能需要进行如下配置:
1. -XX:+UseCMSCompactAtFullCollection:使用并发收集器时,开启对年老代的压缩。2. -XX:CMSFullGCsBeforeCompaction=0:上面配置开启的情况下,这里设置多少次 Full GC 后,对年老代进行压缩
常见配置汇总
堆设置
-Xms: 初始堆大小
-Xmx: 最大堆大小
-XX:NewSize=n: 设置年轻代大小
-XX:NewRatio=n: 设置年轻代和年老代的比值。如: 为 3,表示年轻代与年老代比值为 1:3,年轻代占整个年轻代年老代和的 1 /4
-XX:SurvivorRatio=n: 年轻代中 Eden 区与两个 Survivor 区的比值。注意 Survivor 区有两个。如:3,表示 Eden:Survivor=3:2,一个 Survivor 区占整个年轻代的 1 /5
-XX:MaxPermSize=n: 设置持久代大小
收集器设置
-XX:+UseSerialGC: 设置串行收集器
-XX:+UseParallelGC: 设置并行收集器
-XX:+UseParalledlOldGC: 设置并行年老代收集器
-XX:+UseConcMarkSweepGC: 设置并发收集器
垃圾回收统计信息
-XX:+PrintGC
-XX:+PrintGCDetails
-XX:+PrintGCTimeStamps
-Xloggc:filename
并行收集器设置
-XX:ParallelGCThreads=n: 设置并行收集器收集时使用的 CPU 数。并行收集线程数。-XX:MaxGCPauseMillis=n: 设置并行收集最大暂停时间
-XX:GCTimeRatio=n: 设置垃圾回收时间占程序运行时间的百分比。公式为 1 /(1+n)
并发收集器设置
-XX:+CMSIncrementalMode: 设置为增量模式。适用于单 CPU 情况。-XX:ParallelGCThreads=n: 设置并发收集器年轻代收集方式为并行收集时,使用的 CPU 数。并行收集线程数。
学习链接:
https://segmentfault.com/a/11…
https://snailclimb.top/JavaGu…
感谢两位大佬的文章,受益匪浅