java面试题——多线程&&JVM(一)

一、开场白

简单的介绍一下自己的工作经历与职责,在校或者工作中主要的工作内容,主要负责的内容;(你的信息一清二白的写在简历上,这个主要为了缓解面试者的压力)

介绍下自己最满意的,有技术亮点的项目或平台,重点介绍下自己负责那部分的技术细节;(主要考察应聘者对自己做过的事情是否有清晰的描述,判断做的事情的复杂度)

相关问题答案为个人整理,仅供参考。

二、Java多线程相关

1.线程池的原理,为什么要创建线程池?创建线程池的方式;

复制代码

1.线程池是指在初始化一个多线程应用程序过程中创建一个线程集合,然后在需要执行新的任务时重用这些线程而不是新建一个线程。线程池中线程的数量通常完全取决于可用内存数量和应用程序的需求。然而,增加可用线程数量是可能的。
 线程池中的每个线程都有被分配一个任务,一旦任务已经完成了,线程回到池子中并等待下一次分配任务。

2.①线程池改进了一个应用程序的响应时间。由于线程池中的线程已经准备好且等待被分配任务,应用程序可以直接拿来使用而不用新建一个线程。② 线程池节省了CLR 为每个短生存周期任务创建一个完整的线程的开销并可以在任务完成后回收资源。
  ③ 线程池根据当前在系统中运行的进程来优化线程时间片。④线程池允许我们开启多个任务而不用为每个线程设置属性。⑤ 线程池允许我们为正在执行的任务的程序参数传递一个包含状态信息的对象引用。⑥ 线程池可以用来解决处理一个特定请求最大线程数量限制问题。

3.① newSingleThreadExecutor
  创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。
  ②newFixedThreadPool
  创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。
  ③newCachedThreadPool
  创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)
  能够创建的最大线程大小。
  ④newScheduledThreadPool
  创建一个大小无限的线程池。此线程池支持定时以及周期性执行任务的需求。
参考链接:https://www.cnblogs.com/Leo_wl/archive/2012/03/27/2418997.html

     https://blog.csdn.net/wolf909867753/article/details/77500625/

复制代码

2.线程的生命周期,什么时候会出现僵死进程;

复制代码

