1. 面向对象和面向过程的区别
- 面向对象 = 多态 + 继承 = 先抽象,后具体;
- 面向过程 = 分解问题 + 逻辑为先 = 先细节,再整体。
- 面向对象就是你叫服务员倒一杯水给你。怎么倒你不用管。
- 面向过程就是你教服务员如何一步一步倒一杯水给你。
- “面对过程”就是将世界上各种物体看成一个有联系的大系统,而且由各种小系统组成,小系统之间也有着紧密的联系;“面对对象”就是将世界看成一个个相互独立的对象,相互之间没有联系,但是一旦在外力的刺激下,对象和对象就会以一定关系组合起来,产生一定的交互,也形成一定意义上的“过程”。
2. Java的四个基本特性(抽象、封装、继承,多态)
3. Overload和Override的区别|
override(重写,覆盖)
- 方法名、参数、返回值相同。
- 子类方法不能缩小父类方法的访问权限(在Jdk5.0之后,子类返回值可以是父类返回值的子类)。
- 子类方法不能抛出比父类方法更多的异常(但子类方法可以不抛出异常)。
- 存在于父类和子类之间。
- 方法被定义为final不能被重写。
- 被覆盖的方法不能为private,否则在其子类中只是新定义了一个方法,并没有对其进行覆盖。
overload(重载,过载)
- 参数类型、个数、顺序至少有一个不相同。
- 不能重载只有返回值不同的方法名。
- 针对于一个类而言。
- 不能通过访问权限、返回类型、抛出的异常进行重载;
- 方法的异常类型和数目不会对重载造成影响;
4. 构造器Constructor是否可被override
构造器Constructor不能被继承,因此不能重写Overriding,但可以被重载Overloading。
5. 访问控制符public,protected,private,以及默认的区别
作用域 当前类 同一package 子孙类 其他package
public √ √ √ √
protected √ √ √ ×
friendly √ √ × ×
private √ × × ×
6. 是否可以继承String类
在Java中,只要是被定义为final的类,也可以说是被final修饰的类,就是不能被继承的。
7. String和StringBuffer、StringBuilder的区别
- 如果要操作少量的数据用 = String
- 单线程操作字符串缓冲区 下操作大量数据 = StringBuilder
- 多线程操作字符串缓冲区 下操作大量数据 = StringBuffer
- 操作大量数据,速度上的较量:StringBuilder>StringBuffer>String
8. hashCode和equals方法的关系
如果两个对象相等(equal),它们的hashcode一定相同;
如果两个对象有相同的hashcode,它们不一定相等(equal);
之所以这样设计是为了在Map中更快的查找到对象(相对于线性搜索);
一般Map都设计成数组+链表的结构,使用hashcode去查找对象需要两个步骤,首先使用hashcode定位数组下标索引,然后遍历该数组元素对应的链表,找到equals的元素;
Object默认的hashcode实现对于不同的对象会返回不同的值,因此,在上面那个例子中,不同的对象(即使同一个类型,类中的值一样)有着不同的hashcode;
值的散列就像在车库储存货物,不同的货物能被存放到不同的车库。比较有效查找货物办法是将不同的货物存到不同的车库中,而不是同一个车库;
参考博客,因为hashcode 而出现的问题
https://www.cnblogs.com/chenpi/p/5489494.html
9. 抽象类和接口的区别
1、抽象类:
如果这个类里面的方法有一个是抽象的,那么这个类必须是抽象的。
抽象类中的抽象方法,子类必须实现。
抽象类中可以有变量,可以有方法体的方法。
如果这个类里面的方法有一个是抽象的,那么这个类必须是抽象的。
抽象类中的抽象方法,子类必须实现。
抽象类中可以有变量,可以有方法体的方法。
子类只能extends 一个抽象类
2、接口:
接口中方法都默认是public 的,不能有方法体。
接口中的变量都常量,都是public static final 的,必须先赋值。
如果一个类实现了此接口,必须覆盖接口中的所有抽象方法。
子类只能extends 一个抽象类
2、接口:
接口中方法都默认是public 的,不能有方法体。
接口中的变量都常量,都是public static final 的,必须先赋值。
如果一个类实现了此接口,必须覆盖接口中的所有抽象方法。
子类可以implements 多个接口
接口可以extends 多个接口
子类可以implements 多个接口
接口可以extends 多个接口
10. 自动装箱与拆箱
equals() 比较的是两个对象的值(内容)是否相同。
"==" 比较的是两个对象的引用(内存地址)是否相同,也用来比较两个基本数据类型的变量值是否相等。
- Integer 自定拆装箱
Integer i = 10; //装箱 Integer i = Integer.valueOf(10);public static Integer valueOf(int i) { // 没有设置的话,IngegerCache.high 默认是127
if(i >= -128 && i <= IntegerCache.high)
return IntegerCache.cache[i + 128];
else
return new Integer(i);
}
看Integer.valueOf() 的源码,在-128至 127 范围类,
Integer i1 =200;
Integer i2 =200;
i1 == i2 结果是false, 是Integer对象,比较的是内存地址,但是由于自动装箱, 没在-128至 127 之内,所以是新建对象,他们内存地址不相等。
Integer i3 =100;
Integer i4 =100;
i3==i4 结果为true ,上边的源码可以看到,执行自动装箱时,值范围在
-128至 127 之间,所以取的是缓存的内存地址,i3 和 i4是同一个对象。
Integer i5 =new Integer(100);
Integer i6 =new Integer(100);
i5 ==i6 结果为false, 因为比较的是内存地址,但是他们都是new 的内存地址,所以此时为false。 -
String 自定拆装箱
String str1 ="abc"; String str2 ="abc";
str1 == str2 true
str2.equals(str2) true
String str3 =new String("abc"); String str4 =new String("abc");
str3 == str4 false
str3 .equals(str4) true
String d ="2"; String e ="23"; e = e.substring(0, 1); e.equals(d) true e==d false
11. 什么是泛型、为什么要使用以及泛型擦除
12. Java中的集合类及关系图
13. HashMap实现原理(看源代码)
- 数组+链表,
- jdk8 , 当key==null, 则hash 值为 0,value 没有null的限制
- 没有同步,多线程不安全
14. HashTable实现原理(看源代码)
- 数组+链表jdk8 ,
- value ==null 报错NullPointerException key ==null 则key.hashCode()报错 NullpointerExcepiton
- 采取synchronized method 线程安全
15. HashMap和HashTable区别
- HashTable默认大小 11,负载因子为0.75f,扩容扩容1倍加1, int newCapacity = (oldCapacity << 1) + 1;
HashTable key不能放null ,否则会报错。 - HashMap 默认大小16,负载因子0.75,扩容一倍, oldCap << 1
16. HashTable如何实现线程安全(看源代码)
17. ArrayList和vector区别(看源代码)
- ArrayList 线程不不安全,
- vector 采用 synchronized method 保证线程安全。
- ArrayList 默认大小为10,扩容为原来的一半 int newCapacity = oldCapacity + (oldCapacity >> 1);;
- vector 默认大小为 10,扩容时如果设置扩容大小,就按照大小进行扩容;如果没设置扩容大小,就按照vector初始容量扩容一倍。int newCapacity = oldCapacity + ((capacityIncrement > 0) ? capacityIncrement : oldCapacity);
18. ArrayList和LinkedList区别及使用场景
19. Collection和Collections的区别
ava.util.Collection 是一个集合接口。它提供了对集合对象进行基本操作的通用接口方法。
Collection
├List
│├LinkedList
│├ArrayList
│└Vector
│ └Stack
└Set
java.util.Collections 是一个包装类。它包含有各种有关集合操作的静态多态方法。
集合接口。它提供了对集合对象进行基本操作的通用接口方法。
Collection
├List
│├LinkedList
│├ArrayList
│└Vector
│ └Stack
└Set
java.util.Collections 是一个包装类。它包含有各种有关集合操作的静态多态方法。
20. Concurrenthashmap实现原理(看源代码)
21. Error、Exception区别
Exception:
- 可以是可被控制(checked)或者不可控制(unchecked);
- 表示一个由程序员导致的错误;
- 应该在应用程序级被处理;
Error:
- 总是不可控制的(unchecked);
- 经常用来表示系统错误或者底层资源错误;
- 如果可能的话,应该在系统级被捕捉
22. Unchecked Exception和Checked Exception,各列举几个
Unchecked Exception 写代码时,编译器不强制要求我们必须处理异常。
Checked Exception写代码时,编译器要求我们必须进行显式处理的异常。
这其中,Error 和 RuntimeException是Unchecked Exception,其他的都是 Checkd Exception 。
RuntimeException:
IllegalArgumentException, IndexOutOfBoundsException ,NullPointerException ,ConcurrentModificationException
Exception:
DataFormatException ,InterruptedException ,IOException ,ClassNotFoundException
23. Java中如何实现代理机制(JDK、CGLIB)
/** 第一种方式,基于Java 接口类 的动态代理
public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces, InvocationHandler h)
throws IllegalArgumentException
*/
Object object = Proxy.newProxyInstance(String.class.getClassLoader(), null, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return null;
}
});
第二种是基于cglib 的 字节码代理
JDK动态代理
优点
不依赖第三方jar包, 使用方便
随着JDK的升级,JDK动态代理的性能在稳步提升
缺点
只能代理实现了接口的类
执行速度较慢
适用场景
如果你的程序需要频繁、反复地创建代理对象,则JDK动态代理在性能上更占优。
CGLIB
优点
由于是动态生成字节码实现代理,因此代理对象的执行速度较快, 约为JDK动态代理的1.5 ~ 2倍
可以代理没有实现接口的对象
缺点
不能代理final类
动态生成字节码虽然执行较快,但是生成速度很慢,根据网上一些人的测试结果,cglib创建代理对象的速度要比JDK慢10 ~ 15倍。
适用场景
不需要频繁创建代理对象的应用,如Spring中默认的单例bean,只需要在容器启动时生成一次代理对象。
24. 多线程的实现方式
- new Thread(MyThread extends Thread(){})
- new Thread(MyThread implements Runnable)
- 使用ExecutorService、Callable、Future实现有返回结果的多线程。其中如果要返回结果的使用Callable接口,结果用Future进行接收。如果不用返回结果使用Runnable接口。
25. 线程的状态转换
Java中的线程的生命周期大体可分为5种状态。
- 1. 新建(NEW):新创建了一个线程对象。
- 2. 可运行(RUNNABLE):线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取cpu 的使用权 。
- 3. 运行(RUNNING):可运行状态(runnable)的线程获得了cpu 时间片(timeslice) ,执行程序代码。
- 4. 阻塞(BLOCKED):阻塞状态是指线程因为某种原因放弃了cpu 使用权,也即让出了cpu timeslice,暂时停止运行。直到线程进入可运行(runnable)状态,才有机会再次获得cpu timeslice 转到运行(running)状态。阻塞的情况分三种:
- 等待阻塞:运行(running)的线程执行o.wait()方法,JVM会把该线程放入等待队列(waitting queue)中。
- 同步阻塞:运行(running)的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池(lock pool)中。
- 其他阻塞:运行(running)的线程执行Thread.sleep(long ms)或t.join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入可运行(runnable)状态。
- 5. 死亡(DEAD):线程run()、main() 方法执行结束,或者因异常退出了run()方法,则该线程结束生命周期。死亡的线程不可再次复生。
26. 如何停止一个线程
目前百度的,貌似不完整,后期补充。
27. 什么是线程安全
线程安全就是说多线程访问同一代码,不会产生不确定的结果。
28. 如何保证线程安全
- 使用不变类如,String
- 使用原子类对象
- 使用线程安全的集合
- 使用 synchronized 或者 Lock 对共享变量进行保护
29. Synchronized如何使用
- 修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象;
- 修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象;
- 修饰一个静态的方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象;
- 修饰一个类,其作用的范围是synchronized后面括号括起来的部分,作用主的对象是这个类的所有对象。
总结一下:
- 无论synchronized关键字加在方法上还是对象上,如果它作用的对象是非静态的,则它取得的锁是对象;如果synchronized作用的对象是一个静态方法或一个类,则它取得的锁是对类,该类所有的对象同一把锁。
- 每个对象只有一个锁(lock)与之相关联,谁拿到这个锁谁就可以运行它所控制的那段代码。
- 实现同步是要很大的系统开销作为代价的,甚至可能造成死锁,所以尽量避免无谓的同步控制。
30. synchronized和Lock的区别
- 主要相同点:Lock能完成synchronized所实现的所有功能
- 主要不同点:Lock有比synchronized更精确的线程语义和更好的性能。synchronized会自动释放锁,而Lock一定要求程序员手工释放,并且必须在finally从句中释放
- 在资源竞争不是很激烈的情况下,Synchronized的性能要优于ReetrantLock,但是在资源竞争很激烈的情况下,Synchronized的性能会下降几十倍,但是ReetrantLock的性能能维持常态;
31. 多线程如何进行信息交互
- 多线程函数传递参数:在传统的同步开发模式下,当我们调用一个函数时,通过这个函数的参数将数据传入,并通过这个函数的返回值来返回最终的计算结果。但在多线程的异步开发模式下,数据的传递和返回和同步开发模式有很大的区别。由于线程的运行和结束是不可预料的,因此,在传递和返回数据时就无法象函数一样通过函数参数和return语句来返回数据。
- 通过Property的构造方法传递数据 。
- 通过Property的set 方法传递参数。
- 多线程之间如何传递参数:
- java 并发包中的 Exchanger的使用。
- 是通过共享变量,线程之间通过该变量进行协作通信;
- synchronized方法或synchronized块中使用对象锁,用wait(),notify(),以及notifyAll()进行通信
- Condition和ReentrantLock,也可以同样实现线程间的交互协作。
- 通过队列(本质上也是线程间共享同一块内存)来实现消费者和生产者的模式来进行通信;
- 线程同步
- Lock
- synchronized
- 线程通信
- synchronized方法或synchronized块中使用对象锁,用wait(),notify(),以及notifyAll()进行通信。
- Condition和ReentrantLock,也可以同样实现线程间的交互协作。
- 生产者消费者模式
32. sleep和wait的区别(考察的方向是是否会释放锁)
- 这两个方法来自不同的类分别是Thread.slelep和Object.wait Object.notify Object.notifyAll
- 最主要是sleep方法没有释放锁,而wait方法释放了锁,使得其他线程可以使用同步控制块或者方法。
- wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用
synchronized(x){
x.notify()
//或者wait()
}
33. 多线程与死锁
- 死锁是两个或更多线程阻塞着等待其它处于死锁状态的线程所持有的锁
34. 如何才能产生死锁
- 互斥条件:一个资源每次只能被一个进程使用。
- 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
- 不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。
- 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
35. 什么叫守护线程,用什么方法实现守护线程
什么叫守护线程:
Daemon的作用是为其他线程的运行提供便利服务,守护线程最典型的应用就是GC。
只要当前JVM实例中尚存在任何一个非守护线程没有结束,守护线程就全部工作;
只有当最后一个非守护线程结束时,守护线程随着JVM一同结束工作。 如何实现守护线程:
hread daemonTread = new Thread();
//设置true则为守护线程
daemonThread.setDaemon(true);
//判断是否为守护xian'c
daemonThread.isDaemon();
37. java并发包concurrent及常用的类
38. volatile关键字
- 保证共享变量对所有线程的可见性
-
- 当写一个volatile变量时,JMM会把该线程对应的本地内存中的变量强制刷新到主内存中去;
- 这个写会操作会导致其他线程中的缓存无效。
- 保证此变量对所有的线程的可见性,这里的“可见性”,如本文开头所述,当一个线程修改了这个变量的值,volatile 保证了新值能立即同步到主内存,以及每次使用前立即从主内存刷新。但普通变量做不到这点,普通变量的值在线程间传递均需要通过主内存(详见:Java内存模型)来完成。
- 禁止指令重排序优化
-
- 有volatile修饰的变量,赋值后多执行了一个“load addl $0x0,(%esp)”操作这个操作相当于一个内存屏障(指令重排序时不能把后面的指令重排序到内存屏障之前的位置),只有一个CPU访问内存时,并不需要内存屏障;(什么是指令重排序:是指CPU采用了允许将多条指令不按程序规定的顺序分开发送给各相应电路单元处理)。
39. Java中的NIO,BIO,AIO分别是什么
- BIO,同步阻塞式IO,简单理解:一个线程处理一个连接,发起和处理IO请求都是同步的
- 在JDK1.4之前,用Java编写网络请求,都是建立一个ServerSocket,然后,客户端建立Socket时就会询问是否有线程可以处理,如果没有,要么等待,要么被拒绝。即:一个连接,要求Server对应一个处理线程。
- NIO,同步非阻塞IO,简单理解:一个线程处理多个连接,发起IO请求是非阻塞的但处理IO请求是同步的
- 在Java里的由来,在JDK1.4及以后版本中提供了一套API来专门操作非阻塞I/O,我们可以在java.nio包及其子包中找到相关的类和接口。由于这套API是JDK新提供的I/O API,因此,也叫New I/O,这就是包名nio的由来。这套API由三个主要的部分组成:缓冲区(Buffers)、通道(Channels)、多通道选择器(Selector)和非阻塞I/O的核心类组成。
在理解NIO的时候,需要区分,说的是New I/O还是非阻塞IO,New I/O是Java的包,NIO是非阻塞IO概念。这里讲的是后面一种。
NIO本身是基于事件驱动思想来完成的,其主要想解决的是BIO的大并发问题: 在使用同步I/O的网络应用中,如果要同时处理多个客户端请求,或是在客户端要同时和多个服务器进行通讯,就必须使用多线程来处理。也就是说,将每一个客户端请求分配给一个线程来单独处理。这样做虽然可以达到我们的要求,但同时又会带来另外一个问题。由于每创建一个线程,就要为这个线程分配一定的内存空间(也叫工作存储器),而且操作系统本身也对线程的总数有一定的限制。如果客户端的请求过多,服务端程序可能会因为不堪重负而拒绝客户端的请求,甚至服务器可能会因此而瘫痪。
NIO基于Reactor,当socket有流可读或可写入socket时,操作系统会相应的通知引用程序进行处理,应用再将流读取到缓冲区或写入操作系统。
也就是说,这个时候,已经不是一个连接就要对应一个处理线程了,而是有效的请求,对应一个线程,当连接没有数据时,是没有工作线程来处理的。
- 在Java里的由来,在JDK1.4及以后版本中提供了一套API来专门操作非阻塞I/O,我们可以在java.nio包及其子包中找到相关的类和接口。由于这套API是JDK新提供的I/O API,因此,也叫New I/O,这就是包名nio的由来。这套API由三个主要的部分组成:缓冲区(Buffers)、通道(Channels)、多通道选择器(Selector)和非阻塞I/O的核心类组成。
- AIO,异步非阻塞IO,简单理解:一个有效请求一个线程,发起和处理IO请求都是异步的BIO
- 与NIO不同,操作系统负责处理内核区/用户区的内存数据迁移和真正的IO操作,应用程序只须直接调用API的read或write方法即可。这两种方法均为异步的,对于读操作而言,当有流可读取时,操作系统会将可读的流传入read方法的缓冲区,并通知应用程序;对于写操作而言,当操作系统将write方法传递的流写入完毕时,操作系统主动通知应用程序。在JDK1.7中,这部分内容被称作NIO.2,主要在java.nio.channels包下增加了下面四个异步通道:
AsynchronousSocketChannel
AsynchronousServerSocketChannel
AsynchronousFileChannel
AsynchronousDatagramChannel
其中的read/write方法,会返回一个带回调函数的对象,当执行完读取/写入操作后,直接调用回调函数。
- 与NIO不同,操作系统负责处理内核区/用户区的内存数据迁移和真正的IO操作,应用程序只须直接调用API的read或write方法即可。这两种方法均为异步的,对于读操作而言,当有流可读取时,操作系统会将可读的流传入read方法的缓冲区,并通知应用程序;对于写操作而言,当操作系统将write方法传递的流写入完毕时,操作系统主动通知应用程序。在JDK1.7中,这部分内容被称作NIO.2,主要在java.nio.channels包下增加了下面四个异步通道:
41. 序列化与反序列化
- 序列化 (Serialization),就是将对象序列化为二进制形式(字节数组),一般也将序列化称为编码(Encode),主要用于网络传输、数据持久化等;
- 反序列化(deserialization),则是将从网络、磁盘等读取的字节数组还原成原始对象,以便后续业务的进行,一般也将反序列化称为解码(Decode),主要用于网络传输对象的解码,以便完成远程调用。
- 在Java中,对象的序列化与反序列化被广泛应用到RMI(远程方法调用)及网络传输中。
42. 常见的序列化协议有哪些
- json
- xml
43. 内存溢出和内存泄漏的区别
- 内存泄露是指你的应用使用资源之后没有及时释放,导致应用内存中持有了不需要的资源这是一种状态描述。
- 内存溢出是指你的应用的内存已经不能满足正常使用了,堆栈已经达到系统设置的最大值,进而导致崩溃,这事一种结果描述
44. Java内存模型及各个区域的OOM,如何重现OOM
- 堆
- 属于线程共享的内存区,存储对象本身以及数组。
- 方法区
- 方法区属于线程共享的内存区域,又称Non-Heap(非堆),主要用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
- 栈-java栈
- 属于线程私有的数据区域,与线程同时创建,总数与线程关联,代表Java方法执行的内存模型。每个方法执行时都会创建一个栈桢来存储方法的的变量表、对象引用地址、操作数栈、动态链接方法、返回值、返回地址等信息。
- 栈-本地方法栈
- 本地方法栈与Java栈的作用和原理非常相似。区别只不过是Java栈是为执行Java方法服务的,而本地方法栈则是为执行本地方法(Native Method)服务的。
- 程序计数器
- 属于线程私有的数据区域,是一小块内存空间,主要代表当前线程所执行的字节码行号指示器。字节码解释器工作时,通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。
- 没有OOM
45. 出现OOM如何解决
46. 用什么工具可以查出内存泄漏
47. Java内存管理及回收算法
- 垃圾回收标记算法
- 引用计数算法
- 根搜索算法
- 垃圾回收算法
- 标记-清除算法
最基础的垃圾收集算法,算法分为“标记”和“清除”两个阶段:首先标记出所有需要回收的对象,在标记完成之后统一回收掉所有被标记的对象。
标记-清除算法的缺点有两个:首先,效率问题,标记和清除效率都不高。其次,标记清除之后会产生大量的不连续的内存碎片,空间碎片太多会导致当程序需要为较大对象分配内存时无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。
- 复制算法:
将可用内存按容量分成大小相等的两块,每次只使用其中一块,当这块内存使用完了,就将还存活的对象复制到另一块内存上去,然后把使用过的内存空间一次清理掉。这样使得每次都是对其中一块内存进行回收,内存分配时不用考虑内存碎片等复杂情况,只需要移动堆顶指针,按顺序分配内存即可,实现简单,运行高效。
复制算法的缺点显而易见,可使用的内存降为原来一半。
- 标记-整理算法
标记-整理算法在标记-清除算法基础上做了改进,标记阶段是相同的标记出所有需要回收的对象,在标记完成之后不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,在移动过程中清理掉可回收的对象,这个过程叫做整理。
标记-整理算法相比标记-清除算法的优点是内存被整理以后不会产生大量不连续内存碎片问题。
复制算法在对象存活率高的情况下就要执行较多的复制操作,效率将会变低,而在对象存活率高的情况下使用标记-整理算法效率会大大提高。
- 分代收集算法
根据内存中对象的存活周期不同,将内存划分为几块,java的虚拟机中一般把内存划分为新生代和年老代,当新创建对象时一般在新生代中分配内存空间,当新生代垃圾收集器回收几次之后仍然存活的对象会被移动到年老代内存中,当大对象在新生代中无法找到足够的连续内存时也直接在年老代中创建。
- 标记-清除算法
48. Java类加载器及如何加载类(双亲委派)
- 类加载器
- 引导类加载器(bootstrap class loader):它用来加载 Java 的核心库,是用原生代码来实现的,并不继承自
java.lang.ClassLoader
。 - 扩展类加载器(extensions class loader):它用来加载 Java 的扩展库。Java 虚拟机的实现会提供一个扩展库目录。该类加载器在此目录里面查找并加载 Java 类。
- 系统类加载器(system class loader):它根据 Java 应用的类路径(CLASSPATH)来加载 Java 类。一般来说,Java 应用的类都是由它来完成加载的。可以通过
ClassLoader.getSystemClassLoader()
来获取它。 - 除了系统提供的类加载器以外,开发人员可以通过继承
java.lang.ClassLoader
类的方式实现自己的类加载器,以满足一些特殊的需求。
- 引导类加载器(bootstrap class loader):它用来加载 Java 的核心库,是用原生代码来实现的,并不继承自
- 双亲委派模型是一种组织类加载器之间关系的一种规范,他的工作原理是:如果一个类加载器收到了类加载的请求,它不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,这样层层递进,最终所有的加载请求都被传到最顶层的启动类加载器中,只有当父类加载器无法完成这个加载请求(它的搜索范围内没有找到所需的类)时,才会交给子类加载器去尝试加载。
49. xml解析方式
参考java文章:JAXB(Java Architecture for XML Binding) 点击打开链接
50. Statement和PreparedStatement之间的区别
关系:PreparedStatement继承自Statement,都是接口
区别:PreparedStatement可以使用占位符,是预编译的,批处理比Statement效率高
51.数据库事物隔离级别
- (一)可读取未确认(Read uncommitted)写事务阻止其他写事务,避免了更新遗失。但是没有阻止其他读事务。 存在的问题:脏读。即读取到不正确的数据,因为另一个事务可能还没提交最终数据,这个读事务就读取了中途的数据,这个数据可能是不正确的。 解决办法就是下面的“可读取确认”。
- (二)可读取确认(Read committed)写事务会阻止其他读写事务。读事务不会阻止其他任何事务。 存在的问题:不可重复读。即在一次事务之间,进行了两次读取,但是结果不一样,可能第一次id为1的人叫“李三”,第二次读id为1的人就叫了“李四”。因为读取操作不会阻止其他事务。 解决办法就是下面的“可重复读”。
- (三)可重复读(Repeatable read)
读事务会阻止其他写事务,但是不会阻止其他读事务。 存在的问题:幻读。可重复读阻止的写事务包括update和delete(只给存在的表加上了锁),但是不包括insert(新行不存在,所以没有办法加锁),所以一个事务第一次读取可能读取到了10条记录,但是第二次可能读取到11条,这就是幻读。 解决办法就是下面的“串行化”。 - (四)可串行化(Serializable)
读加共享锁,写加排他锁。这样读取事务可以并发,但是读写,写写事务之间都是互斥的,基本上就是一个个执行事务,所以叫串行化。