JVM与多线程面试

烟雨 4年前 (2021-08-02) 阅读数 377 #面试
文章标签 JavaJVM面试

一、用户线程和内核线程

Java中,线程(Thread)其实是用户线程,对应操作系统还有个内核线程。
用户线程和内核线程之间存在,多对一,一对一,多对多等关系。

多对一线程模型

多个用户线程对应一个内核线程上。

优点

用户线程的操作对内核线程来说是透明的,不需要用户态和内核态频繁切换,使线程的创建,调度,同步非常快。

缺点

容易阻塞,如果其中一个用户线程阻塞,其他线程也会受到印象,即并发不行。
无法对用户线程设置优先级。内核线程不知道有哪些用户线程。

一对一线程模型

一个线程对应一个内核线程。

优点

实现线程模型容器简单。

缺点

用户线程大部分操作都映射到内核线程上,会导致用户态和内核态平凡的切换,开销大

多对多线程模型

多个用户线程对应多个内核线程上。

优点

克服了多对一模型并发度不高的缺点,又克服了一对一模型中一个用户进程占用太多内核级程序,开销太大的缺点。

缺点

实现困难。

二、JVM的运行是数据区有哪些?

问的就是JVM模型
线程共享区域:堆 、方法区/元空间、运行时常量池。
非线程共享区域:栈、程序计数器、本地方法栈。

一个方法入栈的结构:局部变量表、操作数栈、动态链接、方法出口。image.png

三、堆内存包含了哪些补分?

image.png

四、内存溢出和内存泄漏

内存溢出:指的是JVM可用内存不足。常见的OOM:栈溢出,堆溢出,方法区溢出,本机内存直接溢出。
内存泄漏:不适用的内存,缺不能被回收,这个就是内存泄漏。

五、对象头包含哪些补分

再Hotpot虚拟机中,对象在内存中分为3块区域:对象头,实例数据,对齐填充。

image.png

详细可参考:https://blog.zender.top/post/02_JVM.html

六、设置堆空间的最大值(-Xmx)应该考虑哪些因数?

需要根据物理机内存情况、业务场景来定。
举个通用的例子:物理内存8G,系统暂用0.5G,剩余7.5G。
那么堆空间建议占用7.5*0.8 = 6G
详细设置可参考:https://blog.zender.top/post/02_JVM.html 的七、JVM内存优化举例(估算)。

七、Java8默认使用的是什么垃圾回收器?

Java8版本是Hotspot JVM,默认使用的是并行垃圾收集器(Parallal GC)。

八、什么是并行垃圾收集?

并行垃圾收集,指的是多个GC工作线程并行的执行垃圾回收。充分利用多核心CPU能力完成垃圾收集。
详细可参考:https://blog.zender.top/post/04_JVM.html

九、如果CPU使用率突然飙升,如何排查?

1、先通过top命令找到CPU使用率很高的进程ID。

image.png

2、执行top -p单独监控该进程。
3、在第二步的监控页面输入H,获取当前进程下的所有线程信息。找到消耗CPU特别搞的线程ID

image.png

4、执行jstack 线程ID,对当前线程做dump。输出所有的线程信息,同时将第三步得到的线程编号转换成16进制,在堆栈信息里面去找对应线程。

image.png

5、最后解读线程信息,定位代码位置。

十、G1垃圾回收器中的三色标记

三色标记是一种垃圾回收算法。它可以让JVM减少发生STW(Stop the world),达到清除JVM垃圾的目的
黑色:该对象标记过了,且对象下的属性也全部被标记过了。
灰色:该对象已经被垃圾收集器扫描过了,但对象中还没有被扫描。
白色:表示对象没有被垃圾回收器访问过,对象不可达。表示为垃圾对象。
详细可参考:https://blog.zender.top/post/05_JVM.html

十一、类加载过程,类加载器

加载 --> 验证 --> 准备 --> 解析 --> 初始化 --> 使用 --> 卸载。

image.png

类加载器
    引导类加载器,扩展类加载器,应用类加载器,自定义加载器

十二、双亲委派机制

简单说就是,自己先不加载,委派给父类加载,父类再加载不了,委派给父类的父类加载,如果上级都加载不了,就自己加载。
保证了核心类不会被篡改,且类不会重复加载

image.png

详细可参考:https://blog.zender.top/post/01_JVM.html

十三、如何排查OOM问题

JDK1.8推荐生产环境开启如下参数:
-XX:+HeapDumpOnOutOfMemoryError  当OOM发生是自动dump堆内存信息。
-XX:HeapDumpPath=/temp/heapdumpError dump堆内存信息存放目录。

再使用VisualVM查看dump堆内存信息。

十四、什么是多线程中的上下文切换?

CPU给每个线程分配CPU时间片,线程再分配获得时间片内执行任务。

image.png