1.①新建(new Thread),当创建Thread类的一个实例(对象)时,此线程进入新建状态(未被启动)。例如:Thread  t1=new Thread();
  ②就绪(runnable,调用Thread类的start方法,线程已经被启动,进入就绪状态,正在等待被分配给CPU时间片,也就是说此时线程正在就绪队列中排队等候得到CPU资源。
  ③运行(running),线程获得CPU资源正在执行任务(执行run()方法),此时除非此线程自动放弃CPU资源或者有优先级更高的线程进入,线程将一直运行到结束或者时间片结束。
  ④堵塞(blocked),由于某种原因导致正在运行的线程让出CPU并暂停自己的执行,即进入堵塞状态。阻塞结束后线程进入就绪状态。
  ⑤死亡(dead),当线程执行完毕(run方法运行结束)或被其它线程杀死,线程就进入死亡状态,这时线程不可能再进入就绪状态等待执行。
2. 一个进程在调用exit命令结束自己的生命的时候,其实它并没有真正的被销毁,而是留下一个称为僵尸进程(Zombie)的数据结构(系统调用exit,它的作用是使进程退出,但也仅仅限于将一个正常的进程变成一个僵尸进程,并不能将其完全销毁)。在Linux进程的状态中,僵尸进程是非常特殊的一种,它已经放弃了几乎所有内存空间,没有任何可执行代码,也不能被调度,仅仅在进程列表中保留一个位置,记载该进程的退出状态等信息供其他进程收集,除此之外,僵尸进程不再占有任何内存空间。它需要它的父进程来为它收尸。
  避免僵尸进程的出现的一种办法是父进程调用wait、waitpid等待子进程结束(即对其进行回收),但这样做有一个弊端就是在子进程结束前父进程会一直阻塞,不能做任何事情。另外一种更好的方法就是调用两次fork函数。
参考链接:http://blog.chinaunix.net/uid-20672257-id-3264775.html

复制代码

3.什么是线程安全,如何实现线程安全;

复制代码

1.如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的
2.从保证原子性、可见性、顺序性方面来保证线程安全
   通常锁和同步方法(或者同步代码块)保证原子性
   volatile关键字来保证可见性
   通过volatile在一定程序上保证顺序性,另外还可以通过synchronized和锁来保证顺序性
参考链接:http://www.jasongj.com/java/thread_safe/

复制代码

4.创建线程池有哪几个核心参数? 如何合理配置线程池的大小?

复制代码

1.corePoolSize: 核心线程数,能够同时执行的任务数量,
  maximumPoolSize:除去缓冲队列中等待的任务,最大能容纳的任务 数(其实是包括了核心线程池数量),
  keepAliveTime:超出workQueue的等待任务的存活时间,就是指maximumPoolSize里面的等待任务的存活时间
  unit:时间单位,workQueue:阻塞等待线程的队列,一般使用new LinkedBlockingQueue()这个,如果不指定容量,会一直往里边添加,没有限制,
  workQueue永远不会满,一般选择没有容量上限的队列,
  threadFactory:创建线程的工厂,使用系统默认的类,
  handler:当任务数超过maximumPoolSize时,对任务的处理策略,默认策略是拒绝添加

2.任务的性质:CPU密集型任务,IO密集型任务和混合型任务。
  任务的优先级:高,中和低。
  任务的执行时间:长,中和短。
  任务的依赖性:是否依赖其他系统资源,如数据库连接。
参考链接:https://blog.csdn.net/yxpjx/article/details/51872916
        https://blog.csdn.net/guanyueliuxing/article/details/53585209

复制代码

5.volatile、ThreadLocal的使用场景和原理;

复制代码

ThreadLocal是各线程将值存入该线程的map中,以ThreadLocal自身作为key,需要用时获得的是该线程之前存入的值。如果存入的是共享变量,那取出的也是共享变量,并发问题还是存在的。
ThreadLocal的主要用途是为了保持线程自身对象和避免参数传递,主要适用场景是按线程多实例(每个线程对应一个实例)的对象的访问,并且这个对象很多地方都要用到。
  参考链接:https://blog.csdn.net/qq_36632687/article/details/79551828
volatile变量可用于提供线程安全,但是只能应用于非常有限的一组用例:多个变量之间或者某个变量的当前值与修改后值之间没有约束。因此,单独使用 volatile 还不足以实现计数器、互斥锁或任何具有与多个变量相关的不变式(Invariants)的类(例如 “start <=end”)。
volatile使用条件:①对变量的写操作不依赖于当前值。②该变量没有包含在具有其他变量的不变式中。

复制代码

6.ThreadLocal什么时候会出现OOM的情况

复制代码

ThreadLocal的实现是这样的:每个Thread 维护一个 ThreadLocalMap 映射表,这个映射表的 key 是 ThreadLocal实例本身,value 是真正需要存储的 Object。
ThreadLocal里面使用了一个存在弱引用的map, map的类型是ThreadLocal.ThreadLocalMap. Map中的key为一个threadlocal实例。这个Map的确使用了弱引用,不过弱引用只是针对key。每个key都弱引用指向threadlocal。 当把threadlocal实例置为null以后,没有任何强引用指向threadlocal实例,所以threadlocal将会被gc回收。但是,我们的value却不能回收,而这块value永远不会被访问到了,所以存在着内存泄露。因为存在一条从current thread连接过来的强引用。只有当前thread结束以后,current thread就不会存在栈中,强引用断开,Current Thread、Map value将全部被GC回收。最好的做法是在ThreadLocal的get(),set(),remove()的时候都会清除线程ThreadLocalMap里所有key为null的value。

复制代码

7.synchronized、volatile区别、synchronized锁粒度、模拟死锁场景、原子性与可见性;

复制代码

1.1)volatile本质是在告诉jvm当前变量在寄存器中的值是不确定的,需要从主存中读取,synchronized则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住.
2)volatile仅能使用在变量级别,synchronized则可以使用在变量,方法.
3)volatile仅能实现变量的修改可见性,而synchronized则可以保证变量的修改可见性和原子性.
4)volatile不会造成线程的阻塞,而synchronized可能会造成线程的阻塞.

