Java如何实现跨平台?

不直接跑在操作系统上,而是跑在 JVM 上。

JDK和JRE有什么区别?

  • **JRE(Java Runtime Environment)**Java 程序的运行环境:只有JVM+核心类库(如 java.lang.*、java.util.*)
  • **JDK(Java Development Kit)**Java 开发工具包:JRE+开发工具(编译器(javac)、打包工具(jar)、诊断工具(jconsole、jmap、jstack 等))

类的加载流程是怎样的?

  1. 加载:从磁盘本地文件加载到JVM内存
  2. 链接
    • 验证:检查字节码是否符合JVM规范
    • 准备:为静态变量赋默认值(数值型为0,布尔为false,引用为null)
    • 解析:将符号引用替换为直接引用(内存地址)
  3. 初始化:执行静态代码块
  4. 使用
  5. 卸载

有哪几种类加载器?

  1. 启动类加载器(Bootstrap ClassLoader):负责加载核心类库(如java.lang.*、java.util.*)
  2. 扩展类加载器(Extension ClassLoader):负责加载 扩展目录($JAVA_HOME/lib/ext) 下的类库
  3. 应用类加载器(Application ClassLoader):负责加载 classpath 下的类(我们自己写的代码、第三方依赖)
  4. 自定义类加载器(Custom ClassLoader):开发者可自己继承 ClassLoader,实现特殊的加载逻辑

什么是双亲委派机制?

类加载后由下向上请求,由上向下加载,这种方式主要是为了保证核心类库的安全,同时避免重复加载同一个类;

当一个类加载器(ClassLoader)收到“加载某个类”的请求时,它不会自己立刻去加载,而是先把请求交给它的“父加载器”去尝试。如果父加载器能找到并加载,就用父加载器的结果。如果父加载器找不到,才由自己来加载。

为什么有时候要打破双亲委派?

比如在 Tomcat 或者 SPI 这种场景下,因为父加载器加载不到我们应用里的类,或者我们需要不同Web 应用要有自己独立的类空间(避免类冲突),这时候就需要自定义类加载器,不走默认的委派规则。

如何打破双亲委派?

比如Tomcat主要通过WebAppClassLoader重写loadClass方法,优先加载当前应用类,仅对核心类委托父类加载的方式,打破了双亲委派的“父类优先”原则,满足了Web容器的特殊需求

如何判断两个class对象是否相同

  1. 类的完整类名必须一致,包括包名
  2. 加载这个类的ClassLoader(指ClassLoader实例对象)必须相同

JVM 内存结构(运行时数据区)有哪些?

  1. 堆:存放几乎所有对象实例和数组
  2. 虚拟机栈:每个线程执行 Java 方法时都会创建一个栈帧,用来存储局部变量、操作数栈、返回地址等
  3. 方法区:存储类的结构信息(类元数据)、静态变量、常量池、方法字节码等,JDK8前叫永久代(堆内存),JDK8之后改叫元空间(本地内存)
  4. 程序计数器:记录当前线程正在执行的字节码指令地址
  5. 本地方法栈:为 JVM 调用本地(Native)方法服务

栈帧是什么,里面有哪些东西?

虚拟机栈是由一系列 栈帧(Stack Frame) 组成的,每次调用一个方法,就会在栈里压入一个新的栈帧;方法执行完毕,就会弹出对应的栈帧

  1. 局部变量表(Local Variables):存放方法的参数和方法体内定义的局部变量
  2. 操作数栈(Operand Stack):用于方法执行中的各种运算(加减乘除)、临时存放数据、方法返回值
  3. 动态链接(Dynamic Linking):存放指向运行时常量池的引用,用于支持将符号引用 →转为直接引用
  4. 方法出口(Return Address):记录方法调用后返回的位置

常见的垃圾回收算法以及他们的优劣势?

  1. 标记-清除:从 GC Roots 出发,标记所有存活的对象,清理掉没有被标记的对象(垃圾)
    • 优点:实现简单,不需要额外内存
    • 缺点:会产生大量内存碎片
  2. 标记-整理:先标记存活对象,把所有存活对象往一端移动,然后清理边界以外的垃圾
    • 优点:没有内存碎片
    • 缺点:移动对象时需要调整引用,成本高
  3. 复制算法:将内存分为两块,只使用一块,GC时将存活对象复制到另一块
    • 优点:没有内存碎片,复制效率高
    • 缺点:会浪费一半内存
  4. 分代收集:新生代使用复制算法,老年代使用标记-清除/标记-整理算法
    • 优点:结合各算法优势,提高效率
    • 缺点:需要分区管理,增加实现复杂度

怎么判断一个对象是不是垃圾?

  1. 引用计数法:每个对象维护一个“引用计数器”,有一个地方引用它时 +1,引用失效时 -1;当计数器 = 0,就认为对象可以回收。
    • 优点:实现简单,判断效率高
    • 缺点:存在循环引用的问题
  2. 可达性分析算法:从一组 GC Roots(栈、静态变量、常量池、本地方法引用)出发,沿引用链往下找,能找到的对象存活(可达),找不到的就是垃圾(不可达)
    • 优点:解决了循环引用的问题

GC Roots有哪些?

  1. 线程栈里的局部变量(方法里 new 的对象)
  2. 类的静态变量
  3. 本地方法(JNI)里引用的对象

有哪些引用类型?

  1. 强引用:GC 永远不会回收它(对象一定存活)
  2. 软引用:GC 会根据内存情况决定是否回收软引用对象(内存紧张才回收)
  3. 弱引用:GC 一发现弱引用对象就会回收,不管内存是否够用(一遇 GC 就回收)
  4. 虚引用:不能通过虚引用获取对象(get() 永远返回 null)(可以看作已经回收了)

什么情况对象从新生代进入老年代?

  1. 存活年龄达到阈值(默认15)
  2. 大对象直接进入老年代(可通过参数控制)
  3. survivor区空间不足放不下
  4. 动态年龄判断,同龄对象占空间超过一半,则小于等于该年龄的对象都会提前进入老年代

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

  • 内存泄露:无用对象太多,无法被GC回收,导致内存被垃圾占满了,等于是占着茅坑不拉屎(如ThreadLocal没有remove())
  • 内存溢出:有用对象太多,内存被用完了,属于是茅坑被占满了,没位置了(如递归没有控制边界)

G1为什么支持大内存回收低延迟的?

因为G1把堆划分成多个独立的小区域(Region),采用按 Region 回收 + 并行 + 并发标记 + 按收益优先回收的方式,实现了可预测的停顿时间控制。

什么情况触发Full GC?

  • 老年代空间不足 + Major GC 失败
  • 方法区/元空间溢出
  • System.gc() 调用
  • 大对象晋升失败
  • 空间担保机制

空间分配担保机制是什么?

空间担保机制指的是在触发 Minor GC 前,JVM 会先检查老年代的可用空间是否足够存放新生代中可能晋升的对象,如果不够,就提前触发一次 Full GC 来“担保”空间安全,避免内存溢出。

cms的垃圾回收流程?

  1. 初始标记:标记 GC Roots 能直接关联到的对象,需要STW
  2. 并发标记:从初始标记的对象出发,并发地遍历整个对象图,找到所有可达对象
  3. 重新标记:修正并发标记期间因为用户线程运行而导致的标记遗漏,需要STW
  4. 并发清除:清除掉未被标记的垃圾对象,释放空间,可能产生内存碎片

cms垃圾回收哪几步需要stw?

初始标记和重新标记

字符串实现1.8和1.9的区别?

1.8使用字符数组char[],1.9使用字符数组byte[]