jvm面试题详解

  1. 简述 Java 内存管理机制,以及垃圾回收的原理和使用过 Java 调优工具
    内存管理的职责为分配内存,回收内存。 没有自动内存管理的语言/平台容易发生
    错误。
    典型的问题包括悬挂指针问题,一个指针引用了一个已经被回收的内存地址,导致
    程序的运行完全不可知。
    另一个典型问题为内存泄露,内存已经分配,但是已经没有了指向该内存的指针,
    导致内存泄露。 程序员要花费大量时间在调试该类问题上。
  2. 描述 JVM 加载 class 文件的原理机制
    JVM 中类的装载是由类加载器(ClassLoader)和它的子类来实现的,Java 中的类
    加载器是一个重要的 Java 运行时系统组件,它负责在运行时查找和装入类文件中的类。
    由于 Java 的跨平台性,经过编译的 Java 源程序并不是一个可执行程序,而是一个
    或多个类文件。当 Java 程序需要使用某个类时,JVM 会确保这个类已经被加载、连接
    (验证、准备和解析)和初始化。类的加载是指把类的.class 文件中的数据读入到内存中,
    通常是创建一个字节数组读入.class 文件,然后产生与所加载类对应的 Class 对象。加载
    完成后,Class 对象还不完整,所以此时的类还不可用。当类被加载后就进入连接阶段,
    这一阶段包括验证、准备(为静态变量分配内存并设置默认的初始值)和解析(将符号
    引用替换为直接引用)三个步骤。最后 JVM 对类进行初始化,包括:1)如果类存在直接
    的父类并且这个类还没有被初始化,那么就先初始化父类;2)如果类中存在初始化语句,
    就依次执行这些初始化语句。
    类的加载是由类加载器完成的,类加载器包括:根加载器(BootStrap)、扩展加
    载器(Extension)、系统加载器(System)和用户自定义类加载器(java.lang.ClassLoader
    的子类)。从 Java 2(JDK 1.2)开始,类加载过程采取了父亲委托机制(PDM)。PDM
    更好的保证了 Java 平台的安全性,在该机制中,JVM 自带的 Bootstrap 是根加载器,
    其他的加载器都有且仅有一个父类加载器。类的加载首先请求父类加载器加载,父类加
    载器无能为力时才由其子类加载器自行加载。JVM 不会向 Java 程序提供对 Bootstrap
    的引用。下面是关于几个类加载器的说明:
    Bootstrap:一般用本地代码实现,负责加载 JVM 基础核心类库(rt.jar);
    Extension:从 java.ext.dirs 系统属性所指定的目录中加载类库,它的父加载器是
    Bootstrap;
    System:又叫应用类加载器,其父类是 Extension。它是应用最广泛的类加载器。
    它从环境变量 classpath 或者系统属性 java.class.path 所指定的目录中记载类,是用户
    自定义加载器的默认父加载器。
  3. 说说 JVM 原理?内存泄漏与溢出的区别?何时产生内存泄漏?
    答:
    JVM 原理:
    JVM 是 Java Virtual Machine(Java 虚拟机)的缩写,它是整个 java 实现跨平台的最
    核心的部分,所有的 Java 程序会首先被编译为.class 的类文件,这种类文件可以在虚拟
    机上执行,也就是说 class 并不直接与机器的操作系统相对应,而是经过虚拟机间接与
    操作系统交互,由虚拟机将程序解释给本地系统执行。JVM 是 Java 平台的基础,和实
    际的机器一样,它也有自己的指令集,并且在运行时操作不同的内存区域。JVM 通过抽
    象操作系统和 CPU 结构,提供了一种与平台无关的代码执行方法,即与特殊的实现方法、
    主机硬件、主机操作系统无关。JVM 的主要工作是解释自己的指令集(即字节码)到 CPU
    的指令集或对应的系统调用,保护用户免被恶意程序骚扰。JVM 对上层的 Java 源文件
    是不关心的,它关注的只是由源文件生成的类文件(.class 文件)。
    内存泄漏与溢出的区别: 1) 内存泄漏是指分配出去的内存无法回收了。 2) 内存溢出是指程序要求的内存,超出了系统所能分配的范围,从而发生溢出。比如用
    byte 类型的变量存储 10000 这个数据,就属于内存溢出。
  1. 内存溢出是提供的内存不够;内存泄漏是无法再提供内存资源。
    何时产生内存泄漏:
  2. 静态集合类:在使用 Set、Vector、HashMap 等集合类的时候需要特别注意,有可
    能会发生内存泄漏。当这些集合被定义成静态的时候,由于它们的生命周期跟应用程序
    一样长,这时候,就有可能会发生内存泄漏。
  3. 监听器:在 Java 中,我们经常会使用到监听器,如对某个控件添加单击监听器
    addOnClickListener(),但往往释放对象的时候会忘记删除监听器,这就有可能造成内
    存泄漏。好的方法就是,在释放对象的时候,应该记住释放所有监听器,这就能避免了
    因为监听器而导致的内存泄漏。
  4. 各种连接:Java 中的连接包括数据库连接、网络连接和 io 连接,如果没有显式调用
    其 close()方法,是不会自动关闭的,这些连接就不能被 GC 回收而导致内存泄漏。一般
    情况下,在 try 代码块里创建连接,在 finally 里释放连接,就能够避免此类内存泄漏。
  5. 外部模块的引用:调用外部模块的时候,也应该注意防止内存泄漏。如模块 A 调用了
    外部模块 B 的一个方法,如:public void register(Object o)。这个方法有可能就使得
    A 模块持有传入对象的引用,这时候需要查看 B 模块是否提供了去除引用的方法,如
    unregister()。这种情况容易忽略,而且发生了内存泄漏的话,比较难察觉,应该在编写
    代码过程中就应该注意此类问题。
  6. 单例模式:使用单例模式的时候也有可能导致内存泄漏。因为单例对象初始化后将在
    JVM 的整个生命周期内存在,如果它持有一个外部对象(生命周期比较短)的引用,那
    么这个外部对象就不能被回收,而导致内存泄漏。如果这个外部对象还持有其它对象的
    引用,那么内存泄漏会更严重,因此需要特别注意此类情况。这种情况就需要考虑下单
    例模式的设计会不会有问题,应该怎样保证不会产生内存泄漏问题。
  1. GC 线程是否为守护线程?
    GC 线程是守护线程。线程分为守护线程和非守护线程(即用户线程)。只要当前 JVM 实
    例中尚存在任何一个非守护线程没有结束,守护线程就全部工作;只有当最后一个非守
    护线程结束时,守护线程随着 JVM 一同结束工作。
  2. Java 的类加载器都有哪些,每个类加载器都有加载那些类,什么是双亲
    委派模型,是做什么的?
    答:
    类加载器按照层次,从顶层到底层,分为以下三种:
    (1)启动类加载器(Bootstrap ClassLoader)
    这个类加载器负责将存放在 JAVA_HOME/lib 下的,或者被-Xbootclasspath 参数所
    指定的路径中的,并且是虚拟机识别的类库加载到虚拟机内存中。启动类加载器无法
    被 Java 程序直接引用。
    (2)扩展类加载器(Extension ClassLoader)
    这个加载器负责加载 JAVA_HOME/lib/ext 目录中的,或者被 java.ext.dirs 系统变量
    所指定的路径中的所有类库,开发者可以直接使用扩展类加载器
    (3)应用程序类加载器(Application ClassLoader)
    这个加载器是 ClassLoader 中 getSystemClassLoader()方法的返回值,所以一般也
    称它为系统类加载器。它负责加载用户类路径(Classpath)上所指定的类库,可直
    接使用这个加载器,如果应用程序没有自定义自己的类加载器,一般情况下这个就是
    程序中默认的类加载器
    双亲委派模型:
    双亲委派模型要求除了顶层的启动类加载器外,其他的类加载器都应当有自己的父类
    加载器。这里类加载器之间的父子关系一般不会以继承关系来实现,而是都使用组合
    关系来复用父加载器的代码
    工作过程:
    如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把
    这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加
    载请求最终都应该传递到顶层的启动类加载器中,只有当父类加载器反馈自己无法完
    成这个请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去加载。
    好处:
    Java 类随着它的类加载器一起具备了一种带有优先级的层次关系。例如类 Object,
    它放在 rt.jar 中,无论哪一个类加载器要加载这个类,最终都是委派给启动类加载器
    进行加载,因此 Object 类在程序的各种类加载器环境中都是同一个类,判断两个类
    是否相同是通过 classloader.class 这种方式进行的,所以哪怕是同一个 class 文件如
    果被两个 classloader 加载,那么他们也是不同的类。
    加载过程如图:
  3. 垃圾回收器(GC)的基本原理是什么?垃圾回收器可以马上回收内存吗?
    如何通知虚拟机进行垃圾回收?
    答:
    1、对于 GC 来说,当程序员创建对象时,GC 就开始监控这个对象的地址、大小以及
    使用情况。通常,GC 采用有向图的方式记录和管理堆(heap)中的所有对象。通过这
    种方式确定哪些对象是"可达的",哪些对象是"不可达的"。当 GC 确定一些对象为"不
    可达”时,GC 就有责任回收这些内存空间。
    2、可以。程序员可以手动执行 System.gc(),通知 GC 运行,但是 Java 语言规范并
    不保证 GC 一定会执行。
  4. System.gc();或者 Runtime.getRuntime().gc();
  5. Java 中会存在内存泄漏吗,请简单描述。
    答:理论上 Java 因为有垃圾回收机制(GC)不会存在内存泄露问题(这也是 Java 被广
    泛使用于服务器端编程的一个重要原因);然而在实际开发中,可能会存在无用但可达
    的对象,这些对象不能被GC回收也会发生内存泄露。一个例子就是hibernate的Session
    (一级缓存)中的对象属于持久态,垃圾回收器是不会回收这些对象的,然而这些对象
    中可能存在无用的垃圾对象。下面的例子也展示了 Java 中发生内存泄露的情况:
    package com.bjsxt;
    import java.util.Arrays;
    import java.util.EmptyStackException;

