Java虚拟机(JVM)面试题2

简介: Java虚拟机(JVM)面试题2

常用的 JVM 调优的参数都有哪些?

JVM三大性能调优参数-Xms -Xmx -Xss的含义

-Xss:规定了每个线程虚拟机栈的大小

-Xms:堆的初始值

-Xmx:堆能达到的最大值

例如

-Xms2g:初始化堆大小为 2g;

-Xmx2g:堆最大内存为 2g;

常用的 JVM 调优的参数:

-XX:NewRatio=4:设置年轻的和老年代的内存比例为1:4;


-XX:SurvivorRatio=8:设置新生代Eden和Survivor 比例为8:2;


-XX:+UseParNewGC:指定使用ParNew + Serial Old垃圾回收器组合;


-XX:+UseParallelOldGC:指定使用ParNew + ParNew Old 垃圾回收器组合;


-XX:+UseConcMarkSweepGC:指定使用CMS + Serial Old垃圾回收器组合;


-XX:+PrintGC:开启打印gc信息;


-XX:+PrintGCDetails: 打印gc详细信息。


对象

对象的创建方式有哪几种?

通过 new 关键字

这是最常用的一种方式,通过 new 关键字调用类的有参或无参构造方法来创建对象。比如 Object obj = new Object();

通过 Class 类的 newInstance() 方法

通过反射来实现,这种默认是调用类的无参构造方法创建对象。比如 Person p2 = (Person) Class.forName("com.ys.test.Person").newInstance();

通过 Constructor 类的 newInstance 方法

这和第二种方法类时,都是通过反射来实现。通过 java.lang.relect.Constructor 类的 newInstance() 方法指定某个构造器来创建对象。例如下面的指定Person类的第一个构造器来创建一个对象(getConstructors()[0]是指第一个构造器)


Person p3 = (Person) Person.class.getConstructors()[0].newInstance();


实际上第二种方法利用 Class 的 newInstance() 方法创建对象,其内部调用还是 Constructor 的 newInstance() 方法。


利用 Clone 方法


Clone 是 Object 类中的一个方法,通过 对象A.clone() 方法会创建一个内容和对象 A 一模一样的对象 B,clone 克隆,顾名思义就是创建一个一模一样的对象出来。这是浅克隆


Person p4 = (Person) p3.clone();


反序列化

序列化是把堆内存中的 Java 对象数据,通过某种方式把对象存储到磁盘文件中或者传递给其他网络节点(在网络上传输)。而反序列化则是把磁盘文件中的对象数据或者把网络节点上的对象数据,恢复成Java对象模型的过程。


Java对象创建的流程

Java普通对象的创建流程,这里讨论的仅仅是普通Java对象,不包含数组和Class对象。


简单说java创建对象的流程

虚拟机遇到一条new指令时,首先去检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已被加载过(加载、解析和初始化)。如果没有,那么须先执行相应的类加载过程,接下来虚拟机将为新创建的对象分配内存,内存分配完成后,虚拟机需要将分配到的内存空间都初始化为零值(不包括对象头),对对象头进行初始化的设置,例如这个对象是哪个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的GC分代年龄等信息。这些信息存放在对象的对象头(Object Header)之中。执行init方法是为了把对象按照程序员的意愿进行初始化


Java对象创建的流程

  • new指令

虚拟机遇到一条new指令时,首先去检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已被加载过(加载、解析和初始化)。如果没有,那么须先执行相应的类加载过程。

  • 分配内存

接下来虚拟机将为新创建的对象分配内存。对象所需的内存的大小在类加载完成后便可完全确定。分配方式有“指针碰撞(Bump the Pointer)”和“空闲列表(Free List)”两种方式,具体由所采用的垃圾收集器是否带有压缩整理功能决定。

  • 初始化(默认初始化)

内存分配完成后,虚拟机需要将分配到的内存空间都初始化为零值(不包括对象头),这一步操作保证了对象的实例字段在Java代码中可以不赋初始值就直接使用,程序能访问到这些字段的数据类型所对应的零值。

  • 对象的初始设置

接下来虚拟机要对对象进行必要的设置,例如这个对象是哪个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的GC分代年龄等信息。这些信息存放在对象的对象头(Object Header)之中。根据虚拟机当前的运行状态的不同,如对否启用偏向锁等,对象头会有不同的设置方式。


方法(自定义初始化)

