什么时候会触发full gc?
- 当系统主动调用System.gc()函数的时候,只是建议虚拟机执行 Full GC,但是虚拟机不一定真正去执行。不建议使用这种方式,而是让虚拟机管理内存。
- 老年代空间不足,当老年代空间不足会触发full gc
- JDK1.7之前永久代空间满会触发full gc
- 空间分配担保失败在发生Minor GC之前,虚拟机会检查老年代最大可用的连续空间是否大于新生代所有对象的总空间,如果大于,则此次Minor GC是安全的。如果小于,则虚拟机会查看HandlePromotionFailure设置值是否允许担保失败。如果HandlePromotionFailure=true,那么会继续检查老年代最大可用连续空间是否大于历次晋升到老年代的对象的平均大小,如果大于,则尝试进行一次Minor GC,但这次Minor GC依然是有风险的;如果小于或者HandlePromotionFailure=false,则改为进行一次Full GC。
- 未指定老年代和新生代大小,堆伸缩时会产生fullgc,所以一定要配置-Xmx、-Xms
什么时候会触发Minor Gc?
Minor GC 通常在新生代中的 Eden 区域满时,或者 Survivor 区域中的对象年龄达到一定阈值时触发。下面分别介绍这两种情况:
- Eden 区域满时:当 Java 应用程序创建新对象时,这些对象会被分配到 Eden 区域中。当 Eden 区域满时,无法再分配出任何连续的内存空间,此时就需要进行 Minor GC 了。Minor GC 会将 Eden 区域中不再使用的对象清理掉,同时将存活的对象移到 Survivor 区域。
- Survivor 区域中的对象年龄达到一定阈值:当对象在 Survivor 区域中经历一定次数的垃圾回收后,其年龄就会增加。当对象年龄达到一定阈值时,就会被移到老年代中去。这个阈值的默认值为 15,也可以通过 JVM 参数 -XX:MaxTenuringThreshold 来进行设置。当 Survivor 区域中的对象年龄达到一定阈值时,也会触发 Minor GC。
总的来说,Minor GC 是针对新生代中的垃圾回收,它通常在 Eden 区域满时或者 Survivor 区域中对象年龄达到一定阈值时被触发。Minor GC 的目的是清除新生代中不再使用的对象,同时将存活的对象移到 Survior 区域和老年代中。
Minor Gc 和 Full GC有什么不同呢?
- Major GC 是清理老年代。
- Full GC 是清理整个堆空间—包括年轻代和老年代。
频繁发生GC如何优化
jstat -gcutil 5280 1000
命令是用于监视 Java 虚拟机的垃圾回收统计信息的命令。这个命令通常在命令行中执行,用于实时监测 Java 应用程序的内存管理和垃圾回收情况。
解释一下命令的各个部分:
jstat
: Java 虚拟机统计信息工具,用于监测虚拟机状态和性能数据。
-gcutil
: 表示查询垃圾回收的统计信息,包括各个垃圾回收器的使用情况。
5280
: 进程的进程 ID 或虚拟机标识符(JVM ID),用于指定要监控的 Java 虚拟机实例。这个值需要根据实际情况进行替换。
1000
: 表示每隔 1000 毫秒(1秒)收集一次统计信息并输出。
可能原因
- 内存泄漏(代码有问题,对象引用没及时释放,导致对象不能及时回收)。
- 死循环。在死循环中,对象不断被创建,但无法被正常释放,最终导致堆内存耗尽,触发 Full GC 进行垃圾回收。
- 大对象。 尤其是大对象,80%以上的情况就是他。 那么大对象从哪里来的呢?
- 数据库(包括MySQL和MongoDB等NoSQL数据库),结果集太大。
- 第三方接口传输的大对象。
- 消息队列,消息太大。
JVM调优
对应的参数列表
-XX:+PrintGC 输出GC日志
-XX:+PrintGCDetails 输出GC的详细日志
-XX:+PrintGCTimeStamps 输出GC的时间戳(以基准时间的形式)
-XX:+PrintGCDateStamps 输出GC的时间戳(以日期的形式,如 2013-05-04T21:53:59.234+0800)
-XX:+PrintHeapAtGC 在进行GC的前后打印出堆的信息
-Xloggc:../logs/gc.log 日志文件的输出路径
当元空间出现内存碎片化时,我们会着重关注是不是创建了大量的类加载器。
通过进一步分析,发现是由于反射导致创建大量 DelegatingClassLoader。其核心原理如下:
在 JVM 上,最初是通过 JNI 调用来实现方法的反射调用,当 JVM 注意到通过反射经常访问某个方法时,它将生成字节码来执行相同的操作,称为膨胀(inflation)机制。如果使用字节码的方式,则会为该方法生成一个 DelegatingClassLoader,如果存在大量方法经常反射调用,则会导致创建大量 DelegatingClassLoader。
JVM调优
-Xmx –Xms:指定 java 堆最大值(默认值是物理内存的 1/4(<1GB))和初始 java 堆最小值(默认值是物理内存的 1/64(<1GB))
开发过程中,通常会将 -Xms 与 -Xmx 两个参数配置成相同的值,其目的是为了能够在 java 垃圾回收机制清理完堆区后不需要重新分隔计算堆区的大小而浪费资源。
创建大量MAP导致内存暴涨,优化kafka消息
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 堆的限制
对象创建的过程
- 类加载检查
- 分配内存
指针碰撞 :
- 适用场合 :堆内存规整(即没有内存碎片)的情况下。
- 原理 :用过的内存全部整合到一边,没有用过的内存放在另一边,中间有一个分界指针,只需要向着没用过的内存方向将该指针移动对象内存大小位置即可。
- 使用该分配方式的 GC 收集器:Serial, ParNew
空闲列表 :
- 适用场合 : 堆内存不规整的情况下。
- 原理 :虚拟机会维护一个列表,该列表中会记录哪些内存块是可用的,在分配的时候,找一块儿足够大的内存块儿来划分给对象实例,最后更新列表记录。
- 使用该分配方式的 GC 收集器:CMS
初始化零值
设置对象头
执行init方法
类加载过程
- 加载:通过classloader加载类
- 验证:JVM虚拟机验证字节码文件
- 准备:为类变量分配内存和初始化
- 解析:将符号引用替换为直接引用
- 初始化:调用
<clinit> ()
真正初始化类
强引用,弱引用,软引用,虚引用的区别
强引用:当内存不足时,JVM 开始进行 GC(垃圾回收),对于强引用对象,就算是出现了OOM 也不会对该对象进行回收,死都不会收。
软引用:当系统内存充足的时候,不会被回收;当系统内存不足时,它会被回收,软引用通常用在对内存敏感的程序中,比如高速缓存就用到软引用,内存够用时就保留,不够时就回收。
弱引用:弱引用需要用到 java.lang.ref.WeakReference 类来实现,它比软引用的生存周期更短。对于只有弱引用的对象来说,只要有垃圾回收,不管 JVM 的内存空间够不够用,都会回收该对象占用的内存空间。
虚引用:虚引用需要 java.lang.ref.Phantomreference 类来实现。顾名思义,虚引用就是形同虚设。与其它几种引用不同,虚引用并不会决定对象的生命周期。
强引用对象会被GC清除吗?
不会被清除,因为强引用对象本身就是GC ROOT,除非该方法已经从栈中弹出,或者不在使用才会被清除
新生代老年代比例
内存泄漏和内存溢出的区别
在Java中,内存泄漏(Memory Leak)和内存溢出(Memory Overflow)是两个不同的概念,它们有一些区别。
- 内存泄漏(Memory Leak): 内存泄漏指的是在程序运行过程中,由于错误的内存管理导致无法再被使用的对象仍然占据着内存空间,而这些占用的内存无法被释放。这意味着系统的可用内存逐渐减少,直到最终导致内存耗尽。常见的内存泄漏场景包括:
- 未正确释放动态分配的内存:如果程序在分配了内存后没有及时释放,这些内存就会一直占据系统资源。
- 对象引用仍存在但不再需要:如果某个对象的引用仍然存在,即使该对象不再需要,垃圾回收器也无法回收该对象所占用的内存。
- 内存溢出(Memory Overflow): 内存溢出指的是程序在申请内存时,无法获得足够的内存空间以满足其需求,导致无法继续执行程序或者出现异常。当程序需要分配更多内存而可用内存不足时,就会发生内存溢出。常见的内存溢出场景包括:
- 堆内存溢出:当创建过多的对象并且无法被垃圾回收时,堆内存可能会耗尽。这通常是由于程序中存在内存泄漏或者申请了过大的对象导致的。
- 栈内存溢出:当递归调用层级过深、方法调用过多或者栈帧过大时,栈内存可能会耗尽。每个方法调用都会在栈上创建一个栈帧,如果栈帧数量超过了栈的容量,就会导致栈内存溢出。
区别:
- 内存泄漏是指无法释放已经分配的内存,导致内存持续占用和耗尽;而内存溢出则是指无法获得足够的内存空间来满足需求,导致程序无法执行或者出现异常。
- 内存泄漏通常是逐渐发生的,随着时间的推移导致内存占用不断增加;而内存溢出通常是在程序执行过程中突然发生的,因为需要的内存超过了可用内存。
- 内存泄漏是程序中的错误行为,通常是由于忘记释放资源或者引用未及时清理等原因导致的;而内存溢出可能是因为程序设计问题,如递归调用或者对象太大等。
内存泄漏
hbase配置类需要成为static