Java虚拟机

运行时数据区中包含哪些区域?哪些线程共享?哪些线程独享?

img

img

Java 创建一个对象的过程?

  • 检查类是否已经被加载
  • 为对象分配内存空间
  • 为分配的内存空间初始化零值(为对象字段设置零值)
  • 对对象进行其它设置(设置对象头)
  • 执行构造方法

如何访问对象?

  • 使用句柄的方式
  • 使用直接指针的方式

Java 内存模型的原子性、可见性和有序性是通过哪些操作实现的?

  • 原子性:synchronized
  • 可见性:volatile
  • 有序性:volatile和synchronized

什么是双亲委派机制?有什么作用?

Java的类加载器:AppClassLoader -> ExtClassLoader -> BootStrapClassLoader

每一种类加载器都有自己的加载目录,Java中的AppClassLoader、ExtClassLoader 都继承了URLClassLoader,URLClassLoader继承了SecureClassLoader,SecureClassLoader又继承了ClassLoader,每个类加载器对他加载过的类,都是有一个缓存的:

img

双亲委派:向上委托查找,向下委托,作用:保护Java的层的类不会被应用程序覆盖

核心代码:

protected Class<?> loadClass(String name, boolean resolve)
    throws ClassNotFoundException
{
    synchronized (getClassLoadingLock(name)) {
        // First, check if the class has already been loaded
        Class<?> c = findLoadedClass(name);
        if (c == null) {
            long t0 = System.nanoTime();
            try {
                if (parent != null) {
                    c = parent.loadClass(name, false);
                } else {
                    c = findBootstrapClassOrNull(name);
                }
            } catch (ClassNotFoundException e) {
                // ClassNotFoundException thrown if class not found
                // from the non-null parent class loader
            }

            if (c == null) {
                // If still not found, then invoke findClass in order
                // to find the class.
                long t1 = System.nanoTime();
                c = findClass(name);

                // this is the defining class loader; record the stats
                sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                sun.misc.PerfCounter.getFindClasses().increment();
            }
        }
        if (resolve) {
            resolveClass(c);
        }
        return c;
    }
}

Java类加载的全过程是怎么样的?

Java类加载过程

类加载过程:加载 -> 连接 -> 初始化 -> 使用 -> 卸载

  1. 加载:把Java的字节码数据加载到JVM内存当中,并映射成JVM认可的数据结构
  2. 连接:可以分为三个小的阶段:
    • 验证:检查加载到的字节码信息是否符合JVM规范
    • 准备:创建类或接口的静态变量,并赋初始值,半初始化状态
    • 解析:将符号引用转为直接引用
  3. 初始化:创建对象

你了解分代理论吗?

目前绝大部分的JJVM,在针对对象进行垃圾收集的时候,会将对象熬过垃圾收集的次数,视为对象的年龄,依次将对象至少划分为新生代和老年代。分代收集理论基于以下三种假说和经验法则:

  • 弱分代假说

    绝大数对象,在第一次垃圾收集时就会被回收,按照经验法则,这个值高达百分之九十八

  • 强分代假说

    熬过越多次收集过程的对象就越难以消亡

  • 跨代引用假说

    该假说认为只会存在很少的跨代引用。因为只要经过一些次数的垃圾收集,即使还存在跨代引用,新生代会变成老年代,跨代引用也就自然小时了,所以跨代引用的数量不会多

Java堆分为新生代和老年代,针对收集对象处于哪一代,一共有以下四种收集方式:

  • 部分收集
  • 新生代收集(Minor GC/Young GC),只收集新生代垃圾对象
  • 老年代收集(Major GC/Old GC),只收集老年代垃圾对象,目前只有CMS收集器会单独收集老年代对象
  • 混合收集(Mixed GC),收集来自整个新生代以及部分老年代中的垃圾对象,目前只有G1会有这种行为
  • 整堆收集(Full GC):收集整个Java堆和方法区的垃圾收集

JDK 中有几种引用类型?分别的特点是什么?

在JDK1.2之前,一个对象只有“被引用”或者“未被引用”两种状态,但这种描述方式不能满足所有的场景,譬如我们希望描述一类对象:当内存空间还足够时,能保留在内存之中,如果内存空间在垃圾收集器后仍然非常警长,那就可以抛弃这些对象,这个时候就需要对引用的概念进行扩充。