2.如果是使用自定义的锁,或者this对象作为锁,那么锁定的是对象,如果对于静态的对象做锁定,那么锁定的就是类了。
https://blog.csdn.net/a158123/article/details/78616562

复制代码

三、JVM相关

8.JVM内存模型,GC机制和原理;

复制代码

1.按照官方的说法:Java 虚拟机具有一个堆,堆是运行时数据区域,所有类实例和数组的内存均从此处分配。
  JVM主要管理两种类型内存:堆和非堆,堆内存(Heap Memory)是在 Java 虚拟机启动时创建,非堆内存(Non-heap Memory)是在JVM堆之外的内存。
  简单来说,堆是Java代码可及的内存,留给开发人员使用的;非堆是JVM留给自己用的,包含方法区、JVM内部处理或优化所需的内存(如 JIT Compiler,Just-in-time Compiler,即时编译后的代码缓存)、每个类结构(如运行时常数池、字段和方法数据)以及方法和构造方法的代码。
  参考链接:https://blog.csdn.net/ithomer/article/details/6252552
2.JVM在做垃圾回收的时候,会检查堆中的所有对象是否会被这些根集对象引用,不能够被引用的对象就会被垃圾收集器回收。
  引用计数法:给一个对象添加引用计数器,每当有个地方引用它,计数器就加1;引用失效就减1,如果我有两个对象A和B,互相引用,除此之外,没有其他任何对象引用它们,实际上这两个对象已经无法访问,即是我们说的垃圾对象。但是互相引用,计数不为0,导致无法回收
  可达性分析算法:以根集对象为起始点进行搜索,如果有对象不可达的话,即是垃圾对象。这里的根集一般包括java栈中引用的对象、方法区常良池中引用的对象

复制代码

9.GC分哪两种,Minor GC 和Full GC有什么区别?什么时候会触发Full GC?分别采用什么算法?

复制代码

 Minor GC是新生代GC,指的是发生在新生代的垃圾收集动作。由于java对象大都是朝生夕死的,所以Minor GC非常平凡,一般回收速度也比较i快。
 Major GC/Full GC 是老年代GC,指的是发生在老年代的GC,出现Major GC一般经常会伴有Minor GC,Major GC的速度比Minor GC慢的多。
(1)Minor GC发生:当jvm无法为新的对象分配空间的时候就会发生Minor gc,所以分配对象的频率越高,也就越容易发生Minor gc。
(2)Full GC:发生GC有两种情况,①当老年代无法分配内存的时候,会导致MinorGC,②当发生Minor GC的时候可能触发Full GC,由于老年代要对年轻代进行担保,由于进行一次垃圾回收之前是无法确定有多少对象存活,因此老年代并不能清除自己要担保多少空间,因此采取采用动态估算的方法:也就是上一次回收发送时晋升到老年代的对象容量的平均值作为经验值,这样就会有一个问题,当发生一次Minor GC以后,存活的对象剧增(假设小对象),此时老年代并没有满,但是此时平均值增加了,会造成发生Full GC
 年轻代采用了复制法;而老年代采用了标记-整理法

复制代码

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

复制代码

①Bootstrap ClassLoader 这个是JVM加载自身工作需要的类,完全由JVM自己来控制,外部无法访问到这个;
②ExtClassLoader比较特殊的,服务的特定目标在System.getProperty("java.ext.dirs");
③AppClassLoader,父类是ExtClassLoader,"java.class.path"中的类都可以被这个类加载器加载;
④URLClassLoader,一般这个类帮我们实现了大部分的工作,自定义可以继承这个类,这样仅仅在需要的地方做修改就行了;

类的唯一性是由className + classLoader共同决定的,同一个类(名字一样),如果由两个不同的classLoader实例加载,则是两个完全不同的类型,他们的对象是不能相互赋值的。
使用URLClassLoader 来加载一个类,当这个类发生变化后,使用另一个URLClassLoader 重新加载一次,新的类就会被加载到JVM。这是由类的唯一性来决定的。需要注意的是,如果重新加载了,需要把原来的线程停掉,因为他们用的不是最新的类。
参考链接:https://www.cnblogs.com/softidea/p/4336020.html

