Java最全面试宝典
2024-10-17 14:30:34 1 举报第二次挥手:被动关闭方收到FIN包后,发送一个ACK给对方,确认序号为收到序号+1(与SYN相同,一个FIN占用一个序号)
第三次挥手:被动关闭方发送一个FIN,用来关闭被动关闭方到主动关闭方的数据传送,也就是告诉主动关闭方,我的数据也发送完了,不会再给你发数据了
第四次挥手:主动关闭方收到FIN后,发送一个ACK给被动关闭方,确认序号为收到序号+1,至此,完成四次挥手
final修饰后不能被继承
java使用的编码是Unicode编码,Unicode编码中包含了汉字,可以存储
Sring str = "aaa"创建了1个对象常量,jvm分配在了常量池中
String str = new String("aaa) 创建了2个对象,一个在常量池中,一个在堆内存中
一共创建了几个对象?
aa和bb都是常量,当执行字符串拼接的时候,创建了一个新的常量aabb
s = s + " world!";
这两行代码执行后,原始的 String 对象中的内容到底变了没有?
1个引用2个常量对象,原来的hello还存在内存中,只是指针已经指向了新的常量hello world地址
在return之前执行
finall对执行结果不会改变
主线程:负责main方法的执行
垃圾回收线程:负责垃圾回收
线程池默认不启动Worker,接受到请求的时候才启动
在volatile变量读操作的前面会加入一个Load屏障,这样就可以保证对这个变量的读取时,如果被别的处理器修改过了,必须得从其他 处理器的高速缓存(或者主内存)中加载到自己本地高速缓存里,保证读到的是最新数据;在之后会加入一个Acquire屏障,禁止volatile读操作之后的任何读写操作会跟volatile读指令重排序。
与volatie读写内存屏障对比一下,是类似的意思。
Acquire屏障其实就是LoadLoad屏障 + LoadStore屏障,
Release屏障其实就是StoreLoad屏障 + StoreStore屏障
CAS有3个操作数:内存值V,旧的预期值A,要修改的新值B。
当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。
CAS的缺点:
CPU开销过大
在并发量比较高的情况下,如果许多线程反复尝试更新某一个变量,却又一直更新不成功,循环往复,会给CPU带来很到的压力。
不能保证代码块的原子性
CAS机制所保证的知识一个变量的原子性操作,而不能保证整个代码块的原子性。比如需要保证3个变量共同进行原子性的更新,就不得不使用synchronized了。
ABA问题
这是CAS机制最大的问题所在。
同步组件对AQS的使用:
AQS是一个抽象类,主是是以继承的方式使用。
AQS本身是没有实现任何同步接口的,它仅仅只是定义了同步状态的获取和释放的方法来供自定义的同步组件的使用。查看源码可知,在java的同步组件中,AQS的子类(Sync等)一般是同步组件的静态内部类,即通过组合的方式使用。
抽象的队列式的同步器,AQS定义了一套多线程访问共享资源的同步器框架,许多同步类实现都依赖于它,如常用的ReentrantLock/Semaphore/CountDownLatch
它维护了一个volatile int state(代表共享资源)和一个FIFO(双向队列)线程等待队列(多线程争用资源被阻塞时会进入此队列)
semaphore有一个构造函数,可以传入一个int型整数n,表示某段代码最多只有n个线程可以访问,如果超出了n,那么请等待,等到某个线程执行完毕这段代码块,下一个线程再进入。
由此可以看出如果Semaphore构造函数中传入的int型整数n=1,相当于变成了一个synchronized了。
在虚拟机执行到monitorenter指令时,首先要尝试获取对象的锁:如果这个对象没有锁定,或者当前线程已经拥有了这个对象的锁,把锁的计数器+1;
当执行monitorexit指令时,将锁计数器-1;当计数器为0时,锁就被释放了。如果获取对象失败了,那当前线程就要阻塞等待,直到对象锁被另外一个线程释放为止。
Java中Synchronize通过在对象头设置标志,达到了获取锁和释放锁的目的。
由于Java层面的线程与操作系统的原生线程有映射关系,如果要将一个线程进行阻塞或唤起都需要操作系统的协助,这就需要从用户态切换到内核态来执行,这种切换代价十分昂贵,很耗处理器时间,现代JDK中做了大量的优化。
一种优化是使用自旋锁,即在把线程进行阻塞操作之前先让线程自旋等待一段时间,可能在等待期间其他线程已经解锁,这时就无需再让线程执行阻塞操作,避免了用户态到内核态的切换。
现代JDK中还提供了三种不同的 Monitor实现,也就是三种不同的锁:
偏向锁(Biased Locking)
轻量级锁
重量级锁
这三种锁使得JDK得以优化 Synchronized的运行,当JVM检测到不同的竞争状况时,会自动切换到适合的锁实现,这就是锁的升级、降级。当没有竞争出现时,默认会使用偏向锁。
JVM会利用CAS操作,在对象头上的 Mark Word部分设置线程ID,以表示这个对象偏向于当前线程,所以并不涉及真正的互斥锁,因为在很多应用场景中,大部分对象生命周期中最多会被一个线程锁定,使用偏向锁可以降低无竞争开销。
如果有另一线程试图锁定某个被偏向过的对象,JVM就撤销偏向锁,切换到轻量级锁实现。
轻量级锁依赖CAS操作 Mark Word来试图获取锁,如果重试成功,就使用普通的轻量级锁否则,进一步升级为重量级锁。
是java内置的关键字,它提供了一种独占的加锁方式。synchronized的获取和释放锁由JVM实现,用户不需要显示的释放锁,非常方便。然而synchronized也有一些问题:
当线程尝试获取锁的时候,如果获取不到锁会一直阻塞。
如果获取锁的线程进入休眠或者阻塞,除非当前线程异常,否则其他线程尝试获取锁必须一直等待。
ReentrantLock:
ReentrantLock是Lock的实现类,是一个互斥的同步锁。ReentrantLock是JDK 1.5之后提供的API层面的互斥锁,需要lock()和unlock()方法配合try/finally语句块来完成。
等待可中断避免,出现死锁的情况(如果别的线程正持有锁,会等待参数给定的时间,在等待的过程中,如果获取了锁定,就返回true,如果等待超时,返回false)
公平锁与非公平锁多个线程等待同一个锁时,必须按照申请锁的时间顺序获得锁,Synchronized锁非公平锁,ReentrantLock默认的构造函数是创建的非公平锁,可以通过参数true设为公平锁,但公平锁表现的性能不是很好。
从功能角度:
ReentrantLock比 Synchronized的同步操作更精细(因为可以像普通对象一样使用),甚至实现 Synchronized没有的高级功能,如:
等待可中断当持有锁的线程长期不释放锁的时候,正在等待的线程可以选择放弃等待,对处理执行时间非常长的同步块很有用。
带超时的获取锁尝试在指定的时间范围内获取锁,如果时间到了仍然无法获取则返回。
可以判断是否有线程在排队等待获取锁。
可以响应中断请求与Synchronized不同,当获取到锁的线程被中断时,能够响应中断,中断异常将会被抛出,同时锁会被释放。
可以实现公平锁。
从锁释放角度:
Synchronized在JVM层面上实现的,不但可以通过一些监控工具监控 Synchronized的锁定,而且在代码执行出现异常时,JVM会自动释放锁定,但是使用Lock则不行,Lock是通过代码实现的,要保证锁定一定会被释放,就必须将 unLock()放到 finally{}中。
从性能角度:
Synchronized早期实现比较低效,对比 ReentrantLock,大多数场景性能都相差较大。
但是在Java6中对其进行了非常多的改进,
在竞争不激烈时:Synchronized的性能要优于 ReetrantLock;
在高竞争情况下:Synchronized的性能会下降几十倍,但是 ReetrantLock的性能能维持常态。
Files. read():读取文件。
Files. write():写入文件。
Files. exists():检测文件路径是否存在。
Files. createFile():创建文件。
Files. createDirectory():创建文件夹。
Files. delete():删除一个文件或目录。
Files. copy():复制文件。
Files. move():移动文件。
一个 ObjectOutputStream(对象流)对象,接着,使用 ObjectOutputStream 对象的 writeObject(Object obj)方法就可以将参数为 obj的对象写出(即保存其状态),要恢复的话则用输入流。
方法克隆对象。让问题在编译的时候暴露出来总是好过把问题留到运行时
public static void main(String[] args) {
// 服务端开启一个端口进行监听
int port = 8080;
ServerSocket serverSocket = null; //服务端
Socket socket; //客户端
InputStream in = null;
OutputStream out = null;
try {
serverSocket = new ServerSocket(port); //通过构造函数创建ServerSocket,指定监听端口,如果端口合法且空闲,服务器就会监听成功
// 通过无限循环监听客户端连接,如果没有客户端接入,则会阻塞在accept操作
while (true) {
System.out.println("Waiting for a new Socket to establish" + " ," + new Date().toString());
socket = serverSocket.accept();//阻塞 三次握手
in = socket.getInputStream();
byte[] buffer = new byte[1024];
int length = 0;
while ((length = in.read(buffer)) > 0) {//阻塞
System.out.println("input is:" + new String(buffer, 0, length) + " ," + new Date().toString());
out = socket.getOutputStream();
out.write("success".getBytes());
System.out.println("Server end" + " ," + new Date().toString());
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 必要的清理活动
if (serverSocket != null) {
try {
serverSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (in != null) {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (out != null) {
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
包括Selector,这是多路复用器,selector会不断轮询注册的channel,如果某个channel上发生了读写事件,selector就会将这些channel获取出来,我们通过SelectionKey获取有读写事件的channel,就可以进行IO操作。一个Selector就通过一个线程,就可以轮询成千上万的channel,这就意味着你的服务端可以接入成千上万的客户端。
public int id = 100001;
public int bufferSize = 2048;
@Override
public void run() {
init();
}
public void init() {
try {
// 创建通道和选择器
ServerSocketChannel socketChannel = ServerSocketChannel.open();
Selector selector = Selector.open();
InetSocketAddress inetSocketAddress = new InetSocketAddress(
InetAddress.getLocalHost(), 4700);
socketChannel.socket().bind(inetSocketAddress);
// 设置通道非阻塞 绑定选择器
socketChannel.configureBlocking(false);
socketChannel.register(selector, SelectionKey.OP_ACCEPT).attach(
id++);
System.out.println("Server started .... port:4700");
listener(selector);
} catch (Exception e) {
}
}
public void listener(Selector in_selector) {
try {
while (true) {
Thread.sleep(1 * 1000);
in_selector.select(); // 阻塞 直到有就绪事件为止
Set<SelectionKey> readySelectionKey = in_selector
.selectedKeys();
Iterator<SelectionKey> it = readySelectionKey.iterator();
while (it.hasNext()) {
SelectionKey selectionKey = it.next();
// 判断是哪个事件
if (selectionKey.isAcceptable()) {// 客户请求连接
System.out.println(selectionKey.attachment()
+ " - 接受请求事件");
// 获取通道 接受连接,
// 设置非阻塞模式(必须),同时需要注册 读写数据的事件,这样有消息触发时才能捕获
ServerSocketChannel serverSocketChannel = (ServerSocketChannel) selectionKey
.channel();
serverSocketChannel
.accept()
.configureBlocking(false)
.register(
in_selector,
SelectionKey.OP_READ
| SelectionKey.OP_WRITE).attach(id++);
System.out
.println(selectionKey.attachment() + " - 已连接");
// 下面这种写法是有问题的 不应该在serverSocketChannel上面注册
/*
* serverSocketChannel.configureBlocking(false);
* serverSocketChannel.register(in_selector,
* SelectionKey.OP_READ);
* serverSocketChannel.register(in_selector,
* SelectionKey.OP_WRITE);
*/
}
if (selectionKey.isReadable()) {// 读数据
System.out.println(selectionKey.attachment()
+ " - 读数据事件");
SocketChannel clientChannel = (SocketChannel) selectionKey.channel();
ByteBuffer receiveBuf = ByteBuffer.allocate(bufferSize);
clientChannel.read(receiveBuf);
System.out.println(selectionKey.attachment()
+ " - 读取数据:" + getString(receiveBuf));
}
if (selectionKey.isWritable()) {// 写数据
System.out.println(selectionKey.attachment()
+ " - 写数据事件");
SocketChannel clientChannel = (SocketChannel) selectionKey.channel();
ByteBuffer sendBuf = ByteBuffer.allocate(bufferSize);
String sendText = "hello\n";
sendBuf.put(sendText.getBytes());
sendBuf.flip(); //写完数据后调用此方法
clientChannel.write(sendBuf);
}
if (selectionKey.isConnectable()) {
System.out.println(selectionKey.attachment()
+ " - 连接事件");
}
// 必须removed 否则会继续存在,下一次循环还会进来,
// 注意removed 的位置,针对一个.next() remove一次
it.remove();
}
}
} catch (Exception e) {
System.out.println("Error - " + e.getMessage());
e.printStackTrace();
}
}
/**
* ByteBuffer 转换 String
*
* @param buffer
* @return
*/
public static String getString(ByteBuffer buffer) {
String string = "";
try {
for (int i = 0; i < buffer.position(); i++) {
string += (char) buffer.get(i);
}
return string;
} catch (Exception ex) {
ex.printStackTrace();
return "";
}
}
}
每个连接发送过来的请求,都会绑定一个buffer,然后通知操作系统去异步完成读,此时你的程序是会去干别的事儿的,等操作系统完成数据读取之后,就会回调你的接口,给你操作系统异步读完的数据。
public final static int PORT = 9888;
private AsynchronousServerSocketChannel server;
public AIOServer() throws IOException {
server = AsynchronousServerSocketChannel.open().bind(
new InetSocketAddress(PORT));
}
public void startWithFuture() throws InterruptedException,
ExecutionException, TimeoutException {
while (true) {// 循环接收客户端请求
Future<AsynchronousSocketChannel> future = server.accept();
AsynchronousSocketChannel socket = future.get();// get() 是为了确保 accept 到一个连接
handleWithFuture(socket);
}
}
public void handleWithFuture(AsynchronousSocketChannel channel) throws InterruptedException, ExecutionException, TimeoutException {
ByteBuffer readBuf = ByteBuffer.allocate(2);
readBuf.clear();
while (true) {// 一次可能读不完
//get 是为了确保 read 完成,超时时间可以有效避免DOS攻击,如果客户端一直不发送数据,则进行超时处理
Integer integer = channel.read(readBuf).get(10, TimeUnit.SECONDS);
System.out.println("read: " + integer);
if (integer == -1) {
break;
}
readBuf.flip();
System.out.println("received: " + Charset.forName("UTF-8").decode(readBuf));
readBuf.clear();
}
}
public void startWithCompletionHandler() throws InterruptedException,
ExecutionException, TimeoutException {
server.accept(null,
new CompletionHandler<AsynchronousSocketChannel, Object>() {
public void completed(AsynchronousSocketChannel result, Object attachment) {
server.accept(null, this);// 再此接收客户端连接
handleWithCompletionHandler(result);
}
@Override
public void failed(Throwable exc, Object attachment) {
exc.printStackTrace();
}
});
}
public void handleWithCompletionHandler(final AsynchronousSocketChannel channel) {
try {
final ByteBuffer buffer = ByteBuffer.allocate(4);
final long timeout = 10L;
channel.read(buffer, timeout, TimeUnit.SECONDS, null, new CompletionHandler<Integer, Object>() {
@Override
public void completed(Integer result, Object attachment) {
System.out.println("read:" + result);
if (result == -1) {
try {
channel.close();
} catch (IOException e) {
e.printStackTrace();
}
return;
}
buffer.flip();
System.out.println("received message:" + Charset.forName("UTF-8").decode(buffer));
buffer.clear();
channel.read(buffer, timeout, TimeUnit.SECONDS, null, this);
}
@Override
public void failed(Throwable exc, Object attachment) {
exc.printStackTrace();
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String args[]) throws Exception {
// new AIOServer().startWithFuture();
new AIOServer().startWithCompletionHandler();
Thread.sleep(100000);
}
}
有一种mmap技术,也就是内存映射,直接将磁盘文件数据映射到内核缓冲区,这个映射的过程是基于DMA引擎拷贝的,同时用户缓冲区是跟内核缓冲区共享一块映射数据的,建立共享映射之后,就不需要从内核缓冲区拷贝到用户缓冲区了。
光是这一点,就可以避免一次拷贝,但是这个过程中还是会用户态切换到内核态去进行映射拷贝,接着再次从内核态切换到用户态, 建立用户缓冲区和内核缓冲区的映射 ,
接着把数据通过Socket发送出去,还是要再次切换到内核态 ,
接着直接把内核缓冲区里的数据拷贝到Socket缓冲区里去,然后再拷贝到网络协议引擎里,发送出去就可以了,最后切换回用户态 。
减少一次拷贝,但是并不减少切换次数,一共是4次切换,3次拷贝
这个零拷贝技术,就是先从用户态切换到内核态,在内核态的状态下,把磁盘上的数据拷贝到内核缓冲区,同时从内核缓冲区拷贝一些 offset和length到Socket缓冲区;接着从内核态切换到用户态,从内核缓冲区直接把数据拷贝到网络协议引擎里去
同时从Socket缓冲区里拷贝一些offset和length到网络协议引擎里去,但是这个offset和length的量很少,几乎可以忽略
只要2次切换,2次拷贝
epoll也需要调用epoll_wait不断轮询就绪链表,期间也可能多次睡眠和唤醒交替,但是它是设备就绪时,调用回调函数,把就绪fd放入就绪链表中,并唤醒在epoll_wait中进入睡眠的进程。虽然都要睡眠和交替,但是select和poll在“醒着”的时候要遍历整个fd集合,而epoll在“醒着”的时候只要判断一下就绪链表是否为空就行了,这节省了大量的CPU时间。这就是回调机制带来的性能提升。
select,poll每次调用都要把fd集合从用户态往内核态拷贝一次,并且要把current往设备等待队列中挂一次,而epoll只要一次拷贝,而且把current往等待队列上挂也只挂一次(在epoll_wait的开始,注意这里的等待队列并不是设备等待队列,只是一个epoll内部定义的等待队列)。这也能节省不少的开销。
对于任意一个类,都能获取这个类的所有属性和方法,
对任意一个对象,都能调用它的任意属性和方法。
这种动态获取的信息及动态调用对象的方法即为Java的反射。
在运行时判断任意一个对象所属的类
在运行时构造一个类的对象
在运行时调用任意一个对象的方法
将类的.class文件加载到jvm中
对类进行解释,执行类中的static块
classLoader:
只干一件事情:将.class文件加载到jvm中,不会执行static中的内容,只有在newInstance才会去执行static块。
Class.forName(className):
内部调用的方法是 Class.forName(className,true,classloader);
第2个boolean参数表示类是否需要初始化, Class.forName(className)默认是需要初始化。
一旦初始化,就会触发目标对象的static块代码执行,static参数也也会被再次初始化。
ClassLoader.loadClass(className):
内部调用的方法是ClassLoader.loadClass(className,false);
第2个 boolean参数,表示目标对象是否进行链接,false表示不进行链接,不进行链接意味着不进行包括初始化等一些列步骤,那么静态块和静态对象就不会得到执行
静态代理通常只代理一个类,动态代理是代理一个接口下的多个实现类。
静态代理事先知道要代理的是什么,而动态代理不知道要代理什么东西,只有在运行时才知道。
动态代理是实现 JDK 里的 InvocationHandler 接口的 invoke 方法,但注意的是代理的是接口,也就是你的业务类必须要实现接口,通过 Proxy 里的 newProxyInstance 得到代理对象。
还有一种动态代理 CGLIB,代理的是类,不需要业务类继承接口,通过派生的子类来实现代理。通过在运行时,动态修改字节码达到修改类的目的。
使用场景:
AOP 编程就是基于动态代理实现的,比如著名的 Spring 框架、Hibernate 框架等等都是动态代理的使用例子。
2.扩展类加载器(Extension) --加载位置 :jre\lib\ext 中
3.系统(应用)类加载器(System\App) --加载位置 :classpath 中
4.自定义加载器(必须继承 ClassLoader)
访问某个类或接口的静态变量,或者对该静态变量赋值
调用类的静态方法
反射(Class.forName("com.hcx.load"))
初始化一个类的子类(会首先初始化子类的父类)
JVM 启动时标明的启动类,即文件名和类名相同的那个类
假如这个类存在直接父类,并且这个类还没有被初始化,那就初始化直接的父类(不适用于接口)
加入类中存在初始化语句(如 static 变量和 static块),那就依次执行这些初始化语句。
对象.getClass(),例如:"hello".getClass()
Class.forName(),例如:Class.forName("java.lang.String")
由于Java的跨平台性,经过编译的Java源程序并不是一个可执行程序,而是一个或多个类文件。当Java程序需要使用某个类时,JVM会确保这个类已经被加载、连接(验证、准备和解析)和初始化。
类的加载是指把类的.class文件中的数据读入到内存中,通常是创建一个字节数组读入.class文件,然后产生与所加载类对应的Class对象。加载完成后,Class对象还不完整,所以此时的类还不可用。当类被加载后就进入连接阶段,这一阶段包括验证、准备(为静态变量分配内存并设置默认的初始值)和解析(将符号引用替换为直接引用)三个步骤。最后JVM对类进行初始化,包括:
①如果类存在直接的父类并且这个类还没有被初始化,那么就先初始化父类;
②如果类中存在初始化语句,就依次执行这些初始化语句。类的加载是由类加载器完成的,类加载器包括:根加载器(BootStrap)、扩展加载器(Extension)、系统加载器(System)和用户自定义类加载器(java.lang.ClassLoader的子类)。从Java 2(JDK 1.2)开始,类加载过程采取了父亲委托机制(PDM)。PDM更好的保证了Java平台的安全性,在该机制中,JVM自带的Bootstrap是根加载器,其他的加载器都有且仅有一个父类加载器。类的加载首先请求父类加载器加载,父类加载器无能为力时才由其子类加载器自行加载。JVM不会向Java程序提供对Bootstrap的引用。
Survivor的存在意义,就是减少被送到老年代的对象,进而减少Full GC的发生,Survivor的预筛选保证,只有经历16次Minor GC还能在新生代中存活的对象,才会被送到老年代。
设置两个Survivor区最大的好处就是解决了碎片化,刚刚新建的对象在Eden中,经历一次Minor GC,Eden中的存活对象就会被移动到第一块survivor space S0,Eden被清空;等Eden区再满了,就再触发一次Minor GC,Eden和S0中的存活对象又会被复制送入第二块survivor space S1(这个过程非常重要,因为这种复制算法保证了S1中来自S0和Eden两部分的存活对象占用连续的内存空间,避免了碎片化的发生)
Java 语言没有提供释放已分配内存的显式操作方法。
要请求垃圾收集,可以调用下面的方法之一:
但方法区和堆就不同了,一个接口的多个实现类需要的内存可能不一样,我们只有在程序运行期间才会知道会创建哪些对象,这部分内存的分配和回收都是动态的,GC 主要关注的是这部分内存。
ParNew收集器:Serial收集器的多线程版本,也需要stop the world,复制算法。
Parallel Scavenge收集器:新生代收集器,复制算法的收集器,并发的多线程收集器,目标是达到一个可控的吞吐量。如果虚拟机总共运行100分钟,其中垃圾花掉1分钟,吞吐量就是99%。
Serial Old收集器:是Serial收集器的老年代版本,单线程收集器,使用标记整理算法。
Parallel Old收集器:是Parallel Scavenge收集器的老年代版本,使用多线程,标记-整理算法。
CMS(Concurrent Mark Sweep) 收集器:是一种以获得最短回收停顿时间为目标的收集器,标记清除算法,运作过程:初始标记,并发标记,重新标记,并发清除,收集结束会产生大量空间碎片。
G1收集器:标记整理算法实现,运作流程主要包括以下:初始标记,并发标记,最终标记,筛选标记。不会产生空间碎片,可以精确地控制停顿。
CMS收集器以最小的停顿时间为目标的收集器 ,G1收集器可预测垃圾回收的停顿时间。
CMS收集器是使用“标记-清除”算法进行的垃圾回收,容易产生内存碎片,G1收集器使用的是“标记-整理”算法,进行了空间整合,降低了内存空间碎片。
标记-清除算法不需要进行对象的移动,并且仅对不存活的对象进行处理,在存活对象比较多的情况下极为高效,但由于标记-清除算法直接回收不存活的对象,因此会造成内存碎片。
对于方法区中的常量和类,当一个常量没有任何对象引用它,它就可以被回收了。
对于类,如果可以判定它为无用类,就可以被回收了。
时,就将计数器加一,引用失效时,计数器就减一。当一个对象的引用计数器为零时,说
明此对象没有被引用,也就是“死对象”,将会被垃圾回收.
引用计数法有一个缺陷就是无法解决循环引用问题,也就是说当对象 A 引用对象 B,对象
B 又引用者对象 A,那么此时 A,B 对象的引用计数器都不为零,也就造成无法完成垃圾回
收,所以主流的虚拟机都没有采用这种算法。
Roots 没有任何引用链相连时,则说明此对象不可用。
在 java 中可以作为 GC Roots 的对象有以下几种:
虚拟机栈中引用的对象
方法区类静态属性引用的对象
方法区常量池引用的对象
本地方法栈 JNI 引用的对象
虽然这些算法可以判定一个对象是否能被回收,但是当满足上述条件时,一个对象比不一定会被回收。
当一个对象不可达 GC Root 时,这个对象并不会立马被回收,而是出于一个死缓的阶段,若要被真正的回收需要经历两次标记:
如果对象在可达性分析中没有与 GC Root 的引用链,那么此时就会被第一次标记并且进行一次筛选,筛选的条件是是否有必要执行 finalize()方法。当对象没有覆盖 finalize()方法或者已被虚拟机调用过,那么就认为是没必要的。
如果该对象有必要执行 finalize()方法,那么这个对象将会放在一个称为 F-Queue 的对队列中,虚拟机会触发一个 Finalize()线程去执行,此线程是低优先级的,并且虚拟机不会承诺一直等待它运行完,这是因为如果 finalize()执行缓慢或者发生了死锁,那么就会造成 F-Queue 队列一直等待,造成了内存回收系统的崩溃。GC 对处于 F-Queue 中的对象进行第二次被标记,这时,该对象将被移除”即将回收”集合,等待回收。
JDBC接口及相关类在java.sql包和javax.sql包里。
我们可以用它来连接数据库,执行SQL查询,存储过程,并处理返回的结果。
速度要快于 Statement 对象。
作为Statement的子类 , PreparedStatement 继承了 Statement 的所有功能 。三 种方法 execute、 executeQuery 和 executeUpdate 已被更改以使之不再需要参数
Statement 一般用于执行固定的没有参数的SQL
PreparedStatement 一般用于执行有 ?参数预编译的SQL语句。
PreparedStatement支持?操作参数,相对于Statement更加灵活。
PreparedStatement可以防止SQL注入,安全性高于Statement。
②PreparedStatement 尽最大可能提高性能.DB 有缓存机制,相同的预编译语句再次被调用不会再次需要
编译。
③最重要的一点是极大地提高了安全性.Statement 容易被 SQL 注入,而PreparedStatementc 传入的内容不会和 sql 语句发生任何匹配关系。
驱动只有在通过的Class.forName反射机制来加载的时候才会出现
ResultSet.TYPE_SCROLL_INSENSITIVE:游标可以上下移动,一旦它创建后,数据库里的数据再发生修改,对它来说是透明的。
ResultSet.TYPE_SCROLL_SENSITIVE:游标可以上下移动,如果生成后数据库还发生了修改操作,它是能够感知到的。
ResultSet.CONCUR_UPDATABLE:我们可以使用的ResultSet的更新方法来更新里面的数据。
java.sql.BatchUpdateException:当批处理操作执行失败的时候可能会抛出这个异常。这取决于具体的JDBC驱动的实现,它也可能直接抛出基类异常java.sql.SQLException中。
java.sql.SQLWarning:SQL操作出现的警告信息。
java.sql.DataTruncation:字段值由于某些非正常原因被截断了
可以设置连接超时时间
提供日志记录的功能
ResultSet大小的最大阈值设置
通过JNDI的支持,可以为servlet容器提供连接池的功能
executeUpdate 用来执行修改,插入,删除操作,执行SELECT会抛出异常
executeQuery只能进行SELECT,从而得到结果集对象,DELETE、UPDATE、INSERT操作都会抛出异常
数据库连接是一种关键的有限的昂贵的资源,对数据库连接的管理能显著影响到整个应用程序的伸缩性和健壮性,影响到程序的性能指标。数据库连接池正是针对这个问题提出来的。
数据库连接池负责分配、管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是重新建立一个;释放空闲时间超过最大空闲时间的数据库连接来避免因为没有释放数据库连接而引起的数据库连接遗漏。这项技术能明显提高对数据库操作的性能。
数据库连接池在初始化时将创建一定数量的数据库连接放到连接池中,这些数据库连接的数量是由最小数据库连接数来设定的。无论这些数据库连接是否被使用,连接池都将一直保证至少拥有这么多的连接数量。连接池的最大数据库连接数量限定了这个连接池能占有的最大连接数,当应用程序向连接池请求的连接数超过最大连接数量时,这些请求将被加入到等待队列中。
当JDBC的Driver类被加载进来时,它会自己注册到DriverManager类里面,然后我们会把数据库配置信息传成DriverManager.getConnection()方法,DriverManager会使用注册到它里面的驱动来获取数据库连接,并返回给调用的程序。
RowSet一个最大的好处是它可以是离线的,这样使得它更轻量级,同时便于在网络间进行传输。
2、发起TCP的三次握手
3、建立TCP连接后发起http请求
4、服务器响应http请求,浏览器得到HTML代码
5、浏览器解析HTML代码,并请求HTML代码中的资源
6、浏览器对页面进行渲染呈现给用户
7、连接结束
get传输数据是通过URL请求,以field(字段) = value的形式,置于URL后,并用“?”连接,多个请求数据间用“&”连接
get传输数据量小,因为受URL长度限制,但是效率高
get是不安全的,因为URL是可见的,可能会泄漏私密信息
get方式只能支持ASCII字符,向服务器传的中文字符可能会乱码
post传输数据是通过HTTP的post机制。将字段和对应值封存在请求实体中发送给服务器。这个过程用户是不可见的
post可以传输大量数据,所以上传文件时只能用post
post支持标准字符集,可以正确传递中文字符
post 较get安全性高
2xx:成功--表示请求已被成功接收、理解、接受
3xx:重定向--要完成请求必须进行更进一步的操作
4xx:客户端错误--请求有语法错误或请求无法实现
5xx:服务器端错误--服务器未能实现合法的请求
204:请求被受理但没有资源可以返回
206:客户端只是请求资源的一部分,服务器只对请求的部分资源执行GET方法,相应报文中通过Content-Range指定范围的资源。
301:永久性重定向
302:临时重定向
303:与302状态码有相似功能,只是它希望客户端在请求一个URI的时候,能通过GET方法重定向到另一个URI上
304:发送附带条件的请求时,条件不满足时返回,与重定向无关
307:临时重定向,与302类似,只是强制要求使用POST方法
400:请求报文语法有误,服务器无法识别
401:请求需要认证
403:请求的对应资源禁止被访问
404:服务器无法找到对应资源
500:服务器内部错误
503:服务器正忙
重定向是客户端行为
http是超文本传输协议,信息是明文传输。https则是具有安全性的ssl加密传输协议
http默认80端口,https默认443端口
HTTP/2 与 HTTP/1.x 的主要区别?
针对每个域只使用一个多路复用的连接
压缩头部信息减小开销
允许服务器主动推送应答到客户端的缓存中
b、请求首部字段
c、请求内容实体
b、响应首部字段
c、响应内容实体
通过cookie和session解决
HTTPS方式与web服务器通信的步骤?
2、web服务器收到客户端请求后,将网站的证书信息(证书中包含公钥)传送一份给客户端
3、客户端的浏览器与web服务器开始协商SSL连接的安全等级,也就是信息的加密等级
4、客户端的浏览器根据双方同意的安全等级,建立会话秘钥,然后利用网站的公钥将会话秘钥加密,并传送给网站
5、web服务器利用自己的私钥解密出会话秘钥
6、web服务器利用会话秘钥加密与客户端之间的通信
Connection:连接的管理
Cache-Control:缓存的控制
Transfer-Encoding:报文主体的传输编码方式
Accept:可处理的媒体类型
Accept-Charset:可接收的字符集
Accept-Encoding:可接受的内容编码
Accept-Language:可接受的自然语言
Location:令客户端重新定向到的URI
Server:HTTP服务器的安装信息
Content-Type:实体主类的类型
Content-Encoding:实体主体适用的编码方式
Content-Language:实体主体的自然语言
Content-Length:实体主体的的字节数
Content-Range:实体主体的位置范围,一般用于发出部分请求时使用
用TCP协议把数据包送出去后,TCP不会对传送后的情况置之不理,它一定会向对方确认是否成功送达。握手过程中使用了TCP的标志:SYN和ACK
接收端收到后,回传一个带有SYN/ACK标志的数据包以示传达确认信息。
最后,发送端再回传一个带ACK标志的数据包,代表“握手”结束。
第二次挥手:被动关闭方收到FIN包后,发送一个ACK给对方,确认序号为收到序号+1(与SYN相同,一个FIN占用一个序号)
第三次挥手:被动关闭方发送一个FIN,用来关闭被动关闭方到主动关闭方的数据传送,也就是告诉主动关闭方,我的数据也发送完了,不会再给你发数据了
第四次挥手:主动关闭方收到FIN后,发送一个ACK给被动关闭方,确认序号为收到序号+1,至此,完成四次挥手
通常,它用于告知服务端两个请求是否来自同一浏览器,如保持用户的登录状态。
Cookie 使基于无状态的 HTTP 协议记录稳定的状态信息成为了可能。
Session 对象存储特定用户会话所需的属性及配置信息。这样,当用户在应用程序的 Web 页之间跳转时,存储在 Session 对象中的变量将不会丢失,而是在整个用户会话中一直存在下去。当客户端关闭会话,或者 Session 超时失效时会话结束。
是 web 服务器发送给浏览器的一块信息,浏览器会在本地一个文件中给每个 web 服务器存储 cookie。以后浏览器再给特定的 web 服务器发送请求时,同时会发送所有为该服务器存储的 cookie。
是存储在 web 服务器端的一块信息。session 对象存储特定用户会话所需的属性及配置信息。当用户在应用程序的 Web 页之间跳转时,存储在 Session 对象中的变量将不会丢失,而是在整个用户会话中一直存在下去。
cookie 存在于客户端,临时文件夹中;
session存在于服务器的内存中,一个session域对象为一个用户浏览器服务
②安全性
cookie是以明文的方式存放在客户端的,安全性低,可以通过一个加密算法进行加密后存放;
session存放于服务器的内存中,所以安全性好
③网络传输量
cookie会传递消息给服务器;
session本身存放于服务器,不会有传送流量
④生命周期(以30分钟为例)
cookie的生命周期是累计的,从创建时,就开始计时,30分钟后,cookie生命周期结束;
session的生命周期是间隔的,从创建时,开始计时如在30分钟,没有访问session,那么session生命周期被销毁。但是,如果在30分钟内(如在第29分钟时)访问过session,那么,将重新计算session的生命周期。关机会造成session生命周期的结束,但是对cookie没有影响。
⑤访问范围
cookie为多个用户浏览器共享;
session为一个用户浏览器独享
而session机制采用的是在服务器端保持状态的方案。
由于才服务器端保持状态的方案在客户端也需要保存一个标识,所以session机制可能需要借助于cookie机制来达到保存标识的目的。
2、对数组进行循环,调用每个cookie的getName方法
但程序需要为某个客户端的请求创建一个session的时候,服务器首先检查这个客户端的请求里是否包含了一个session标识,称为session id;如果已经包含一个session id则说明以前已经为此客户创建过session,服务器就按照session id把这个session检索出来使用(如果检索不到,可能会新建一个,这种情况可能出现在服务端已经删除了该用户对应的session对象,但用户人为地在请求的URL后面附加上一个JSESSION的参数)。
如果客户请求不包含session id,则为此客户创建一个session并且生成一个与此session相关联的session id,这个session id将在本次响应中返回给客户端保存。
cookie保存在浏览器中,不安全。而session是保存在服务端的。cookie的生命周期很长,而session很短,一般也就几十分钟。
cookie是保存在客户端,session保存在服务器端,cookie保存着session相关信息。
如果cookie没有超时,那么浏览器每次请求都会带上该cookie信息,服务器端根据cookie信息从session缓存中获取相对应的session。这两个信息有一个超时,用户连接即宣告关闭。
会话的超时由服务器来维护,它不同于Cookie的失效日期。
首先,会话一般基于驻留内存的cookie,不是持续性的cookie,因而也就没有截至日期。
即使截取到JSESSIONID cookie,并为它设定一个失效日期发送出去。浏览器会话和服务器会话也会截然不同。
Session 复制,任何一个服务器上的 Session 发生改变(增删改),该节点会把这个 Session 的所有内容序列化,然后广播给所有其它节点。
共享 Session,服务端无状态话,将用户的 Session 等信息使用缓存中间件来统一管理,保障分发到每一个服务器的响应结果都一致。
所以,保持登录的关键不是 cookie,而是通过cookie 保存和传输的 session ID,其本质是能获取用户信息的数据。
除了 cookie,还通常使用 HTTP 请求头来传输。但是这个请求头浏览器不会像 cookie 一样自动携带,需要手工处理。
距离上一次收到客户端发送的session id时间间隔超过了session的最大有效时间
服务器进程被停止
Servlet 是由 Java 提供用于开发 web 服务器应用程序的一个组件,运行在服务端,由 servlet 容器管理,用来生成动态内容。
一个 servlet 实例是实现了特殊接口 Servlet 的 Java 类,所有自定义的 servlet 均必须实现 Servlet 接口。
Servlet 是 html 代码和 Java 代码分离,侧重逻辑控制,mvc 设计思想中 jsp 位于视图层,servlet 位于控制层
web 容器收到以.jsp 为扩展名的 url 请求时,会将访问请求交给tomcat 中 jsp 引擎处理,每个 jsp 页面第一次被访问时,jsp 引擎将 jsp 代码解释为一个 servlet 源程序,接着编译servlet 源程序生成.class 文件,再由web 容器 servlet 引擎去装载执行 servlet 程序,实现页面交互。
request request 域:指一次请求范围内有效,从 http 请求到服务器处理结束,返回响应的整个过程。在这个过程中使用 forward(请求转发)方式跳转多个 jsp,在这些页面里你都可以使用这个变量
session session 域:指当前会话有效范围,浏览器从打开到关闭过程中,转发、重定向均可以使用
application context 域:指只能在同一个 web 中使用,服务器未关闭或者重启,数据就有效
<%!…%>用于在JSP页面中申明变量或方法,可以在该页面中的<%…%>脚本中调用,声明的变量相当于Servlet中的定义的成员变量。
转发: request.getRequestDispatcher("转发路径").forward(request, response);
2.在大多数Browser/Server结构的Web应用中,浏览器直接通过HTML或者JSP的形式与用户交互,响应用户的请求
3.JSP在服务器上执行,并将执行结果输出到客户端浏览器,我们可以说基本上与浏览器无关
2、表单提交中文时出现乱码request.seCharacterEncoding("gb2312″)对请求进行统一编码
3、数据库连接出现乱码 要涉及中文的地方全部是乱码,解决办法:在数据库的数据库URL中加上useUnicode=true&characterEncoding=GBK就可以了。
4、通过过滤器完成
5、在server.xml中的设置编码格式
CGI:来一个请求就创建一个进程,用完就销毁,效率低于servlet
jsp只在第一次执行的时候会转化成servlet,以后每次执行,web容器都是直接执行编译后的servlet,所以jsp和servlet只是在第一次执行的时候不一样,jsp慢一点,以后的执行都是相同的。
在实现上基于函数回调,可以对几乎所有请求进行过滤;
缺点是一个过滤器实例只能在容器初始化时调用一次;
在过滤器中修改字符编码;
在过滤器中修改HttpServletRequest的一些参数,包括:过滤低俗文字、危险字符等。
清理项目:
pre-clean:执行清理前需要完成的工作。
clean:清理上一次构建生成的文件。
post-clean:执行清理后需要完成的工作
mvn clean :调用 Clean 生命周期的 clean 阶段,实际执行 pre-clean 和 clean 阶段
mvn test :调用 Default 生命周期的 test 阶段,实际执行 test 以及之前所有阶段
mvn clean install :调用 Clean 生命周期的 clean 阶段和 Default 生命周期 的 install 阶段,实际执行 pre-clean 和 clean ,install 以及之前所有阶段。
使用“mvn clean package”命令进行项目打包,该命令具体做了什么?
使用资源插件:maven-resources-plugin执行资源文件的处理;
使用编译插件:maven-compiler-plugin编译所有源文件生成class文件至target\classes目录下;
使用资源插件:maven-resources-plugin执行测试资源文件的处理;
使用编译插件:maven-compiler-plugin编译测试目录下的所有源代码;
使用插件:maven-surefire-plugin运行测试用例;
项目依赖了两个jar包,其中A-B-C-D , A-D。由于第二条路径最短,所以项目使用的是第二个D。
JVM提供了同步控制功能:static final,利用JVM进行类加载的时候保证数据同步。
在内部类中创建对象实例,只要应用中不使用内部类,JVM就不会去加载该类,就不会创建我们要创建的单例对象。
什么是观察者设计模式(observer design pattern )?实现一个观察者设计模式。
观察者模式可以实现表示层和数据逻辑层的分离,并定义了稳定的消息更新传递机制,抽象了更新接口,使得可以有各种各样不同的表示层作为具体观察者角色;
观察者模式在观察目标和观察者之间建立一个抽象的耦合;
观察者模式支持广播通信;
观察者模式符合开闭原则(对拓展开放,对修改关闭)的要求。
如果一个观察目标对象有很多直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间;
如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用,可能导致系统崩溃;
观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化。
什么是Servlet?
<validate-on-match>和<background-validation>有什么区别?
这些Handler将组成一个Handler链
Server会启动链上所有的Handler
启动Connector,打开端口,接受客户端请求。
②DispatcherServle 对请求 URL 进行解析,得到请求资源标识符(URL),然后根据该 URL 调用 HandlerMapping,将请求映射到处理器 HandlerExcutionChain;
③DispatchServlet 根据获得 Handler 选择一个合适的 HandlerAdapter 适配器处理;
④Handler 对数据处理完成以后将返回一个 ModelAndView()对象给 DisPatchServlet;
⑤Handler 返回的 ModelAndView()只是一个逻辑视图并不是一个正式的视图,DispatcherSevlet 通过
ViewResolver 试图解析器将逻辑视图转化为真正的视图 View;
⑥DispatcherServle 通过 model 解析出 ModelAndView()中的参数进行解析最终展现出完整的 view 并返回给客户端;
作用:接收请求、响应结果,相当于转发器,有了DispatcherServlet 就减少了其它组件之间的耦合度。
2.处理器映射器HandlerMapping
作用:根据请求的URL来查找Handler
3.处理器适配器HandlerAdapter
注意:在编写Handler的时候要按照HandlerAdapter要求的规则去编写,这样适配器HandlerAdapter才可以正确的去执行Handler。
4.处理器Handler
5.视图解析器 ViewResolver
作用:进行视图的解析,根据视图逻辑名解析成真正的视图(view)
6.视图View
View是一个接口, 它的实现类支持不同的视图类型(jsp,freemarker,pdf等等)
Spring MVC的优点?
通常一个Xml映射文件,都会写一个Dao接口与之对应,这个Dao接口的工作原理是什么?
Mapper接口是没有实现类的,当调用接口方法时,接口全限名+方法名拼接字符串作为key值,可唯一定位一个MapperStatement。在Mybatis中,每一个select、insert、update、delete标签,都会被解析为一个MapperStatement对象。
Mybatis使用<code style="margin: 0px 2px; padding: 2px 4px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; font-size: inherit; line-height: inherit; border-radius: 4px; color: rgb(233, 105, 0); background: rgb(248, 248, 248);">RowBounds</code>对象进行分页,它是针对ResultSet结果集执行的内存分页,而非物理分页。可以在sql内直接书写带有物理分页的参数来完成物理分页功能,也可以使用分页插件来完成物理分页。
分页插件的基本原理是使用Mybatis提供的插件接口,实现自定义插件,在插件的拦截方法内拦截待执行的sql,然后重写sql,根据dialect方言,添加对应的物理分页语句和物理分页参数。
2 insert into xxxx (xxx) values (#{xxxx})
3</insert>
2<if/>标签:对于该标签的执行,当test的值为true时,会将其包含的sql片段拼接到其后所在的sql语句中。
3<where/>标签
4<set/>标签
5<foreach/>标签:实现对于数组于集合的遍历。
6<choose/>标签:该标签只可以包含<when/><otherwise/>,可以包含多个<when/>与一个<otherwise/>。他们联合使用,完成java中的开关语句switch..case功能。
7<when/>标签
8<otherwise/>标签
9<bind/>标签
2、Mapper接口方法的输入参数类型和mapper.xml中定义的每个sql的parameterType的类型相同
3、Mapper接口方法的输出参数类型和mapper.xml中定义的每个sql的resultType的类型相同
4、Mapper.xml文件中的namespace即是mapper接口的类路径。
依赖注入(DI),spring 使用 javaBean 对象的 set 方法或者带参数的构造方法为我们在创建所需对象时将其属性自动设置所需要的值的过程,就是依赖注入的思想。
面向切面编程(AOP),在面向对象编程(oop)思想中,我们将事物纵向抽成一个个的对象。而在面向切面编程中,我们将一个个的对象某些类似的方面横向抽成一个切面,对这个切面进行一些如权限控制、事物管理,记录日志等。公用操作处理的过程就是面向切面编程的思想。AOP 底层是动态代理,如果是接口采用 JDK 动态代理,如果是类采用CGLIB 方式实现动态代理。
2、通过 ProxyFactory 类的 getInvoker 方法使用 ref 生成一个 AbstractProxyInvoker 实例,到这一步就完成具体服务到 Invoker 的转化
C:一致性Consistency (取舍:强一致性、单调一致性、会话一致性、最终一致性、弱一致性)
A:可用性 Availability
P:分区容错性Partition tolerance
三个重要概念:
动态路由表:Zuul支持Eureka路由,手动配置路由,这俩种都支持自动更新
路由定位:根据请求路径,Zuul有自己的一套定位服务规则以及路由表达式匹配
反向代理:客户端请求到路由网关,网关受理之后,在对目标发送请求,拿到响应之后在 给客户端
在该隔离级别,所有事务都可以看到其他未提交事务的执行结果。本隔离级别很少用于实际应用,因为它的性能也不比其他级别好多少。读取未提交的数据,也被称之为脏读(Dirty Read)。
Read Committed(读取提交内容)
这是大多数数据库系统的默认隔离级别(但不是MySQL默认的)。它满足了隔离的简单定义:一个事务只能看见已经提交事务所做的改变。这种隔离级别 也支持所谓的不可重复读(Nonrepeatable Read),因为同一事务的其他实例在该实例处理其间可能会有新的commit,所以同一select可能返回不同结果。
Repeatable Read(可重读)
这是MySQL的默认事务隔离级别,它确保同一事务的多个实例在并发读取数据时,会看到同样的数据行。不过理论上,这会导致另一个棘手的问题:幻读 (Phantom Read)。简单的说,幻读指当用户读取某一范围的数据行时,另一个事务又在该范围内插入了新行,当用户再读取该范围的数据行时,会发现有新的“幻影” 行。
Serializable(可串行化)
这是最高的隔离级别,它通过强制事务排序,使之不可能相互冲突,从而解决幻读问题。简言之,它是在每个读的数据行上加上共享锁。在这个级别,可能导致大量的超时现象和锁竞争。
create or replace procedure 存储过程名(param1 in type,param2 out type)
as
变量 1 类型(值范围);
变量 2 类型(值范围);
Begin
Select count(*) into 变量 1 from 表 A where 列名=param1;
If (判断条件) then
Select 列名 into 变量 2 from 表 A where 列名=param1;
Dbms_output Put_line(‘打印信息’);
Elsif (判断条件) then
Dbms_output Put_line(‘打印信息’);
Else
Raise 异常名(NO_DATA_FOUND);
End if;
Exception
When others then
Rollback;
End;
存储过程参数不带取值范围,in 表示传入,out 表示输出
变量带取值范围,后面接分号
在判断语句前最好先用 count(*)函数判断是否存在该条操作记录
用 select …into…给变量赋值
在代码中抛异常用 raise+异常名