简述GC垃圾回收

有了GC,程序员就不需要再手动的去控制内存的释放。当Java虚拟机发觉内存资源紧张的时候,就会自动地去清理无用对象(没有被引用到的对象)所占用的内存空间

Native关键字

Java是一个跨平台的语言,既然是跨了平台,所付出的代价就是牺牲一些对底层的控制,而Java要实现对底层的控制,就需要借助一些其他语言的帮助,这个就是native的作用。
Native method就是一个java调用非java代码的接口,例如Thread.start()方法为本地方法,凡是带了native关键字,说明Java权限已经达不到了,需要调用底层C语言的库。具体体现为native方法会进入本地方法栈,会调用本地方法接口(JNI),然后进入本地方法库

GC如何判断对象可以被回收?

  1. 引用计数法(已被淘汰的算法)
    1. 每一个对象有一个引用属性,新增一个引用时加一,引用释放时减一,计数为0的时候可以回收。

但是这种计算方法,有一个致命的问题,无法解决循环引用的问题

  1. 可达性分析算法(根引用)
    1. 从GcRoot开始向下搜索,搜索所走过的路径被称为引用链,当一个对象到GcRoot没有任何引用链相连时,则证明此对象是不可用的,那么虚拟机就可以判定回收。
    2. 那么GcRoot有哪些?
      1. 虚拟机栈中引用的对象
      2. 方法区中静态属性引用的对象。
      3. 方法区中常量引用的对象
      4. 本地方法栈中(即一般说的native方法)引用的对象
  2. 此外,不同的引用类型的回收机制是不一样的
    1. 强引用:通过关键字new的对象就是强引用对象,强引用指向的对象任何时候都不会被回收,宁愿OOM也不会回收。
    2. 软引用:如果一个对象持有软引用,那么当JVM堆空间不足时,会被回收。一个类的软引用可以通过java.lang.ref.SoftReference持有。
    3. 弱引用:如果一个对象持有弱引用,那么在GC时,只要发现弱引用对象,就会被回收。一个类的弱引用可以通过java.lang.ref.WeakReference持有。
    4. 虚引用:几乎和没有一样,随时可以被回收。通过PhantomReference持有。 用来跟踪对象的

      说一下 jvm 的主要组成部分?及其作用?

      类加载器(ClassLoader)
      运行时数据区(Runtime Data Area)
      执行引擎(Execution Engine)
      本地库接口(Native Interface)

      java内存区域

      image.png
      方法区(公有):用户存储已被虚拟机加载的类信息,常量,静态常量**及时编译器编译后的代码等数据
      其中包含常量池:用户存放编译器生成的各种字面量和符号引用。
      异常状态:OutOfMemoryErroy(OOM)
      image.png
      堆(公有):是JVM所管理的内存中最大的一块。唯一目的就是存放实例对象,几乎所有的实例都在这里分配。Java堆是垃圾收集器管理的主要区域,因此很多时候回也被称为“GC”堆。
      异常状态:OutOfMemoryErroy
      虚拟机栈(线程私有):描述的是方法执行的内存模型:每个方法在执行时都会创建一个栈帧,用来存储局部变量表,操作数栈,动态连接、方法出口等信息。每一个方法从调用直至完成的过程,就对应这一个栈帧在虚拟机中入栈到出栈的过程。
      异常状态:OutOfMemoryError StackOverflowError
      本地方法栈(线程私有):与虚拟机栈所发挥的作用相似。它们之间的区别不过是虚拟机栈为虚拟机执行java方法,而本地方法栈为虚拟机使用到的Native方法服务
      程序计数器(线程私有):一块较小的内存,当前线程所执行的字节码的行号指示器。字节码解释器工作时,就是通过改变这个计数器的值来选取下一条需要执行的字节码指令**。

image.png

javammode.png

