面试题

一 JVM 内存区域
面试题_第1张图片

1) 方法区:存放要加载类的信息(类名,修饰符),静态变量,构造函数,final定义的常数。
运行时常量池在这里,用于生成和储存常量的引用。
在host中对应持久代。执行GC的情况少
2)堆
GC最频繁的,线程共享,在虚拟机启动时创建。堆里存放实例,数组,new对象
3)虚拟机栈
每个线程对应一个虚拟机栈,它是线程私有,生命周期和线程一样,每个方法被执行时产生一个栈帧,栈帧用于存储局部变量表、动态链接、操作数和方法出口等信息,当方法被调用时,栈帧入栈,当方法调用结束时,栈帧出栈。

4)本地方法栈
本地方法栈用于支持native方法的执行,存储了每个native方法的执行状态。
虚拟机执行Java方法。
5)程序计数器
划分在CPU上
作用是:JVM在解释字节码(.class)文件时,存储当前线程执行的字节码行号
每个程序计数器只能记录一个线程的行号,因此它是线程私有的。
如果程序当前正在执行的是一个java方法,则程序计数器记录的是正在执行的虚拟机字节码指令地址,如果执行的是native方法,则计数器的值为空,此内存区是唯一不会抛出OutOfMemoryError的区域。

二 GC机制
本地方法栈,程序计数器,虚拟机栈 不需要进行垃圾回收,因为生命周期是跟线程同步的随着线程的销毁,内存会自动释放。
方法区和堆区需要垃圾回收。

查找内存的方法:

 引用计数法:引用一次就加1,有个问题两个对象互相引用,但是都已经没用了
 可达性分析:从一个GC roots的根节点出发,向下搜索,找到的标记,没找到的就是垃圾

 强引用:new出来的都是强引用
 软引用:只有JVM内存不足就被回收
 弱引用:只要GC,就立马回收
 虚引用:作用就是做一些跟踪记录

什么样的类需要被回收:
1 该类的所有实例都已经被回收
2 加载该类的ClassLoad已经被回收
3 该类对应的反射Java.lang.Class对象没有被任何地方引用

垃圾清除的地方主要针对堆
面试题_第2张图片
总共10, 伊甸园区 8 ,s0 1,s1 1 又叫幸存区
总共3G ,老年代2G ,年轻代 1G
年轻代正常情况下创建对象,默认在伊甸园区里,伊甸园满了就要回收,用可达性算法去回收。
如果有引用是不能被回收的,那就利用复制算法,挪到S0里面去,同时标记一下,达到空间
当S0也收满了,S1也会进行回收
S1对伊甸园进行判断,并进行年纪判断,活下来的记录为1。
S2回收S0的,用可达性分析,这个年纪就为2.
如果经历了15次还没有死掉,就放入老年代

老年代如果满了,产生一种负GC,还是用可达性分析进行
年轻代GC的时候时间会短
老年代时间负GC时间长,需要尽量减少负GC,要回收整个区域。
大部分时候使用标记整理法。
面试题_第3张图片
它有着优于其他收集器的地方:简单而高效,对于限定单个CPU的环境来说,Serial收集器由于没有线程交互的开销,专心做垃圾收集自然可以获得最高的单线程收集效率。

面试题_第4张图片
Parallel Scavenge的特别之处就在于它关注的是吞吐量,也就是运行代码时间与(运行代码时间+垃圾收集器时间)的比值,比如运行代码时间为99分钟,垃圾收集器运行时间为1分钟,那么吞吐量就是99%,追求高吞吐量可以最大程度的利用CPU资源完成运算的任务,这就比较适合关注后台运算,而与用户交互较少的场景。

前面两个都会产生stw,停顿
面试题_第5张图片
Cms四个步骤:初始标记:与对象由关系的,会产生stw,
并发标记不会产生暂停,会有错乱现象,会有漏标现象,在重新标记的阶段补上,在并发清理开始清除

面试题_第6张图片

双亲委派机制

类加载器的类别

BootstrapClassLoader(启动类加载器)

c++编写,加载java核心库 java.*,构造ExtClassLoaderAppClassLoader。由于引导类加载器涉及到虚拟机本地实现细节,开发者无法直接获取到启动类加载器的引用,所以不允许直接通过引用进行操作

ExtClassLoader (标准扩展类加载器)

java编写,加载扩展库,如classpath中的jrejavax.*或者
java.ext.dir 指定位置中的类,开发者可以直接使用标准扩展类加载器。

AppClassLoader(系统类加载器)

java编写,加载程序所在的目录,如user.dir所在的位置的class

CustomClassLoader(用户自定义类加载器)

