关于java:Java面试问题总结JVM

4次阅读

共计 6996 个字符,预计需要花费 18 分钟才能阅读完成。

1.JVM 运行时内存区域划分

线程 独享 区域:程序计数器,本地办法栈,虚拟机栈
线程 共享 区域:元空间(<=1.7 办法区), 堆

  • ** 程序计数器 **:线程 公有 ,是一块较小的内存空间,能够看做是以后 线程执行的字节码指示器 ,也是 惟一的没有定义 OOM 的区块
  • ** 本地办法栈 **:用于 执行 Native 办法 时应用
  • ** 虚拟机栈 **:用于 存储局部变量,操作数栈,动静链接,办法进口等信息
  • ** 元空间 **:存储已被虚拟机加载的 类元信息,常量,动态变量** 即时编译器编译后的代码等数据仍旧存储在办法区中,办法区位于堆中 **
  • ** 堆 **:存储 对象实例

示例:

/**
 * @author: jujun chen
 * @description: 应用了 CGLIB 来动静生成类,元空间存储类信息,-XX:MetaspaceSize=10m -XX:MaxMetaspaceSize=10m
 * 如果只设置堆的大小,并不会溢出
 * @date: 2019/4/7
 */
public class JavaMetaSpaceOOM {static class OOMObject{}
    public static void main(final String[] args) {while (true){Enhancer enhancer = new Enhancer();
            enhancer.setSuperclass(OOMObject.class);
            enhancer.setUseCache(false);
            enhancer.setCallback(new MethodInterceptor() {
                @Override
                public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {return methodProxy.invokeSuper(o,objects);
                }
            });
            enhancer.create();}
    }

}

2.OOM, 及 SOE 的示例、起因,排查办法

//OOM -Xmx20m -Xms20m -XX:+HeapDumpOnOutOfMemoryError
public class OOMTest {public static void main(String[] args) {List<Object> objList = new ArrayList();
        while(true) {objList.add(new Object());
        }
    }
}

//SOE 栈异样 -Xss125k
public class SOETest() {
    static int count = 0;
    public static void main(String[] args) {
        try {stackMethod();
        } catch(Error err) {err.printStackTrace();
            System.out.println("执行 count=" + count);
        }
    }
    private static void stackMethod() {
        count ++;
        stackMethod();}
}
  • OOM 排查 :如果能看到 日志 ,能够从打印的日志中获取到发送异样的代码行,再去代码中查找具体哪块的代码有问题。如果没有记录日志,通过 设置的 -XX:+HeapDumpOnOutOfMemoryError 在产生 OOM 的时候 生成.hprof 文件 ,再导入JProfiler 可能看到是因为哪个对象造成的 OOM,再通过这个对象去代码中寻找
  • SOE 排查 :栈的深度个别为 1000-2000 深度, 超过了深度或者超过了栈大小就会导致 SOE,通过打印的 日志 定位错误代码地位,检测是否有 有限递归 ,产生了 死循环 等状况,批改代码

3. 如何判断对象能够回收或存活

判断是否能够回收,或者存活次要是看:

  1. 堆中 是否存在该实例
  2. 加载该类的classloader 是否曾经被回收
  3. 该类的 java.lang.Class 对象在 任何中央没有被援用,也就是不可能通过反射办法获取该类信息

4. 哪些对象能够作为 GC ROOT 对象

  1. 虚拟机栈 (栈帧中的本地变量表) 中援用的对象
  2. 本地办法中 JNI 援用的对象
  3. 办法区中的动态变量和常量援用的对象

5. 常见的 GC 算法

  1. 标记 - 革除算法:先标记出存活的对象,再革除没有被标记的对象,毛病是:标记革除过程效率不高;会产生内存碎片
  2. 复制算法:将内存划分成等同大小两块,只应用其中一块内存,当这一块的内存快用完后,将已存活的对象复制到另外一块内存,再对已应用的内存空间进行一次清理
  3. 标记 - 整顿算法:标记出已存活的对象,将对象挪动到内存一端,再对端以外的内存进行清理回收
  4. 分代收集算法:年老代应用复制算法, 永恒代应用 标记 - 革除或者标记 - 整顿算法