说一下堆栈的区别?

  1. 内存存储的是局部变量内存存储的是实体
    2. 栈内存的更新速度要快于堆内存,因为局部变量的生命周期很短;
    3. 栈内存存放的变量生命周期一旦结束就会被释放,而堆内存存放的实 体会被垃圾回收机制不定时的回收

    新生区中的Eden、Form和To

    一般情况下,新创建的对象都会被分配到Eden区(一些大对象特殊处理),这些对象经历过第一次Minor GC后,如果仍然存活,将会被移到Survivor区。对象在Survivor区中每熬过一次Minor GC,年龄就会增加1岁,当他的年龄增加到一定程度时,就会被移动到老年代中。因为年轻代中的对象基本都是朝生夕死的,所以在年轻代的垃圾回收算法使用的是复制算法(具体可见:JVM笔记(3)-垃圾回收机制中的复制算法)。
    在GC开始的时候,对象只会存在于Eden区和名为“From”的Survivor区,Survivor区“To”是空的。紧接着进行GC,Eden区中所有存活的对象都会被复制到“To”,而在“From” 区中,仍存活的对象会根据他们的年龄值来决定去向。年龄达到一定值(年龄阈值,可以通过-XX:MaxTenuringThreshold来设置)的对象会被移动到年老代中,没有达到阈值的对象会被复制到“To”区域。经过这次GC后,Eden区和From区已经被清空。这个时候,“From”和“To”会交换他们的角色,也就是新的“To”就是上次GC前的“From” ,新的“From”就是上次GC前的“To”。不管怎样,都会保证名为To的Survivor区域是空的。Minor GC会一直重复这样的过程,直到“To”区被填满,“To”区被填满之后,会将所有对象移动到年老代中。

    Minor GC和Full GC的区别

    Minor GC:指发生在新生代的垃圾收集动作,该动作非常频繁。
    Full GC/Major GC:指发生在老年代的垃圾收集动作,出现了Major GC,经常会伴随至少一次的Minor GC。Major GC的速度一般会比Minor GC慢10倍以上。

    什么时候发生Full DC

    1. System.gc()方法的调用
    此方法的调用是建议JVM进行Full GC,虽然只是建议而非一定,但很多情况下它会触发 Full GC,从而增加Full GC的频率,也即增加了间歇性停顿的次数。强烈影响系建议能不使用此方法就别使用,让虚拟机自己去管理它的内存,可通过通过-XX:+ DisableExplicitGC来禁止RMI调用System.gc。
    2、老年代空间不足
    老年代空间只有在新生代对象转入及创建大对象、大数组时才会出现不足的现象,当执行Full GC后空间仍然不足,则抛出如下错误:
    java.lang.OutOfMemoryError: Java heap space
    为避免以上两种状况引起的Full GC,调优时应尽量做到让对象在Minor GC阶段被回收、让对象在新生代多存活一段时间及不要创建过大的对象及数组。
    3、永久代空间不足
    JVM规范中运行时数据区域中的方法区,在HotSpot虚拟机中又被习惯称为永生代或者永生区,Permanet Generation中存放的为一些class的信息、常量、静态变量等数据,当系统中要加载的类、反射的类和调用的方法较多时,Permanet Generation可能会被占满,在未配置为采用CMS GC的情况下也会执行Full GC。如果经过Full GC仍然回收不了,那么JVM会抛出如下错误信息:
    java.lang.OutOfMemoryError: PermGen space
    为避免Perm Gen占满造成Full GC现象,可采用的方法为增大Perm Gen空间或转为使用CMS GC。
    4、CMS GC时出现promotion failed和concurrent mode failure
    对于采用CMS进行老年代GC的程序而言,尤其要注意GC日志中是否有promotion failed和concurrent mode failure两种状况,当这两种状况出现时可能会触发Full GC。
    5、HandlePromotionFailure
    在发生Minor GC之前,虚拟机会先检查老年代最大可用的连续空间是否大于新生代所有对象总空间,如果这个条件成立,那么Minor GC可以确保是安全的。如果不成立,则虚拟机会查看HandlePromotionFailure设置值是否允许担保失败。如果允许,那么会继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小,如果大于,将尝试着进行一次Minor GC,尽管这次Minor GC是有风险的;如果小于,或者HandlePromotionFailure 设置不允许冒险,那这时也要改为进行一次Full GC。
    统计得到的Minor GC晋升到老年代的平均大小大于老年代的剩余空间,这是一个较为复杂的触发情况,例如程序第一次触发Minor GC后,有6MB的对象晋升到老年代,那么当下一次Minor GC发生时,首先检查老年代的剩余空间是否大于6MB,如果小于6MB,则执行Full GC。
    当新生代采用PS GC时,方式稍有不同,PS GC是在Minor GC后也会检查,例如上面的例子中第一次Minor GC后,PS GC会检查此时旧生代的剩余空间是否大于6MB,如小于,则触发对旧生代的回收。
    除了以上4种状况外,对于使用RMI来进行RPC或管理的Sun JDK应用而言,默认情况下会一小时执行一次Full GC。可通过在启动时通过 java -Dsun.rmi.dgc.client.gcInterval=3600000来设置Full GC执行的间隔时间或通过-XX:+ DisableExplicitGC来禁止RMI调用System.gc。
    6、堆中分配很大的对象
    所谓大对象,是指需要大量连续内存空间的java对象,例如很长的数组,此种对象会直接进入老年代,而老年代虽然有很大的剩余空间,但是无法找到足够大的连续空间来分配给当前对象,此种情况就会触发JVM进行Full GC。
    为了解决这个问题,CMS垃圾收集器提供了一个可配置的参数,即-XX:+UseCMSCompactAtFullCollection 开关参数,用于在“享受”完Full GC服务之后额外免费赠送一个碎片整理的过程,内存整理的过程无法并发的,空间碎片问题没有了,但提顿时间不得不变长了,JVM设计者们还提供了另外一个参数 -XX:CMSFullGCsBeforeCompaction,这个参数用于设置在执行多少次不压缩的Full GC后,跟着来一次带压缩的

    JVM8为什么要增加元空间?

    持久代(永久代)的缺点:
    持久代中包含了虚拟机中所有可通过反射获取到的数据,比如class和Method对象 ,JVM用于描述应用程序中用到的类和方法的元数据也存储在持久代中。
    永久代的调优是很困难的,虽然可以设置永久代的大小,但是很难确定一个合适的大小,因为其中的影响因素很多,比如类数量的多少、常量数量的多少等。
    原因:
    1、字符串存在永久代中,容易出现性能问题和内存溢出。
    2、类及方法的信息等比较难确定其大小,因此对于永久代的大小指定比较困难,太小容易出现永久代溢出,太大则容易导致老年代溢出。
    3、永久代会为 GC 带来不必要的复杂度,并且回收效率偏低。

    JVM8中元空间有哪些特点?

  • 类及相关的元数据的生命周期与类加载器一致
  • 每个加载器有专门的存储空间。
  • 不会单独回收某个类。
  • 省略了gc的扫描和压缩时间
  • 元空间里的对象的位置是固定的。
  • 如果发现某个加载器不再存货了,会把相关的空间整个回收

    元空间

    JDK8之前,Hotspot虚拟机的方法区实际上是永久代实现的。
    在JDK8之后,Hotspot虚拟机不再使用永久代,而是采用了全新的元空间。类的元信息被存储在元空间中。元空间没有使用堆内存,而是与堆不相连的本地内存区域。所以,理论上系统可以使用的内存有多大,元空间就有多大,所以不会出现永久代存在时的内存溢出问题。这项改造也是有必要的,永久代的调优是很困难的,虽然可以设置永久代的大小,但是很难确定一个合适的大小,因为其中的影响因素很多,比如类数量的多少、常量数量的多少等。

    说一下类加载的执行过程?

    类加载分为以下 5 个步骤:
    1. 加载:根据查找路径找到相应的 class 文件然后导入;
    2. 检查:检查加载的 class 文件的正确性;
    3. 准备:给类中的静态变量分配内存空间;
    4. 解析:虚拟机将常量池中的符号引用替换成直接引用的过程。
    符号引用就理解为一个标示,而在直接引用直接指向内存中的地
    址;
    5. 初始化:对静态变量和静态代码块执行初始化工作。

    类加载器

    启动类加载器(Bootstrap ClassLoader): 由C++语言实现(针对HotSpot),负责将存放在\lib目录或-Xbootclasspath参数指定的路径中的类库加载到内存中,即负责加载Java的核心类。
    (其他类加载器: 由Java语言实现,继承自抽象类ClassLoader。)
    扩展类加载器(Extension ClassLoader): 负责加载\lib\ext目录或java.ext.dirs系统变量指定的路径中的所有类库,即负责加载Java扩展的核心类之外的类。
    应用程序类加载器(Application ClassLoader): 负责加载用户类路径(classpath)上的指定类库,我们可以直接使用这个类加载器,通过ClassLoader.getSystemClassLoader()方法直接获取。一般情况,如果我们没有自定义类加载器默认就是用这个加载器。

    双亲委派机制

    双亲委派机制的工作流程:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把请求委托给父加载器去完成,依次向上,因此,所有的类加载请求最终都应该被传递到顶层的启动类加载器中,只有当父加载器在它的搜索范围中没有找到所需的类时,即无法完成该类的加载,自驾在其才会尝试自己去加载该类。
    向上委托,向下加载
  1. 应用程序类加载器收到类加载的请求!
  2. 将这个请求向上委托给父类加载器去完成,一直向上委托,直到启动类加载器
  3. 启动加载器检查是否能够加载当前这个类,能加载就结束,使用当前的加载器,否则,抛出异常,通知子加载器进行加载
  4. 重复步骤3