在上面的工作都完成了之后,从虚拟机的角度看,一个新的对象已经产生了,但是从Java程序的角度看,对象还没创建完,执行init方法是为了把对象按照程序员的意愿进行初始化(应该是将构造函数中的参数赋值给对象的字段),这样一个真正可用的对象才算完全产生出来。


Java对象内存布局

在HotSpot虚拟机中,对象在内存中存储的布局可以分为3块区域:对象头(Header)、实例数据(Instance Data)、对其填充(Padding)。

对象头

HotSpot虚拟机的对象头包含两部分信息,第一部分用于存储对象自身的运行时数据,如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等。另一部分是类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例(并不是所有的虚拟机实现都必须在对象数据上保留类型指针,也就是说,查找对象的元数据信息并不一定要经过对象本身)。如果对象是一个Java数组,那在对象头中还必须有一块用于记录数组长度。


元数据:描述数据的数据。对数据及信息资源的描述信息。在Java中,元数据大多表示为注解。


实例数据

实例数据部分是对象真正存储的有效信息,也是在程序代码中定义的各种类型的字段内容,无论从父类继承下来的,还是在子类中定义的,都需要记录起来。这部分的存储顺序会虚拟机默认的分配策略参数和字段在Java源码中定义的顺序影响(相同宽度的字段总是被分配到一起)。

对齐填充

对齐填充部分并不是必然存在的,也没有特别的含义,它仅仅起着占位符的作用。由于HotSpot VM的自动内存管理系统要求对象的起始地址必须是8字节的整数倍,也就是说,对象的大小必须是8字节的整数倍。而对象头部分正好是8字节的倍数(1倍或者2倍),因此,当对象实例数据部分没有对齐时,就需要通过对齐填充来补全。


分配内存的两种方式


类加载完成后,接着会在Java堆中划分一块内存分配给对象。内存分配根据Java 堆是否规整,有指针碰撞和空闲列表两种方式

指针碰撞

如果Java堆的内存是规整,即所有用过的内存放在一边,而空闲的的放在另一边。分配内存时将位于中间的指针指示器向空闲的内存移动一段与对象大小相等的距离,这样便完成分配内存工作。


空闲列表

如果Java堆的内存不是规整的,则需要由虚拟机维护一个列表来记录哪些内存是可用的,这样在分配的时候可以从列表中查询到足够大的内存分配给对象,并在分配后更新列表记录。


选择哪种分配方式是由 Java 堆是否规整来决定的,而 Java 堆是否规整又由所采用的垃圾收集器是否带有压缩整理功能决定。


分配内存的的时候要处理并发安全问题

在实际的开发过程中,会经常的创建对象。在并发的情况下创建对象会出现并发安全的问题,例如多个并发执行的线程创建对象,分配内存的时候,有可能在Java堆中的同一个位置申请(也就是并发的时候同时有两个不同的对象分配的内存是同一块内存),这就需要对这部分内存空间进行加锁或者采用CAS等操作保证线程安全,即保证该区域只分配给一个线程。作为虚拟机,必须保证线程安全。通常来讲虚拟机采用两种方式保证线程安全


CAS + 失败重试

CAS是一种乐观锁的实现方式,每次不加锁假设没有冲突的去完成某项操作,如果因为冲突导致操作失败就重试,直到成功为止

TLAB

TLAB (Thread Local Allocation Buffer,线程本地分配缓冲区)是 Java 中内存分配的一个概念,它是在 Java 堆中划分出来每个线程的内存区域,专门在该区域为该线程创建的对象分配内存。它的主要目的是在多线程并发环境下需要进行内存分配的时候,减少线程之间对于内存分配区域的竞争,加速内存分配的速度。TLAB 本质上还是在 Java 堆中的,因此在 TLAB 区域的对象,也可以被其他线程访问。


如果没有使用TLAB,多个并发执行的线程创建对象,分配内存的时候,有可能在Java堆中的同一个位置申请,这就需要对这部分内存空间进行加锁或者采用CAS等操作保证线程安全,即保证该区域只分配给一个线程。


使用了TLAB之后,JVM会针对每个线程在堆内存中预留一个内存区域,在预留这个操作发生的时候,需要进行加锁或者采用CAS等操作进行保护,避免多个线程预留同一个区域。一旦确定了某个区域分配给某个线程,之后该线程需要分配内存的时候,会优先在这片区域申请。这个区域对于该线程分配内存这个操作而言是线程私有的,因此在分配的时候不用进行加锁等操作,从而既保护了线程安全又提升了分配速度。(注意是预留区域需要加锁,只有确定了某个区域分配给某个线程才不用加锁)