6. 常见的 JVM 性能监测剖析工具

  1. jps
    可能查看正在运行的虚拟机过程,并显示虚拟机的执行主类及过程 ID
  2. jstat [option vmid [interval[s|ms] [count]] ]
    能够显示本地或者近程虚拟机中的类装载、内存、垃圾收集、JIT 编译等运行数据
  3. jinfo
    实时查看和调整虚拟机的各项参数
  4. jmap
    生成堆转储快照
  5. jhat

    生成页面剖析导出的堆存储快照

  6. jstack
    用于生成虚拟机以后时刻的线程快照
  7. jstatd

    启动 RMI 服务端程序,代理本地的 Java 过程,供近程计算机连贯调式

    1. 查看以后 JVM 应用的垃圾收集器

    java -XX:+PrintFlagFinal -version 或者 java -XX:+PrintCommandLineFlags -version

    1. jconsole

    Java 监督和治理控制台,可能监控内存,线程,类等

    1. jvisualvm

    多合一监督工具

更多材料请学习官网:https://docs.oracle.com/en/ja…

7.JVM 优化

  1. 响应工夫优先:年老代设的大些,直到靠近零碎的最低响应工夫限度。年老代设大,能够缩小达到年轻代的对象。对于永恒代的设置须要参考:永恒代并发收集的次数、年老代和永恒代回收工夫比例,调整达到一个适合的值
  2. 吞吐量优先:年老代设的大些,永恒代较小

8. 什么时候会触发 FullGC

  1. 永恒代空间有余
  2. 手动调用触发 gc

9. 类加载器有几种

  1. Bootstrap ClassLoader
    负责 加载 JDK 自带的 rt.jar 包中的类文件 ,它是 所有类加载器的父 加载器,Bootstrap ClassLoader 没有任何父类加载器。
  2. Extension ClassLoader 负责 加载 Java 的扩大类库 ,也就是从jre/lib/ext 目录下或者 java.ext.dirs 零碎属性指定的目录下 加载类。
  3. System ClassLoader 负责 从 classpath 环境变量中加载类文件,classpath 环境变量通常由 ”-classpath” 或 “-cp” 命令行选项来定义,或是由 jar 中 Mainfest 文件的 classpath 属性指定,System ClassLoader 是 Extension ClassLoader 的子加载器
  4. 自定义加载器

10. 什么是双亲委派模型?双亲委派模型的毁坏

一个类在加载的时候,首先会将加载申请委派给父加载器,只有当父加载器反馈无奈加载实现这个申请时,子加载器才会尝试本人加载
双亲委派模型的毁坏指的是不依照双亲委派模型来加载类,比方 JNDI,它的代码由启动类加载器加载, 但 JDNI 须要调用部署在 ClassPath 的 JNDI 接口,但启动类加载器是不晓得这些代码的,所以就有了线程上下文类加载器(Thread Context ClassLoader),能够通过 java.lang.Thread 类 setContextClassLoader 设置类加载器,通过这个父加载器就能够申请子类加载器实现类加载的动作。

11. 类的生命周期

类的生命周期一个有 7 个阶段:加载、验证、筹备、解析、初始化、应用、卸载

  • 加载:
    加载阶段,虚拟机须要实现以下 3 件事
  • 通过类的全限定名来获取此类的二进制字节流
  1. 将字节流所代表的动态存储构造转化为办法区的运行时数据结构
  2. 在内存中生成代表这个类的 java.lang.Class 对象,作为办法区这个类的各种数据拜访入口
  • 验证:分 4 个验证

    1. 文件格式验证,验证是否合乎 Class 文件格式的标准,并且能被以后版本的虚拟机解决
    2. 元数据验证,对字节码形容的信息进行语义剖析,以保障其形容的信息合乎 Java 语言标准的要求
    3. 字节码验证, 通过数据流和控制流剖析,确定程序语义是否非法、合乎逻辑
    4. 合乎援用验证,是对类本身以外的信息进行匹配性校验(常量池中各种合乎援用)
  • 筹备:正式为类变量分配内存并设置初始值的阶段,这里设置初始值是数据类型的默认值
  • 解析:虚拟机将常量池中的符号援用替换为间接援用的过程
  • 初始化:执行类结构器的过程

12. 强援用、软援用、弱援用、虚援用

  1. 强援用:大部分应用都是强援用,当内存不足时,会 OOM,程序异样终止,也不会随便回收具备强援用的对象
  2. 软援用:内存足够时,不会革除对象,在内存不足时就会回收这些对象
  3. 弱援用:弱援用的对象,在产生 GC 的时候,就会被回收
  4. 虚援用:虚援用次要用来跟踪垃圾回收的流动,虚援用必须和援用队列联结应用。

