Java面经

1. Java里面如何判断一个对象是否存活

  • 引用计数算法
    • 为每个对象设置一个对象引用计算器,每当有地方引用到这个对象时,计数器加一,每当引用失效的时候,该计数器就自动减一。任何时刻当该对象的引用变为0的时候,说明该对象不再被引用。
    • 缺点:当objA和objB相互引用的时候,他们的引用计数器都是1,他们相互引用着对方,但实际上这两个对象已经把不能被访问了,于是引用计数器不能通知系统回收他们。
    • 可达性分析:算法基本思想是,通过一个根节点RootGC作为一个起始点,从这个节点往下搜索,搜索所走过的路劲就是引用链,当一个对象到RootGC没有引用链的时候,说明对象没有被引用了。按照图论的说法是既是一个GCRoot节点到某个节点不可达。
    • Java中可以用作GCRoot的对象:虚拟机栈中引用的对象,方法区中静态属性引用的对象,方法区中常量引用的对象,本地方法栈中引用的对象。
    • 是否当对象不可达的时候,对象就会马上被回收?并不是。第一次系统检测到RootGC节点不可达的时候,进行第一次的标记,然后系统检查有没有覆盖finalize方法,如果有就执行finalize方法,如果该对象在finalize方法中与任何一个对象进行关联的话便可以不会被回收。

转载自:https://blog.csdn.net/peterchan88/article/details/52992795

2. Linux进程、线程间通信有哪几种?

  • 管道(pipe):管道通信有两种方式,一种是半双工的通信,数据只能单向流动。二是只能在具有亲缘关系的进程间使用,也就是父子进程之间
  • 信号量(semophore):信号量是一个计数器,可以用来控制多个进程堆共享资源的访问。信号是比较复杂的通信方式,用于通知接受进程有某种事件发生,除了用于进程间通信外,进程还可以发送信号给进程本身;不能用于传递消息,只能用于同步
  • 消息队列(message queue):消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点
  • 信号(signal):信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。主要作为进程间以及同一进程不同线程之间的同步手段。
  • 共享内存(shared memory):共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号量,配合使用,来实现进程间的同步和通信。但是共享内存要注意读写问题
  • 套接字(socket):套解字也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同机器间的进程通信

转载自:https://blog.csdn.net/gatieme/article/details/50908749

