Jvm面试题
3-1 Jvm面试题
注意 : 本文聊的是JVM面试题,如果您还没有了解过JVM。请移步github
https://github.com/1367379258/BigDataED/tree/master/java/jvm
上面这个jvm分栏,是jvm的一些基础知识,面试前必看哦。
这里是easy的java基础面试
下面是总的阅览:
java基础
java集合
JVM
多线程
mysql_数据库
计算机网络
nosql_redis
设计模式
操作系统
消息中间件activeMq
SSM框架面试题
服务中间件Dubbo
**以下都是面试题或者面试中常见的知识点哦 **
1-Jvm中的类加载
1-1什么是类加载?
一个类被加载到jvm中,就是类加载。
1-2简单说一下类加载过程?
过程分为: 加载,链接,初始化三部分。
加载: 是Java将字节码数据从不同的数据源读取到JVM中,并反射为Jvm认可的数据结构。(class 对象)
注意: 加载时 用户可以自定义类加载器
链接: 把原始的类定义信息平滑的转入 JVM 运行过程中。
{
验证: 检验字节码信息是否符合Java 虚拟机规范
准备: 创建类或接口中的 静态变量 并初始化。
解析: 将常量池的符号引用 -------> 直接引用
}
直接引用: 直接指向目标的指针,相对偏移量或者间接定位到目标的句柄。
初始化: 真正去执行类初始化的代码逻辑。
{
静态字段的赋值。
静态初始化代码块内逻辑执行,不过父类初始化逻辑优先于当前类型逻辑。
}
1-3类加载器
JVM 中内置了三个重要的 ClassLoader,除了 BootstrapClassLoader 其他类加载器均由 Java 实现且全部继承自java.lang.ClassLoader:
BootstrapClassLoader(启动类加载器) :最顶层的加载类,由C++实现,负责加载 %JAVA_HOME%/lib目录下的jar包和类或者或被 -Xbootclasspath参数指定的路径中的所有类。
ExtensionClassLoader(扩展类加载器) :主要负责加载目录 %JRE_HOME%/lib/ext 目录下的jar包和类,或被 java.ext.dirs 系统变量所指定的路径下的jar包。
AppClassLoader(应用程序类加载器) :面向我们用户的加载器,负责加载当前应用classpath下的所有jar包和类。
1-4双亲委派模型是?
1-4-1概念
当前类加载器尝试去加载某个类型的时候( 除非父加载器找不到相应类型), 否则尽量将这个任务代理到当前加载器的父加载器去处理。
使拥委派模型的目的是 避免重复加载Java类型。
1-4-2案例
我们写一个案例来实践一下:
package com.xlg.ClassLoader;
/**
* @program: designpattern
* @description: 类加载器案例
* @author: Mr.Wang
* @create: 2020-11-12 17:24
**/
public class ClassLoaderDemo {
public static void main(String[] args) {
System.out.println("ClassLoaderDemo's ClassLoader is "+ ClassLoaderDemo.class.getClassLoader());
System.out.println("The Parent of ClassLoaderDemo's ClassLoader is " + ClassLoaderDemo.class.getClassLoader().getParent());
System.out.println("The GrandParent of ClassLoaderDemo's ClassLoader is " + ClassLoaderDemo.class.getClassLoader().getParent().getParent());
}
}
结果是:
ClassLoaderDemo's ClassLoader is sun.misc.Launcher$AppClassLoader@18b4aac2
The Parent of ClassLoaderDemo's ClassLoader is sun.misc.Launcher$ExtClassLoader@5e2de80c
The GrandParent of ClassLoaderDemo's ClassLoader is null
AppClassLoader的父类加载器为ExtClassLoader ExtClassLoader的父类加载器为null,null并不代表ExtClassLoader没有父类加载器,而是 BootstrapClassLoader 。
1-4-3源码
接下里,我们来看那一下源码 java.lang.ClassLoader 的loadClass方法
private final ClassLoader parent;
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// 首先,检查请求的类是否已经被加载过
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {//父加载器不为空,调用父加载器loadClass()方法处理
c = parent.loadClass(name, false);
} else {//父加载器为空,使用启动类加载器 BootstrapClassLoader 加载
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
//抛出异常说明父类加载器无法完成加载请求
}
if (c == null) {
long t1 = System.nanoTime();
//自己尝试加载
c = findClass(name);
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
1-4-4问题?
是的,没错,双亲委派模型是有问题的...
双亲委派模型的问题是: Java中有很多的SPI接口。SPI接口是Java核心类库的一部分,是由BootStrap加载的,但是SPI实现的Java类一般是AppClassLoader来加载的。BootStrap找不到SPI接口的实现类。==== 父类加载器无法找到子类加载器路径中的类。
哈哈哈 搞jdk的那些人能想不到这种问题,肯定有解决方案啊。
解决 : 线程上下文加载器( ContextClassLoader) 是Thread 的一个变量,可以setContextLoader(classLoader)。 如果不做设置,默认AppContextLoader,在核心类库使用SPI接口时,传递的类加载器使用ContextClassLoader , 可以成功加载SPI接口的实现类。
比如JDBC驱动。
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
Iterator<Driver> driversIterator = loadedDrivers.iterator();
try{
while(driversIterator.hasNext()) {
driversIterator.next();
}
} catch(Throwable t) {
// Do nothing
}
ServiceLoader.load源码
public static <S> ServiceLoader<S> load(Class<S> service) {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
return ServiceLoader.load(service, cl);
}
详细的博客:
真正理解线程上下文类加载器(多案例分析)
1-4-5框架中的使用
SpringFactoriesLoader类似于SPI , 这块可以看一下SpringBoot自动配置类的加载,注意从spring.factory文件中读取,最后形成JavaConfig类 , 并加载到了JVM中。
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
String factoryTypeName = factoryType.getName();
return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
}
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
MultiValueMap<String, String> result = cache.get(classLoader);
if (result != null) {
return result;
}
try {
Enumeration<URL> urls = (classLoader != null ?
classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
result = new LinkedMultiValueMap<>();
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
for (Map.Entry<?, ?> entry : properties.entrySet()) {
String factoryTypeName = ((String) entry.getKey()).trim();
for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
result.add(factoryTypeName, factoryImplementationName.trim());
}
}
}
cache.put(classLoader, result);
return result;
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
}
2-OOM-FullGC-GC-STW
2-1OOM
2-1-1概念
Out Of Memory: 当JVM 因为 没有足够的内存来为对象分配空间并且垃圾回收器也没有空间可回收时,就会抛出这error。( 没有空闲内存,且垃圾收集器也无法提供更多内存。)
2-1-2怎么排查
服务器运行日志,抛出内存溢出异常。
核心系统日志文件,
2-1-3哪些会导致OOM?OOM会出现在什么时候?
Java 堆内存溢出: 此种情况最常见,一般由于内存泄漏或堆的大小设置不当引起的,可以通过 -Xms,-Xmx设置。
Java 元空间溢出(1.8): 设置 -XX:MaxMetaSpace= 大小
Java 虚拟机栈溢出: 虽然不会抛出OOM,但是会抛出*Error( 程序中存在死循环或者深度递归调用造成的。) 设置-Xss大小解决。
注意: 当然如果JVM 试图去扩展栈空间的时候,失败,则会抛出OOM Error。
直接内存不足,也会导致OOM。 (注意: 直接内存是不会引发fullgc的,一定要注意。只能在堆空间不足时,堆通知垃圾回收器fullgc时,顺带着收集直接内存中的垃圾。)
2-2FullGC-GC-STW
2-2-1fullGC-MinorGC
从年轻代空间( edon, survivor) 回收内存称为 Mirror GC;
清理整个堆空间--包括年轻代和老年代。
2-2-2fullGc触发条件
System.gc(). 系统建议执行Full GC但是可以不执行 有个配置可以让它不生效。
老年代空间不足。
元空间内存不足。
通过MinorGC 进入老年代的平均大小大于老年代的可用内存
由Eden区,from Space 区向To Space复制时,对象大小大于To space区,则对象转存到,且old 区可用内存小于该对象大小。
2-2-4GC垃圾回收机制
触发条件:
程序调用 system.gc()
系统自身来决定GC 触发的时机。
2-2-5STW
当程序运行到这些安全点( 方法调用,循环跳转,异常跳转 )的时候,会 暂停所有当前 运行的线程。( stop the word ,STW )
其实VM会设置一个标志,当线程执行到安全点的时候,会轮询检测这个标志,如果发现需要GC,则线程会自己挂起,直到GC 结束才恢复执行。
2-2-6定位fullGc发生的原因?有哪些方式?
printGCDetials: 查看fullGc频率和时长
dump 查看内存中哪些对象多
堆大或者是生产环境,开始jmc飞行一段时间之后,通过数据定位问题。
2-3Jvm怎样判断一个对象是否可回收? 怎样的对象能作为GcRoot?
在java中采用了可达性分析算法。通过一些列的"GCroot"对象作为起点进行搜索,如果"Gc Root " 和 一个对象之间没有可达路径,则该对象是不可达的,但是不可达对象不一定是可回收对象。
被判定不可达对象要成为可回收对象要经过 两次标记的过程.
两次标记:
第一次: 判断是否需要执行 finalize() 方法。
第二次: 如果被筛选定为有必要执行,则会放入Queue队列,并自动创建一个低优先级的finalize线程来执行释放操作。如果在一个对象被释放前被其他对象引用,则该对象会被移除Queue队列。
怎么样的对象可以作为GCRoot?
虚拟机栈中引用的对象,方法区中类静态属性引用的对象,方法区常量池引用的对象,本地方法栈JNI引用的对象。
3-内存泄漏和内存溢出
3-1内存泄漏
程序动态分配了内存,但在程序结束时没有释放这部分内存,导致那部分内存不可用(大多时候都是 代码设计引起的).
java 内存泄漏, 当被分配的对象可达,但已经没有时。
Student s1 = new Student();
s2
ArrayList.add(s1) add(s2)
s1 == null
s2 == null
但是此时 这两个对象所占内存并没有释放掉,为什么呢?
因为ArrayList 中还有着对象的引用。
3-2内存溢出
堆上无内存可完成实例分配且堆上无法扩展时-----> OOM
方法区 ( 运行时常量池 ) 无法满足内存分配需求。
虚拟机栈 动态扩展时,无法申请到足够的内存。
3-3两者的区别
内存泄漏是导致 内存溢出的原因之一: 内存泄漏积累起来将导致内存溢出。
内存溢出可以通过完善代码来避免,内存溢出可以通过调整配置来减少发生频率,但是无法彻底解决。
3-4如何检测 内存泄漏?
JProfiler, Optimizeit Profiler。
3-5如何避免内存泄漏和溢出?
尽早释放无用对象的引用。
使用临时变量的时候,让引用变量在退出活动域后自动设置为null, 暗示垃圾回收器来收集该对象,防止发生内存泄漏。
程序进行字符串处理时,尽量避免是用 String, 而使用StringBuffer,因为在每一个string对象都会独立占用内存一块区域。
3-6检查内存泄漏的工具
MemoryAnalyzer, EclipseMAT, JProfile、
本文地址:https://blog.csdn.net/qq_41773026/article/details/109648040
上一篇: Spring 的 AOP 简介