java基础
- Arrays.sort实现原理和Collections.sort实现原理
解答:在小规模(size<7)数组中,直接插入排序的效率要比快速排序高。
- 线程池的种类,区别和使用场景
- 使用线程池的好处和线程的调度过程
优点:
- 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗
- 提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行
- 提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
调度过程:
如下图所示,当提交一个新任务到线程池时,线程池的处理流程如下:
- 首先线程池判断基本线程池是否已满?没满,创建一个工作线程来执行任务。满了,则进入下个流程。
- 其次线程池判断工作队列是否已满?没满,则将新提交的任务存储在工作队列里。满了,则进入下个流程。
- 最后线程池判断整个线程池是否已满?没满,则创建一个新的工作线程来执行任务,满了,则交给饱和策略来处理这个任务。
- 线程池如何调优 ?
- 线程池的最大线程数目根据什么确定 ?
- 动态代理的几种方式
- jdk动态代理:jdk动态代理是由Java内部的反射机制来实现的,目标类基于统一的接口(InvocationHandler)
- Cglib动态代理:JDK的动态代理机制只能代理实现了接口的类,而不能实现接口的类就不能实现JDK的动态代理,cglib是针对类来实现代理的,其原理是对指定的目标类生成一个子类,并覆盖其中方法实现增强,但因为采用的是继承,所以不能对final修饰的类进行代理。
- HashMap的并发问题
Hashmap在并发环境下,可能出现的问题:
- 多线程put时可能会导致get无限循环,具体表现为CPU使用率100%;
- 原因:在向HashMap put元素时,会检查HashMap的容量是否足够,如果不足,则会新建一个比原来容量大两倍的Hash表,然后把数组从老的Hash表中迁移到新的Hash表中,迁移的过程就是一个rehash()的过程,多个线程同时操作就有可能会形成循环链表,所以在使用get()时,就会出现Infinite Loop的情况
- 多线程put时可能导致元素丢失
- 原因:当多个线程同时执行addEntry(hash,key ,value,i)时,如果产生哈希碰撞,导致两个线程得到同样的bucketIndex去存储,就可能会发生元素覆盖丢失的情况
- 了解LinkedHashMap的应用吗
简答:因为LinkedHashMap是基于hash表和双向链表来实现的,并且可以控制访问顺序,所以可以通过LinkedHashMap构造一个LRU(最近最少使用)的缓存;
- 反射的原理,反射创建类实例的三种方式是什么?
反射机制/原理:指程序在运行的时候能够获取自身的信息。如果知道一个类的名称或者一个实例对象,就能把这个类的所有方法和变量信息(方法名、变量名、方法、修饰符、类型、方法参数等信息都找出来)。
三种方式创建类实例:
- 通过Object类中的getClass()方法获取;
- 通过静态属性.class来获取其对应的Class对象。
- 通过给定的类的字符串名称,使用Class类中的forNmame()方法来获取。
- cloneable接口实现原理,浅拷贝or深拷贝
实现原理:在堆中克隆出和原对象一样的对象,并将这个新对象的地址赋予新的引用;
浅拷贝:只克隆对象自身以及它所包含的所有对象的引用地址
深拷贝:克隆对象自身以及他说包含的所有对象实例注:所有的基本数据类型,无论是浅克隆还是深克隆,都会进行原值克隆,它们不像对象,不是存储在堆中的。
- Java NIO使用
- 事件驱动模型
- 避免多线程
- 单线程处理多任务
- 非阻塞I/O,I/O读写不再阻塞,而是返回0
- 基于block的传输,通常比基于流的传输更高效
- 更高级的IO函数,zero-copy
- IO多路复用大大提高了Java网络应用的可伸缩性和实用性
简述NIO的最佳实践,比如netty,mina
hashtable和hashmap的区别
简答:
- HashTable是基于directory实现,是线程安全的,HashMap基于Map实现,非线程安全;它们存储的都是键值对,HashMap可以接收null的键(key)值(value),而HashTable则不行;
- HashMap的工作原理
简答:使用hash方法,通过put和get方法插入和获取对象。在put时,通过调用hashCode()计算key的hash码而获得bucket位置,进一步存储,HashMap会根据当前bucket的占用情况自动调整容量(如果超过容量因子0.75,那么通过rehash将容量扩为原来的两倍)。如果在插入时,发生哈希碰撞,那么使用链表将产生碰撞的元素组织起来;在get时,首先调用hashcode计算key的hash码而获取bucket位置,然后在循环遍历bucket位置的链表,并进一步调用equals()方法确定键值对。
- arraylist和linkedlist区别及应用场景
区别:这两个类的区别主要是数据结构的不同。arrayList是基于数组实现的链表,所以使用索引在数组中搜索和读取数据是很快的,并且获取数据的时间复杂度为O(1),但是删除数据的开销比较大,因为这需要重新排列数组中的所有的数据。LinkedList是基于双向链表的,所以在插入数据时速度更快。
应用场景:需要支持随机访问的,使用ArrayList;
插入和删除元素更频繁,使用LinkedList,因为插入和删除不涉及重排数据。
- String,Stringbuffer,StringBuilder的区别?
1.可变与不可变
String类中使用字符数组存储字符串,但是有final修饰,所以可知String是不可变的,如下图:
StringBuffer 和StringBuilder 都继承自AbstractStringBuilder,而在AbstractStringBuilder中,字符串是也是使用字符数组保存,但是没有final修饰符,所以StringBuffer 和StringBuilde这两种对象都是可变的,如下图:
2.线程是否安全
- String中对象是被final修饰,所以对象是不可变的,也就可以理解为常量,所以是线程安全的;
StringBuffer 和StringBuilder 都继承自AbstractStringBuilder,StringBuffer对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的;StringBuild没有对方法加同步锁,所以是非线程安全的
- 有没有可能2个不相等的对象有相同的hashcode
hashCode 契约:
- 在一个运行的进程中,相等的对象必须要有相同的哈希码
- 请注意这并不意味着以下常见的误解:
- 不相等的对象一定有着不同的哈希码——错!
有同一个哈希值的对象一定相等——错!
- TreeMap的实现原理
- HashMap不保证数据有序,LinkedHashMap保证数据插入顺序,TreeMap可以保证按照key的大小顺序进行存储。
- TreeMap采用红黑树的结构进行存储,红黑树可以使树具有很好地平衡性,并且操作速度可以的达到log(n);
- TreeMap在put键值对时,首先检查是否有其中已经存在,如果存在就把old value替换掉,如果不存在,则新添加一个节点,并且对红黑树做平衡操作;
- TreeMap在getEntry时,使用key按照二叉树的方式进行搜索,复杂度为log(n);
- TreeMap输出时,采用LDR(中序遍历)的方式进行输出
a. 空节点,没有后继
b. 有右子树的节点,后继就是右子树的“最左节点”
c. 无右子树的节点,后继就是该节点所在左子树的第一个祖先节点
JVM相关
- 类的实例化顺序,比如父类静态数据,构造函数,字段,子类静态数据,构造函数,字段,他们的执行顺序
答:先静态、先父后子。
先静态:父静态 > 子静态
优先级:父类 > 子类 静态代码块 > 非静态代码块 > 构造函数
一个类的实例化过程:
1.父类中的static代码块,当前类的static
2.顺序执行父类的普通代码块
3.父类的构造函数
4.子类普通代码块
5.子类(当前类)的构造函数,按顺序执行。
6.子类方法的执行,
- JVM内存分代
分为新生代和老年代,新生代又分为Eden区、From区和To区,三者之间的比例为8:1:1
- JVM垃圾回收机制,何时触发MinorGC等操作
JVM采用的是分代垃圾回收机制:对于不同的对象生命周期不同,把不同生命周期的对象放在不同的代上,不同的代可以采用最合适的垃圾回收方式进行回收;
垃圾回收机制主要是发生在JVM堆中,JVM堆中是存放实例对象的地方,所以当在堆中分配空间给对象时,如果没有足够的存储空间,则会触发MinorGC操作,对未有引用的对象进行回收,或将大的对象直接移入到老年代中。
- jvm中一次完整的GC流程(从ygc到fgc)是怎样的,对象如何晋升到老年代
对象优先分配在新生代中,如果新生代没有足够的空间,则触发minor GC;大对象(指的是需要大量的连续内存空间)直接进入老年代;长期存活对象(指对象在新生代出生并经过第一次major GC后仍然存活,年龄加1,若年龄超过限制(15),则称为长期存活对象)晋升到老年代;
你知道哪几种垃圾收集器,各自的优缺点,重点讲下cms,g1
新生代和老生代的内存回收策略
新生代采用复制回收算法,老年代采用的是清除回收/
深入分析了Classloader,双亲委派机制
对Java内存模型的理解,以及其在并发中的应用
Java内存模型主要是定义程序中各个变量的访问规则。
在并发应用中,Java线程之间的通信主要是由Java内存模型(JMM)控制,如下图
。所有的变量都存放在主内存中,每个线程都有自己的工作内存,并且每个线程的工作内存中南都保存着主内存中变量的副本,线程对变量的操作,只能在自己的工作内存中完成,而不能直接操作主内存中的变量。不同的线程之间,不能直接访问对方共组内存中的变量,线程间变量的传递都需要通过主内存来完成。
- 指令重排序,内存栅栏等
指令重排序:编译器或者运行环境需要优化程序性能而采取对指令进行重新排序执行的手段。
内存栅栏(内存屏障):是让一个CPU处理单元中的内存状态对其它处理单元可见的一项技术。
内存栅栏提供了两个功能
- 确保从另一个CPU来看栅栏的两边的所有指令都是正确的程序顺序,而保持程序顺序的外部可见性;
- 实现内存数据可见性,确保内存数据会同步到CPU缓存子系统。
- **内存溢出(OOM)错误,栈溢出(stackoverflow)错误,永久代溢出(permgen space)错误
要搞清楚这几个溢出错误,就需要了解JVM在运行时的内存分布,并且需要清楚每个区域内存放的什么东西以及哪些区域会发生溢出。在程序运行时,JVM主要分为5个部分:程序计数器、虚拟机栈、本地方法栈、方法区、Java堆;在这5个区域中,只有程序计数器(主要存放即将执行的字节码的地址)不会发生溢出的情况,其余4个都会发生溢出:
JVM常用参数
tomcat结构,类加载器流程
volatile的语义,它修饰的变量一定线程安全吗
(1)volatile 变量具有 synchronized 的可见性特性,及如果一个字段被声明为volatile,java线程内存模型确保所有的线程看到这个变量的值是一致的
(2)禁止进行指令重排序
(3)不保证原子性注:
① 重排序:重排序通常是编译器或运行时环境为了优化程序性能而采取的对指令进行重新排序执行的一种手段
② 原子性:不可中断的一个或一系列操作
③ 可见性:锁提供了两种主要特性:互斥和可见性,互斥即一次只允许一个线程持有某个特定的锁,因此可使用该特性实现对共享数据的协调访问协议,这样,一次就只有一个线程能够使用该共享数据。可见性要更加复杂一些,它必须确保释放锁之前对共享数据做出的更改对于随后获得该锁的另一个线程是可见的。
- volatile的实现原理
如果对声明了volatile的变量进行写操作,JVM就会向处理器发送一条Lock前缀的指令,该Lock指令会使这个变量所在缓存行的数据回写到系统内存,根据缓存一致性协议,每个处理器都会通过嗅探在总线上传输的数据来检查自己缓存的值是否已过期,当处理器发现自己的缓存行对应的地址被修改,就会将当前处理器的缓存行设置成无效状态,在下次访问相同内存地址时,强制执行缓存行填充。
- volatile的使用场景
volatile 主要用来解决多线程环境中内存不可见问题。对于一写多读,是可以解决变量同步问题,但是如果多写,就无法解决线程安全问题;
- 在日常工作当中volatile大多被在状态标志的场景当中,如:要通过一个线程来终止另外一个线程的场景
- G1和CMS区别,吞吐量优先和响应优先的垃圾收集器选择
G1收集器:是吞吐量优先、面向服务器端应用的垃圾回收器,过程分为:初始标记、并发标记、最终标记、筛选回收。整体上是基于“标记-整理”算法进行回收,局部上是采用“复制”算法那,不会产生内存碎片;
CMS收集器:响应时间优先,以获取最短回收停顿时间为目标的收集器,是基于“标记-清除”算法实现的,分为4个步骤:初始标记、并发标记、重新标记、并发清除;
- 如果一个类不在classpath下,为什么会抛ClassNotFoundException异常,如果在不改变类路径的前期下,怎样才能正确加载这个类?
ClassPath的作用就是用来指定类的搜索路径,用于保存一些目录和Jar文件的地址,这些路径就是为了Java程序在编译和运行的时候搜索类而用的;当编译器检查到import/packag这个语句时,它会查找Classpath所指定的目录,并检查子目录中是否存在,然后找出名称吻合的已编译的文件(.class文件),如果没有就会报错。
- 说一下强引用、软引用、弱引用、虚引用以及他们之间和gc的关系
强引用:new出的对象之类的引用, 只要强引用还在,永远不会回收
软引用:引用但非必须的对象,内存溢出异常之前,回收
弱引用:非必须的对象,对象能生存到下一次垃圾收集发生之前。
虚引用:对生存时间无影响,在垃圾回收时得到通知。
JUC/并发相关
ThreadLocal用过么,原理是什么
作用:为每一个线程创建一个变量副本,每个线程都可以修改自己所拥有的变量副本,并且不会影响其他线程的副本。
原理:在ThreadLocal中有一个Map,可用于存储每一个线程的变量副本
Thread同步机制的比较:在同步机制中,通过对象锁保证在同一时间只有一个线程访问变量。而使用TreadLocal是通过为每一个线程创建变量副本,从而隔离多个线程对数据的访问冲突。每个线程都有了自己的变量副本,那么也就没有必要再该变量进行同步了。
对于多线程资源共享问题,同步机制采用了“时间换空间”的方式,而TreadLocal采用了“空间换时间”的方式;Synchronized和Lock的区别
- Synchronized(隐式锁):可以用于方法,或代码块,采用的是悲观锁机制,即线程获得的是独占锁;独占锁意味着其他线程只能通过阻塞来等待释放锁;
- Lock(显示锁):需要显示的指定起始位置和终止位置,一般使用ReentrantLock类做为锁,多个线程中必须要使用一个ReentrantLock类做为对 象才能保证锁的生效。且在加锁和解锁处需要通过lock()和unlock()显示指出。所以一般会在finally块中写unlock()以防死锁。Lock采用的是乐观锁机制;所谓乐观锁机制,是指每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败则重试,知道成功为止。乐观锁实现的机制就是CAS操作
- synchronized 的原理
synchronized可以保证方法或者代码块在运行时,同一时刻只有一个方法可以进入临界区,同时还可以保证共享变量的内存可见性。
- cas是什么,他会产生什么问题(ABA问题的解决,如加入修改次数、版本号)
CAS(compare and swap),比较并操作,CAS是一项乐观锁机制,当多个线程尝试使用CAS同时操作同一个变量时,只有其中一个线程能更新变量的值,而其他线程都会失败。失败的线程不会被挂起,而是被告知竞争失败,并可以再次尝试;
CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。
出现的问题:在CAS操作中,会出现ABA问题,就是如果V的值先由A变为B,再由B变为A,那么仍然认为它发生了变化,并且需要重新执行算法中的步骤。
解决方法:不是更新某个引用的值,而是更新两个值,包括一个引用和一个版本号,即使这个值由A变为B,然后为变为A,版本号也是不同的。
Spring
- Spring AOP与IOC的实现原理
AOP主要是用来处理系统中分布于各个模块(方法)中交叉关注点的问题(即一些具有横切性质的系统级服务),如事务管理、安全检查、缓存、对象池管理等。AOP 代理是由 AOP 框架动态生成的一个对象,该对象可作为目标对象使用。
AOP代理分为两类:
- 静态代理:使用AOP框架提供的命令进行编译,从而在编译阶段就生成AOP代理类,也称为编译时增强;
- 动态代理:运行时借助于JDK动态代理、CGLIB等方式在内存中“临时”生成AOP代理类,也称为运行时增强;
IOC容器指的就是具有依赖注入功能的容器,IOC容器负责实例化、定位、配置应用程序中的对象及建立这些对象间的依赖。通过使用IOC容器,应用程序无需直接在代码中new相关的对象;
- Spring的beanFactory和factoryBean的区别
- BeanFactory:提供了最基本的IOC容器的功能,也就是该接口定义了IOC容器最基本的形式;它是一个接口,仅仅给出了IOC容器所遵循的最底层和最基本的规范,它的实现有XmlBeanFactory、DefaultListableBeanFactory、ApplicationContext等;
- FactoryBean:主要为应用生成所需的对象;
- 为什么CGlib方式可以对接口实现代理?
CGlib是使用字节码处理框架ASM,来转换字节码并生成新的类?
- Spring的事务隔离级别,实现原理
Srping的事务本质就是数据库对事务的支持,没有数据库的事务支持,spring是无法提供事务功能的。
隔离级别:
| 常量| 解释|
| :-------- | --------:|
| ISOLATION_DEFAULT| 这是个 PlatfromTransactionManager 默认的隔离级别,使用数据库默认的事务隔离级别。另外四个与 JDBC 的隔离级别相对应。|
| ISOLATION_READ_UNCOMMITTED | 这是事务最低的隔离级别,它充许另外一个事务可以看到这个事务未提交的数据。这种隔离级别会产生脏读,不可重复读和幻像读。 |
| ISOLATION_READ_COMMITTED| 保证一个事务修改的数据提交后才能被另外一个事务读取。另外一个事务不能读取该事务未提交的数据。 |
| ISOLATION_REPEATABLE_READ| 这种事务隔离级别可以防止脏读,不可重复读。但是可能出现幻像读。 |
| ISOLATION_SERIALIZABLE| 这是花费最高代价但是最可靠的事务隔离级别。事务被处理为顺序执行。|
算法&数据结构&设计模式
- 海量url去重类问题(布隆过滤器)
解决方案:
- hash到不同节点,分而治之
- 布隆过滤器
- mapreduce
- 快速排序
采用分治思想,选取一个元素作为基准元素,然后对数组进行分区操作,使基准元素左边的元素值都大于基准元素,基准元素右边的元素值都小于基准元素。这样第一轮之后,基准元素就排序到正确位置。然后采用递归操作,分别对基准元素两边的分区做同样的操作;