13. 编译器会对指令做哪些优化?

编译器优化分编译期和运行期

  • 编译期:
    1. 标注查看,查看变量应用前是否已被申明、变量与赋值之间的数据类型是否可能匹配,对常量进行折叠
    2. 数据及控制流剖析,查看诸如程序局部变量在应用前是否有赋值、是否所有的受检异样都被正确处理等问题
    3. 将语法糖还原为根底的语法结构
    4. 生成字节码
  • 运行期:
    即时编译器 JIT 会把运行频繁的代码编译成与本地平台相干的机器码,并进行各种档次的优化
    Client Compiler: 会进行局部性的优化,分三阶段:第一阶段,一个平台独立的前端将字节码结构成一种高级中间代码示意 HIR,HIR 应用动态单调配(SSA)的模式来代表代码值,在字节码上做办法内联,常量流传等根底优化;第二阶段,从 HIR 中产生低级中间代码,在这之前会做空值查看打消,范畴查看打消等。第三阶段,在 LIR 上调配寄存器,并在 LIR 上做窥孔优化,最初产生机器码
    Server Compiler: 会执行无用代码打消、循环展开、循环表达式外提、打消公共子表达式、常量流传、基本块重排序、范畴检测打消、空值查看打消,另外还能依据解释器或 Client Compiler 提供的性能监控信息,进行一些不稳固的激进优化,比方守护内联、分支频率预测等
  • 几种经典的优化技术:
  • 公共子表达式打消
    如果一个表达式 E 曾经计算过,并且从先前的计算到当初 E 中所有变量的值都没有发生变化,那么 E 的这次呈现就成为公共子表达式
  • 数组范畴查看打消
    编译期就判断数组是否在正当的范畴内,如果在,那就能够在循环中把数组的上下界查看打消。另外还有隐式异样解决,虚构机会注册一个 Segment Fault 信号的异样处理器,但如果代码常常为空,耗费工夫比判空慢,但虚构机会依据运行期收集到的信息抉择应用判空还是隐式异样解决
  • 办法内联
    一能够给“公共子表达式打消”等其余优化技术提供根底。虚办法即多态状况下,如果要确定是否能内联,虚拟机须要向“类型继承关系剖析”器查问,如果只有一个版本,那能够进行内联,但还会留一个“逃生门”,这种内联称为“守护内联”,如果继承关系发生变化,虚构机会通过“逃生门”退回到解释状态执行,或从新编译。
    如果是多版本办法,虚构机会通过“内联缓存”,在第一次调用的时候将指标办法版本缓存起来,下次调用的时候查看版本是否统一,如果不统一就会勾销内联,查找虚构办法表进行办法分派
  • 逃逸剖析
    剖析对象动静作用域,对象是否作为调用参数传递到其余办法中(办法逃逸),是否有被其余线程拜访(线程逃逸)。如果没有以上状况,虚构机会做一些高效优化:栈上调配、同步打消(去掉同步措施)、标量替换(将对象成员变量复原到原始类型)

14.Serial、Parallel、CMS、G1 收集器特点



  • Serial

    单线程收集器,在进行垃圾收集时,必须暂停所有的工作线程直到完结,该收集器进展工夫长,-XX:+UseSerialGC 应用串行垃圾收集器

  • Parallel

    采纳多线程来扫描并压缩堆,进展工夫短,回收效率高,-XX:+UseParNewGC 应用并发标记扫描垃圾回收器

  • CMS 基于“标记 - 革除”算法,一共分初始标记、并发标记、从新标记、并发革除,并发重置 5 个阶段

    在初始标记、从新标记阶段须要 STW,并且 CMS 收集器占用 CPU 资源较多,无奈解决浮动垃圾

    并发重置阶段从新初始化 CMS 数据结构和数据,为下次垃圾回收做筹备

    (-XX:CMSInitiatingOccupancyFraction 调整老年代占用多少触发回收;-XX:+UseCMSCompactAtFullCollection 默认开启,在行将触发 FullGC 前对内存碎片进行整顿;-XX:CMSFullGCsBeforeCompaction 设置执行多少次不压缩的 FullGC 后,来一次带压缩的的碎片整顿)

  • G1 能够跟用户程序并发进行垃圾收集;分代收集,将堆划分成多个大小相等的独立 Region 区域;空间整合,默认就会进行内存整理;可预测的进展,G1 跟踪各个 Region 的回收取得的空间大小和回收所须要的经验值,保护一个优先列表;