复制代码

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

复制代码

1.双亲委派模型要求除顶层启动类加载器外其余类加载器都应该有自己的父类加载器;类加载器之间通过复用关系来复用父加载器的代码。

2.①1.当Application ClassLoader 收到一个类加载请求时,他首先不会自己去尝试加载这个类,而是将这个请求委派给父类加载器Extension ClassLoader去完成。  
  ②2.当Extension ClassLoader收到一个类加载请求时,他首先也不会自己去尝试加载这个类,而是将请求委派给父类加载器Bootstrap ClassLoader去完成。
  ③.如果Bootstrap ClassLoader加载失败(在<JAVA_HOME>\lib中未找到所需类),就会让Extension ClassLoader尝试加载。  
  ④如果Extension ClassLoader也加载失败,就会使用Application ClassLoader加载。
  ⑤如果Application ClassLoader也加载失败,就会使用自定义加载器去尝试加载。  
  ⑥如果均加载失败,就会抛出ClassNotFoundException异常。 

优点:Java类伴随其类加载器具备了带有优先级的层次关系,确保了在各种加载环境的加载顺序。  
保证了运行的安全性,防止不可信类扮演可信任的类。

复制代码

12.什么情况下我们需要破坏双亲委派模型;

复制代码

①用户去继承java.lang.ClassLoader的唯一目的就是为了重写loadClass()方法
②如果在应用程序的全局范围内都没有设置过,那么这个类加载器默认就是应用程序类加载器。了有线程上下文类加载器,JNDI服务使用这个线程上下文类加载器去加载所需要的SPI代码,也就是父类加载器请求子类加载器去完成类加载动作,这种行为实际上就是打通了双亲委派模型的层次结构来逆向使用类加载器,已经违背了双亲委派模型,但这也是无可奈何的事情。
③用户对程序的动态性的追求导致的,例如OSGi的出现。在OSGi环境下,类加载器不再是双亲委派模型中的树状结构,而是进一步发展为网状结构。
参考链接:https://blog.csdn.net/zhangcanyan/article/details/78993959

复制代码

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

复制代码

①-Xmx3g -Xms3g -Xmn1g -Xss256k 
-Xmx3g -Xms3g,将JVM最大内存与初始内存设置相等,避免JVM垃圾回收后重新分配内存;-Xmn1g,年轻代Sun官方推荐配置为整个堆的3/8,通常设置为1/3或1/4;-Xss256k,减小每条线程的堆栈大小,能生成更多的线程。
②-XX:NewRatio=4 
调整年轻代与年老代比例(年轻代:年老代=1:4)
-XX:MaxTenuringThreshold=0 
设置晋升到老年代的对象年龄。如果设置为0的话,则年轻代对象不经过Survivor区,直接进入年老代。如果将此值设置为一个较大值,则年轻代对象会在Survivor区进行多次复制。
③UseConcMarkSweepGC 
JVM在server模式下默认使用PararrelScavenge+SerialOld的收集器组合进行内存回收,不支持与用户线程并发执行。可使用ParNew+CMS+SerialOld的收集器组合进行内存回收(SerialOld收集器做为CMS收集器出现ConcurrentModeFailure失败后的后备收集器使用),减少stop-the-world时间。
④-XX:CMSFullGCsBeforeCompaction 
使用CMS时,设置CMS收集器在进行若干次垃圾收集后再启动一次内存碎片整理。
参考链接:https://blog.csdn.net/opensure/article/details/46715769
        https://blog.csdn.net/zhaocong89/article/details/51305681

复制代码

14.JVM虚拟机内存划分、类加载器、垃圾收集算法、垃圾收集器、class文件结构是如何解析的;

参考链接:https://blog.csdn.net/a78270528/article/details/81867862

     https://blog.csdn.net/wyz_2017/article/details/78753168

     https://blog.csdn.net/sunny243788557/article/details/52796904

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值