1.java虚拟机内存模型以及作用

  1. 程序计数器:线程私有。记录当前线程执行字节码文件的行数。因为线程的执行过程是通过cpu分配时间片来进行的。因此为了线程切换后能够准确的恢复线程状态,所以需要一个线程私有的内存来记录当前线程具体执行字节码文件的行数。
  2. 虚拟机栈:线程私有。每个方法在执行的同时都会在Java 虚拟机栈中创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息;
  3. 本地方法栈:线程私有。而本地方法栈用于管理本地方法的调用。也就是说用于管理非java语言执行的方法(native关键字修饰的方法)的调用。
  4. 堆区:线程共享。用于存放引用数据类型的对象
  5. 方法区:用于存放常量以及静态变量、类加载信息等

image.png

2.什么是垃圾回收机制(GC)

java语言不同于c/c++的一点就是,java语言中的垃圾回收是jvm通过守护线程的方式来执行的。使用者不需要显式的进行垃圾回收。只有当虚拟机空闲或者当前堆内存不足时才会进行垃圾回收。
同时,java中的引用数据类型包括以下几种引用

  • 强引用:发生 gc 的时候不会被回收。(当一个引用数据类型的引用作用域结束,也就是说虚拟机栈消失,那么该对象也就不在和GcRoot有关联,因此会被垃圾回收)
  • 软引用:有用但不是必须的对象,在发生内存溢出之前会被回收。
  • 弱引用:有用但不是必须的对象,在下一次GC时会被回收。
  • 虚引用(幽灵引用/幻影引用):无法通过虚引用获得对象,用 PhantomReference 实现虚引用,虚引用的用途是在 gc 时返回一个通知。

    3.什么是stop the world

    定义:是指在Gc过程中,会产生应用程序的停顿。停顿时整个应用程序的线程都会被暂停,没有任何响应。可以理解为将所有用户线程冻结在了某一时间点。频繁的stop the world会造成卡顿,所以应该尽可能减少stop the world的发生
    原因:因为如果在Gc过程中用户线程还能继续运行,那么会导致GcRoot统计等其他垃圾回收相关数据分析不准确
    声明:stop the world和哪款垃圾回收器无关,任何一款垃圾回收器都会触发stop the world。哪怕是G1垃圾回收器。只能说垃圾回收器在不断优化的过程中尽可能的减少stop the world的次数,从而缩短整个gc时间

    4.如何判定对象是否该被回收

  1. 引用计数法:为每个对象创建一个引用计数器,当对象被引用时+1,对象被释放时-1。计数器=0代表该对象可以被垃圾回收。但是这样存在一个循环引用问题。假设A引用B,同时B引用A。那么A与B的计数器一直为1,无法被回收
  2. 可达性分析法:以GCRoot为根节点向下搜索,搜索其所有走过的引用链路。当一个对象与GCRoot没有任何链路链接时,对其进行垃圾回收。以下对象为GcRoot
  • 虚拟机栈(栈帧中的本地变量表)中引用的对象
  • 本地方法栈中 JNI(即一般说的 Native 方法)引用的对象
  • 方法区中静态变量引用的对象
  • 方法区中常量引用的对象

    5.垃圾回收机制的算法有哪些

    标记清除法

    定义:从GCRoot开始进行可达性分析,标记需要回收的对象,进行垃圾回收,是一种基础且简单的垃圾回收算法。是针对于待回收对象而操作的垃圾回收算法。因此,待回收对象越少,效率越高。
    步骤:
  1. 标记需要进行回收的对象
  2. 回收该对象释放内存空间

优缺点:

  1. 优点:方法简单,不许要对象移动
  2. 缺点:标记再逐个清理对象的过程较长,效率较低。同时会产生大量的内存碎片,浪费内存空间

image.png

标记整理法

定义:从GCRoot开始进行可达性分析,标记所有存活的对象,标记完成后,将对象移动到内存空间的一侧紧密的排列在一起,形成一片连续的内存空间。然后再对边界外的内存空间进行整理。是针对于存活对象而言的一种垃圾收回算法,因此,存活的对象越少,效率越高。
步骤:

  1. 标记所有存活的对象
  2. 将所有存活对象移动到内存的一侧连续空间内
  3. 清空边界外的内容

