## 什么时候会触发full gc? 1. 当系统主动调用System.gc()函数的时候,只是建议虚拟机执行 Full GC,但是虚拟机不一定真正去执行。不建议使用这种方式,而是让虚拟机管理内存。 2. 老年代空间不足,当老年代空间不足会触发full gc 3. JDK1.7之前永久代空间满会触发full gc 4. 空间分配担保失败在发生**Minor GC**之前,虚拟机会检查**老年代最大可用的连续空间**是否**大于新生代所有对象的总空间**,如果大于,则此次**Minor GC是安全的**。如果小于,则虚拟机会查看**HandlePromotionFailure**设置值是否允许担保失败。如果HandlePromotionFailure=true,那么会继续检查老年代最大可用连续空间是否大于**历次晋升到老年代的对象的平均大小**,如果大于,则尝试进行一次Minor GC,但这次Minor GC依然是有风险的;如果小于或者HandlePromotionFailure=false,则改为进行一次Full GC。 5. 未指定老年代和新生代大小,堆伸缩时会产生fullgc,所以一定要配置-Xmx、-Xms ## JVM调优 -Xmx –Xms:指定 java 堆最大值(默认值是物理内存的 1/4(<1GB))和初始 java 堆最小值(默认值是物理内存的 1/64(<1GB)) 开发过程中,通常会将 -Xms 与 -Xmx 两个参数配置成相同的值,其目的是为了能够在 java 垃圾回收机制清理完堆区后不需要重新分隔计算堆区的大小而浪费资源。 ## CMS垃圾收集器 CMS(标记并发清除垃圾收集器),对比以前的jvm垃圾收集器,最大区别在于并发,当GC线程工作的时候,用户线程不会完全停止,会与GC线程并发执行 CMS主要目的为了避免老年代GC出现长时间的卡顿,主要步骤可以分为以下4点 * **初始标记:** 暂停所有的其他线程,并记录下直接与 root 相连的对象,速度很快 ; * **并发标记:** 同时开启 GC 和用户线程,用一个闭包结构去记录可达对象。但在这个阶段结束,这个闭包结构并不能保证包含当前所有的可达对象。因为用户线程可能会不断的更新GC ROOT,所以 GC 线程无法保证可达性分析的实时性。所以这个算法里会跟踪记录这些发生引用更新的地方 * **重新标记:** 重新标记阶段就是为了修正并发标记期间因为用户程序继续运行而导致标记产生变动的那一部分对象的标记记录,这个阶段用户线程会被暂停,但是停顿时间一般会比初始标记阶段的时间稍长,远远比并发标记阶段时间短 * **并发清除:** 开启用户线程,同时 GC 线程开始对未标记的区域做清扫。 CMS垃圾收集器的缺点 * 产生大量空间碎片:因为老年代是标记-清楚算法,所以会产生大量的空间碎片 * 对CPU资源敏感:应该它与用户线程并行, 所以会导致系统资源被占用 * 内存需要预留:CMS垃圾收集器可以一边回收垃圾,一边处理用户线程,那需要在这个过程中保证有充足的内存空间供用户使用。如果CMS运行过程中预留的空间不够用,会报错 ## G1垃圾收集器 G1是一款面向服务器的垃圾收集器,主要针对配备多核CPU及大容量内存的机器,可以预测垃圾收集时间,还兼具高吞吐量的性能特征。 G1垃圾收集器将Java堆分为多个大小相等的Region,每个Region都可以是新生代、老年代,或者未标注的区域,G1垃圾收集器还有一个Humongous区域,对于堆中的大对象,会默认分配到此区域,当然G1垃圾收集器也会将H区作为老年代的一部分。 G1垃圾收集器的垃圾回收过程: * **初始标记**:标记从根节点直接可达的对象。这个阶段是STW的,。所以说用户线程会被暂停 * **并发标记**:在整个堆中进行并发标记。即和用户进程并行执行。在并发阶段,如果发现区域对象都是垃圾,那么这个区域会被立即回收。同时在这个过程中会计算每个区域的对象活性 * **再次标记**:由于应用程序持续进行,需要修正上一次的标记结果。这个阶段也是STW的 * **筛选回收**:首先对各个 Region 中的回收价值和成本进行排序(**G1 收集器在后台维护了一个优先列表,每次根据允许的收集时间,优先选择回收价值最大的 Region**),根据用户所期望的 GC 停顿时间来制定回收计划。此阶段其实也可以做到与用户程序一起并发执行,但是因为只回收一部分 Region,时间是用户可控制的,而且停顿用户线程将大幅度提高收集效率。 ## CMS和G1的区别 * STW的时间:CMS是以最小STW时间为目标,而G1主要是建立可预测的停顿时间模型,可以控制STW时间 * 适用范围区别:CMS收集器是老年代的收集器,可以配合新生代的Serial和ParNew收集器一起使用,G1收集器收集范围是老年代和新生代。不需要结合其他收集器使用 * 垃圾碎片:CMS会产生大量的空间碎片,而G1由于是Region管理,降低了内存碎片的可能性 ## 垃圾回收算法有哪些? 垃圾回收算法主要有三种: * 标记清除算法:先标记,后清楚。会产生大量的空间碎片 * 标记复制算法:复制存活的对象。空间利用率不高 * 标记整理算法:标记清除算法的改进,清楚之后对碎片进行处理 ## 如何判断对象是否为垃圾 主要有两种方法可以判断: * 引用计数法:无法解决相互引用的问题 * 可达性分析:作为GC ROOT * 虚拟机栈中引用的对象 * 本地方法栈中引用的对象 * 方法区中类静态属性引用的对象 * 方法区中常量引用的对象 * 所有被同步锁持有的对象 ## jvm内存区域 jvm内存区域大体上可以分为线程私有和线程共有两大部分 线程私有主要包括 * 程序计数器:记录当前线程所执行的字节码的位置,为了线程切换后可以恢复到正确的位置,程序计数器被设计为私有的 * Java 虚拟机栈:java方法的调用都是通过java虚拟机栈来实现的。方法调用就压栈,方法结束调用就弹出栈。栈由一个个栈帧组成,而每个栈帧中都拥有:局部变量表、操作数栈、动态链接、方法返回地址等。 * 本地方法栈:**虚拟机栈为虚拟机执行 Java 方法 (也就是字节码)服务,而本地方法栈则为虚拟机使用到的 Native 方法服务。** 在 HotSpot 虚拟机中和 Java 虚拟机栈合二为一。 线程共有部分主要包括: * 堆:JDK1.7之前,java堆主要分为一下三部分,新生代,老年代,永久代 * 方法区:方法区会存储已被虚拟机加载的类信息、字段信息、方法信息、常量、静态变量。其中常量被存储在运行时常量池中,string被存储在字符串常量池中,字符串常量池在jdk1.8之后就被移动到堆上 * 直接内存:直接内存是一种特殊的内存缓冲区,直接在本地内存上面分配,直接内存的分配不会受到 Java 堆的限制 ## 对象创建的过程 1. 类加载检查 2. 分配内存 指针碰撞 : - 适用场合 :堆内存规整(即没有内存碎片)的情况下。 - 原理 :用过的内存全部整合到一边,没有用过的内存放在另一边,中间有一个分界指针,只需要向着没用过的内存方向将该指针移动对象内存大小位置即可。 - 使用该分配方式的 GC 收集器:Serial, ParNew 空闲列表 : - 适用场合 : 堆内存不规整的情况下。 - 原理 :虚拟机会维护一个列表,该列表中会记录哪些内存块是可用的,在分配的时候,找一块儿足够大的内存块儿来划分给对象实例,最后更新列表记录。 - 使用该分配方式的 GC 收集器:CMS 3. 初始化零值 4. 设置对象头 5. 执行init方法 ## 类加载过程 1. 加载:通过classloader加载类 2. 验证:JVM虚拟机验证字节码文件 3. 准备:为类变量分配内存和初始化 4. 解析:将符号引用替换为直接引用 5. 初始化:调用` ()`真正初始化类 ## 强引用,弱引用,软引用,虚引用的区别 强引用:当内存不足时,JVM 开始进行 GC(垃圾回收),对于强引用对象,就算是出现了OOM 也不会对该对象进行回收,死都不会收。 软引用:当系统内存充足的时候,不会被回收;当系统内存不足时,它会被回收,软引用通常用在对内存敏感的程序中,比如高速缓存就用到软引用,内存够用时就保留,不够时就回收。 弱引用:弱引用需要用到 java.lang.ref.WeakReference 类来实现,它比软引用的生存周期更短。对于只有弱引用的对象来说,只要有垃圾回收,不管 JVM 的内存空间够不够用,都会回收该对象占用的内存空间。 虚引用:虚引用需要 java.lang.ref.Phantomreference 类来实现。顾名思义,虚引用就是形同虚设。与其它几种引用不同,虚引用并不会决定对象的生命周期。 ## 强引用对象会被GC清除吗? 不会被清除,因为强引用对象本身就是GC ROOT,除非该方法已经从栈中弹出,或者不在使用才会被清除 ## 新生代老年代比例 ![1](assets/061921034534396.png)