1. 首页
  2. 面试宝典
  3. JVM篇
  4. JVM面试第一篇

JVM面试第一篇

1.JVM内存模型

Java内存模型在主存和线程之间加入了工作内存的概念,来解决缓存和CPU处理速度不一直的问题,同时内存模型规定一个线程如何或何时看到其他线程修改过后的共享变量的值,它通过限制处理器优化和内存屏蔽两种方式,解决了由于多线程通过共享变量通讯时,存在的本地内存不一致,编译器对代码的指令重排,处理器对代码的乱序执行等问题,保证了共享内存的原子性、可见性、有序性

2.JVM的内存结构

JVM的内存结构划分了5个区域,分别是程序计数器,虚拟机栈,本地方法栈,方法区,堆(heap)五部分;
程序计数器:主要记录程序的执行位置,线程恢复后能会到达正确的执行位置
Java虚拟机栈:Java虚拟机栈是线程私有的,生命周期与线程相同,它描述了方法从调用到执行完成的过程,实值上对应一个栈帧在虚拟机栈中入栈到出栈的过程
本地方法栈:。主要记录一些本地的Native方法
方法区:方法区是各个线程共享的内存区域,主要用于存储虚拟机加载的类信息,字段,方法,静态变量,常量池。
堆:Java堆也是线程共享的内存区域,是Java虚拟机所管理内存中最大的一块,主要用于存放对象实例

3.GC分哪两种,Minor GC 和Full GC有什么区别?分别采用什么算法?

答:
Minor GC:负责回收新生代的对象,由于新生代中对象产生的快,消亡的也快,因此新生代是基于复制算法进行对象回收的
Major GC:负责回收老年代的对象,采用的是标记–整理算法
Full GC:发生在老年代,是Minor GC和Full GC的和,会清理整个堆空间,包括新生代(Eden,Survivor),老年代(Old Gen)

4.什么时候会触发Full GC?

1.老年代空间不足
2.统计得到Yong GC晋升到老年代的平均大小大于老年代剩余空间的一半
3.主动触发Full GC 如:System.gc()
4.使用CMS GC时出现promotion failed和concurrent mode failure错误时有可能触发Full GC

5.JVM中可以作为GC Root的有哪些?

1.Java虚拟机栈(栈帧中的本地变量表)中引用的对象
2.方法区中的类静态属性引用的对象
3.方法区中常量引用的对象
4.本地方法栈中JNI(即一般说的Native方法)的引用对象

6.JVM里的有几种classloader,为什么会有多种?

1.启动类加载器(Bootstrap ClassLoader):主要负责加载JAVA_HOME\lib目录中和被-Xbootclasspath参数所指定路径下的类库中的类加载到虚拟机内存中,启动加载器无法被Java程序直接引用
2.拓展加载器(Extension ClassLoader):主要负责加载JAVA_HOME\lib\ext目录中和被java.ext.dirs系统变量所指定的路径下的所有类库中的类加载到虚拟机内存中,拓展加载器可以被开发者直接引用
3.应用加载器(Application ClassLoader):主要负责加载用户路径(ClassPath)所有类库中的类,开发者可以直接使用这个类加载器,如果应用程序没有自己的类加载器,一般情况下,这个就是程序的默认加载器
4.自定义加载器,应用程序根据自己的需要自定义类加载器

通过多个不同的ClassLoader类加载器的成次关系确保了一种类只能被一种类加载器加载,保证了JVM中一个类只存在一份

7.什么是双亲委派机制?介绍一些运作过程,双亲委派模型的好处;

由自定义类加载器–>应用程序类加载–>拓展类加载器–>启动类加载器 这样的成次关系称为双亲委派模型。
运行过程:各个类加载器通过使用组合关系来复用父加载器的代码,当一个类加载器接到类的加载请求,它首先不会自己去尝试加载这个类而是把这个请求委托给父类加载器去完成,每个层次的类加载器都是如此,因此所有的类都会到达顶层的启动类加载器,只有当父类加载器无法完成这个加载请求时,子加载器才会去尝试自己去加载