image.png
这样的好处是不同层次的类加载器具有不同优先级,比如所有Java对象的超级父类java.lang.Object,位于rt.jar,无论哪个类加载器加载该类,最终都是由启动类加载器进行加载,保证安全。即使用户自己编写一个java.lang.Object类并放入程序中,虽能正常编译,但不会被加载运行,保证不会出现混乱。

垃圾收集算法

标记-清除算法

最基础的收集算法是“标记-清除”(Mark-Sweep)算法,如同他的名字一样,算法分为“标记”和“清除”两个阶段。

  1. 首先标记出所有需要回收的对象
  2. 在标记完成后统一回收所有被标记的对象

image.png
不足
效率问题:标记和清除两个过程的效率都不高
空间问题:标记清除之后产生大量不连续的内存碎片,空间碎片太多可能会导致以后程序运行过程中需要分配较大对象时,无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。

复制算法

目的:为了解决效率问题。
可用内存按容量大小划分为大小相等的两块,每次只使用其中的一块。当一块内存使用完了,就将还存活着的对象复制到另一块上面,然后再把已使用的内存空间一次清理掉。这样使得每次都是对整个半区进行内存回收,内存分配时也就不用考虑内存碎片等复杂情况。
image.png
缺点: 将内存缩小为了原来的一半。
现代的商业虚拟机都采用这种收集算法来回收新生代(具体可见:JVM笔记(4)-堆(JVM中的年代),元空间,强、弱、软、虚引用),IBM公司的专门研究表明,新生代中对象98%对象是“朝生夕死”的,所以不需要按照1:1的比例来划分内存空间,而是将内存分为较大的Eden空间和两块较小的Survivor空间,每次使用Eden和其中一块Survivor。HotSpot虚拟机中默认Eden和Survivor的大小比例是8:2。