注意: 当该线程创建的对象大于TLAB中的剩余内存或者TLAB的内存已用尽时,再采用CAS + 失败重试的方式分配内存。


对象的访问方式(对象的定位)

Java程序需要通过 JVM 栈上的引用访问堆中的具体对象。对象的访问方式取决于 JVM 虚拟机的实现。目前主流的访问方式有句柄和直接指针两种方式。


直接指针: 指向对象,代表一个对象在内存中的起始地址。


句柄: 可以理解为指向指针的指针,维护着对象的指针。句柄不直接指向对象,而是指向对象的指针(句柄不发生变化,指向固定内存地址,因为对象的指针是固定的,只是对象的指针所指向的地址可能会发生变化),再由对象的指针指向对象的真实内存地址。


直接指针

如果使用直接指针访问,对象的引用中存储的直接就是对象地址,那么Java堆对象内部的布局中就必须考虑如何放置访问类型数据的相关信息。

优势:速度更快,节省了一次指针定位的时间开销。由于对象的访问在Java中非常频繁,因此这类开销积少成多后也是非常可观的执行成本。HotSpot 中采用的就是这种方式。

句柄访问

可以理解为指向指针的指针,维护着对象的指针。Java堆中划分出一块内存来作为句柄池,对象的引用中存储对象的句柄地址,可以在句柄池中找到这个句柄,句柄中包含了对象实例数据与对象类型数据各自的具体地址信息,句柄不直接指向对象,而是指向对象的指针(句柄不发生变化,指向固定内存地址,因为对象的指针是固定的,只是对象的指针所指向的地址可能会发生变化),再由对象的指针指向对象的真实内存地址


具体构造如下图所示



优势:引用中存储的是稳定的句柄地址,在对象被移动(垃圾收集时移动对象是非常普遍的行为)时只会改变句柄中的实例数据指针(也就是会改变句柄中对象的指针指向的地址),而引用本身不需要修改(也就是句柄所指向的对象指针这个值是不会变得)。

垃圾回收机制

简述Java垃圾回收机制

在java中,程序员是不需要手动的去释放一个对象的内存的,而是由虚拟机自行执行。在JVM中,有一个垃圾回收线程,它是低优先级的,在正常情况下是不会执行的,只有在虚拟机空闲或者当前堆内存不足时才会触发执行,扫描那些没有被任何引用的对象并将它们进行回收。

GC是什么?为什么要GC?

GC是什么?

GC 是垃圾收集的意思(Gabage Collection)

为什么要GC(垃圾回收的目的)?

内存处理是编程人员容易出现问题的地方,忘记或者错误的内存回收会导致程序或系统的不稳定甚至崩溃,Java 提供的 GC 功能可以自动监测对象从而达到自动回收内存的目的

(因为创建的对象中会有很多没有用的对象占大量的内存空间,这个时候就需要把这些没用的对象回收并释放它们所占的内存空间)


什么时候进行垃圾回收?

当GC扫描到没有被引用的对象时候

垃圾回收的优点和原理?

垃圾回收的优点?

  • java程序员在编写程序时不再考虑内存管理的问题。
  • 由于有这个垃圾回收机制,java中的对象不再有“作用域”的概念,只有引用的对象才有“作用域”。
  • 垃圾回收机制有效的防止了内存泄露,可以有效的使用可使用的内存。

垃圾回收的原理?

垃圾回收器通常作为一个单独的低级别的线程运行,在不可预知的情况下对内存堆中已经死亡的或很长时间没有用过的对象进行清除和回收。程序员不能调用垃圾回收器让他进行垃圾回收。我们虽然可以调用System.gc()主动通知虚拟机进行垃圾回收,但是进不进行垃圾回收是GC来决定的


垃圾回收器可以马上回收内存吗?有什么办法主动通知虚拟机进行垃圾回收?

垃圾回收器可以马上回收内存吗?

可以 ,对于GC来说,当程序员创建对象时,GC就开始监控这个对象的地址、大小以及使用情况。通常,GC采用有向图的方式记录和管理堆(heap)中的所有对象。通过可达性分析算法这种方式确定哪些对象是”可达的”,哪些对象是”不可达的”。当GC确定一些对象为”不可达”时,GC就有责任回收这些内存空间。