好处:双亲委派模型保证了类与加载器一起具有了一种优先级的层次关系,确保了直接类只会在一种类加载器中加载,保证了直接类在jvm中只存在一份,保证了Java程序的稳定性

8.怎么打破双亲委派模型?

1.自己写一个类加载器;
2.重写 loadClass() 方法
3.重写 findClass() 方法
这里最主要的是重写 loadClass 方法,因为双亲委派机制的实现都是通过这个方法实现的,先找父加载器进行加载,如果父加载器无法加载再由自己来进行加载,源码里会直接找到根加载器,重写了这个方法以后就能自己定义加载的方式了。

9.有哪些实际场景是需要打破双亲委派模型的?

JNDI 服务,它的代码由启动类加载器去加载,但 JNDI 的目的就是对资源进行集中管理和查找,它需要调用独立厂商实现部部署在应用程序的 classpath 下的 JNDI 接口提供者(SPI, Service Provider Interface) 的代码,但启动类加载器不可能“认识”之些代码,该怎么办?
为了解决这个困境,Java 设计团队只好引入了一个不太优雅的设计:**线程上下文件类加载器(Thread Context ClassLoader)。这个类加载器可以通过 java.lang.Thread 类的 setContextClassLoader() 方法进行设置,如果创建线程时还未设置,它将会从父线程中继承一个;如果在应用程序的全局范围内都没有设置过,那么这个类加载器默认就是应用程序类加载器。有了线程上下文类加载器,JNDI 服务使用这个线程上下文类加载器去加载所需要的 SPI 代码,也就是父类加载器请求子类加载器去完成类加载动作,这种行为实际上就是打通了双亲委派模型的层次结构来逆向使用类加载器,已经违背了双亲委派模型,但这也是无可奈何的事情。Java 中所有涉及 SPI 的加载动作基本上都采用这种方式,例如 JNDI、JDBC、JCE、JAXB 和 JBI 等。

10.常见的JVM调优方法有哪些?可以具体到调整哪个参数,调成什么值?

堆设置
-Xms:初始堆大小
-Xmx:最大堆大小
-XX:NewSize=n:设置年轻代大小
-XX:NewRatio=n:设置年轻代和年老代的比值。如:为3,表示年轻代与年老代比值为 1:3,年轻代占整个年轻代年老代和的 1/4
-XX:SurvivorRatio=n:年轻代中 Eden 区与两个 Survivor 区的比值。注意 Survivor 区有两个。如:3,表示 Eden:Survivor=3:2,一个Survivor区占整个年轻代的 1/5
-XX:MaxPermSize=n:设置持久代大小

  1. 收集器设置
    -XX:+UseSerialGC:设置串行收集器
    -XX:+UseParallelGC:设置并行收集器
    -XX:+UseParalledlOldGC:设置并行年老代收集器
    -XX:+UseConcMarkSweepGC:设置并发收集器
  2. 垃圾回收统计信息
    -XX:+PrintGC:开启打印 gc 信息
    -XX:+PrintGCDetails:打印 gc 详细信息
    -XX:+PrintGCTimeStamps
    -Xloggc:filename
  3. 并行收集器设置
    -XX:ParallelGCThreads=n:设置并行收集器收集时使用的 CPU 数
    -XX:MaxGCPauseMillis=n:设置并行收集最大暂停时间
    -XX:GCTimeRatio=n:设置垃圾回收时间占程序运行时间的百分比
  4. 并发收集器设置
    -XX:+CMSIncrementalMode:设置为增量模式。适用于单 CPU 情况
    -XX:ParallelGCThreads=n:设置并发收集器年轻代收集方式为并行收集时,使用的 CPU 数。并行收集线程数

发表评论

  1. 星星
    星星 【小白】 @回复

    不错!