java编写,用户自定义的类加载器,可加载指定路径的class文件

Mtbatis 缓存 默认一级sqlsessison级别
一级:同一个sqlSession对于一个SQL语句,执行后就存在缓存中,下次就从缓存里取 ,test 操作的时候,第一次下面是会有SQL语句的,第二次是没有的。
失效情况: 1 同一个Sqlseeion语句要求是,所以不同sqlSession肯定失效。
2两次查询期间执行了任何一次增删改操作
3清除了缓存也就是使用了 sqlSession.clearCache方法
二级 需要手动开启 映射文件级别
1 在xml中 value改成true,setting name cacheEnabled
2 在需要使用的映射文件加上cache配置
3 pojo类需要实现序列化接口
二级缓存的回收策略:
1 LRU 最近最小使用

FIFO:先进先出
SOFT:移除软引用的对象
WEAK:移除弱引用的对象

测试二级缓存,把一级缓存关闭了

整合第三方缓存:实现Cache接口

             EhCache缓存框架
             导入ehcache包,以及整合包,日志包
             
             

mybatis 一对多,添加一个部门
可以根据name,name一样的话,使用自增的主键
面试题_第7张图片
当传两个或者多个参数值的时候,mybatis会默认将这些参数放在map集合中
两种方式:
1) 键为:0,1,2,3,4,…. 以参数为值
2) 键为param1,param2,param3,param4 以参数为值
1)Dept设置的是别名,把did和dname赋值给dept这个对象
关联对应的话,就是用id coium 主键映射 对应的关系
多个参数就是map集合

面试题_第8张图片

多线程
首先线程储存在栈里
关于进程: 特点:1 独立性:有私有的地址空间。被访问需要进程自身的允许
2 动态性:有生命周期,和不同的状态,一个系统中活动的指令
3 并发性:多个进程在单个处理器上并发进行,多个进程之间不会互相影响
线程的进行是随机
面试题_第9张图片
线程的状态

1 新建状态 线程对象创建完成
2 就绪状态,线程调用对象执行了start()方法
3 执行状态,线程被CUP调用的时候
4 阻塞状态,运行的线程因为某些原因放弃了对cpu的使用权
分为三类:1 等待阻塞: 线程执行的过程中使用了wait方法,是本线程进入阻塞状态
2 同步阻塞: 线程执行的时候获取同步锁失败了,锁被其他线程占用了。
3 其他阻塞,比如线程执行了sleep()方法引起的阻塞
5 死亡状态,执行结束或者因为异常退出的run()方法,结束了线程生命周期
wait方法和sleep()方法的差别
使用wait方法会释放锁,而sleep方法不会去释放锁
wait用于线程交互,sleep用于线程暂停
14、什么是 Callable 和 Future?

Callable 接口类似于 Runnable,但Runnable 不会返回结果,并且无法抛出返回结果的异常,而 Callable被线程执行后,可以返回值,这个返回值可以被 Future 拿到,也就是说,Future 可以拿到异步执行任务的返回值。
可以认为是带有回调的 Runnable。
Future 接口表示异步任务,是还没有完成的任务给出的未来结果。所以说 Callable用于产生结果,Future 用于获取结果。

线程互斥
有若干个线程都要使用某一共享资源时,任何时刻最多只允许一个线程去使用,其它要使用该资源的线程必须等待,直到占用资源者释放该资源。线程互斥可以看成是一种特殊的线程同步。

为什么调用start方法是会执行run()方法,而不能直接执行run()方法?
因为start方法执行,代表创建新的线程,线程启动了,并且会执行run方法里的代码。
执行run方法并不会去创建线程,也不会去调用线程,只是把run方法当做普通方法进行。

不可变对象:String,基本类的包装类 天生线程安全

线程中的调度算法:抢占式调度,优先级高的先占用,
一个是分时调度

为什么使用Executor康佳比使用应用线程要好?
1)复用已经存在的线程减少对线程对象的创建和销毁
2)有效控制最大并发线程,提高资源利用率
3)框架中有定时,定期,扩展的功能

如何停止一个正在运行的线程?
使用Thread里的interrupt方法,是一个运行中的线程不会去中断它,而是是一个被阻塞的线程抛出异常,提前结束阻塞状态,退出。

notify()和notifyall方法,用来唤醒使用了wait()方法的线程,前面只能唤醒一个。

所谓后台(daemon)线程,是指在程序运行的时候在后台提供一种通用服务的线程
JVM 的垃圾回收线程就是 Daemon 线程,Finalizer 也是守护线程。