可达性分析算法


通过一系列为GC Roots的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连时,则证明该对象是不可用的。如果对象在进行可行性分析后发现没有与GC Roots相连的引用链,也不会理解死亡。它会暂时被标记上并且进行一次筛选,筛选的条件是是否与必要执行finalize()方法。如果被判定有必要执行finaliza()方法,就会进入F-Queue队列中,并有一个虚拟机自动建立的、低优先级的线程去执行它。稍后GC将对F-Queue中的对象进行第二次小规模标记。如果这时还是没有新的关联出现,那基本上就真的被回收了。


有什么办法主动通知虚拟机进行垃圾回收?

程序员可以调用System.gc()主动通知虚拟机进行垃圾回收,但依旧无法保证GC一定会执行。

Java 中都有哪些引用类型?

强引用

在 Java 中最常见的就是强引用,把一个对象赋给一个引用变量,这个引用变量就是一个强引用。当一个对象被强引用变量引用时,它处于可达状态,它是不可能被垃圾回收机制回收的。因此强引用是造成 Java 内存泄漏的主要原因之一。


Object obj = new Object(); //只要obj还指向Object对象,Object对象就不会被回收

obj = null;  //手动置null

只要强引用存在,垃圾回收器将永远不会回收被引用的对象,哪怕内存不足时,JVM也会直接抛出OutOfMemoryError,不会去回收。如果想中断强引用与对象之间的联系,可以显示的将强引用赋值为null,这样一来,JVM就可以适时的回收对象了


软引用

软引用是用来描述一些非必需但仍有用的对象。在内存足够的时候,软引用对象不会被回收,只有在内存不足时,系统则会回收软引用对象,如果回收了软引用对象之后仍然没有足够的内存,才会抛出内存溢出异常。这种特性常常被用来实现缓存技术,比如网页缓存,图片缓存等。

在 JDK1.2 之后,用java.lang.ref.SoftReference类来表示软引用。


在运行下面的Java代码之前,需要先配置参数 -Xms2M -Xmx3M,将 JVM 的初始内存设为2M,最大可用内存为 3M。


public class TestOOM {
  private static List<Object> list = new ArrayList<>();
  public static void main(String[] args) {
       testSoftReference();
  }
  private static void testSoftReference() {
    for (int i = 0; i < 10; i++) {
      byte[] buff = new byte[1024 * 1024];
      SoftReference<byte[]> sr = new SoftReference<>(buff);
      list.add(sr);
    }
    System.gc(); //主动通知垃圾回收
    for(int i=0; i < list.size(); i++){
      Object obj = ((SoftReference) list.get(i)).get();
      System.out.println(obj);
    }
  }
}

打印结果

我们发现无论循环创建多少个软引用对象,打印结果总是只有最后一个对象被保留,其他的obj全都被置空回收了。

这里就说明了在内存不足的情况下,软引用将会被自动回收。

弱引用

弱引用需要用 WeakReference 类来实现,它比软引用的生存期更短,对于只有弱引用的对象来说,只要垃圾回收机制一运行,不管 JVM 的内存空间是否足够,总会回收该对象占用的内存。


private static void testWeakReference() {
    for (int i = 0; i < 10; i++) {
      byte[] buff = new byte[1024 * 1024];
      WeakReference<byte[]> sr = new WeakReference<>(buff);
      list.add(sr);
    }
    System.gc(); //主动通知垃圾回收
    for(int i=0; i < list.size(); i++){
      Object obj = ((WeakReference) list.get(i)).get();
      System.out.println(obj);
    }
  }


虚引用

虚引用是最弱的一种引用关系,如果一个对象仅持有虚引用,那么它就和没有任何引用一样,它随时可能会被回收,在 JDK1.2 之后,用 PhantomReference 类来表示,通过查看这个类的源码,发现它只有一个构造函数和一个 get() 方法,而且它的 get() 方法仅仅是返回一个null,也就是说将永远无法通过虚引用来获取对象,它不能单独使用,虚引用必须要和 ReferenceQueue 引用队列一起使用,虚引用的主要作用是跟踪对象被垃圾回收的状态。