引用类型定义特点
强引用(Strong Reference)通过new关键赋值的引用只要强引用关系还存在,垃圾收集器永远不会回收掉引用的对象
软引用(Soft Reference)还有用,但非必须的对象内存不够时一定会被GC,长期不用也会被GC
弱引用(Weak Reference)非必须对象被弱引用关联的对象只能生存到下一次垃圾收集发生为止。无论当前内存是否足够,都会回收
虚引用(Phantom Reference)“幽灵引用”或者“幻影引用”对象被垃圾收集器回收时收到一个系统通知

一个对象从加载到JVM,再到GC清除,都经历了什么过程?

详细步骤说明:

  1. 用户创建一个对象,JVM首先需要到方法区去找对象的类型信息,然后再创建对象。

  2. JVM要实例化一个对象,首先要在堆中先创建一个对象 -> 半初始化状态

  3. 对象首先会分配在堆内存中新生代的Eden区,然后经过一次Minor GC,对象如果存活,就会进入S区,在后续的每次GC中,如果对象一直存活,就会在S区来回拷贝,每移动一次,年龄加1,年龄最大值是15,默认就是最大年龄是15。超过年龄先之后,对象转入老年代。

  4. 当方法执行结束后,栈中的指针会先移除掉。

  5. 堆中的对象,经过Full GC就会被标记为垃圾,然后被GC线程清理掉。

怎么样确定一个对象不是垃圾?

有两种定位垃圾的方式:

  1. 引用计数法:这种方式是给堆内存当中的每个对象记录一个引用个数,引用个数为0的就认为是垃圾。这是早期JDK采用的方式,引用计数无法解决循环引用的问题
  2. 根可达算法:这种方式是在内存中,从引用根对象向下一直找引用,找不到的对象就是垃圾

什么是GC Root?

  • 在虚拟机栈(栈帧中的本地变量表)中引用的对象,譬如各个线程被调用的方法堆栈中使用到的参数、局部变量、临时变量等。
  • 在方法区中类静态属性引用的对象,譬如Java类的引用类型静态变量
  • 在方法区中常量引用的对象,譬如字符串常量池里的引用。
  • 在本地方法栈中JNI(即通常所说的Native方法)引用的对象
  • Java虚拟机内部的引用,如基本数据类型对应的Class对象,一些常驻的异常对象(比如NullPointException),还有系统类加载器。
  • 所有被同步锁(synchronized关键字)持有的对象
  • 反映Java虚拟机内部情况的JMXBean、JVMTI中注册的回调、本地代码缓存等

JVM有哪些垃圾回收算法?

算法速度空间开销移动对象
Mark-Sweep中等少(但会堆积碎片)
Mark-Compact最慢少(不堆积碎片)
Copying最快通常需要活对象的2倍大小(不堆积碎片)

什么是STW?

STW:stop the world,是在垃圾回收算法执行过程当中,需要将JVM内存冻结的一种状态,在STW状态下,Java所有的线程都是停止执行的,GC线程除外,只有native方法可以执行,但是,不能与JVM交互,GC各种算法优化的重点,就是减少STW,同时这也是JVM调优的重点。

JVM有哪些垃圾回收器?

收集器串行、并行或并发新生代、老年代算法
Serial串行新生代复制算法
Serial Old串行老年代标记-整理
ParNew并行新生代复制算法
Parallel Scavenge并行新生代复制算法
Parallel Old并行老年代标记-整理
CMS并发老年代标记-清除
G1并发不区分标记-整理+复制算法

什么是三色标记算法?

CMS的核心算法就是三色标记。

三色标记:是一种逻辑上的抽象,将每个内存对象分成三种颜色:黑色:表示自己和成员变量都已经标记完毕。灰色:自己标记完了,但是成员变量还没有完全标记完。白色:自己未标记完。

CMS通过增量标记increment update的方式来解决漏标的问题。

在G1当中采用SATB的方式来避免错标和漏标的情况。

如何回收方法区?

方法区的垃圾回收主要有两种,废弃的常量和无用的类,其中要称为无用的类,要同时满足下面三个条件:

  • Java堆中不存在该类的任何实例对象
  • 加载该类的类加载器已经被回收
  • 该类对应的java.lang.Class对象不在任何地方被引用,且无法在任何地方通过反射访问该类的方法

JVM 中的安全点和安全区各代表什么?

写屏障你了解吗?

解决并发扫描时对象消失问题的两种方案?

CMS 垃圾收集器的步骤?

1、初始标记阶段:STW 只标记出根对象直接引用的对象

2、并发标记:继续标记其他对象,与应用程序时并发执行