标记-整理算法

复制收集算法在对象存活率较高时,就要进行较多的复制操作,效率就会变低。 根据老年代的特点,提出了“标记-整理”算法。
标记过程仍然与”标记-清除“算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉边界以外的内存。
image.png

分代收集算法

一般是把Java堆分为新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法。
在新生代中,每次垃圾收集时都发现有大批对象死去,只有少量存活,那就选用复制算法。
在老年代中,因为对象存活率高、没有额外空间对它进行分配担保,就必须采用“标记-清除”或“标记-整理”算法来进行回收。

简述分代垃圾回收器是怎么工作的?

分代回收器有两个分区:老生代和新生代,新生代默认的空间占比总空 间的 1/3,老生代的默认占比是 2/3。
新生代使用的是复制算法,新生代里有 3 个分区:Eden、 To Survivor、From Survivor,它们的默认占比是 8:1:1,它的执行流 程如下:

  1. 把 Eden + From Survivor 存活的对象放 入 To Survivor 区;
  2. 清空 Eden 和 From Survivor 分区;
  3. From Survivor 和 To Survivor 分区交换, From Survivor 变 To Survivor, To Survivor 变 From Survivor。
  4. 每次在 From Survivor 到 To Survivor 移动时都存活的对象,年龄就+1,当年龄到达 15(默认配置是15)时,升级为老生代。大对象也会直接进入老生代。

