• JVM面试总结


    Java虚拟机 JVM

    常见面试题如下:

    1. JVM内存模型
    2. 垃圾回收机制
    3. 那些对象可以作为GC Root
    4. Minor GC和Full GC
    5. 对象什么时候进入老年代
    6. 类加载过程
    7. 双亲委派模型和破坏双亲委派模型
    8. JVM何时开始类加载
    9. JVM常见的参数
    10. 线上CPU过高如何排查
    11. 什么时候需要JVM调优
    12. 如何JVM调优
    13. 常见的垃圾收集器

    JVM内存模型

    程序计数器:一块较小的内存空间, 是当前线程所执行的字节码的行号指示器,每条线程都要有一个独立的程序计数器,正在执行 java 方法的话,计数器记录的是虚拟机字节码指令的地址(当前指令的地址)。如果是 Native 方法,则为空。唯一一个在虚拟机中没有规定任何 OutOfMemoryError 情况的区域

    虚拟机栈(线程私有):是描述java方法执行的内存模型,每个方法在执行的同时都会创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用直至执行完成
    的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。栈帧随着方法调用而创建,随着方法结束而销毁——无论方法是正常完成还是异常完成(抛出了在方法内未被捕获的异
    常)都算作方法结束。

    本地方法栈(线程私有):和虚拟机栈所发挥的作用是非常相似的,它们之间的区别不过是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则为虚拟机使用到的Native方法服务。

    :Java堆是Java虚拟机所管理的内存中最大的一块。Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存,也是垃圾收集器进行垃圾收集的最重要的内存区域。Java 堆从 GC 的角度还可以细分为: 新生代(Eden 区、From Survivor 区和 To Survivor 区)和老年代。

    方法区:方法区用于存储已被JVM加载的类信息、常量、静态变量,运行时常量池是方法区的一部分,class文件除了有类的字段、接口、方法等描述信息之外,还有常量池用于存放编译期间生成的各种字面量和符号引用。方法区也被称为永久代.

    在 Java8中为了避免永久代的内存溢出以及OutOfMemoryError使用元空间替代永久代,区别在于:元空间并不在虚拟机中,而是使用
    本地内存。因此,默认情况下,元空间的大小仅受本地内存限制。类的元数据放入 native
    memory, 字符串池和类的静态变量放入 java 堆中,这样可以加载多少类的元数据就不再由
    MaxPermSize 控制, 而由系统的实际可用空间来控制。

    GC如何确定垃圾/确定死亡对象

    • 引用计数法[循环引用问题]
    • 可达性分析算法 [那些可以作为GC Root]

    那些对象可以作为GC Roots

    • 虚拟机栈(栈帧中的本地变量表)中引用的对象。
    • 方法区中类静态属性引用的对象。
    • 方法区中常量引用的对象。
    • 本地方法栈中JNI(即一般说的Native方法)引用的对象。

    GC如何回收垃圾/垃圾收集算法

    分代收集算法
    :GC主要作用于堆 堆可分为新生代和老年代 不同区域使用不同的收集算法,新生代对象每次垃圾收集都会有大批量的对象死去,少数生还,选用复制算法,只需要付出少量存活对象的复制成本即可完成收集,老年代中对象存活率高且没有额外空间对它进行分配担保,所以使用“标记-清除”或“标记-整理”算法进行回收。

    复制算法
    :按内存容量将内存划分为等大小
    的两块。每次只使用其中一块,当这一块内存满后将尚存活的对象复制到另一块上去,把已使用
    的内存清掉。新生代分为1个Eden区和2个Survivor区(分别叫from和to)每次只使用其中的2块,且Eden和Survivor的大小为8/1

    标记-清除算法: 分为标记和清除2个阶段,先标记需要回收的对象,标记完成后统一回收所有需要的对象。会产生较多不连续的内存碎片,可能会在下次分匹配大对象时候无法找到连续够大的内存空间而提前出发一次垃圾收集动作。

    标记-整理:比标记-清除多了一步整理,可以避免产生较多的内存碎片.

    垃圾收集器

    CMS收集器(Concurrent Mark Sweep)

    它是一种以获取最短回收停顿时间为目标的收集器。优点:并发收集,低停顿。基于“标记-清除”算法。目前很大一部分Java应用都集中在互联网站或B/S系统的服务端上,这类应用尤其重视服务的响应速度,希望系统停顿时间最短,以给用户带来较好的体验,CMS收集器就非常符合这类应用的需求。

    执行步骤

    1. 初始标记(CMS initial mark):需要“Stop The World”,标记GC Roots能直接关联到的对象,速度快。
    2. 并发标记(CMS concurrent mark):进行GC Roots Tracing 过程
    3. 重新标记(CMS remark):需要“Stop The World”,修正并发标记期间,因用户程序继续运行而导致标记产生变动的那一部分对象的标记记录。停顿时间:初始标记<重新标记<<并发标记
    4. 并发清除(CMS concurrent sweep):时间较长。

    缺点

    1. 对CPU资源非常敏感,面向并发设计的程序都会对CPU资源较敏感。在并发阶段会因为占用了一部分线程[CPU资源]会使应用程序变慢,总吞吐量降低,CMS默认的回收线程数: (CPU数量+3)/4

    2. 无法处理浮动垃圾,可能出现“Concurrent Mode Failure”失败而导致另一次Full GC的产生。并发清理阶段用户程序运行产生的垃圾过了标记阶段所以无法在本次收集中清理掉,称为浮动垃圾。CMS收集器默认在老年代使用了68%的空间后被激活。若老年代增长的不是很快,可以适当调高参数-XX:CMSInitiatingOccupancyFraction 提高触发百分比,但调得太高会容易导致“Concurrent Mode Failure”失败。

    3. 基于“标记-清除”算法会产生大量空间碎片。提供开关参数-XX:+UseCMSCompactAtFullCollection 用于在“ 享受”完Full GC服务之后进行碎片整理过程,内存整理的过程是无法并发的。但是停顿时间会变长。-XX:CMSFullGCsBeforeCompation 设置在执行多少次不压缩的Full GC后,进行一次带压缩的。

    G1垃圾收集器

    G1收集器可以在几乎不牺牲吞吐量的前提下完成低停顿的内存回收,这是由于它能够极力避免全区域的垃圾收集,之前的收集器进行收集的范围都是整个新生代或老年代,而G1将整个Java堆(包括新生代、老年代)划分为多个大小固定的独立区域(Region),并且跟踪这些区域里面的垃圾堆积程度,在后台维护一个优先列表,每次根据允许的收集时间,优先回收垃圾最多的区域(这就是Garbage First名称的由来)。区域划分、有优先级的区域回收,保证了G1收集器在有限的时间内可以获得最高的收集效率。与CMS相比有两个显著改进:

    1. 基于标记-整理算法实现收集器
    2. 非常精确地控制停顿

    执行步骤

    1. 初始标记:GC Roots可以直接关联到的对象,并且修改TAMS(Next Top at Mark Start)的值,让下一阶段用户程序并发执行的时候,能在真确可用的Region中创建对象。这阶段需要停顿线程但是很短。
    2. 并发标记:从GC Root开始对堆中的对象进行可达性分析,找出存活的对象,可并发执行,耗时长。
    3. 最终标记:为了修正上一个标记阶段因为用户线程的运行导致标记变化的部分。需要停顿线程可以并发执行。
    4. 筛选回收:对每个Region的回收价值和成本进行排序,按用户期望的GC停顿时间来制定回收计划。

    GC触发条件

    可以分为2方面新生代Eden区的Minor GC和老年代的Full GC

    Minor GC

    1. 当Eden区满时,触发Minor GC

    Full GC

    1. 调用System.gc时,系统建议执行Full GC,但是不必然执行
    2. 老年代空间不足
    3. 方法区空间不足

    JVM常见参数

    4种引用类型

    类加载过程

    类从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期包括:加载、验证、准备、解析、初始化、使用和卸载7个阶段。其中JVM 类加载机制分为五个部分:加载,验证,准备,解析,初始化

    加载:这个阶段会在内存中生成一个代表这个类的 java.lang.Class 对 象,作为方法区这个类的各种数据的入口。

    验证:确保 Class 文件的字节流中包含的信息是否符合当前虚拟机的要求,并
    且不会危害虚拟机自身的安全。

    准备:为类变量分配内存并设置类变量的初始值阶段,即在方法区中分配这些变量所使
    用的内存空间。

    解析:指虚拟机将常量池中的符号引用替换为直接引用的过程。

    类加载器

    从顶到底分为->启动类加载器->扩展类加载器->应用程序类加载器->自定义类加载器 //前三种是JVM提供的。

    1. 启动类加载器负责加载 JAVA_HOMElib 目录中的,或通过-Xbootclasspath 参数指定路径中的,且被
      虚拟机认可(按文件名识别,如 rt.jar)的类。
    2. 扩展类加载器负责加载 JAVA_HOMElibext 目录中的,或通过 java.ext.dirs 系统变量指定路径中的类
      库。
    3. 应用程序类加载器 负责加载用户路径(classpath)上的类库。

    双亲委派模型

    双亲委派的意思是如果一个类加载器需要加载类,那么首先它会把这个类请求委派给父类加载器去完成,每一层都是如此。一直递归到顶层,当父加载器无法完成这个请求时,子类才会尝试去加载。

    好处:采用双亲委派的一个好处是比如加载位于 rt.jar 包中的类 java.lang.Object,不管是哪个加载
    器加载这个类,最终都是委托给顶层的启动类加载器进行加载,这样就保证了使用不同的类加载
    器最终得到的都是同样一个 Object 对象。

    JVM调优

    什么时候需要调优

    1. GC频繁[考虑新生代Eden MinorGC 老年代 Full GC]
    2. 单次GC时间过长[STW 一般超过1s]
    3. 应用占用CUP过高[需要排查是否有内存泄漏]
    4. 堆外内存占用过高导致应用响应慢。

    如何调优

    GC频繁

    一般新生代的空间不大,Eden区域被填满的时间较快,可以通过增加Eden区域的大小来降低Minor GC的频率。
    增加Eden区域的大小是否会导致单次Minor GC时间增加

    Eden区域增加会导致增加了T1(扫描时间),但是T2(复制对象)的时间和存活的对象数有关系,而不是和Eden区域大小有关系,且T2时间远大于T1,所以增加Eden不会显著增加单次GC时间。

    Full GC一般指全局GC,而Major GC一般指的是老年代的GC.这在《深入理解JVM虚拟机第二版中》说法是有误的,这里将这2个GC都称为是老年代的GC,在第三版中做了更正。这2中需要根据不同的垃圾收集器CMS OR G1做具体判断,比如设置Region等。

    GC时间过长: 处理方式同上需要根据不同垃圾收集器来处理

    应用占用CUP过高: 直接排查内存溢出 正对性处理即可.

    外部环境导致的响应慢: 适当减少外部应用对服务器内存的占用。

    线上CPU过高如何排查

  • 相关阅读:
    spring in action小结4.1
    spring in action小结3 运行时值注入
    python-__init__.py 与模块对象的关系
    Python-常用库扩展
    Qt-优化布局结构
    Python-文件修改器
    C语言-数据结构(一)
    Python-PyQt4学习笔记
    Python-PyQt4学习资料汇总
    Linux-查看C语言手册及man的特殊用法
  • 原文地址:https://www.cnblogs.com/threecha/p/14005188.html
Copyright © 2020-2023  润新知