可重复锁:线程可以进入人一个它已经拥有的锁同步的代码块。
synchronized、ReentrantLock 都是可重入的锁,可重入锁相对来说简化了并发编程的开发。

乐观锁和悲观锁
悲观锁:每次获取数据都以为,别人会来取,所以就给数据上锁,别人想拿这个数据就会堵塞,直到它拿到锁。数据库里表锁,行锁就是这种凡是,操作前先上锁。java 里的Synchronized
乐观锁:每次拿数据的时候,都觉得别人不会修改,只有更新的时候会去判断一下这个期间有没有更新过数据。
实现方式:1版本标识来确定一下读到的数据是否与提交的数据一致。
2当多个线程尝试使用 CAS 同时更新同一个变量时,只有其中一个线程能更新变量的值,而其它线程都失败,失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次尝试。 CAS 操作中包含三个操作数 —— 需要读写的内存位置(V)、进行比较的预期原值(A)和拟写入的新值(B)。如果内存位置 V 的值与预期原值 A 相匹配,那么处理器会自动将该位置值更新为新值 B。否则处理器不做任何操作。
CAS 缺点:
1、ABA 问题:
比如说一个线程 one 从内存位置 V 中取出 A,这时候另一个线程 two 也从内存中取出 A,并且 two 进行了一些操作变成了 B,然后 two 又将 V 位置的数据变成 A,这时候线程 one 进行 CAS 操作发现内存中仍然是 A,然后 one 操作成功。尽管线程 one 的 CAS 操作成功,但可能存在潜藏的问题。从 Java1.5 开始 JDK 的 atomic包里提供了一个类 AtomicStampedReference 来解决 ABA 问题。
2、循环时间长开销大:
对于资源竞争严重(线程冲突严重)的情况,CAS 自旋的概率会比较大,从而浪费更多的 CPU 资源,效率低于 synchronized。
3、只能保证一个共享变量的原子操作:
当对一个共享变量执行操作时,我们可以使用循环 CAS 的方式来保证原子操作,但是对多个共享变量操作时,循环 CAS 就无法保证操作的原子性,这个时候就可以用锁。

SynchronizedMap和ConcurrentHashMap的区别?
Synchronized是锁住一个表,只有一个锁
ConcurrentHashMap是简化hash表分为16个桶,一个桶一个锁,一次锁住一个桶。

volatile的使用场景?
volatile 保证内存可见性和禁止指令重排
volatile 用于多线程下的单次操作(单次读或者写)

wait,notify,notifyAll不在thread类里?
因为Java提供的锁不是线程级,是对象级的。

ThreadLocal 是 Java 里一种特殊的变量。每个线程都有一个 ThreadLocal 就是每个线程都拥有了自己独立的一个变量,竞争条件被彻底消除了。它是为创建代价高昂的对象获取线程安全的好方法,比如你可以用 ThreadLocal 让SimpleDateFormat 变成线程安全的,因为那个类创建代价高昂且每次调用都需要创建不同的实例所以不值得在局部范围使用它,如果为每个线程提供一个自己独有的变量拷贝,将大大提高效率。首先,通过复用减少了代价高昂的对象的创建个数。其次,你在没有使用高代价的同步或者不变性的情况下获得了线程安全。

interrupt 就中断线程,处于一个中断状态
interrupted 查询线程的中断状态,并把这个状态清除了

怎么检查线程是否拥有锁? holdslock()的方法

Tread中yield方法:使线程从运行状态变为就绪状态

线程池中submit()和execute方法区别?
execute方法返回的是void,submit返回的是future对象

可以直接调用run方法但是就是和普通方法一样,要执行线程中的方法必须要start方法

如何确保main()方法所在的线程是Java程序最后结束的线程?
使用Thread类里的join()方法确保所有程序创建的线程在main()方法退出前结束。
join()方法就是让CPU先执行这个方法

线程之间的通信: wait,notify,notifyAll。

保证线程安全办法:使用并发锁,使用volatile关键字,使用线程安全类

同步块比同步方法好,限定的范围小,不会锁住整个对象

创建守护线程:Thread类的setDaemon(true),用之前要start方法

java.util.Timer 是一个工具类,可以用于安排一个线程在未来的某个特定时间执行。Timer 类可以用安排一次性任务或者周期任务。
java.util.TimerTask 是一个实现了 Runnable 接口的抽象类,我们需要去继承这个类来创建我们自己的定时任务并使用 Timer 去安排它的执行。

面试题_第10张图片

什么是反射:可以在运行时获取一个类的所有信息,可以获取到任何定义的信息(包括成员变量,成员方法,构造器等),并且可操纵类的字段,方法,构造器等部分。
获取完整类名:

你可能感兴趣的