3. tcp三次握手、四次挥手

  • TCP三次握手是建立连接,四次挥手是关闭连接。
  • TCP的概述:TCP把连接作为最基本的对象,每一条TCP连接都有两个端点,这种端点我们叫作套接字(socket),它的定义为端口号拼接到IP地址即构成了套接字,例如,若IP地址为192.3.4.16 而端口号为80,那么得到的套接字为192.3.4.16:80。
  • TCP报文几个字段的含义:
    • 同步SYN,在连接建立时用来同步序号。当SYN=1,ACK=0,表明是连接请求报文,若同意连接,则响应报文中应该使SYN=1,ACK=1
    • 确认ACK,仅当ACK=1时,确认号字段才有效。TCP规定,在连接建立后所有报文的传输都必须把ACK置1
    • 确认号ack,占4个字节,是期望收到对方下一个报文的第一个数据字节的序号。例如,B收到了A发送过来的报文,其序列号字段是501,而数据长度是200字节,这表明B正确的收到了A发送的到序号700为止的数据。因此,B期望收到A的下一个数据序号是701,于B在发送给A的确认报文段中把确认号置为701
    • 序号seq,占4个字节,TCP连接中传送的字节流中的每个字节都按顺序编号。例如,一段报文的序号字段值是 301 ,而携带的数据共有100字段,显然下一个报文段(如果还有的话)的数据序号应该从401开始
    • 终止FIN,用来释放连接。当FIN=1,表明此报文的发送方的数据已经发送完毕,并且要求释放;
  • TCP三次握手的主要过程:
    • TCP服务器进程先创建传输控制块TCB,时刻准备接受客户进程的连接请求,此时服务器进入了LISTEN(监听)状态。
    • TCP客户进程也先创建传输控制块TCB
    • 然后向服务器发出连接请求报文,这时,报文首部的同步位SYN=1,同时选择一个初始序列号seq=xTCP规定,SYN=1的TCP报文不能携带数据,但是消耗一个序列号。TCP客户端进入SYN-SENT(同步已发送状态)。
    • TCP服务器如果收到报文后同意连接,则发出确认报文,SYN=1,ACK=1,ack=x+1,seq=y。在发送确认报文的时候,TCP服务器也需要为自己初始化一个序列号。TCP服务器进入SYN-RCVD(同步收到状态)。这个报文也不能携带数据,但是要消耗一个序列号。
    • TCP客户进程收到确认后,还要向服务器给出确认。确认报文为SYN=1,ACK=1,ack=y+1,seq=x+1。此时TCP连接建立,客户端进入ESTABLISHED(已建立连接)状态
    • 当服务器收到客户端的确认后,也进入ESTABLISHED状态
  • 问题:为什么最后客户端还要再发送一次确认?
    答案:主要防止已经失效的连接请求报文突然又传送到了服务器,从而产生错误。如果使用的是两次握手建立连接,假设有这样一种场景,客户端发送了第一个请求连接并且没有丢失,只是因为在网络结点中滞留的时间太长了,由于TCP的客户端迟迟没有收到确认报文,以为服务器没有收到,此时重新向服务器发送这条报文,此后客户端和服务器经过两次握手完成连接,传输数据,然后关闭连接。此时此前滞留的那一次请求连接,网络通畅了到达了服务器,这个报文本该是失效的,但是,两次握手的机制将会让客户端和服务器再次建立连接,这将导致不必要的错误和资源的浪费。
    如果采用的是三次握手,就算是那一次失效的报文传送过来了,服务端接受到了那条失效报文并且回复了确认报文,但是客户端不会再次发出确认。由于服务器收不到确认,就知道客户端并没有请求连接。
  • TCP是全双工连接,说以每个方向都必须单独进行关闭。
  • TCP四次挥手过程:
    • 客户端发出连接释放报文,并且停止发送数据。释放报文首部:FIN=1,seq=u。此时客户端进入FIN-WAIT-1(终止等待1)状态TCP规定,释放报文即使不携带数据,也要消耗一个序列号。
    • 服务器收到连接释放报文,发布确认报文,ACK=1,ack=u+1,seq=v。此时服务端进入CLOSE-WAIT(关闭等待)状态。TCP服务器通知高层的应用进程,客户端向服务器的方向就释放了,这时候处于半关闭状态,即客户端已经没有数据要发送了,但是服务器若发送数据,客户端依然要接受。这个状态还要持续一段时间,也就是整个CLOSE-WAIT状态持续的时间。
    • 客户端收到服务器确认请求之后,此时客户端进入FIN-WAIT-2(终止等待2)状态,等待服务器发送连接释放报文(在这之前还需要接受服务器发送的最后的数据)。
    • 服务器将最后的数据发送完毕之后,就向客户端发送释放报文:FIN=1,ack=u+1,假定此时的序列号为seq=w,此时,服务器就进入了LAST-ACK(最后确认)状态,等待客户端的确认。
    • 客户端收到服务器连接释放报文后,必须发出确认,ACK=1,ack=w+1,squ=u+1。此时客户端进入TIME-WAIT(时间等待)状态。需要注意的是,此时的TCP连接还没释放,必须经过2∗MSL(最长报文段寿命)的时间后,当客户端撤销相应的TCB后,才进入CLOSED状态。
    • 服务器只要收到确认报文之后,马上进入CLOSED状态。同样,撤销TCB后,就结束了这次的TCP连接。可以看到,服务器结束TCP连接的时间要比客户端早一些。
  • 问题:为什么客户端最后还要等待2MSL:
    • 保证客户端发送的最后一个ACK报文能够到达服务器,因为这个ACK报文可能丢失,站在服务器的角度看来,我已经发送了FIN+ACK报文请求断开了,客户端还没有给我回应,应该是我发送的请求断开报文它没有收到,于是服务器又会重新发送一次,而客户端就能在这个2MSL时间段内收到这个重传的报文,接着给出回应报文,并且会重启2MSL计时器。
    • 防止类似与“三次握手”中提到了的“已经失效的连接请求报文段”出现在本连接中。**客户端发送完最后一个确认报文后,在这个2MSL时间中,就可以使本连接持续的时间内所产生的所有报文段都从网络中消失。**这样新的连接中不会出现旧连接的请求报文。
  • 问题:为什么连接是三次握手,关闭连接是四次挥手:
    • 因为TCP是全双工。关闭连接时,服务器收到对方的FIN报文,仅仅表示对方不再发送数据,但是对方还能接受数据,而自己并非全部数据都已经发送出去了。
  • 问题:如果已经建立连接,但是客户端出现故障怎么办?
    • TCP还设有一个保活计时器,显然,客户端如果出现故障,服务器不能一直等下去,白白浪费资源。服务器每收到一次客户端的请求后都会重新复位这个计时器,时间通常是设置为2小时,若两小时还没有收到客户端的任何数据,服务器就会发送一个探测报文段,以后每隔75分钟发送一次。若一连发送10个探测报文仍然没反应,服务器就认为客户端出了故障,接着就关闭连接。