优缺点

  1. 优点:解决了标记清除法内存碎片的问题
  2. 缺点:需要进行对象的移动,同时必须等到GCRoot全部搜索完成后才能确定出哪些内存空间时连续的,再进行后续的移动,会影响效率

image.png

复制法

定义:将内存空间分为两个区域,一个区域保持空闲状态,一个区域用来存储对象。从GCRoot开始进行可达性分析,当标记对象存活后,直接将对象移动到空间区域内顺序摆放。GCRoot分析完成后,所有的存活对象就已经移动到了空闲内存区域,然后清空当前内存空间,相当于完成了一个空闲与忙碌内存空间的装换。是针对与存活对象来说的,因此,存活对象越少,效率越高。
步骤:

  1. GCRoot分析存活的对象,当前对象确认存活后,直接顺序摆放到空闲的内存空间。
  2. GcRoot分析完成后,清理当前内存空间即可

优缺点:

  1. 优点:效率高,GCRoot可达性分析完成后,既可以进行区域清除,不用标记整理等多个步骤。
  2. 缺点:浪费内存空间,因为始终有一片内存空间是空的。

    6.堆区不同分区对应的垃圾回收算法以及对应的执行步骤

    image.png

    永久代

    顾名思义,对象永久存活于此内存区域。但是如果永久代内存已满或者已经到达临界值,也是会触发Full GC。
    (注:Java8中已经移除了永久代,新加了一个叫做元数据区的native内存区)

    新生代(占比1/3)

    描述:新生代按比例8:1:1分为Eden区、From区、To区。
    对象特性:新生代的对象都是占用内存空间较小,朝生夕死,年龄较低。
    垃圾回收算法:因为新生代中的对象都是朝生夕死,每次触发新生代垃圾回收时,存活的对象较少,待回收的对象较多,因此复制算法带来的效率收益较高,所以才采用了复制算法进行新生代的垃圾回收
    步骤:当对象存入时,默认存入Eden区,大对象直接存入老年代。如果发现Eden区内存不足时,触发新生代Gc。复制算法找出Eden以及From区中全部存活的对象,移动到To区,同时清空Eden区和To区,To区与From区置换。并且存活的对象年龄+1,当存活的对象年龄>15(默认值)时,将对象移动到老年代。
    问题:

  3. 为什么suvivor要分为两个区域,一个区域不行吗?

首先理解为复制算法的原理就是用空间换时间。那么理论上将新生代55分即可,但是那样的话会造成较大的空间浪费。
但是如果不是55分成,那么当空间置换到占比小的那一侧时,触发新生代垃圾回收的频率就是提高。
因此为了优化以上两点矛盾,引入了Eden区的概念,Eden区可以理解为From区和To区的缓冲区域,共享区域。从而进行了复制算法上的优化。这样既能保证年轻代GC低频次的触发,还实现了内存空间利用的最大化

老年代(占比2/3)

描述:老年代与新生代内存空间占比2:1
对象特性:占用内存空间较大或存活时间比较久,都是不容易被回收的对象
垃圾回收算法:因为老年代的对象都是不易被回收的对象,反过来将就是每次GC时,待回收的对象较少,因此理论上采用标记清除算法的效率收益较高。但是因为标记清除算法会产生内存碎片,而老年代的对象又相对来说比较占用内存空间,如果内存碎片较多,会频繁的触发老年代垃圾回收甚至堆回收。因此从宏观上来说,短时间内多次触发垃圾回收,标记清除算法带来的收益并不高
那么如果采用复制算法,既浪费内存空间同时每次移动对象都比较多,效率又低,因此才采用了标记整理算法。虽然标记整理算法的效率没有复制算法高,但是标记整理算法对内存空间的利用率最高,宏观上来讲触发老年代GC的次数会比复制算法以及标记整理法低很多。因此最终在老年代才采用了标记整理算法。
步骤:新生代对象进入老年代,当老年代的内存空间不足时,触发GC

7.Minor GC、Major GC、Full GC是什么