老生代当空间占用到达某个值之后就会触发全局垃圾收回,一般使用标记整理的执行算法。
上这些循环往复就构成了整个分代垃圾回收的整体执行流程

空间分配担保

在发生Minor GC之前,虚拟机会先检查老年代最大可用的连续空间是否大于新生代所有对象的总空间,如果这个条件成立,那么Minor GC可以 确保是安全的。如果不成立,则虚拟机会检查HandlePromotionFailure 设置值是否允许担保失败。如果允许,那会继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小,如果大于,则将尝试进行一次Minor GC,尽管这个Minor GC是有风险的。如果小于,或者HandlePromotionFailure设置不允许冒险,那这时也要改为进行一次Full GC。

jvm有哪些垃圾回收器,实际中如何选择?(30K)

JVM - 图9
图中展示了7种作用于不同分代的收集器,如果两个收集器之间存在连线,则说明它们可以搭配使用。虚拟机所处的区域则表示它是属于新生代还是老年代收集器。
新生代收集器(全部的都是复制算法):Serial、ParNew、Parallel Scavenge
老年代收集器:CMS(标记-清理)、Serial Old(标记-整理)、Parallel Old(标记整理)
整堆收集器: G1(一个Region中是标记-清除算法,2个Region之间是复制算法)
同时,先解释几个名词:
1,并行(Parallel):多个垃圾收集线程并行工作,此时用户线程处于等待状态
2,并发(Concurrent):用户线程和垃圾收集线程同时执行
3,吞吐量:运行用户代码时间/(运行用户代码时间+垃圾回收时间)

1.Serial收集器是最基本的、发展历史最悠久的收集器。

特点:单线程、简单高效(与其他收集器的单线程相比),对于限定单个CPU的环境来说,Serial收集器由于没有线程交互的开销,专心做垃圾收集自然可以获得最高的单线程手机效率。收集器进行垃圾回收时,必须暂停其他所有的工作线程,直到它结束(Stop The World)。
应用场景:适用于Client模式下的虚拟机。
Serial / Serial Old收集器运行示意图
JVM - 图10

2.ParNew收集器其实就是Serial收集器的多线程版本。

除了使用多线程外其余行为均和Serial收集器一模一样(参数控制、收集算法、Stop The World、对象分配规则、回收策略等)。
特点:多线程、ParNew收集器默认开启的收集线程数与CPU的数量相同,在CPU非常多的环境中,可以使用-XX:ParallelGCThreads参数来限制垃圾收集的线程数。
   和Serial收集器一样存在Stop The World问题
应用场景:ParNew收集器是许多运行在Server模式下的虚拟机中首选的新生代收集器,因为它是除了Serial收集器外,唯一一个能与CMS收集器配合工作的。
ParNew/Serial Old组合收集器运行示意图如下:
JVM - 图11

3.Parallel Scavenge 收集器与吞吐量关系密切,故也称为吞吐量优先收集器。

