Java虚拟机面试题

垃圾收集算法

  • 标记-清除算法:首先标记出所有需要回收的对象,在标记完成后统一回收掉所有被标记的对象。(产生大量不连续碎片)
  • 复制算法:将内存分为大小相同的两块,每次使用其中的一块。当这一块的内存使用完后,就将还存活的对象复制到另一块去,然后再把使用的空间一次清理掉,这样就使每次的内存回收都是对内存区间的一半进行回收。(内存的利用率不高)
  • 标记-整理算法:让所有存活的对象向一端移动,然后直接清理掉端边界以外的内存。
  • 分代收集算法
    • 新生代选择复制算法,每次收集都会有大量对象死去,只需要付出少量对象的复制成本就可以完成每次垃圾收集。
    • 老年代选择”标记-清除”或”标记-整理”算法,老年代对象存活几率是比较高的,而且没有额外的空间对它进行分配担保。

垃圾收集器

  • 新生代:Serial、ParNew、Parallel Scavenge。
  • 老年代:CMS、Serial Old、Parallel Old。
  • 通用:G1。

Java内存模型是什么

JMM的主要目标是定义程序中变量的访问规则,所有的共享变量都存储在主内存中共享。每个线程有自己的工作内存,工作内存中保存的是主内存中变量的副本,线程对变量的读写等操作必须在自己的工作内存中进行,而不能直接读写主内存中的变量。

什么是类的加载

类的加载指将编译好的 Class 类文件中的字节码读入内存中,将其放在方法区内并在堆中创建对应的 Class 对象。

类的加载分为哪几步

  1. 加载:把class字节码文件通过类加载器装载入内存中。
  2. 验证:保证加载进来的字节码文件符合虚拟机规范,不会造成安全错误。
  3. 准备:为类变量分配内存,并且赋予初值。
  4. 解析:将常量池内的符号引用替换为直接引用的过程。
  5. 初始化:是对类变量初始化,是执行类构造器的过程。

类加载器有哪些

启动类加载器、扩展类加载器、应用加载器(系统加载器)、自定义类加载器。

双亲委派模式是什么

即一个类加载器在加载类时,先把这个请求委托给自己的父类加载器去执行,如果父类加载器还存在父类加载器,就继续向上委托,直到顶层的启动类加载器,如果父类加载器能够完成类加载,就成功返回,如果父类加载器无法完成加载,那么子类加载器才会尝试自己去加载。

双亲委派模式的好处

可以避免类的重复加载,另外也避免了 Java 的核心 API 被篡改。

分代回收

  • 年轻代主要用来存放新创建的对象,年轻代分为 Eden 区和两个 Survivor 区。大部分对象在 Eden 区中生成。当 Eden 区满时,还存活的对象会在两个 Survivor 区交替保存,达到一定次数的对象会晋升到老年代。
  • 老年代用来存放从年轻代晋升而来的,存活时间较长的对象。
  • 永久代,主要保存类信息等内容,这里的永久代是指对象划分方式。

JVM的运行时数据区

  • 线程私有:Java虚拟机栈、本地方法栈、程序计数器。
  • 线程共享:Java堆、方法区。

gc主要处理哪里的数据

方法区和堆。

JVM对象创建到销毁的过程

加载、验证、准备、解析、初始化、使用、卸载。

CMS垃圾回收的过程

类加载的三种方式

  • 通过new关键字来创建类的实例对象。(静态加载)
  • 通过Class.forName()来加载类,然后调用类的newInstance()方法实例化对象。(动态加载)
  • 通过类加载器的loadClass()方法来加载类,然后调用类的newInstance()方法实例化对象。(动态加载)

类加载三种方式的区别

  • 通过new关键字实例化类的对象和通过Class.forName()加载类是当前类加载器,即this.getClass.getClassLoader,只能在当前类路径或者导入的类路径下寻找类。而用指定的ClassLoader来加载类可以从当前类路径外寻找类,这里的ClassLoader甚至可以用户自定义。
  • 我们知道类加载机制的三个过程主要是加载–>连接–>初始化。Class.forName()实际调用的是Class.forName(className,true,this.getClass.getClassLoader),第二个参数表示加载完后是否立即初始化,第三个参数即前文提到的表示是当前类加载器。classLoader.loadClass()实际调用的是classLoader.loadClass(className,false),第二个参数表示加载完成后是否连接,即用此方法加载类,加载完成后不会去初始化,而用Class.forName()加载类加载完成后可以被初始化。所以有些类如果加载完成后需要立即被初始化则必须使用Class.forName()。例如在加载数据库驱动时,一般用Class.forName(“com.mysql.jdbc.Driver”)。这是因为该驱动有一个在静态代码块中注册驱动的过程,所以需要被初始化。
  • 有两个异常:静态加载类时出现的一般是NoClassDefFoundError,动态加载类时出现的一般是ClassNotFoundException。

