谈谈你对JAVA平台的理解
java是一个面向对象编程语言,有两个显著特点,第一:“一次书写,到处运行”,二是JAVA垃圾回收机器,也就是GC。这使得JAVA不用像C++那样时刻考虑着垃圾回收和内存泄露,通过GC,可以将不可达的对象及时回收。说Java是“解释执行”的语言其实也不太准确,虽然我们写的.class文件会被javac编译成字节码,然后再通过JVM编译成机器码。同时,大多JVM其实都有动态编译器,比如HotSpot的JIT(JUST-IN-TIME),将热点代码编译成机器码。这部分就是动态编译了。属于编译执行,所以说java是个“解释执行”并不准确。
Error和Exception的区别
首先,两个都是“thowable”的子类,在Java中只有Throwable类型的实例才可以被抛出(throw)或者捕获(catch)
,然后Error一般指JVM运行时出现的错误,它出乎了程序执行的预计范围,所以是不可捕捉的
,比如OutOfMemory。而Exception指的是由代码逻辑的引发的异常,是可以捕捉的
,变量类型异常等。
异常又分为可检查和不可检查,可检查是可指可以在代码中显式捕捉的异常,不可检查指的是代码逻辑上的异常,一般在编译的时候爆出,如空指针和索引超界。
在编码过程中,除非十分有信心,不然不推荐直接捕捉exception
通用异常,而是应该捕捉他的子类。
final、 finally、 finalize有什么不同?
首先,这三者除了名字看起来像之外,没有什么共同点。
final 修饰符可以让一个变量不可变,让一个方法不可重写,让一个类不可继承。
finally是try-catch异常捕捉中的一个语法,指的是无论捕捉到什么异常,都要执行finally中的代码,通常是关闭JDBC和unlock锁但也有些方法可以让代码逻辑跳过finally。
finalize是垃圾回收的一个方法,现在已经很少人用了,JDK9中已经标记成deprecated
强引用、软引用、弱引用、幻象引用有什么区别?
定义这四个引用程度是为了更合理的进行垃圾回收。
强引用是指声明对象时的引用,只要还有一个强引用指向该对象,那么它就不会垃圾回收器回收。
软引用,在内存充足时,不会被回收,一旦JVM认为内存不足就会,它就会被回收,JVM通过一个公式判断内存是否充足,一般而言倾向于判断其内存不足。
弱引用,只要有垃圾回收动作,就会被回收。弱应用同样可用于内存敏感的缓存
虚引用:样,在任何时候都可能被垃圾回收器回收。虚引用必须和引用队列 (ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如
果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。所以他可以用来跟踪对象是否别垃圾回收器回收。
String、 StringBufer、 StringBuilder有什么区别?
String是一个final class,他的所有成员也都是final,对String进行增删改操作,都会产生其他新的对象,又由于字符串类型经常使用,所以就有了StringBuffer,他的实质上是一个可修改的字符序列,它可以避免由于拼接等操作而导致产生过多中间对象,其次它还是线程安全,所以他在性能上有些不理想,因此就有了StringBuilder,他除了不是线程安全外,其他功能和StringBuffer一样的。
出于安全考虑,String被设计为不可变,是为了不想再引用字符串时,被其他引用对象改变了值,比如路径,文件名,这种字符串变量,一旦改变了就会造成很大的问题,所以要保证String类型的不可变性和某种程度上的可变性,同时,由于String类型使用十分频繁,jdk也对其作出了优化,最开始的时候String里是char[]数组,但是现在的话是btye数组,因为如果用一个char去存字母的话,有点浪费,所以改成用两个BTYE去存,这样能节省一半的逻辑空间,但是理论上的String最大容量少了一半。还有一种优化方式就是字符串常量池,他在编译阶段,会把想用的字符串指向常量池里的同一个对象。
反射机制与动态代理是什么原理
虽然java是解释执行语言,但是java中的反射机制和动态代理使得java可以动态构造对象,获取对象的方法声明的属性等信息。
实现动态代理的方式有很多,比如内置的JDK,还有ASM 与机遇ASM的cglib等。
JDK就是通过反射机制来实现动态代理的,而ASM和cglib是通过更高性能的字节码操作实现动态代理。动态代理的常用场景有RPC调用,和AOP。
动态代理相比于静态代理的优势是 不用像静态代理一样把代理类写死,一个类就要写一个代理类,它能统一的处理。
通过动态代理可以更高效的实现许多代理模式,比如对于一个资源消耗很大实际调用类,我们可以创建一个比较小的代理类来代理他,只有在调用它的时候才会被实例化。
int和Integer
int是java中八个原始数据类型,而Integer是int的包装类,是一个对象,他有一个int类型的储存字段,并且提供了相关操作。引入包装类是因为原始数据类型不支持泛型,比如集合初始化的时候需要传对象参数。同时由于Integer比int大,java也做了相关优化,在调用valueOf
方法时,会缓存-128到127之内的所有整数对象,把值相同的引用指向同一个对象。就像字符串常量池那样。因为在实际中,我们所使用的数字一般都不会很大。出了缓存Integer类型之外,java对其他原始数据类型的包装类也做了相关缓存。
对比Vector、 ArrayList、 LinkedList有何区别?
Vector相比其他两个,是线性安全的,所以性能开销也比其他两个。
ArrayList和Vector都是基于对象数组的动态数组实现方式,在容量不足时都会自动扩容,Vetcot是扩容100%,而ArrayList扩容50%。
LinkedList是双向链表,也是线程不安全的。
LinkedList插入删除元素比动态数组方便很多,而且不用担心因扩容而导致的OOM问题。(没足够的连续地址空间)
对比Hashtable、 HashMap、 TreeMap有什么不同?
HashTable是线程安全的,但是他的实现方式都是暴力加锁,性能额外开销很大,而且不支持null键值对
TreeMap是基于红黑树实现的顺序访问Map,取值和插入的复杂度都是Logn。
HashMap是大多数场景下的首选数据结构,是基于数组和链表实现的Map,存取操作的时间复杂度都是LogN,但是不是线程安全的。所以在并发场合下会导致很多问题,
比如死循环,在重哈希的时候,重新分配桶,多线程进行操作,会导致几个链表节点的指针形成一个圆形。(头插法导致?)
如果想要使用线程安全的HashMap应该使用ConcurrentHashMap,他的实现更为高效合理。
几个版本以来,Java对HashMap做了很大的优化,比如引入了红黑树,在链表长度大于8时,会进行树化,红黑树节点数小于6时重新链表化,但是根据源码里开发人员的注释,同一个位置的元素超过8个的概率只有百万分值6。
为什么hashMap的容量要是2的次方
HashMap源码中有个函数叫做tableSizeFor,他会返回一个最小大于传入参数的2次方数。具体实现是通过-1>>>n-1。
所以每次扩容都通过这个函数进行计算容量。
同时,2的次方可以使得HashMap的存入键值对的时候更为均匀分散,极大程度上的避免了冲突,因为在通过元素在HashMap底层数组的位置的时候,是通过当前容量-1与一个hash值与操作得出来的,如果当前容量不是2的次方的话,减去1得到的2进制就会有0的存在,而0无论与1还是0相与都是0,这样就会有更大的机会让两个值落在同一个桶内。与操作中的hash值,是hashcode函数的值的前16位数。
如何保证容器是线程安全的? ConcurrentHashMap如何实现高效地线程安全
利用并发包提供的线程安全容器类,它提供了:
各种并发容器,比如ConcurrentHashMap、 CopyOnWriteArrayList。
concurrentHashMap早期是使用分离锁和HashEntry实现的。
分离锁,就是把底层数组成几个segment,里面这是HashEntry,也是链表形式存放的。
HashEntry使用了volatile来保证可见性。
相比于同为线性安全的hashtable,他的优势在于,使用了分离锁,分段锁而不是一下子锁住了整个表,所以他的性能自然比HashTable好。
java8之后的concurrentHashMap在结构上去掉了分离锁,使用和HashMap一样的结构
同时引入了CAS和synchronized 等操作,来进行无锁并发操作。
synchronized和ReentrantLock有什么区别?有人说synchronized最慢,这话靠谱吗?
reentrantLock比synchronized灵活,但是使用的时候要进行加锁解锁操作,
早期的synchronized锁确实慢与ReentrantLock,但是经过优化,现在的sychronize在低竞争的场景性能会优于可重入锁。
synchronized底层如何实现?什么是锁的升级、降级?
底层是Monitor,而monitor有三种实现方式,分别是偏斜锁,轻量级锁,重量级锁。
所谓的升级降级,就是JVM在不同的场景对锁的方式进行更换。
在几乎无竞争的情况下,使用偏斜锁,轻度竞争使用轻量级锁,重度竞争使用重量级锁。
一个线程两次调用start()方法会出现什么情况?
一个线程不允许调用两次,一个线程只能有一个状态。
新建,就绪,阻塞,等待,计时,等待,终止
什么情况下Java程序会产生死锁?如何定位、修复?
互相获取锁的时候
public class DeadLockSample extends Thread {
private String frs;
private String second;
public DeadLockSample(String name, String frs, String second) {
super(name);
this.frs = frs;
this.second = second;
}
public void run() {
synchronized (frs) {
Sysem.out.println(this.getName() + " obtained: " + frs);
try {
Thread.sleep(1000L);
synchronized (second) {
Sysem.out.println(this.getName() + " obtained: " + second);
}
} catch (InterruptedException e) {
// Do nothing
}
}
}
public satic void main(String[] args) throws InterruptedException {
String lockA = "lockA";
String lockB = "lockB";
DeadLockSample t1 = new DeadLockSample("Thread1", lockA, lockB);
DeadLockSample t2 = new DeadLockSample("Thread2", lockB, lockA);
t1.sart();
t2.sart();
t1.join();
t2.join();
}
}
并发包中的ConcurrentLinkedQueue和LinkedBlockingQueue有什么区别?
concurrentLinkedList是无界非阻塞的,LinkedBlockingQueue是有界阻塞的。
concurrentLinkedList是基于CAS的,所以是弱一致性,就算有线程在他进行修改,他也是可以继续遍历的。
Java并发类库提供的线程池有哪几种? 分别有什么特点?
Executors目前提供了5种不同的线程池创建配置:主要区别在于不同的ExecutorService类型或者不同的初始参数。
1.newCachedThreadPool(),它是一种用来处理大量短时间工作任务的线程池,具有几个鲜明特点:它会试图缓存线程并重用,当无缓存线程可用时,就会创建新的工作线程;如
果线程闲置的时间超过60秒,则被终止并移出缓存;长时间闲置时,这种线程池,不会消耗什么资源。其内部使用SynchronousQueue作为工作队列。
2. newFixedThreadPool(int nThreads),重用指定数目(nThreads)的线程,其背后使用的是无界的工作队列,任何时候最多有nThreads个工作线程是活动的。这意味着,如
果任务数量超过了活动队列数目,将在工作队列中等待空闲线程出现;如果有工作线程退出,将会有新的工作线程被创建,以补足指定的数目nThreads。
3、newSingleThreadExecutor(),它的特点在于工作线程数目被限制为1,操作一个无界的工作队列,所以它保证了所有任务的都是被顺序执行,最多会有一个任务处于活动状
态,并且不允许使用者改动线程池实例,因此可以避免其改变线程数目。
4.newSingleThreadScheduledExecutor()和newScheduledThreadPool(int corePoolSize),创建的是个ScheduledExecutorService,可以进行定时或周期性的工作调度,
区别在于单一工作线程还是多个工作线程。
5.newWorkStealingPool(int parallelism),这是一个经常被人忽略的线程池, Java 8才加入这个创建方法,其内部会构建ForkJoinPool,利用Work-Stealing算法,并行地处
理任务,不保证处理顺序。
AtomicInteger底层实现原理是什么?如何在自己的产品代码中应用CAS操作?
是int的一个封装类型,使用了CAS,提供了原子性的访问和更新操作;添加一个version
Java提供了哪些IO方式? NIO如何实现多路复用?
java.io nio nio2
nio有buffer channel selector
Java有几种文件拷贝方式?哪一种最高效?
利用java.io类库,直接为源文件构建一个FileInputStream读取,然后再为目标文件构建一个FileOutputStream,
或者,利用java.nio类库提供的transferTo或transferFrom方法实现
Java常见的垃圾收集器有哪些?垃圾回收原理
Serial GC,ParNew GC,CMS,G1 GC
- 可达性
- 计数法
谈谈接口和抽象类有什么区别?
抽象类是不能实例化的类,用abstractclass修饰符,而接口就是纯抽象的类,方法都留给实现类去实现,除了default和static之外的方法不能有具体实现。
都是为了代码复用。
谈谈JVM内存区域的划分,哪些区域可能发生OutOfMemoryError?
程序计数器,java虚拟机栈,堆,方法区,, 运行时常量池, 本地方法栈。
谈谈MySQL支持的事务隔离级别,以及悲观锁和乐观锁的原理和应用场景?
读未提交
读已提交
可重复读
串行化
happen-before
Happen-before关系,是Java内存模型中保证多线程操作可见性的机制,也是对早期语言规范中含糊的可见性概念的一个精确定义。
它的具体表现形式,包括但远不止是我们直觉中的synchronized、 volatile、 lock操作顺序等方面,例如:
线程内执行的每个操作,都保证happen-before后面的操作,这就保证了基本的程序顺序规则,这是开发者在书写程序时的基本约定。
对于volatile变量,对它的写操作,保证happen-before在随后对该变量的读取操作。
对于一个锁的解锁操作,保证happen-before加锁操作。
对象构建完成,保证happen-before于fnalizer的开始动作。
甚至是类似线程内部操作的完成,保证happen-before其他Thread.join()的线程等。
这些happen-before关系是存在着传递性的,如果满足a happen-before b和b happen-before c,那么a happen-before c也成立。
前面我一直用happen-before,而不是简单说前后,是因为它不仅仅是对执行时间的保证,也包括对内存读、写操作顺序的保证。仅仅是时钟顺序上的先后,并不能保证线程交互的
可见性。