特点:属于新生代收集器也是采用复制算法的收集器,又是并行的多线程收集器(与ParNew收集器类似)。
该收集器的目标是达到一个可控制的吞吐量。还有一个值得关注的点是:GC自适应调节策略(与ParNew收集器最重要的一个区别)
GC自适应调节策略:Parallel Scavenge收集器可设置-XX:+UseAdptiveSizePolicy参数。当开关打开时不需要手动指定新生代的大小(-Xmn)、Eden与Survivor区的比例(-XX:SurvivorRation)、晋升老年代的对象年龄(-XX:PretenureSizeThreshold)等,虚拟机会根据系统的运行状况收集性能监控信息,动态设置这些参数以提供最优的停顿时间和最高的吞吐量,这种调节方式称为GC的自适应调节策略。
Parallel Scavenge收集器使用两个参数控制吞吐量:

  • XX:MaxGCPauseMillis 控制最大的垃圾收集停顿时间
  • XX:GCRatio 直接设置吞吐量的大小。

    4.Serial Old是Serial收集器的老年代版本。

    特点:同样是单线程收集器,采用标记-整理算法。
    应用场景:主要也是使用在Client模式下的虚拟机中。也可在Server模式下使用。
    Server模式下主要的两大用途(在后续中详细讲解···):
  1. 在JDK1.5以及以前的版本中与Parallel Scavenge收集器搭配使用。
  2. 作为CMS收集器的后备方案,在并发收集Concurent Mode Failure时使用。

Serial / Serial Old收集器工作过程图(Serial收集器图示相同):
JVM - 图12

5.Parallel Old是Parallel Scavenge收集器的老年代版本。

特点:多线程,采用标记-整理算法。
应用场景:注重高吞吐量以及CPU资源敏感的场合,都可以优先考虑Parallel Scavenge+Parallel Old 收集器。
Parallel Scavenge/Parallel Old收集器工作过程图:
JVM - 图13

6.CMS收集器是一种以获取最短回收停顿时间为目标的收集器。

特点:基于标记-清除算法实现。并发收集、低停顿
应用场景:适用于注重服务的响应速度,希望系统停顿时间最短,给用户带来更好的体验等场景下。如web程序、b/s服务。
CMS收集器的运行过程分为下列4步:
初始标记:标记GC Roots能直接到的对象。速度很快但是仍存在Stop The World问题。
并发标记:进行GC Roots Tracing 的过程,找出存活对象且用户线程可并发执行。
重新标记:为了修正并发标记期间因用户程序继续运行而导致标记产生变动的那一部分对象的标记记录。仍然存在Stop The World问题。
并发清除:对标记的对象进行清除回收。
CMS收集器的内存回收过程是与用户线程一起并发执行的。
CMS收集器的工作过程图:
JVM - 图14
CMS收集器的缺点:

  • 对CPU资源非常敏感。
  • 无法处理浮动垃圾,可能出现Concurrent Model Failure失败而导致另一次Full GC的产生。
  • 因为采用标记-清除算法所以会存在空间碎片的问题,导致大对象无法分配空间,不得不提前触发一次Full GC。

7.G1收集器一款面向服务端应用的垃圾收集器。

特点如下:
1.并行与并发:G1能充分利用多CPU、多核环境下的硬件优势,使用多个CPU来缩短Stop-The-World停顿时间部分收集器原本需要停顿Java线程来执行GC动作,G1收集器仍然可以通过并发的方式让Java程序继续运行。
2.分代收集:G1能够独自管理整个Java堆,并且采用不同的方式去处理新创建的对象和已经存活了一段时间、熬过多次GC的旧对象以获取更好的收集效果。
3.空间整合:G1运作期间不会产生空间碎片,收集后能提供规整的可用内存。
4.可预测的停顿:G1除了追求低停顿外,还能建立可预测的停顿时间模型。能让使用者明确指定在一个长度为M毫秒的时间段内,消耗在垃圾收集上的时间不得超过N毫秒。
G1收集器运行示意图:
JVM - 图15

