Java面试话术

简述JVM内存模型

可以简单将JVM内存管理模型按照是否线程私有做分类,分成不是线程私有而是每个线程都可以访问的堆内存和方法区,以及每个线程私有的栈、程序计数器和本地方法栈。

线程共用

堆内存中主要存放了对象的存储数据,每一个线程中如果要拿到对象实际的值最终都要到堆中获取。
方法区中存放了常量、静态变量、被JVM加载的代码缓存信息。
可以说是如果每一个线程都可以访问、读需要访问的数据就放在了这两块地方。

线程私有

栈描述的是Java方法线程模型,每一个方法的调用就代表了一个栈帧。栈中主要维护了局部变量表,Java基础类型和指向堆内存中的句柄信息都在这张表里。
程序计数器是为了多线程运行环境中记录一个线程被切换时的运行场景。
本地方法栈是Java程序调用Natvie方法而设置的,他的功能与栈差不多。

JVM负责大多数内存的管理,但不是所有内存都归JVM关系,比如说NIO中的缓冲区他的内存在虚拟机之外,他通过堆中的DirectByteBuffer对象作为内存引用来使用,避免了他在Java堆中与Native堆中来回复制,从而达到了提高性能的目的。

HashMap的存储结构、基本实现(get, put)、为什么线程不安全

存储结构

HashMap是一个存储了多组键值对的容器,他的底层数据存储结构是一个数组,这个数组代表hash过后每一个值所对应的桶。有Hash就有碰撞,而HashMap解救碰撞的方法就是在将相同hash值的对象存储在同一个位置上,一开始是以链表的方式存储,而当存储的数量渐渐增大到8时,就会将链接转换为红黑树,以此来增加效率。

Get方法

如何在HashMap中获取存储的已经存在的键值对?参数就是键值对的键,通过对获取键的hasCode()方法的返回值并做加工,并对得到的返回值按照当前数组的链表长度取模,便得到了这个哈希值所对应的位置。接着,根据当前位置的数据结构做便利, 便能够得到他的value值。

Put方法

Put方法其实也是同理,通过键值对的键做Hash操作之后,找到对应在数组中的位置,进行插入操作。和get操作相比,Put方法的后续操作上多出了几步,例如:HashMap中的键值对数量超过设置的阈值,就会开始对数组进行扩容、当数组中实际存储数组的链表长度超过8、或者红黑树中的节点数量小于6,两者之间就会互相转化。

为什么线程不安全

HashMap是线程不安全的一个数据结构,意味着他不能在并发操作下正常功能。有很多地方能够显示出这样的原因,最明显的一点就是点击进入HashMap源码时,就会发现在开头的注释中告知他是线程不安全的。他的线程不安全在代码中也在多处表现了,例如说在做插入时,并没有加锁,就会导致刚刚插入的值,马上利用键来获取,却发现取不到;明明插入了多个值,HashMap中计数的变量却只增加了1。这些情况都会导致在并发环境下,对HashMap的使用会出错。如果要使用键值对的话,更加安全的操作是去使用对应的JUC包下的ConCurrentHashMap。

线程池构造参数、工作原理(生命周期)

线程池是一种使用了池化思想来管理线程的数据结构,他的核心目的便是方便统一控制管理分散在各个地方的线程,减少反复创建线程带来的性能损耗。

构造参数

线程池的构造参数主要有:
核心线程数,
最大线程数,
阻塞队列,
最大存活时间,
拒绝策略

生命周期

当不停地往线程池中塞任务,线程池会发生以下变化,
当线程池中的线程数小于核心线程数时,线程会不断新起线程来执行任务。
当核心线程数塞满,线程池便不会再新创建线程,而是会往阻塞队列中塞任务以供线程池来消耗,不同的阻塞队列有不同的策略。
当阻塞队列也被塞满时,就会再试着创建线程来消耗任务,如果总线程数达到了最大线程数,就会根据当初设置的拒绝策略来执行。
当阻塞队列中积攒的任务被消费完,线程就进入了空闲等待时间,当这个空闲等待时间超过设置的时间,线程就会逐渐被关闭掉,直至核心线程数。

线程池实际的使用场景以及各参数的设置

单业务场景的话,按照性质分:
计算密集性业务场景 核心线程数设置为CPU核数 + 1,来最大化利用CPU。
IO密集性业务场景,因为线程大量的时间消耗在等待上,可以将核心线程数设置为 CPU核数 * 2。
如果一个服务不止干几件事情就要考虑,每件事情上的权重,如果权重相等的话,线程数越多的事情获得CPU分片的概率越大,此时就可以设置阻塞队列中的优先队列来实现权重。

聊聊ThreadLocal

当使用ThreadLocal 维护变量时,每一个访问这个变量的线程都会获得一个对应的副本。每一个线程都可以访问、修改这个副本,而不会影响其他线程中的副本。

内存泄露的隐患

当使用完ThreadLocal后,一定要使用remove()方法进行移除,不然可能会导致内存泄露。导致内存泄漏的原因是因为在使用ThreadLocalMap保存ThreadLocal和对应对象的key-value对时,作为ThreadLocal的key是一个弱引用。当ThreadLocal失去对应引用后,ThreadLocalMap中对应的键值对中的key 就变成了 null,此时GC 没有办法对其进行清楚。

强引用、弱引用、软引用、虚引用

说说双亲委派机制

双亲委派机制是指在Java在加载时会依照一定的优先级来保障类加载器的调用。当一个类加载器在加载类文件的时候,不会先自己加载,而是调用自己的父加载器进行加载。调用优先级从大到小依次为:
启动类加载器,这个加载器主要负责加载<JAVA_HOME>/lib下的文件,由C++编写而成
扩展类加载器,负责加载<JAVA_HOME>/lib/ext目录中的文件,由Java编写而成
应用类加载器,负责加载平时我们自定义的Java类

为什么需要使用双亲委派机制

保障类的唯一性。你可以自定义一个Object类,但它无法被加载到虚拟机中。

Synchronized 和 Lock 的区别、Synchronized 和 Volatile 的区别

Synchronized 和 Lock 的区别

Synchronized 是关键字,Lock 是接口。
Synchronized 可重入、不可中断、非公平
Lock 可重入、可中断、可公平
Synchronized 底层使用指令码的方式控制索
Lock 底层使用 CAS 乐观锁,依赖 AQS 类实现,把所有请求构成一个 FIFO 的队列,对这个队列的操作都通过CAS操作。

Synchronized 锁升级

无锁 -> 偏向锁 -> 轻量级锁 -> 重量级锁

AQS 底层原理

知乎

JDK动态代理 和 cglib动态代理

JDK动态代理

  • 利用反射机制生成一个实现代理接口的匿名类
  • 只提供接口的代理
  • 被代理的对象必须实现 InvocationHandler
  • 代理类中会通过反射的方式调用目标类的原方法

cglib动态代理

  • 利用 asm 对目标类生成子类,做字节码增强
  • 覆盖目标类原方法,调用时先通过代理类增强,再直接调用父类对应方法
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值