转载自:https://blog.csdn.net/qzcsu/article/details/72861891

4. 浏览器输入一个网址,打开网页,其中发生了什么,用了什么协议?

  • DNS域名解析:把输入的网址变成ip,DNS域名解析的过程是首先我们知道我们本地的机器上在配置网络时都会填写DNS,这样本机就会把这个url发给这个配置的DNS服务器,如果能够找到相应的url则返回其ip,否则该DNS将继续将该解析请求发送给上级DNS,整个DNS可以看做是一个树状结构,该请求将一直发送到根直到得到结果。
  • 连接:当解析完之后,我们发送请求,首先要建立一个socket连接。因为socket是通过ip和端口建立的,现在已经拥有了目标ip和端口号,这样我们就可以打开socket连接了。http是一个应用层的协议,在这个层的协议,这是一种通讯规范,也就是因为双方要进行通讯,大家要事先约定一个标准。
  • 请求:连接成功建立后,开始向web服务器发送请求,这个请求一般是GET或POST命令(POST用于FORM参数的传递)。

5. 状态码301和302的区别:

  • 302重定向只是暂时的重定向,搜索引擎会抓取新的内容而保留旧的地址,因为服务器返回302,所以,搜索搜索引擎认为新的网址是暂时的
  • 301重定向是永久的重定向,搜索引擎在抓取新的内容的同时也将旧的网址替换为了重定向之后的网址。

6. String,StringBuffer,StringBuilder之间的区别

  • String是字符串常量
  • StringBuffer是字符串变量,线程安全
  • StringBuilder是字符串变量,非线程安全
  • String 和StringBuffer之间的区别是:String是不可变的对象,因此每次对String对象进行修改都是生成新的String对象,然后将指针指向新的String。而对StringBuffer对象进行修改都是对对象本身进行操作。在某些特别情况下,String对象字符串的拼接其实被JVM解释成StringBuffer对象拼接,所以这些时候,String对象的速度并不会比StringBuffer对象慢。
    以下例子:String a = "This is " + “an” + “apple”;
    StringBuffer b = new StringBuffer(“This is”).append(“an”).append(“apple”);
    这种情况下,生成a的速度比生成b的速度要快是因为在JVM眼里,a=“This is an apple”。如果你的字符串来自其他对象,速度就没这么快
  • 在运行速度方面一般情况下:StringBuilder>StringBuffer>String
  • 在线程安全方面:如果一个StringBuffer对象在字符串缓冲区被多个线程使用时,StringBuffer中很多方法可以带有synchronized关键字,所以可以保证线程是安全的,但StringBuilder的方法则没有该关键字,所以不能保证线程安全,有可能会出现一些错误的操作。所以如果要进行的操作是多线程的,那么就要使用StringBuffer,但是在单线程的情况下,还是建议使用速度比较快的StringBuilder。