Class.forName加载类与ClassLoader加载类有什么区别

Class.forName加载类,加载完后立即初始化;ClassLoader加载类,加载完成后不会去初始化。

什么是内存溢出

申请内存时,没有足够的内存可以使用。

如何判断对象是否死亡

  • 引用计数法:给对象中添加一个引用计数器,每当有一个地方引用它,计数器就加 1;当引用失效,计数器就减 1;任何时候计数器为 0 的对象就是不可能再被使用的。
  • 可达性分析法:通过一系列的称为 “GC Roots” 的对象作为起点,从这些节点开始向下搜索,节点所走过的路径称为引用链,当一个对象到 GC Roots 没有任何引用链相连的话,则证明此对象是不可用的。

Java引用类型有哪些?

  • 强引用(Strong Reference):Java中的引用,默认都是强引用。比如new一个对象,对它的引用就是强引用。对于被强引用指向的对象,就算JVM内存不足OOM,也不会去回收它们。
  • 软引用(Soft Reference):若一个对象只被软引用所引用,那么它将在JVM内存不足的时候被回收,即如果JVM内存足够,则软引用所指向的对象不会被垃圾回收。
  • 弱引用(Weak Reference):若一个对象只被弱引用所引用,那么它将在下一次GC中被回收掉。
  • 虚引用(Phantom Reference):虚引用是四种引用中最弱的一种引用。我们永远无法从虚引用中拿到对象,被虚引用引用的对象就跟不存在一样。

类什么时候被初始化

  • 创建类的实例,也就是new一个对象。
  • 访问某个类或接口的静态变量,或者对该静态变量赋值。
  • 调用类的静态方法。
  • 反射(Class.forName(“com.echo.load”))。
  • 初始化一个类的子类(会首先初始化子类的父类)。
  • JVM启动时标明的启动类,即文件名和类名相同的那个类。

获得一个类对象的方式有哪些

  • 类名.class,例如:String.class。
  • 对象.getClass(),例如:”hello”.getClass()。
  • Class.forName(),例如:Class.forName(“java.lang.String”)。

Java中为什么会有GC机制

  • 安全性考虑。
  • 减少内存泄漏。
  • 减少程序员工作量。

对于Java的GC哪些内存需要回收

JVM中的堆和方法区。

方法区中需要回收的是什么

废弃的常量和无用的类。

废弃的常量的回收

看引用计数,没有对象引用该常量就可以回收。

无用的类的回收

  • 该类所有的实例都已经被回收。Java堆中不存在该类的任何实例。
  • 加载该类的ClassLoader已经被回收。
  • 该类对应的java.lang.Class对象没有任何地方被引用,无法在任何地方通过反射访问该类的属性和方法。

在开发中遇到过内存溢出吗?原因有哪些?解决方法有哪些?

  • 内存溢出的原因:
    • 内存中加载的数据量过于庞大,如一次从数据库取出过多数据。
    • 集合类中有对对象的引用,使用完后未清空,使得JVM不能回收。
    • 代码中存在死循环或循环产生过多重复的对象实体。
    • 使用的第三方软件中的BUG。
    • 启动参数内存值设定的过小。
  • 内存溢出的解决方案:
    1. 修改JVM启动参数,直接增加内存。
    2. 检查错误日志,查看”OutOfMemory”错误前是否有其它异常或错误。
    3. 对代码进行走查和分析,找出可能发生内存溢出的位置。
      1. 检查对数据库查询中,是否有一次获得全部数据的查询。
      2. 检查代码中是否有死循环或递归调用。
      3. 检查是否有大量循环重复产生新对象实体。
      4. 检查List,Map等集合对象是否有使用完后,未清除的问题。
    4. 使用内存查看工具动态查看内存使用情况。

GC垃圾回收的目的

回收堆内存中不再使用的对象所占的内存,释放资源。

什么情况下触发GC

在新生代的Eden区满了,会触发新生代GC(Minor GC),经过多次触发新生代GC存活下来的对象就会升级到老年代,升级到老年代的对象所需的内存大于老年代剩余的内存,则会触发老年代GC(Full GC)。当程序调用System.gc()时也会触发Full GC。