Java面试常见问题集锦,java面试常见问题
Java面试常见问题集锦,java面试常见问题
一、JAVA
JVM在运行时将数据划分为了5个区域来存储。PC Register(PC寄存器)、JVM栈、堆(Heap)、方法区域(MethodArea)、本地方法栈(NativeMethod Stacks)。
方法区:存储已经被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。持久代。
堆:几乎所有的对象实例和数组都在这里分配内存。由年轻代和老年代组成。
Java 栈:栈帧用于存储局部变量表、操作数栈、动态链接、方法返回地址和一些额外的附加信息。
程序计数器:每条线程都有一个独立的的程序计数器,各线程间的计数
器互不影响。
- JVM的垃圾回收机制
触发GC(Garbage Collector)的条件
1)GC在优先级最低的线程中运行,一般在应用程序空闲即没有应用线程在运行时被调用。但下面的条件例外。
2)Java堆内存不足时,GC会被调用。当应用线程在运行,并在运行过程中创建新对象,若这时内存空间不足,JVM就会强制调用GC线程。若GC一次之后仍不能满足内存分配,JVM会再进行两次GC,若仍无法满足要求,则JVM将报“out of memory”的错误,Java应用将停止。
1、对象没有引用
2、作用域发生未捕获异常
3、程序在作用域正常执行完毕
4、程序执行了System.exit()
5、程序发生意外终止(被杀进程等)
垃圾收集算法
1 tracing算法
基于tracing算法的垃圾收集也称为标记和清除(mark-and-sweep)垃圾收集器.
这是最基础的垃圾回收算法,之所以说它是最基础的是因为它最容易实现,思想也是最简单的。标记-清除算法分为两个阶段:标记阶段和清除阶段。标记阶段的任务是标记出所有需要被回收的对象,清除阶段就是回收被标记的对象所占用的空间。具体过程如下图所示:
从图中可以很容易看出标记-清除算法实现起来比较容易,但是有一个比较严重的问题就是容易产生内存碎片,碎片太多可能会导致后续过程中需要为大对象分配空间时无法找到足够的空间而提前触发新的一次垃圾收集动作。
2 Copying算法
为了解决Mark-Sweep算法的缺陷,Copying算法就被提了出来。它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用的内存空间一次清理掉,这样一来就不容易出现内存碎片的问题。具体过程如下图所示:
这种算法虽然实现简单,运行高效且不容易产生内存碎片,但是却对内存空间的使用做出了高昂的代价,因为能够使用的内存缩减到原来的一半。很显然,Copying算法的效率跟存活对象的数目多少有很大的关系,如果存活对象很多,那么Copying算法的效率将会大大降低。
3 compacting算法
为了解决Copying算法的缺陷,充分利用内存空间,提出了Mark-Compact算法。该算法标记阶段和Mark-Sweep一样,但是在完成标记之后,它不是直接清理可回收对象,而是将存活对象都向一端移动,然后清理掉端边界以外的内存。具体过程如下图所示:
4 Generation算法
分代收集算法是目前大部分JVM的垃圾收集器采用的算法。它的核心思想是根据对象存活的生命周期将内存划分为若干个不同的区域。一般情况下将堆区划分为老年代(Tenured Generation)和新生代(Young Generation),老年代的特点是每次垃圾收集时只有少量对象需要被回收,而新生代的特点是每次垃圾回收时都有大量的对象需要被回收,那么就可以根据不同代的特点采取最适合的收集算法。
目前大部分垃圾收集器对于新生代都采取Copying算法,因为新生代中每次垃圾回收都要回收大部分对象,也就是说需要复制的操作次数较少,但是实际中并不是按照1:1的比例来划分新生代的空间的,一般来说是将新生代划分为一块较大的Eden空间和两块较小的Survivor空间,每次使用Eden空间和其中的一块Survivor空间,当进行回收时,将Eden和Survivor中还存活的对象复制到另一块Survivor空间中,然后清理掉Eden和刚才使用过的Survivor空间。
而由于老年代的特点是每次回收都只回收少量对象,一般使用的是Mark-Compact算法。
5. 多态、接口、抽象类
多态:作用:提高程序的扩展性。避免代码的冗余。
一个引用变量倒底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,必须在由程序运行期间才能决定。
public class A {
public String show(D obj) {
return (“A and D”);
}
public String show(A obj) {
return (“A and A”);
}
}
public class B extends A{
public String show(B obj){
return (“B and B”);
}
public String show(A obj){
return (“B and A”);
}
}
public class C extends B{
}
public class D extends B{
}
public class Test {
public static void main(String[] args) {
A a1 = new A();
A a2 = new B();
B b = new B();
C c = new C();
D d = new D();
System.out.println(“1–” + a1.show(b));
System.out.println(“2–” + a1.show©);
System.out.println(“3–” + a1.show(d));
System.out.println(“4–” + a2.show(b));
System.out.println(“5–” + a2.show©);
System.out.println(“6–” + a2.show(d));
System.out.println(“7–” + b.show(b));
System.out.println(“8–” + b.show©);
System.out.println(“9–” + b.show(d));
}
}
结果:
1–A and A//对象A没有b,调用A
2–A and A
3–A and D
4–B and A//对象A没有b,调用A的方法,但A的方法被重写
5–B and A
6–A and D
7–B and B
8–B and B
9–A and D
接口:抽象类的延伸,因为类不能同时继承多个父类,
抽象类:抽象方法必须为public或者protected,抽象类不能用来创造对象,继承抽象类,子类必须实现父类的抽象方法。
6. 抽象类和接口的对比
参数 抽象类 接口
默认的方法实现 它可以有默认的方法实现 接口完全是抽象的。它根本不存在方法的实现
实现 子类使用extends关键字来继承抽象类。如果子类不是抽象类的话,它需要提供抽象类中所有声明的方法的实现。 子类使用关键字implements来实现接口。它需要提供接口中所有声明的方法的实现
构造器 抽象类可以有构造器 接口不能有构造器
与正常Java类的区别 除了你不能实例化抽象类之外,它和普通Java类没有任何区别 接口是完全不同的类型
访问修饰符 抽象方法可以有public、protected和default这些修饰符 接口方法默认修饰符是public。你不可以使用其它修饰符。
main方法 抽象方法可以有main方法并且我们可以运行它 接口没有main方法,因此我们不能运行它。
多继承 抽象方法可以继承一个类和实现多个接口 接口只可以继承一个或多个其它接口
速度 它比接口速度要快 接口是稍微有点慢的,因为它需要时间去寻找在类中实现的方法。
添加新方法 如果你往抽象类中添加新的方法,你可以给它提供默认的实现。因此你不需要改变你现在的代码。 如果你往接口中添加方法,那么你必须改变实现该接口的类。
什么时候使用抽象类和接口
- Java程序运行步骤
0.先父类后子类 - 加载static,包括静态变量,静态构造块,静态实例化构造方法
- 加载实例化构造方法,非静态变量,非静态构造,构造方法
- 进入main主程序
在继承中,程序启动先运行父类子类静态构造块,当用父类对象new子类构造函数时,先运行父类构造块,在运行父类构造函数,然后子类构造块,子类构造函数,若是父类方法被重写,则运行子类中重写的方法 - 进程和线程关系及区别
-
Java多线程实现方式
主要有四种:继承Thread类、实现Runnable接口、实现Callable接口通过FutureTask包装器来创建Thread线程、使用ExecutorService、Callable、Future实现有返回结果的多线程。
第一种:
public class CallableAndFuture {
public static void main(String[] args) {
Callable callable = new Callable() {
public Integer call() throws Exception {
return new Random().nextInt(100);
}
};
FutureTask future = new FutureTask(callable);
new Thread(future).start();
try {
Thread.sleep(5000);// 可能做一些事情
System.out.println(future.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
第二种:
public class CallableAndFuture {
public static void main(String[] args) {
ExecutorService threadPool = Executors.newSingleThreadExecutor();
Future future = threadPool.submit(new Callable() {
public Integer call() throws Exception {
return new Random().nextInt(100);
}
});
try {
Thread.sleep(5000);// 可能做一些事情
System.out.println(future.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
第三种:
public class CallableAndFuture {
public static void main(String[] args) {
ExecutorService threadPool = Executors.newCachedThreadPool();
CompletionService cs = new ExecutorCompletionService(threadPool);
for(int i = 1; i < 5; i++) {
final int taskID = i;
cs.submit(new Callable() {
public Integer call() throws Exception {
return taskID;
}
});
}
// 可能做一些事情
for(int i = 1; i < 5; i++) {
try {
System.out.println(cs.take().get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
}
区别:前面两个启动方式为start(),方法为run(),Thread类实际也是Runnable接口的子类,就是实现了Runnable。
Runnable优点:避免单继承的局限(单继承避免结构上的混乱,即菱形继承问题),一个类可以继承多个接口(接口可以多继承,多实现,主要是里面的方法并没有具体实现)。适合于资源的共享。
后面的Callable方法为call(),可以有返回结果,call()可以抛出受检查的异常,比如ClassNotFoundException,而run()不能抛出受检查的异常。 -
线程的状态
-
锁
-
公平锁和非公平锁
公平锁是指多个线程在等待同一个锁时,必须按照申请锁的先后顺序来一次获得锁。
公平锁的好处是等待锁的线程不会饿死,但是整体效率相对低一些;非公平锁的好处是整体效率相对高一些,但是有些线程可能会饿死或者说很早就在等待锁,但要等很久才会获得锁。其中的原因是公平锁是严格按照请求所的顺序来排队获得锁的,而非公平锁时可以抢占的,即如果在某个时刻有线程需要获取锁,而这个时候刚好锁可用,那么这个线程会直接抢占,而这时阻塞在等待队列的线程则不会被唤醒。
公平锁可以使用new ReentrantLock(true)实现。非公平锁new ReentrantLock()。 -
类锁和对象锁
类锁:在方法上加上static synchronized的锁,或者synchronized(xxx.class)的锁。如下代码中的method1和method2:
对象锁:参考method4, method5,method6.
public class LockStrategy
{
public Object object1 = new Object();
public static synchronized void method1(){}
public void method2(){
synchronized(LockStrategy.class){}
}
public synchronized void method4(){}
public void method5()
{
synchronized(this){}
}
public void method6()
{
synchronized(object1){}
}
} -
悲观锁和乐观锁
悲观锁:假定会发生并发冲突,屏蔽一切可能违反数据完整性的操作。
乐观锁:假定不会发生并发冲突,只在提交操作时检测是否违反数据完整性
两种锁各有优缺点,不可认为一种好于另一种,像乐观锁适用于写比较少的情况下,即冲突真的很少发生的时候,这样可以省去了锁的开销,加大了系统的整个吞吐量。 -
可重入锁
可重入锁,也叫做递归锁,指的是同一线程外层函数获得锁之后 ,内层递归函数仍然有获取该锁的代码,但不受影响。
在JAVA环境下 ReentrantLock 和synchronized 都是可重入锁。可重入锁最大的作用是避免死锁。 -
String、StringBuffer与StringBuilder之间区别
-
三者在执行速度方面的比较:StringBuilder > StringBuffer > String
原因:String:字符串常量,不可改变的对象;
StringBuffer:字符串变量,可改变的对象;
StringBuilder;字符串变量,可改变的对象。 -
StringBuilder:线程非安全的;StringBuffer:线程安全的。StringBuilder与StringBuffer有公共父类AbstractStringBuilder(抽象类)。StringBuilder、StringBuffer的方法都会调用AbstractStringBuilder中的公共方法,如super.append(…)。只是StringBuffer会在方法上加synchronized关键字,进行同步。
-
如果要操作少量的数据用String;单线程操作字符串缓冲区 下操作大量数据StringBuilder;多线程操作字符串缓冲区 下操作大量数据StringBuffer。
-
ArrayList和LinkedList的大致区别:
1.ArrayList是实现了基于动态数组的数据结构,LinkedList基于链表的数据结构。
2.对于随机访问get和set,ArrayList觉得优于LinkedList,因为LinkedList要移动指针。
3.对于新增和删除操作add和remove,LinedList比较占优势,因为ArrayList要移动数据。 -
HashMap和HashTable有什么区别?
重写equals要满足几个条件:自反性、对称性、传递性、一致性。
(1)HashMap是非线程安全的,HashTable是线程安全的。
(2)HashMap的键和值都允许有null存在,而HashTable则都不行。
(3)因为线程安全、哈希效率的问题,HashMap效率比HashTable的要高。 -
HashMap原理
HashMap基于hashing原理,我们通过put()和get()方法储存和获取对象。当我们将键值对传递给put()方法时,它调用键对象的hashCode()方法来计算hashcode,让后找到bucket位置来储存值对象。当获取对象时,通过键对象的equals()方法找到正确的键值对,然后返回值对象。HashMap使用链表来解决碰撞问题,当发生碰撞了,对象将会储存在链表的下一个节点中。 HashMap在每个链表节点中储存键值对对象。
当两个不同的键对象的hashcode相同时,它们会储存在同一个bucket位置的链表中。键对象的equals()方法用来找到键值对。 -
如果HashMap的大小超过了负载因子(load factor)定义的容量
当一个map填满了75%的bucket时候,和其它集合类(如ArrayList等)一样,将会创建原来HashMap大小的两倍的bucket数组,来重新调整map的大小,并将原来的对象放入新的bucket数组中。这个过程叫作rehashing,因为它调用hash方法找到新的bucket位置。 -
ConcurrentHashMap原理
锁分段技术
HashTable容器在竞争激烈的并发环境下表现出效率低下的原因,是因为所有访问HashTable的线程都必须竞争同一把锁,那假如容器里有多把锁,每一把锁用于锁容器其中一部分数据,那么当多线程访问容器里不同数据段的数据时,线程间就不会存在锁竞争,从而可以有效的提高并发访问效率,这就是ConcurrentHashMap所使用的锁分段技术,首先将数据分成一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问。有些方法需要跨段,比如size()和containsValue(),它们可能需要锁定整个表而而不仅仅是某个段,这需要按顺序锁定所有段,操作完毕后,又按顺序释放所有段的锁。这里“按顺序”是很重要的,否则极有可能出现死锁,在ConcurrentHashMap内部,段数组是final的,并且其成员变量实际上也是final的,但是,仅仅是将数组声明为final的并不保证数组成员也是final的,这需要实现上的保证。这可以确保不会出现死锁,因为获得锁的顺序是固定的。
ConcurrentHashMap是由Segment数组结构和HashEntry数组结构组成。Segment是一种可重入锁ReentrantLock,在ConcurrentHashMap里扮演锁的角色,HashEntry则用于存储键值对数据。一个ConcurrentHashMap里包含一个Segment数组,Segment的结构和HashMap类似,是一种数组和链表结构, 一个Segment里包含一个HashEntry数组,每个HashEntry是一个链表结构的元素, 每个Segment守护者一个HashEntry数组里的元素,当对HashEntry数组的数据进行修改时,必须首先获得它对应的Segment锁。
源码解读
ConcurrentHashMap(1.7及之前)中主要实体类就是三个:ConcurrentHashMap(整个Hash表),Segment(桶),HashEntry(节点)
19. ThreadLocal
总之,ThreadLocal不是用来解决对象共享访问问题的,而主要是提供了保持对象的方法和避免参数传递的方便的对象访问方式。归纳了两点:
1、每个线程中都有一个自己的ThreadLocalMap类对象,可以将线程自己的对象保持到其中,各管各的,线程可以正确的访问到自己的对象。
2、将一个共用的ThreadLocal静态实例作为key,将不同对象的引用保存到不同线程的ThreadLocalMap中,然后在线程执行的各处通过这个静态ThreadLocal实例的get()方法取得自己线程保存的那个对象,避免了将这个对象作为参数传递的麻烦。
20. 设计模式的分类
总体来说设计模式分为三大类:
创建型模式,共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。
结构型模式,共七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
行为型模式,共十一种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。
FactoryMethod(工厂方法模式)是一种创建性模式,它定义了一个创建对象的接口,但是却让子类来决定具体实例化哪一个类。
迭代器模式属于行为型模式,其意图是提供一种方法顺序访问一个聚合对象中得各个元素,而又不需要暴露该对象的内部表示。至少可以历遍first,next,previous,last,isOver,或是历遍选择符合某种条件的子元素.
21. 关于java中位运算的左移、右移、无符号右移
<< :左移运算符,num<<n,相当于num*2n;
:右移运算符,num>>n,相当于num/2n;
:无符号右移,忽略符号位,空位以0补齐。
无符号右移规则和右移运算是一样的,只是填充时不管左边的数字是正是负都用0来填充,无符号右移运算只针对负数计算,因为对于正数来说这种运算没有意义。
二、框架
三、数据库
四、算法
前序遍历:abdefgc
中序遍历:debgfac
后序遍历:edgfbca
3. 尾递归
五、其他
相关文章
- 暂无相关文章
用户点评