关于gc的选择
除非应用程序有非常严格的暂停时间要求,否则请先运行应用程序并允许VM选择收集器(如果没有特别要求。使用VM提供给的默认GC就好)。
如有必要,调整堆大小以提高性能。 如果性能仍然不能满足目标,请使用以下准则作为选择收集器的起点:

  • 如果应用程序的数据集较小(最大约100 MB),则选择带有选项-XX:+ UseSerialGC的串行收集器。
  • 如果应用程序将在单个处理器上运行,并且没有暂停时间要求,则选择带有选项-XX:+ UseSerialGC的串行收集器。
  • 如果(a)峰值应用程序性能是第一要务,并且(b)没有暂停时间要求或可接受一秒或更长时间的暂停,则让VM选择收集器或使用-XX:+ UseParallelGC选择并行收集器 。
  • 如果响应时间比整体吞吐量更重要,并且垃圾收集暂停时间必须保持在大约一秒钟以内,则选择具有-XX:+ UseG1GC。(值得注意的是JDK9中CMS已经被Deprecated,不可使用!移除该选项)
  • 如果使用的是jdk8,并且堆内存达到了16G,那么推荐使用G1收集器,来控制每次垃圾收集的时间。
  • 如果响应时间是高优先级,或使用的堆非常大,请使用-XX:UseZGC选择完全并发的收集器。(值得注意的是JDK11开始可以启动ZGC,但是此时ZGC具有实验性质,在JDK15中[202009发布]才取消实验性质的标签,可以直接显示启用,但是JDK15默认GC仍然是G1)

这些准则仅提供选择收集器的起点,因为性能取决于堆的大小,应用程序维护的实时数据量以及可用处理器的数量和速度。
如果推荐的收集器没有达到所需的性能,则首先尝试调整堆和新生代大小以达到所需的目标。 如果性能仍然不足,尝试使用其他收集器
总体原则:减少STOP THE WORD时间,使用并发收集器(比如CMS+ParNew,G1)来减少暂停时间,加快响应时间,并使用并行收集器来增加多处理器硬件上的总体吞吐量。