当一个线程被暂停/剥夺CPU使用权,另外的线程开始执行/继续执行的过程就是上下文切换。
切换的时候需要保存线程的相关进度信息。这个进度信息就是“上下文”。

发生上下文切换的时机

1、线程分配CPU时间片使用完毕。
2、synchronized或lock导致上下文切换。

十五、JAVA内存模型是什么?

Java内存模型是为了屏蔽掉各种硬件和操作系统内存访问差异,以实现让Java程序能在各种平台下都能达到一直的并发效果。
Java内存模型规定所有变量都存储在主内存中,线程需要获取变量时,需要拷贝到自己的工作内存中,处理完毕后,写回主内存。
详细可参考:https://blog.zender.top/post/01_Concurrent.html

十六、什么是原子操作?在JUC中有哪些原子类。

原子操作指的是一个不受其他操作影响的任务单元。
原子操作可避免多线程环境下数据不一致的必须手段。
原子类:AtomicInteger AtomicLong等

十七、什么是CAS,缺点是什么?

CAS为比较替换,统一时刻只有一个线程的CAS会操作成功,其他都失败,失败后自旋。
程序中使用CAS+自旋的方式可以实现原子操作。
缺点,线程自旋可能过度消耗CPU资源。且有个ABA问题,可以通过添加版本号解决。

十八、volatile关键字有什么用,和atomic变量有什么不同?

保证了不同线程对该变量的可见性。
禁止指令重排。
atomic解决的是多线程的安全问题。

十九、Lock接口事什么,对比synchronized它有什么优势?

Lock接口比同步方法和同步方法块提供了跟多扩展性的锁操作
1、Lock接口无条件的。可轮询的(tryLock方法),定时的(tryLock带参数方法),可中断的(lockInterruptibly),可多条件队列的(newCondition方法)锁操作。

image.png

2、Lock接口实现了公平锁和非公平锁,synchronized 只支持非公平锁。 

二十、乐观锁,悲观锁如何理解?

乐观锁:总是假设最坏的情况,每次去拿数据时,都认为别人会修改,进行加锁。
乐观锁:总是假设最好的情况,每次去拿数据时,都认为别人不会修改,不进行加锁。但是在更新数据的时候会判断一下这个期间是否有其他锁去修改了这个数据(可以通过版本号机制)。

二一、什么是死锁,带来什么危害,如何避免死锁。

死锁:指2个火以上的线程,在执行过程中,争抢资源导致一种互相等待的现象。
危害:死锁会是线程卡死,得不到正确的结果。导致CPU资源利用率低。可能导致新的死锁参生。

二二、Callable和Future。

Callable接口类似于Runnable,但是多了一个返回值。这个返回值可以被Future拿到。
异步任务Callable,异步任务结果Future。

二三、什么是FutureTask?它的底层原理?

FutureTask:表示一个可以取消的异步运算。只有当运算完成后,才能取回。否则get方法会被一直阻塞。

image.png

二四、什么是阻塞队列,实现原理是什么?

阻塞队列(BlockingQueue)是一个支持两个附加操作的队列。
阻塞队列是一种特殊的线性表,特殊之处在于,只允许前端(front)删除,后端插入(rear)。和栈一样,是一种收限制的线性表。

二五、CopyOnWriteArrayList可以用于什么应用场景?

CopyOnWriteArrayList写时复制容器
CopyOnWriteArrayList它实现了读取无锁,写操作通过底层数据的copy新副本来实现锁,是一种读写分离的并发策略。
/**
     * Appends the specified element to the end of this list.
     *
     * @param e element to be appended to this list
     * @return {@code true} (as specified by {@link Collection#add})
     */
public boolean add(E e) {
    final ReentrantLock lock = this.lock;
    lock.lock();  // 加锁
    try {
        Object[] elements = getArray();
        int len = elements.length;
        Object[] newElements = Arrays.copyOf(elements, len + 1);  // 拷贝新数组
        newElements[len] = e;
        setArray(newElements);
        return true;
    } finally {
        lock.unlock();  // 释放锁
    }
}
CopyOnWrite并发容器,适合读多写少的场景。

二六、简单说一下AQS。

AQS是用来构建锁和其他同步主键的基础框架,
AQS队列同步器
    结构大致如下,AQS维护了一个共享资源state,通过内置的同步队列来排队工作,该同步列队包含一个头节点,一个未节点。
    一个线程未获取到锁时,会创建一个node对象加入同步队列中,node对象内部的分别持有头节点和未节点的引用。

二七、为什么wait()、notify() 、notifyAll() 这些没有定义在thread类里面?

Java提供的锁不是线程级的,而是对象级的,每个线程都有锁,可以通过线程获得。
wait()、notify() 、notifyAll() 这些方法都是属于锁级别的,有对象才有锁,所以放在了Object中。


版权声明

非特殊说明,本文由Zender原创或收集发布,欢迎转载。

上一篇:Java基础面试题 下一篇:MySQL面试

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

作者文章
热门