3、重新标记:STW对并发执行阶段的对象进行重新标记

4、并发清除:并行。将产生的垃圾清除。清除过程中,应用程序又会不断的产生新的垃圾,叫做浮动垃圾。这些垃圾就要留到下一次GC过程中清除。

CMS 有什么缺点?

  • CMS收集器对CPU资源非常敏感
  • CMS处理器无法处理浮动垃圾
  • 在收集结束的时候,会产生大量的空间碎片

G1垃圾收集器的步骤,G1有什么优缺点?

G1的优点:

  • 停顿时间短
  • 用户可以指定最大的停顿时间
  • 不会产生内存碎片:G1的内存布局并不是固定大小以及固定数量的分代区域划分

缺点:

G1需要记忆集(卡表)来记录新生代和老年代之间的引用关系,这种数据结构在G1中需要占用大量的内存,可能达到整个堆内存容量的20%甚至更多。而且G1中维护记忆集的成本较高,带来了更高的执行负载,影响效率。

CMS在小内存应用上的表现要优于G1,而大内存应用上G1更有优化,大小内存的界限是6GB到8GB。

讲一下内存分配策略?

内存溢出和内存泄漏的区别?

  • 内存溢出(Out of Memory)是指程序在申请内存时,没有足够的空间供其使用

  • 内存泄漏(Memory Leak)是指程序在申请内存后,无法释放已申请的内存空间

如何进行JVM调优?

JVM调优主要是通过定制JVM运行参数来提高Java应用程序的运行速度。

JVM参数有哪些?

JVM参数大致可以分为三类:

1、标准指令:-开头,这些是所有的HotSpot都支持的参数。可以用Java -help打印出来。

2、非标准指令:-开头,这些指令通常是跟特定的HotSpot版本对应的,可以用Java -X打印出来

3、不稳定参数:-XX开头,这一类参数是跟特定HotSpot版本对应的,并且变化非常大,详细的文档资料非常少,在JDK1.8版本下,有几个常用的不稳定指令:Java -XX:+PrintCommandLineFlags:查看当前命令的不稳定指令。

虚拟机基础故障处理工具有哪些?

工具作用
jps虚拟机进程状况工具
jstat虚拟机统计信息监视工具
jinfoJava配置信息工具
jmapJava内存映射工具
jstackJava堆栈跟踪工具
JVisualVM图形化展示
JConsole远程控制

怎么查看一个Java进程的JVM参数,谈谈你了解的JVM参数

打印出所有不稳定参数所有默认值:java -XX:+PrintFlagsInitial

打印出所有最终生效的不稳定指令:java -XX:+PrintFlagsFinal

什么情况下堆内存会溢出,什么情况方法区会内存溢出?

栈溢出的典型例子:

 private static void fun() {
        //递归,调用自己
        fun();
    }

方法区内存溢出的例子:

 private static void fun() {
        //递归,调用自己
        fun();
    }

JDK8 为什么要将永久代改为元空间?

在JDK8之前,Java虚拟机中的永久代(Permanent Generation)用于存储类的元数据信息、静态变量、常量等。然而,永久代存在一些问题,例如固定大小、难以调优、容易导致内存溢出等。

为了解决这些问题,并引入更灵活和可调整大小的内存区域,JDK8将永久代(Permanent Generation)替换为元空间(Metaspace),并将其定义为方法区(Method Area)的一部分。

以下是一些原因解释为什么JDK8将永久代改为元空间,并将其归类为方法区:

内存动态分配:永久代的大小是固定的,无法动态调整,这在一些场景下可能会导致内存溢出。元空间通过使用本地内存(Native Memory)来存储元数据,可以根据应用程序的需要进行动态分配和释放,更加灵活。

类元数据卸载:永久代存储的类元数据在应用程序运行期间是不可卸载的,即使类已经不再使用。而元空间中的类元数据可以进行卸载,当类不再被引用时,虚拟机可以回收其占用的内存,避免了永久代中的内存泄漏问题。

自动调整空间大小:元空间可以根据应用程序的需要自动调整大小,避免了调整永久代大小的繁琐工作。在元空间中,可以通过设置参数来控制元数据的大小和增长策略,以满足应用程序的需求。

元数据分配效率:元空间使用本地内存存储元数据,相较于永久代,元空间的元数据分配效率更高。在永久代中,由于采用传统的JVM对象分配方式,需要进行内存分配和垃圾回收等操作,而元空间中的元数据分配是基于本地内存的,更加高效。