JVM性能调优的原则有哪些?

  1. 多数的Java应用不需要在服务器上进行GC优化,虚拟机内部已有很多优化来保证应用的稳定运行,所以不要为了调优而调优,不当的调优可能适得其反
  2. 在应用上线之前,先考虑将机器的JVM参数设置到最优(适合)
  3. 在进行GC优化之前,需要确认项目的架构和代码等已经没有优化空间。我们不能指望一个系统架构有缺陷或者代码层次优化没有穷尽的应用,通过GC优化令其性能达到一个质的飞跃
  4. GC优化是一个系统而复杂的工作,没有万能的调优策略可以满足所有的性能指标。GC优化必须建立在我们深入理解各种垃圾回收器的基础上,才能有事半功倍的效果
  5. 处理吞吐量和延迟问题时,垃圾处理器能使用的内存越大,即java堆空间越大垃圾收集效果越好,应用运行也越流畅。这称之为GC内存最大化原则
  6. 在这三个属性(吞吐量、延迟、内存)中选择其中两个进行jvm调优,称之为GC调优3选2

    什么情况下需要JVM调优?

  • Heap内存(老年代)持续上涨达到设置的最大内存值
  • Full GC 次数频繁
  • GC 停顿(Stop World)时间过长(超过1秒,具体值按应用场景而定)
  • 应用出现OutOfMemory 等内存异常
  • 应用出现OutOfDirectMemoryError等内存异常( failed to allocate 16777216 byte(s) of direct memory (used: 1056964615, max: 1073741824))
  • 应用中有使用本地缓存且占用大量内存空间
  • 系统吞吐量与响应性能不高或下降
  • 应用的CPU占用过高不下或内存占用过高不下

    在JVM调优时,你关注哪些指标?

  1. 吞吐量:用户代码时间 / (用户代码执行时间 + 垃圾回收时间)。是评价垃圾收集器能力的重要指标之一,是不考虑垃圾收集引起的停顿时间或内存消耗,垃圾收集器能支撑应用程序达到的最高性能指标。吞吐量越高算法越好。
  2. 低延迟:STW越短,响应时间越好。评价垃圾收集器能力的重要指标,度量标准是缩短由于垃圾收集引起的停顿时间或完全消除因垃圾收集所引起的停顿,避免应用程序运行时发生抖动。暂停时间越短算法越好
  3. 在设计(或使用)GC 算法时,我们必须确定我们的目标:一个 GC 算法只可能针对两个目标之一(即只专注于最大吞吐量或最小暂停时间),或尝试找到一个二者的折衷
  4. MinorGC尽可能多的收集垃圾对象。我们把这个称作MinorGC原则,遵守这一原则可以降低应用程序FullGC 的发生频率。FullGC 较耗时,是应用程序无法达到延迟要求或吞吐量的罪魁祸首
  5. 堆大小调整的着手点、分析点:
    1. 统计Minor GC 持续时间
    2. 统计Minor GC 的次数
    3. 统计Full GC的最长持续时间
    4. 统计最差情况下Full GC频率
    5. 统计GC持续时间和频率对优化堆的大小是主要着手点
    6. 我们按照业务系统对延迟和吞吐量的需求,在按照这些分析我们可以进行各个区大小的调整
  6. 一般来说吞吐量优先的垃圾回收器:-XX:+UseParallelGC XX:+UseParallelOldGC,即常规的(PS/PO) STOP World时间比较长 在后面更不容易发生FullGC 因为他做压缩整理操作
  7. 响应时间优先的垃圾回收器:CMS、G1

    JVM常用参数有哪些?

  8. Xms 是指设定程序启动时占用内存大小。一般来讲,大点,程序会启动的快一点,但是也可能会导致机器暂时间变慢

  9. Xmx 是指设定程序运行期间最大可占用的内存大小。如果程序运行需要占用更多的内存,超出了这个设置值,就会抛出OutOfMemory异常
  10. Xss 是指设定每个线程的堆栈大小。这个就要依据你的程序,看一个线程大约需要占用多少内存,可能会有多少线程同时运行等
  11. -Xmn、-XX:NewSize/-XX:MaxNewSize、-XX:NewRatio
    1. 高优先级:-XX:NewSize/-XX:MaxNewSize
    2. 中优先级:-Xmn(默认等效 -Xmn=-XX:NewSize=-XX:MaxNewSize=?)
    3. 低优先级:-XX:NewRatio
  12. 如果想在日志中追踪类加载与类卸载的情况,可以使用启动参数 -XX:TraceClassLoading -XX:TraceClassUnloading

    JVM常用性能调优工具有哪些?

  13. MAT

    1. 提示可能的内存泄露的点

  14. jvisualvm
  15. jconsole
  16. Arthas
  17. show-busy-java-threads

    1. https://github.com/oldratlee/useful-scripts/blob/master/docs/java.md#-show-busy-java-threads

      线上排查问题的一般流程是怎么样的?

  18. CPU占用过高 排查流程

    1. 利用 top 命令可以查出占 CPU 最高的的进程pid ,如果pid为 9876
    2. 然后查看该进程下占用最高的线程id【top -Hp 9876】
    3. 假设占用率最高的线程 ID 为 6900,将其转换为 16 进制形式 (因为 java native 线程以 16 进制形式输出) 【printf ‘%x\n’ 6900】
    4. 利用 jstack 打印出 java 线程调用栈信息【jstack 9876 | grep ‘0x1af4’ -A 50 —color】,这样就可以更好定位问题
  19. 内存占用过高 排查流程
    1. 查找进程id: 【top -d 2 -c】
    2. 查看JVM堆内存分配情况:jmap -heap pid
    3. 查看占用内存比较多的对象 jmap -histo pid | head -n 100
    4. 查看占用内存比较多的存活对象 jmap -histo:live pid | head -n 100