当前位置: 首页 > 科技观察

面试重点:Java虚拟机常见问题详解

时间:2023-03-19 13:36:50 科技观察

1、Java引用的四种状态:强引用:  是使用最广泛的。我们平时写代码的时候,会在堆内存中存放一个新的Object,然后用一个引用指向它,这就是强引用。  如果一个对象有强引用,垃圾收集器将永远不会收集它。当内存空间不足时,Java虚拟机宁可抛出OutOfMemoryError错误导致程序异常终止,也不会通过强引用对象的任意回收来解决内存不足的问题。软引用:  如果一个对象只有软引用,当有足够的内存空间时,垃圾回收器不会回收它;如果内存空间不足,就会回收这些对象的内存。(注:如果内存不足,随时可能被回收。)  只要垃圾回收器不回收,该对象就可以被程序使用。软引用可用于实现对内存敏感的缓存。弱引用:  弱引用和软引用的区别在于只有弱引用的对象生命周期更短。  每次GC,一旦发现只有弱引用的对象,不管当前内存空间是否足够,都会回收其内存。然而,由于垃圾收集器是一个非常低优先级的线程,只有弱引用的对象可能无法快速找到。幻引用:  “幻引用”,顾名思义,是没有用的。与其他类型的引用不同,幻象引用不决定对象的生命周期。如果一个对象只持有虚引用,就好像它没有引用一样,随时可能被垃圾收集器回收。  幻影引用主要用于跟踪被垃圾收集器回收的对象的活动。2、Java中的内存划分:Java程序在运行时,需要在内存中分配空间。为了提高计算效率,将数据划分到不同的空间,因为每个区域都有特定的数据处理方式和内存管理方式。1、程序计数器:(线程私有)每个线程都有一个程序计数器,在创建线程时创建,指向下一条指令的地址。执行本地方法时,其值为undefined2.虚拟机栈:(thread-private)each方法调用时,会创建一个栈帧,用于存放局部变量表、操作栈、动态链接、方法等信息出口。局部变量表存储:编译时已知的基本数据类型和对象引用类型。每个方法被调用到执行完成的过程对应于一个栈帧在虚拟机中从入栈到出栈的过程。在Java虚拟机规范中,针对该区域规定了两个异常:  (1)如果线程请求的堆栈深度过深,超过了虚拟机允许的深度,则抛出StackOverFlowError(如***递归。因为每个栈帧都占用一定的空间,而Xss规定了栈的最大空间,超过这个值就会报错)  (2)虚拟机栈可以动态扩展。如果扩展太大,申请不到足够的OOM3,就会出现本地方法栈:(1)本地方法栈的作用和java虚拟机栈很相似。为虚拟机使用的Native方法提供服务。(2)Java虚拟机没有对本地方法栈的使用和数据结构做出强制性规定。SunHotSpot虚拟机将Java虚拟机栈和本地方法栈合二为一。(3)本地方法栈也会抛出StackOverFlowError和OutOfMemoryError。4、堆:即堆内存(线程共享)(1)堆是java虚拟机管理的最大的一块内存区域。java堆是所有线程共享的一块内存区域。它是在java虚拟机启动时创建的。堆内存的唯一目的是存储对象实例。几乎所有的对象实例都分配在堆内存中。(2)堆是GC管理的主要区域。从垃圾回收的角度来看,由于目前的垃圾回收器采用的是分代回收算法,所以java堆初步可以细分为新生代和老年代。(3)Java虚拟机规定堆可以在物理上不连续的内存空间,只要逻辑上连续即可。它可以是固定的,也可以在实现中动态扩展。如果在堆内存中实例分配没有完成,无法扩展堆大小,则会抛出OutOfMemoryError异常。5、方法区:(线程共享)(1)用于存放已经被虚拟机加载的类信息、常量、静态变量、实时编译器编译后的代码等数据。(2)SunHotSpot虚拟机将方法区称为永久代(PermanentGeneration),方法区的最后一部分是运行时常量池。3、Java对象在内存中的状态:reachable/reachable:一个Java对象创建后,如果被一个或多个变量引用,则为reachable。也就是说,可以从根节点到达对象。  其实就是从根节点开始扫描,只要对象在引用链中,就是可达的。可恢复:  Java对象在不再被任何变量引用时进入可恢复状态。  在回收对象之前,对象的finalize()方法进行资源清理。如果变量在finalize()方法中再次引用了对象,则对象再次变为可达,否则对象进入不可达状态Unreachable:  Java对象没有被任何变量引用,系统正在调用如果finalize()对象的方法仍然没有使对象可达(对象仍未被变量引用),则对象将变得不可达。  当Java对象处于不可达状态时,系统实际上会回收该对象占用的资源。四、判断对象死亡的两种常用算法:1、引用计数算法:给对象加上一个引用计数器。每当有对它的引用时,计数器值就会加1;当引用变为无效时,计数器值将减1。;任何时候计数器为0的对象将不再可用。但是主流的java虚拟机并没有使用引用计数算法来管理内存。主要原因是很难解决对象之间的循环引用问题。2.根搜索算法:(jvm采用的算法)设置几种根对象。当任何一个根对象(GCRoot)对某个对象不可达时,就认为这个对象可以被回收。五、垃圾收集算法1.标记-清除算法:标记阶段:首先经过根节点,标记从根节点开始的所有可达对象。因此,未标记的对象是未引用的垃圾对象;清除阶段:清除所有未标记的对象。2.复制算法:(新一代GC)  将原来的内存空间分成两块,每次只使用其中一块。垃圾回收时,将正在使用的内存中存活的对象复制到未使用的内存块中,然后清除正在使用的内存块中的所有对象。3、标记-排序算法:(老年代GC)标记阶段:首先通过根节点,从根节点开始标记所有可达对象。因此,未标记的对象是未引用的垃圾对象。整理阶段:所有幸存的对象将被压缩到内存的一端;之后,边界外的所有空间都会被清理掉4.分代收集算法:低存活率:少量对象存活,适合复制算法:在新生代中,每发现大量对象死亡一次GC,只有少量存活下来(新生代中98%的对象都是“生死存亡”),那么使用复制算法,只需要以复制少量存活对象为代价进行GC即可。高存活率:大量对象存活,适合标记-清理/标记-组织:在老年代,由于对象存活率高,没有额外的空间分配保证,所以需要使用“标记”-clean"/"mark-Tidyup"GC算法。六、垃圾收集器1.串行收集器:(serialcollector)这种收集器是一种单线程的收集器,但是它的单线程的含义并不仅仅意味着它只会使用一个CPU或者一个收集线程来完成垃圾收集工作,更重要的是,当它在进行垃圾回收时,必须暂停所有其他工作线程(Stop-The-World:暂停用户正常工作的所有线程),直到收集结束。2.ParNew收集器:Serial收集器的多线程版本(使用多线程进行GC)  ParNew收集器是Serial收集器的多线程版本。  是一款运行在服务器模式下的新一代收集器。除了Serial收集器,目前只有它可以与CMS收集器配合使用。CMS收集器是一个被认为是划时代的并发收集器,所以如果有一个垃圾收集器可以和它一起使用,让它更高效,那么这个收集器也一定是不可或缺的一部分。3.ParNewScanvenge收集器  与ParNew类似,但更注重吞吐量。目标:实现具有可管理吞吐量的收集器。不能同时调整暂停时间和吞吐量。我们的一方想要购买更少的暂停时间,而另一方想要高吞吐量。其实,这是矛盾的。因为:在GC期间,垃圾回收工作总量保持不变。如果暂停时间减少,频率会增加;因为频率增加,意味着GC会频繁执行,吞吐量会下降。性能会下降。吞吐量:CPU花在用户代码上的时间/CPU总消耗时间的比值,即=运行用户代码的时间/(运行用户代码的时间+垃圾回收时间)。例如,如果虚拟机总共运行了100分钟,垃圾回收耗时1分钟,则吞吐量为99%。4、G1收集器:  是当今收集器发展中最前言的成就之一。直到jdk1.7,Sun公司才认为已经达到足够成熟的程度,可以商用了。5、CMS收集器:(老年代收集器)CMS收集器(ConcurrentMarkSweep:并发标记清除)是一种以获取最短恢复停顿时间为目标的收集器。适用于Internet站点或B/S系统服务器上的应用。这类应用特别注重服务器的响应速度,希望系统停顿时间最短。7、Java堆内存划分:Java中的堆是JVM管理的最大的内存空间,主要用来存放各种类型的实例对象。在Java中,堆分为两个不同的区域:年轻代(Young)和老年代(Tenured)。新生代(Young)分为三个区域:Eden、FromSurvivor、ToSurvivor。这样划分的目的是为了让JVM更好的管理堆内存中的对象,包括内存分配和回收。1.年轻代年轻代用于存放新创建的对象。大小随堆大小的增减而相应变化。默认值为堆大小的1/15。可以通过-Xmn参数将新生代设置为固定大小。您还可以使用-XX:NewRatio来设置年轻代与老年代的大小比例。年轻代的特点是对象更新速度快,短时间内产生大量“死对象”。新生代的特点是产生大量死对象,如果产生连续的可用空间,则采用copyandclear算法和并行收集器进行垃圾收集。新生代的垃圾回收称为初级回收(minorgc)。2.OldgenerationFullGC是发生在老年代的垃圾回收动作,使用mark-clear算法。在现实生活中,老一辈的人往往比新一代的人“死得早”。堆内存中的老年代(Old)与此不同。老年代中的对象几乎都存活在Survivor区,不会那么容易“死”。因此,FullGC的发生次数不会像MinorGC那样频繁,而且FullGC的执行时间也会比MinorGC长。另外,mark-clear算法在收集垃圾时,会产生大量的内存碎片(即不连续的内存空间)。之后,当需要为更大的对象分配内存空间时,如果找不到足够的连续内存空间,就会提前触发一次GC收集动作。3.***代***代是Hotspot虚拟机特有的概念,是方法区的一种实现,这是其他JVM所没有的。在Java8中,第一代被完全移除,取而代之的是另一块不与堆相连的本地内存——元空间。第一代或“PermGen”包含JVM所需的应用程序元数据,用于描述应用程序中使用的类和方法。请注意,第一代不是Java堆内存的一部分。***代存放JVM运行时使用的类。第一代还包含JavaSE库的类和方法。第一代的对象在fullGC期间被垃圾收集。8、类加载机制:虚拟机从Class文件中加载描述类的数据到内存中,并对数据进行校验、转换、解析、初始化,最终形成虚拟机可以直接使用的Java类型.这是虚拟机加载机制的类。对应常见笔试题注:子类初始化问题:遇到主动调用,即父类访问子类中的静态变量和方法,子类会被初始化;否则,只会初始化父类。注意:访问类或接口的静态变量(特殊情况:如果是用staticfinal修饰的常量,则类不会被显式初始化。被staticfinal修饰的变量会被显式初始化)。上面的运行效果表明,由于c是finalstatic修饰的静态常量,因此根本没有调用静态代码块中的内容,即没有显式初始化该类。