public class PhantomReference<T> extends Reference<T> {
    /**
     * Returns this reference object's referent.  Because the referent of a
     * phantom reference is always inaccessible, this method always returns
     * <code>null</code>.
     *
     * @return  <code>null</code>
     */
    public T get() {
        return null;
    }
    public PhantomReference(T referent, ReferenceQueue<? super T> q) {
        super(referent, q);
    }
}


引用队列(ReferenceQueue)

引用队列可以与软引用、弱引用以及虚引用一起配合使用,当垃圾回收器准备回收一个对象时,如果发现它还有引用,那么就会在回收对象之前,把这个引用加入到与之关联的引用队列中去。程序可以通过判断引用队列中是否已经加入了引用,来判断被引用的对象是否将要被垃圾回收,这样就可以在对象被回收之前采取一些必要的措施。


与软引用、弱引用不同,虚引用必须和引用队列一起使用。


JVM中的永久代中会发生垃圾回收吗

jdk1.8以前

理论上垃圾回收不会发生在永久代

jdk1.8以后

1.8以后由于改成了元空间,元空间的默认情况下内存空间是使用的操作系统的内存空间,所以空间的容量是比较充裕的,不会发生元空间的空间不足问题。

新生代垃圾回收器和老年代垃圾回收器都有哪些?有什么区别?

新生代垃圾回收器和老年代垃圾回收器都有哪些?

新生代回收器:Serial、ParNew、Parallel Scavenge

老年代回收器:Serial Old、Parallel Old、CMS

整堆回收器:G1


有什么区别?

  • 新生代垃圾回收器一般采用的是复制算法进行垃圾回收,优点是效率高,缺点是内存利用率低;
  • 老年代回收器一般采用的是标记-整理的算法进行垃圾回收,优点是解决了标记-清理算法存在的内存碎片问题,缺点是仍需要进行局部对象移动,一定程度上降低了效率。

Java会存在内存泄漏吗?请简单描述

内存泄漏是指不再被使用的对象或者变量一直被占据在内存中。 严格来说,只有对象不会再被程序用到了,但是GC又不能回收他们的情况,才叫内存泄漏。


理论上来说,Java是有GC垃圾回收机制的,也就是说,不再被使用的对象,会被GC自动回收掉,自动从内存中清除。


但是,即使这样,Java也还是存在着内存泄漏的情况,java导致内存泄露的原因很明确:长生命周期的对象持有短生命周期对象的引用就很可能发生内存泄露,尽管短生命周期对象已经不再需要,但是因为长生命周期对象持有它的引用而导致不能被回收,这就是java中内存泄露的发生场景。


为什么新生代使用“复制算法”,老年代使用“标记-整理法”?

因为在Java程序中,大部分对象都是“朝生夕死”,很快会被回收的,所以新生代的minorGC的触发频率要远大于老年代的majorGC,这就需要效率更高的“复制算法”,而老年代由于majorGC触发频率比较低,所以可以选择效率较低的“标记整理法”来节约内存。

通俗易懂

为什么新生代使用“复制算法”

因为新生代里绝大部分都是垃圾对象,可以使用复制算法将小部分存活对象复制到另一个区域,然后留下来的都是垃圾对象,可以一网打尽,一下子清除掉。因为存活的对象少,所以“复制”的次数少;虽然留下来的垃圾对象多,但是可以一网打尽,所以“删除”的次数也少。就像你有一个文件夹,里面有 1000 张图片,其中只有 3 张是有用的,你要删除余下的 997 张垃圾图片,你会怎么删除呢?你肯定不会一张一张的去删除 997 张垃圾图片,这样需要删除 997 次,你显然认为把 3 张有用的图片复制出去,然后将整个文件夹干掉来的快。这也就是为什么新生代使用复制算法合适、使用标记清除算法不合适。


为什么老年代使用“标记-整理法”


因为老年代里都是些“老不死”的对象,假设你有一个文件夹,里面有 1000 张图片,其中 997 张是有用的,你会怎样删除这其中的 3 张垃圾图片呢?你肯定不会将 997 张有用的图片复制出去,然后将整个文件夹干掉,因为复制的代价太大了,耗时久,而且 997 张图片的位置都变了,反应在 java 对象上,就是 997 个 java 对象相互之间引用的地址都得换个遍。相反,你会挨个去删除 3 张垃圾图片,因为删除次数少,也不需要大量移动文件。所以老年代适合使用标记清除算法、不适合使用复制算法( 移动 997 张图片,就为了可以整体一次删除 3 个垃圾图片,好傻 )


