jvm面试题

1.jvm内存模型:

线程独占:java虚拟机栈、本地方法栈、程序计数器

线程共享:堆、方法区(jdk1.7永久代和1.8的元空间都是方法区的实现)

2.java虚拟机栈

java虚拟机栈是线程私有的,每个方法执行的时候,java虚拟机栈都会同步创建一个栈帧,栈帧中存储操作数栈、局部变量表、动态链接、方法出口等信息,

每个方法被调用直至执行完毕的过程对应一个栈帧在虚拟机栈中的入栈和出栈过程

3.本地方法栈

与java虚拟机栈类似,用来保存执行方法的信息,不同的是,执行java方法使用的是java虚拟机栈,执行native方法使用的是本地方法栈

4.程序计数器

保存当前线程执行的字节码的位置,每个线程都会有一个程序计数器,只为执行java方法服务,执行native方法,程序计数器为空

5.堆

java内存管理最大的一个区域,目的是存放对象,数据被多个线程共享,当堆中内存不足时会产生OOM异常,根据对象的存活周期不同,jvm把对象进行分代管理,由垃圾回收器自动回收

6.方法区

又称非堆区,多个线程共享,主要用来存储已被虚拟机加载的类信息、常量、静态变量,即时编译器编译后的代码缓存等数据,jdk1.7的永久代和jdk1.8的元空间都是方法区的一种实现

7.jvm内存可见性

 

 

 JMM定义了程序中各个共享变量的访问规则,线程对于变量的操作只能在自己的工作内存中进行,而不能直接对主内存操作,由于指令重排序,读写的顺序会被打乱,因此JMM需要提供原子性,可见性,有序性保证

 

 

 8.类加载与卸载

 

 

 其中验证,准备,解析合称链接

加载是整个"类加载 class loading"过程中的一个阶段,不要混淆。加载阶段,java虚拟机需要做3件事:

1.通过一个类的全限定名获取定义此类的二进制字节流

2.将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构

3.在内存中生成一个代表这个类的java.lang.class对象,作为方法区这个类的各种数据访问的入口

验证确保class文件符合当前虚拟机的要求,不会危害到虚拟机自身安全

准备进行内存分配,为static修饰的类变量分配内存,并设置初始值(0或null)。不包含final修饰的静态变量,因为final变量在编译时分配,这时候分配的初始值为零值,假设一个变量定义为:public static int value = 123;则设置变量的初始值应该为0, 而不是123。 把value赋值为123的putstatic指令是在程序被编译后,存放于类构造器<clinit>()方法之中,所以把value赋值为123的动作将在初始化阶段才会执行。如果字段属性表中存在ConstantValue属性,那么在准备阶段会将value赋值为ConstantValue属性所指定的值 。例如:public static final int value = 123; 那么在准备阶段,则会将value赋值为123;

解析将常量池中的符号引用替换为直接引用的过程,直接引用为直接指向目标的指针或相对偏移量

初始化主要完成静态块执行以及静态变量的赋值,先初始化父类,再初始化当前类,只有对类主动使用时才会初始化。触发条件包括:创建类的示例时、访问类的静态变量或静态方法时,使用class.forName反射类的时候或者某个子类初始化的时候

java自带的加载器加载的类,在虚拟机的生命周期中是不会被卸载的,只有用户自定义的加载器加载的类才可以被卸载

1.加载机制-双亲委派模式

各个加载器加载范围:

  Bootstrap classloader:负责加载%java_HOME%\lib目录下的类或者被-Xbootclasspath参数所指定的路径中存放的。而且是java虚拟机能够识别的(按照文件识别,如rt.jar、tools.jar,名字不符合的类库即使放在lib目录也不会被加载)类库加载到虚拟机的内存中。

  Ext classloader:负责加载<JAVA_HOME>\lib\ext目录中,或者被java.ext.dirs系统变量所指定的路径中的所有的类库

  App classloader: 负责加载用户类路径(classpath)上的所有类库

  优点:

  1.避免类的重复加载

  2.避免java的核心API被篡改

 如何打破双亲委派机制?