转自:https://blog.csdn.net/rmn190/article/details/1492013
https://www.cnblogs.com/su-feng/p/6659064.html

7. JVM的内存分配和垃圾回收机制

  • JVM把内存划分为6个部分:PC寄存器(也叫程序计数器)、虚拟机栈、堆、方法区、运行时常量池、本地方法栈
    • PC寄存器(程序计数器):用于记录当前线程运行时的位置,每一个线程都有一个独立的程序计数器,线程的阻塞、恢复、挂起等一系列操作都需要程序计数器的参与,因此必须是线程私有的。
    • Java虚拟机栈:在创建线程时创建,用来存储栈帧,因此也是线程私有的。Java程序中的方法在执行时,会创建一个栈帧,用于存储方法运行时的临时数据和中间结果,包括局部变量表、操作数栈、动态链接、方法出口等信息。如果栈的深度大于虚拟机允许的最大深度,则抛出StackOverflowError异常。
    • Java堆:java堆被所有线程共享。堆的主要作用是存储对象。如果堆空间不够,但扩展时又不能申请到足够的内存时则抛出OutOfMemoryError异常。
    • 方法区:方法区被各个线程共享,用于存储静态变量、运行时常量池等信息。
    • 本地方法栈:本地方法栈的主要作用就是支持native方法,比如在java中调用C/C++。
  • GC回收机制
    • 哪些内存需要被回收:堆和方法区,堆和方法区的内存都是动态分配的,所以需要动态回收。这部分内存的回收依赖GC完成。
    • 什么时候回收:引用计数法可达性分析。但是JVM只用可达性分析。
  • 怎么回收
    • 标记-清除算法:遍历所有的GC Root,分别标记可达的对象和不可达的对象。缺点:效率低,回收得到的内存不连贯。
    • 复制算法:将内存分为两块,每次只使用一块。当这一块内存满了,就将还存活的对象复制到另一块上,并且严格按照内存地址排列,然后将已使用的那块内存统一回收。优点:能够得到连续的内存。缺点:浪费一半的内存。
    • 分代算法:在Java中,把内存中的对象按生命长短分为新生代,老年代,永久代。新生代指局部变量这种不能存活太久的变量。老年代指一些生命周期长的。永久代指加载的class信息这种永远存在的。新生代和老年代存储在Java虚拟机的堆上,永久代存储在方法区上
分代 回收方法
新生代 复制算法
老年代 标记-清除算法
永久代 ————————

转自:https://blog.csdn.net/u010429424/article/details/77333311

8. Java中的深拷贝与浅拷贝

  • 浅拷贝:浅拷贝的按位拷贝对象,它会创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值。如果属性是内存地址,拷贝的就是内存地址。如果其中一个对象改变这个内存地址存储的值,那么就会影响另一个对象。
  • 深拷贝:深拷贝会拷贝属性,并拷贝属性指向的动态分配的内存。当对象和它所引用的对象一起拷贝时,发生深拷贝。深拷贝比浅拷贝速度慢并且花销大。
  • 举例
 class Body implements Cloneable{
    public Head head;
    public Body() {}
    public Body(Head head) {this.head = head;}

    @Override
    protected Object clone() throws CloneNotSupportedException {
        Body newBody =  (Body) super.clone();
        newBody.head = (Head) head.clone();
        return newBody;
    }

}
class Head implements Cloneable{
    public  Face face;

    public Head() {}
    public Head(Face face){this.face = face;}
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
} 

public class Main{
	public static void main(String[] args) throws CloneNotSupportedException {
  		Body body = new Body(new Head());
    	Body body1 = (Body) body.clone();
   		System.out.println("body == body1 : " + (body == body1) );
    	System.out.println("body.head == body1.head : " +  (body.head == body1.head));
	}
}

在上面的例子中原始的Body和拷贝的Body中的Head是不同的对象,但是因为Head类里面关于Face的拷贝函数不是深拷贝。示意图如下:
在这里插入图片描述
转自:https://blog.csdn.net/u014727260/article/details/55003402

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章