15.JVM 加载 class 文件的原理是什么

JVM 中类的装载是由 ClassLoader 和它的子类来实现的,Java ClassLoader 是一个重要的 Java 运行时零碎组件。它负责在运行时查找和装入类文件的类。

Java 中的所有类,都须要由类加载器装载到 JVM 中能力运行。类加载器自身也是一个类,而它的工作就是把 class 文件从硬盘读取到内存中。在写程序的时候,咱们简直不须要关怀类的加载,因为这些都是隐式装载的,除非咱们有非凡的用法,像是反射,就须要显式的加载所须要的类。

类装载形式,有两种
(1)隐式装载,程序在运行过程中当碰到通过 new 等形式生成对象时,隐式调用类装载器加载对应的类到 jvm 中,
(2)显式装载,通过 class.forname() 等办法,显式加载须要的类 , 隐式加载与显式加载的区别:两者实质是一样的。

Java 类的加载是动静的,它并不会一次性将所有类全副加载后再运行,而是保障程序运行的根底类 (像是基类) 齐全加载到 jvm 中,至于其余类,则在须要的时候才加载。这当然就是为了节俭内存开销。

16. jvm 最大内存限度多少

(1)堆内存调配

JVM 初始调配的内存由 -Xms 指定,默认是物理内存的 1 /64;JVM 最大调配的内存由 -Xmx 指定,默认是物理内存的 1 /4。默认空余堆内存小 于 40% 时,JVM 就会增大堆直到 -Xmx 的最大限度;空余堆内存大于 70% 时,JVM 会缩小堆直到 -Xms 的最小限度。因而服务器个别设置 -Xms、-Xmx 相等以防止在每次 GC 后调整堆的大小。

(2)非堆内存调配

JVM 应用 -XX:PermSize 设置非堆内存初始值,默认是物理内存的 1 /64;由 XX:MaxPermSize 设置最大非堆内存的大小,默认是物理内存的 1 /4。

(3)VM 最大内存

首先 JVM 内存限度于理论的最大物理内存,假如物理内存无限大的话,JVM 内存的最大值跟操作系统有很大的关系。简略的说就 32 位处理器虽 然可控内存空间有 4GB, 然而具体的操作系统会给一个限度,这个限度个别是 2GB-3GB(一般来说 Windows 零碎下为 1.5G-2G,Linux 系 统下为 2G-3G),而 64bit 以上的处理器就不会有限度了。

(3)上面是以后比拟风行的几个不同公司不同版本 JVM 最大堆内存:

17.jvm 是如何实现线程的

线程是比过程更轻量级的调度执行单位。线程能够把一个过程的资源分配和执行调度离开。一个过程里能够启动多条线程,各个线程可共享该过程的资源(内存地址,文件 IO 等),又能够独立调度。线程是 CPU 调度的根本单位。

支流 OS 都提供线程实现。Java 语言提供对线程操作的同一 API,每个曾经执行 start(),且还未完结的 java.lang.Thread 类的实例,代表了一个线程。

Thread 类的要害办法,都申明为 Native。这意味着这个办法无奈或没有应用平台无关的伎俩来实现,也可能是为了执行效率。

实现线程的形式

A. 应用内核线程实现内核线程 (Kernel-Level Thread, KLT) 就是间接由操作系统内核反对的线程。

内核来实现线程切换

内核通过调度器 Scheduler 调度线程,并将线程的工作映射到各个 CPU 上

程序应用内核线程的高级接口,轻量级过程(Light Weight Process,LWP)

用户态和内核态切换耗费内核资源

应用用户线程实现

零碎内核不能感知线程存在的实现

用户线程的建设、同步、销毁和调度齐全在用户态中实现

所有线程操作须要用户程序本人解决,复杂度高

用户线程加轻量级过程混合实现

轻量级过程作为用户线程和内核线程之间的桥梁

18. 什么是 Java 内存模型

Java 内存模型(简称 JMM),JMM 决定一个线程对共享变量的写入何时对另一个线程可见。从形象的角度来看,JMM 定义了线程和主内存之间的形象关系:线程之间的共享变量存储在主内存(main memory)中,每个线程都有一个公有的本地内存(local memory),本地内存中存储了该线程以读 / 写共享变量的正本。

本地内存是 JMM 的一个抽象概念,并不实在存在。它涵盖了缓存,写缓冲区,寄存器以及其余的硬件和编译器优化。其关系模型图如下图所示:

正文完
 0