定义:

  1. Minor GC是新生代GC,指的是发生在新生代的垃圾收集动作。由于java对象大都是朝生夕死的,所以Minor GC非常频繁,一般回收速度也比较快。(一般采用复制算法回收垃圾)
  2. Major GC是老年代GC,指的是发生在老年代的GC,通常执行Major GC会连着Minor GC一起执行。Major GC的速度要比Minor GC慢的多。(可采用标记清楚法和标记整理法)
  3. Full GC是清理整个堆空间,包括年轻代和老年代

触发条件:

  1. Monir GC:当Eden区的内存不足时,触发monir gc
  2. Mojor GC/Full GC:当新生代晋升到老年代的对象内存空间不足时或永久代内存不足时,会触发Mojor GC/Full GC

    8.垃圾收集器

    定义垃圾收集器是垃圾回收算法的具体实现,不同版本的垃圾收集器以及不同版本的JDK垃圾回收算法都有所不同
    图示:
    image.png
    表格表示:

  3. Serial收集器(复制算法): 新生代单线程收集器,标记和清理都是单线程,优点是简单高效;

  4. ParNew收集器 (复制算法): 新生代收并行集器,实际上是Serial收集器的多线程版本,在多核CPU环境下有着比Serial更好的表现;
  5. Parallel Scavenge收集器 (复制算法): 新生代并行收集器,追求高吞吐量,高效利用 CPU。吞吐量 = 用户线程时间/(用户线程时间+GC线程时间),高吞吐量可以高效率的利用CPU时间,尽快完成程序的运算任务,适合后台应用等对交互相应要求不高的场景;
  6. Serial Old收集器 (标记-整理算法): 老年代单线程收集器,Serial收集器的老年代版本;
  7. Parallel Old收集器 (标记-整理算法): 老年代并行收集器,吞吐量优先,Parallel Scavenge收集器的老年代版本;
  8. CMS(Concurrent Mark Sweep)收集器(标记-清除算法): 老年代并行收集器,以获取最短回收停顿时间为目标的收集器,具有高并发、低停顿的特点,追求最短GC回收停顿时间。
  9. G1(Garbage First)收集器 ( 标记整理 + 复制算法来回收垃圾 ): Java堆并行收集器,G1收集器是JDK1.7提供的一个新收集器,G1收集器基于“标记-整理”算法实现,也就是说不会产生内存碎片。此外,G1收集器不同于之前的收集器的一个重要特点是:G1回收的范围是整个Java堆(包括新生代,老年代),而前六种收集器回收的范围仅限于新生代或老年代。

    9.类加载机制

    定义:虚拟机将类的描述信息从class文件读取到内存,并通过校验、准备、解析、初始化的过程将class文件解析成虚拟机可以调用的java对象
    具体步骤:

  10. 加载:根据查找路径找到相应的 class 文件然后导入;

  11. 验证:检查加载的 class 文件的正确性;
  12. 准备:给类中的静态变量分配内存空间;
  13. 解析:虚拟机将常量池中的符号引用替换成直接引用的过程。符号引用就理解为一个标示,而在直接引用直接指向内存中的地址;
  14. 初始化:对静态变量和静态代码块执行初始化工作。

类加载器:

  1. 启动类加载器:是虚拟机自身的一部分,用来加载Java_HOME/lib/目录中的,或者被 -Xbootclasspath 参数所指定的路径中并且被虚拟机识别的类库
  2. 扩展类加载器:负责加载\lib\ext目录或Java. ext. dirs系统变量指定的路径中的所有类库
  3. 应用程序类加载器:负责加载用户类路径(classpath)上的指定类库,我们可以直接使用这个类加载器。一般情况,如果我们没有自定义类加载器默认就是用这个加载器
  4. 自定义类加载器:顾名思义,用户自定义的类加载器

    10.双亲委派机制

    定义:类加载过程中,下层类加载器会委派上层类加载器进行类的加载,以此类推到顶层类加载器,当上层类加载器无法加载对应类时,才会委派下层类加载器进行加载。这个过程叫做双亲委派机制
    目的:防止核心类被覆盖重写。比如说用户定义了java.io类。那么如果没有双亲委派机制,会导致jdk中java.io里面的类被重写。