重写父类的loadclass方法即可。(如果不想打破,只需要重写findclass方法)

 10.常见的垃圾回收算法:

  引用计数算法:通过对象被引用的次数确定对象是否被使用,缺点是无法解决循环引用的问题

  标记清除算法:

  缺点1:执行效率不稳定,如果java堆中包含大量对象,而且大部分是需要被回收的,这时必须要进行大量的标记-清除的动作,导致标记-清除两个过程的执行效率随对象数量增长而降低。

  缺点2:标记清除会产生大概不连续的内存碎片,导致当以后在程序运行过程中需要分配较大对象时无法找到足够的连续空间而不得不提前触发另一次的垃圾收集动作

  标记-复制算法:

    优点:解决内存碎片问题

    缺点1:如果内存中多数对象是存活的,会产生大量的内存复制开销

  标记整理算法:与标记清除算法相同,不过在清除后会进行内存整理

  分代收集算法: 当前商业虚拟机的垃圾收集都是采用垃圾收集算法,根据对象存活周期不同,将内存分为几块,一版分为新生代和老年代,新生代有大批量对象死去、存活的对象少,采用复制算法,只需要占用部分存活对象的内存开销就可以了。

  老年代中大部分对象都是存活的,只有少量对象会死去,采用标记-清除、标记-整理算法

11.CMS垃圾收集器

 

 

 

 

 

                                      CMS执行流程图

缺点1,存在浮动垃圾:由于CMS收集器无法处理"浮动垃圾",有可能会出现"con-current Mode Failure"失败进而导致另一次完全"stop the world"的Full gc的产生.在CMS的并发标记和并发清理阶段,用户线程还在继续运行,程序在继续运行自然还会伴随有新的垃圾对象不断产生,但这一部分垃圾对象是出现在标记过程结束之后,CMS无法在当次手机中处理掉它们,只好等下一次垃圾收集时再清理掉。这一部分垃圾称为"浮动垃圾".

为什么并发标记阶段会产生浮动垃圾?

因为并发标记阶段,用户线程和垃圾收集线程同时运行,在初始标记阶段标记为GCROOTS的对象,在并发标记阶段很有可能用户线程将一个可达对象标记为不可达,例如a引用b,初始标记阶段b被扫描为可达,但是用户线程之后对a.b=null,这个时候b还是作为可达的对象,因此称为浮动垃圾.

缺点2,存在并发失败:由于在垃圾收集阶段,用户线程也需要运行,那就需要留出足够的内存空间给用户线程使用,因此CMS收集器不像其他收集器一样,等老年代基本完全被填满了再进行收集,必须预留一部分空间并发收集时的程序运作使用。在JDK5的默认设置下,CMS收集器当老年代使用了68%的空间后就会被激活,这是一个偏保守的设置,如果在实际应用中,老年代增长的不是很快的情况下,意味着在CMS垃圾收集时用户线程不需要很大的内存,此时可以适当调高参数-XX:CMSInitiatingOccu-pancyFraction的值来提高CMS的触发百分比,降低内存回收频率,获取更好的性能。到了JDK6时,CMS垃圾收集器的启动阈值就已经默认提升92%.但这又会面临另一种风险:要是CMS运行期间预留的内存无法满足程序分配新对象的需要,就会出现一次并发失败,这时候虚拟机会启动后背预案:冻结用户线程的执行,临时启用Serial old收集器来重新进行老年代的垃圾收集,serial old是单线程的,可想而知,停顿时间会非常长。所以参数-XX:CMSInitiatingOccu-pancyFraction设置的太高将会很容易导致大量的并发失败产生,性能反而降低,用户在生产环境中应根据实际应用情况来权衡设置

缺点3,会产生空间碎片:CMS是基于标记清除算法实现的垃圾收集器,收集结束时会有大量空间碎片产生。空间碎片过多时,将会给大对象分配带来麻烦,往往会出现老年代明明有很多内存空间,却不能找到足够大的连续空间来分配当前对象,而不得不提前触发Full gc,

为了解决这个问题,CMS收集器提供了一个-XX:+UseCMSCompactAtFullCollection开关参数(默认开启,jdk9开始废弃),用于在CMS收集器不得不进行Full GC时开启内存碎片的合并整理过程,由于这个内存整理必须移动存活对象,(在shenandoah和ZGC出现前)是无法并发的。这样空间碎片问题解决了,但停顿时间又会变长,因此虚拟机设计者们还提供了另外一个参数-XX:CMSFullGCsBeforeCompaction(此参数从jdk9开始废弃),这个参数的作用是要求CMS收集器在执行过若干次(数量由参数值决定)不整理空间的full GC之后,下一次进入Full GC前会先进行碎片整理(默认值为0,表示每次进入Full GC时都进行碎片整理)

 参考:https://www.jianshu.com/p/1e4011617650

posted @ 2020-11-16 14:04  Nullpointer_dxy  阅读(116)  评论(0编辑  收藏  举报