public class MyStack {
private T[] elements;
private int size = 0;
private static final int INIT_CAPACITY = 16;
public MyStack() {
elements = (T[]) new Object[INIT_CAPACITY];
}
public void push(T elem) {
ensureCapacity();
elements[size++] = elem;
}
public T pop() {
if(size == 0)
throw new EmptyStackException();
return elements[--size];
}
private void ensureCapacity() {
if(elements.length == size) {
elements = Arrays.copyOf(elements, 2 * size + 1);
}
}
}
上面的代码实现了一个栈(先进后出(FILO))结构,乍看之下似乎没有什么明显的问
题,它甚至可以通过你编写的各种单元测试。然而其中的 pop 方法却存在内存泄露的问
题,当我们用 pop 方法弹出栈中的对象时,该对象不会被当作垃圾回收,即使使用栈的
程序不再引用这些对象,因为栈内部维护着对这些对象的过期引用(obsolete
reference)。在支持垃圾回收的语言中,内存泄露是很隐蔽的,这种内存泄露其实就是
无意识的对象保持。如果一个对象引用被无意识的保留起来了,那么垃圾回收器不会处
理这个对象,也不会处理该对象引用的其他对象,即使这样的对象只有少数几个,也可
能会导致很多的对象被排除在垃圾回收之外,从而对性能造成重大影响,极端情况下会
引发 Disk Paging(物理内存与硬盘的虚拟内存交换数据),甚至造成
OutOfMemoryError。
579. GC 是什么?为什么要有 GC?
答:GC 是垃圾收集的意思,内存处理是编程人员容易出现问题的地方,忘记或者错误的
内存回收会导致程序或系统的不稳定甚至崩溃,Java 提供的 GC 功能可以自动监测对象
是否超过作用域从而达到自动回收内存的目的,Java 语言没有提供释放已分配内存的显
示操作方法。Java 程序员不用担心内存管理,因为垃圾收集器会自动进行管理。要请求
垃圾收集,可以调用下面的方法之一:System.gc() 或 Runtime.getRuntime().gc() , 但 JVM 可以屏蔽掉显示的垃圾回收调用。
垃圾回收可以有效的防止内存泄露,有效的使用可以使用的内存。垃圾回收器通常是作
为一个单独的低优先级的线程运行,不可预知的情况下对内存堆中已经死亡的或者长时
间没有使用的对象进行清除和回收,程序员不能实时的调用垃圾回收器对某个对象或所
有对象进行垃圾回收。在 Java 诞生初期,垃圾回收是 Java 最大的亮点之一,因为服务
器端的编程需要有效的防止内存泄露问题,然而时过境迁,如今 Java 的垃圾回收机制已
经成为被诟病的东西。移动智能终端用户通常觉得 iOS 的系统比 Android 系统有更好
的用户体验,其中一个深层次的原因就在于 Android 系统中垃圾回收的不可预知性。
补充:垃圾回收机制有很多种,包括:分代复制垃圾回收、标记垃圾回收、增量垃圾回
收等方式。标准的 Java 进程既有栈又有堆。栈保存了原始型局部变量,堆保存了要创建
的对象。Java 平台对堆内存回收和再利用的基本算法被称为标记和清除,但是 Java 对
其进行了改进,采用“分代式垃圾收集”。这种方法会跟 Java 对象的生命周期将堆内存
划分为不同的区域,在垃圾收集过程中,可能会将对象移动到不同区域:
• 伊甸园(Eden):这是对象最初诞生的区域,并且对大多数对象来说,这里是它们唯
一存在过的区域。
• 幸存者乐园(Survivor):从伊甸园幸存下来的对象会被挪到这里。
• 终身颐养园(Tenured):这是足够老的幸存对象的归宿。年轻代收集(Minor-GC)
过程是不会触及这个地方的。当年轻代收集不能把对象放进终身颐养园时,就会触发
一次完全收集(Major-GC),这里可能还会牵扯到压缩,以便为大对象腾出足够的
空间。
与垃圾回收相关的 JVM 参数:
• -Xms / -Xmx --- 堆的初始大小 / 堆的最大大小
• -Xmn --- 堆中年轻代的大小
• -XX:-DisableExplicitGC --- 让 System.gc()不产生任何作用
• -XX:+PrintGCDetail --- 打印 GC 的细节
• -XX:+PrintGCDateStamps --- 打印 GC 操作的时间戳

原文地址:https://www.cnblogs.com/linanana/p/12546332.html