三种GC触发的情况?

Minor (卖 ne3)GC触发的条件

新生代中Eden空间不足,对象优先在Eden中分配,当Eden中没有足够空间时,虚拟机将发生一次Minor GC,因为Java大多数对象都是朝生夕灭,所以Minor GC非常频繁,而且速度也很快;

发生Minor GC时,虚拟机会检测之前每次晋升到老年代的平均大小是否大于老年代的剩余空间大小,如果大于,则进行一次Full GC,如果小于,则查看HandlePromotionFailure设置是否允许担保失败,如果允许,那只会进行一次Minor GC,如果不允许,则改为进行一次Full GC。


触发Major(mei4 啧)GC的条件

  • 在进行MajorGC之前,一般都先进行了一次MinorGC,使得有新生代的对象进入老年代,当老年代空间不足时就会触发MajorGC。
  • 当无法找到足够大的连续空间分配给新创建的较大对象时,也会触发MajorGC进行垃圾回收腾出空间。

触发FullGC的条件

调用System.gc时,系统建议执行Full GC,但是不必然执行

老年代空间不足

方法区空间不足

通过Minor GC后进入老年代的平均大小大于老年代的可用内存

由Eden区、survivor space1(From Space)区向survivor space2(To Space)区复制时,当永久代满时也会引发Full GC,会导致Class、Method元信息的卸载。


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

有两种方法,分别是引用计数法和可达性分析算法


1、引用计数法

在对象中添加一个引用计数器,如果被引用计数器加 1,引用失效时计数器减 1,如果计数器为 0 则被标记为垃圾。


弊端:如果一个对象A持有对象B,而对象B也持有一个对象A(两个对象相互引用),那发生了类似操作系统中死锁的循环持有,这种情况下A与B的counter恒大于1,会使得GC永远无法回收这两个对象


解析:在对象头处维护一个counter,每增加一次对该对象的引用计数器自加,如果对该对象的引用失联,则计数器自减。当counter为0时,表明该对象已经被废弃,不处于存活状态。这种方式一方面无法区分软、虛、弱、强引用类别。另一方面,会造成死锁,假设两个对象相互引用始终无法释放counter,永远不能GC。


2、可达性分析算法

通过一系列为GC Roots的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连时,则证明该对象是不可用的。如果对象在进行可行性分析后发现没有与GC Roots相连的引用链,也不会理解死亡。它会暂时被标记上并且进行一次筛选,筛选的条件是是否与必要执行finalize()方法。如果被判定有必要执行finalize()方法,就会进入F-Queue队列中,并有一个虚拟机自动建立的、低优先级的线程去执行它。稍后GC将对F-Queue中的对象进行第二次小规模标记。如果这时还是没有新的关联出现,那基本上就真的被回收了。


那么那些点可以作为GC Roots呢?

一般来说,如下情况的对象可以作为GC Roots:

  • 虚拟机栈(栈桢中的本地变量表)中的引用的对象
  • 类静态属性引用的对象
  • 常量引用的对象
  • 本地方法栈中JNI(Native方法)的引用的对象


如果对象的引用被置为null,垃圾收集器是否会立即释放对象占用的内存?

不会,在下一个垃圾回调周期中,这个对象将是被可回收的。


也就是说并不会立即被垃圾收集器立刻回收,而是在下一次垃圾回收时才会释放其占用的内存。


finalize()方法什么时候被调用?析构函数(finalization)的目的是什么?

垃圾回收器(garbage colector)决定回收某对象时,就会运行该对象的finalize()方法;


finalize是Object类的一个方法,该方法在Object类中的声明如下


protected void finalize() throws Throwable { }


在垃圾回收器执行时会调用被回收对象的finalize()方法,可以覆盖此方法来实现对其资源的回收。


注意:一旦垃圾回收器准备释放对象占用的内存,将首先调用该对象的finalize()方法,并且下一次垃圾回收动作发生时,才真正回收对象占用的内存空间

析构函数(finalization)的目的是什么?

答案是大部分时候,什么都不用做(也就是不需要重载)。只有在某些很特殊的情况下,比如你调用了一些native的方法(一般是C写的),可以要在finaliztion里去调用C的释放函数。

说一下 JVM 有哪些垃圾回收器?


图中展示了7种作用于不同分代的收集器,如果两个收集器之间存在连线,则说明它们可以搭配使用。虚拟机所处的区域则表示它是属于新生代还是老年代收集器。


新生代收集器(全部的都是复制算法):Serial、ParNew、


Parallel(佩 肉 no3)Scavenge


老年代收集器:CMS(标记-清理)、Serial Old(标记-整理)、Parallel Old(标记整理)


整堆收集器: G1(一个Region中是标记-清除算法,2个Region之间是复制算法) 同时,先解释几个名词:


并行(Parallel):多个垃圾收集线程并行工作,此时用户线程处于等待状态

并发(Concurrent):用户线程和垃圾收集线程同时执行

吞吐量:运行用户代码时间/(运行用户代码时间+垃圾回收时间)


目录
相关文章
|
6天前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
20 2
|
11天前
|
存储 算法 Java
大厂面试高频:什么是自旋锁?Java 实现自旋锁的原理?
本文详解自旋锁的概念、优缺点、使用场景及Java实现。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
大厂面试高频:什么是自旋锁?Java 实现自旋锁的原理?
|
16天前
|
存储 缓存 Oracle
Java I/O流面试之道
NIO的出现在于提高IO的速度,它相比传统的输入/输出流速度更快。NIO通过管道Channel和缓冲器Buffer来处理数据,可以把管道当成一个矿藏,缓冲器就是矿藏里的卡车。程序通过管道里的缓冲器进行数据交互,而不直接处理数据。程序要么从缓冲器获取数据,要么输入数据到缓冲器。
Java I/O流面试之道
|
12天前
|
存储 缓存 Java
大厂面试必看!Java基本数据类型和包装类的那些坑
本文介绍了Java中的基本数据类型和包装类,包括整数类型、浮点数类型、字符类型和布尔类型。详细讲解了每种类型的特性和应用场景,并探讨了包装类的引入原因、装箱与拆箱机制以及缓存机制。最后总结了面试中常见的相关考点,帮助读者更好地理解和应对面试中的问题。
38 4
|
13天前
|
存储 Java 程序员
Java基础的灵魂——Object类方法详解(社招面试不踩坑)
本文介绍了Java中`Object`类的几个重要方法,包括`toString`、`equals`、`hashCode`、`finalize`、`clone`、`getClass`、`notify`和`wait`。这些方法是面试中的常考点,掌握它们有助于理解Java对象的行为和实现多线程编程。作者通过具体示例和应用场景,详细解析了每个方法的作用和重写技巧,帮助读者更好地应对面试和技术开发。
54 4
|
25天前
|
存储 Java 程序员
Java面试加分点!一文读懂HashMap底层实现与扩容机制
本文详细解析了Java中经典的HashMap数据结构,包括其底层实现、扩容机制、put和查找过程、哈希函数以及JDK 1.7与1.8的差异。通过数组、链表和红黑树的组合,HashMap实现了高效的键值对存储与检索。文章还介绍了HashMap在不同版本中的优化,帮助读者更好地理解和应用这一重要工具。
53 5
|
24天前
|
存储 Java
[Java]面试官:你对异常处理了解多少,例如,finally中可以有return吗?
本文介绍了Java中`try...catch...finally`语句的使用细节及返回值问题,并探讨了JDK1.7引入的`try...with...resources`新特性,强调了异常处理机制及资源自动关闭的优势。
19 1
|
25天前
|
存储 算法 Java
Java虚拟机(JVM)的内存管理与性能优化
本文深入探讨了Java虚拟机(JVM)的内存管理机制,包括堆、栈、方法区等关键区域的功能与作用。通过分析垃圾回收算法和调优策略,旨在帮助开发者理解如何有效提升Java应用的性能。文章采用通俗易懂的语言,结合具体实例,使读者能够轻松掌握复杂的内存管理概念,并应用于实际开发中。
|
23天前
|
算法 Java
JAVA 二叉树面试题
JAVA 二叉树面试题
14 0
|
1月前
|
存储 安全 Java
jvm 锁的 膨胀过程?锁内存怎么变化的
【10月更文挑战第3天】在Java虚拟机(JVM)中,`synchronized`关键字用于实现同步,确保多个线程在访问共享资源时的一致性和线程安全。JVM对`synchronized`进行了优化,以适应不同的竞争场景,这种优化主要体现在锁的膨胀过程,即从偏向锁到轻量级锁,再到重量级锁的转变。下面我们将详细介绍这一过程以及锁在内存中的变化。
37 4