Java面试指南

0一、String 为何是 final


  一、String 类是一个不可变类,被 final 修改的类不能被继承,这样提升了 String 类使用的安全性。
  二、String 类的主要变量 value[]都被设计成private final 的,这样在多线程时,对 String对象的访问是能够保证安全。
  三、JVM 对 final 修饰的类进行了编译优化,设计成 final,JVM 不用对相关方法在虚函数表中查询,直接定位到 String 类的相关方法调用,提升了执行效率。面试


0二、Class.forName 和 和 ClassLoader 的区别


  Class.forNameClassLoader 都是用来装载类的,对于类的装载通常为分三个阶段加载、连接、编译,它们装载类的方式是有区别。
  首先看一下 Class.forName(..),forName(..)方法有一个重载方法 forName(className,boolean,ClassLoader)。 它有三个参数,第一个参数是类的包路径,第二个参数是 boolean类型,为 true 地表示 Loading 时会进行初始化,第三个就是指定一个加载器;当你调用class.forName(..)时,默认调用的是有三个参数的重载方法,第二个参数默认传入 true,第三个参数默认使用的是当前类加载时用的加载器。
  ClassLoader.loadClass()也有一个重载方法,从源码中能够看出它默认调的是它的重载方法 loadClass(name, false),当第二参数为 false 时,说明类加载时不会被连接。 这也是二者之间最大区别,前者在加载的时候已经初始化,后者在加载的时候尚未连接。若是你须要在加载时初始化一些东西,就要用 Class.forName 了,好比咱们经常使用的驱动加载,实际上它的注册动做就是在加载时的一个静态块中完成的。因此它不能被 ClassLoader 加载代替。数据库


0三、进程和线程的区别


image.png

  定义:进程是具备必定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位。线程是进程的一个实体,是 CPU 调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。
  特色:
  一、一个进程能够拥有不少个线程,但每一个线程只属于一个进程。
  二、线程相对进程而言,划分尺度更小,并发性能更高。
  三、进程在执行过程当中拥有独立的内存单元,而多个线程共享内存,从而极大地提升了程序的运行效率。
  四、线程必须依赖应用,在应用中调度,每一个线程必须执行的入口、出口、执行序列,线程是不可以独立存在运行的。
  五、进程是资源分配的基本单位,线程是处理机调度的基本单位,全部的线程共享其所属进程的全部资源与代码。
  六、多线程的意义在于一个应用程序中,有多个执行部分能够同时执行。但操做系统并无将多个线程看作多个独立的应用,来实现进程的调度和管理以及资源分配。
编程


0四、Java 的引用类型有哪几种


  Java 自从 JDK1.2 版本开始,引入四种引用的类型,它们由强到弱依次是:强引用(StongReference) 、软引用(SoftReference)、弱引用(**WeakReference **)、虚引用(PhantomReference) ,它们的各自特色及使用领域。
  强引用: 代码中最经常使用看到的 Object o = new Object();这里就是强引用,只要引用在,GC 就不回收它,若是 JVM 内存空间不足会抛出 OutOfMemoryError。
  软引用:一般用描述一些有用但不是必需的对象,若是 JVM 内存不足时,会被 GC,一般被用来做为一些缓存模块的设计,并且不容易 OOM。
  弱引用:比软引用还低级别的引用,软引用通常是内存不足时回收,而弱引用只要被 GC 扫描线程发现就会回收掉,即使是 JVM 内存还充足的状况下。
  虚引用:如其名,虚无般的存在,彻底不会影响对象的生命周期,若是一个对象仅持有虚引用,就如同没有引用同样,可能随时被回收掉,通常会与强用队列关联使用,通常只用于对象回收的事件传递。数组


0五、Http报文结构


image.png

image.png
  上图为请求报文,包括“请求行①②③”、“请求头部④”、“请求包体⑤”,请求行①②③之间是用空格隔开的,面试回答时捡主要的说,不必把全部的参数都介绍一下。
  请求报文包括 “请求行”“ 请 求头部”“ 请求包体”;请求行中主要包括:请求方式、请求地址、Http 版本,它们之间用空格分开。请求头部主要包括 : Accept: 告 诉 服 务 端 客 户 端 接 受 什 么 类 型 的 响 应 ; Accept-Language:客户端可接受的天然语言; User-Agent:请求端的浏览器以及服务器类型; Accept-Encoding:客户端可接受的编码压缩格式; Accept-Charset:可接受的应答的字符集; Host:请求的主名,容许多个域名同处一个 IP 地址,即虚拟主机; connection:链接方式(close 或 keep-alive); Cookie:存储于客户端扩展字段,向同一域名的服务端发送属于该域的 cookie;空行:最后一个请求头以后是一个空行,发送回车符和换行符,通知服务器如下再也不有请求头;
  请求包体:也是请求正文,业务报文。
  响应报文包括**“状态行”** 、 “响应头部”“响应包体”;状态行中包括:Http 协议版本、状态码以及状态码描述(经常使用状态码及描述, 500:服务器内部错误, 404:页面找不到, 200OK:表示请求成功返回, 403:服务器收到请求但拒绝服务;其它的 1xx,2xx,3xx,4xx,5xx系,你们能够网上查找一下,蛮重要,能说出来就行)。
  响应头部, Server:响应服务器类型; Content-Type:响应数据的文档类型; Cache-Control:响应输出到客户端后,服务端经过该报文头属告诉客户端如何控制响应内容的缓存。
  响应包体,真正的业务报文,也就是请求指望的返回数据。


0六、Http 如何 处理长链接


  Http目前有两个版本分别是 Http1.0 和 Http1.1,Http1.0 默认是短链接,若是须要长链接支持,须要加上Connection: Keep-alive;Http1.1 的版本默认是支持长链接请求的方式,能够在抓取的请求中看到 Connection: Keep-alive,若是不想用长链接,须要在报文首部加上 Connection:close;对于默认的长链接能够经过 Keep-Alive:timeout=N 进行超时时间设置。
  [追问]对长链接数据传输完成的识别:
  第一种:经过 Content-Length 指示的大小,若是传的报文长度达到了 Content-Length,则认为传输完成。
  第二种:动态请求生成的文件中每每不包含 Content-Length,每每是经过分块传输入,服务器不能预先判断文件大小,这里要经过 Transfer-Encoding:chunked 模式来传输数据。Chunked 是按块进行数据传输的,这时候就要根据 chunked 编码来判断,chunked 编码的数据在最后有一个空 chunked 块,代表本次传输数据结束。浏览器


0七、TCP三次握手和四次挥手


image.png
image.png
  图来源于网上,若是要看懂这个图,先来了解一下几个简单的概念:SYN 表示创建链接,FIN 表示关闭链接,ACK 表示响应,序号是随机产生的但做用很大,这里不详细说了,这几个关键字在面试的时候有必要先解释一下。
  TCP 创建链接和断开链接的操做有几个很重要的关键字,分别: SYN 表示请求创建链接、ACK 表示响应、FIN 表示关闭链接请求、随机序列 会 随传送报文 的 字节数增长 (SYN 、FIN 都) 算位的,即使没有字节传送,序列也会增长)
  TCP 创建链接的三次握手, 第一次握手:主机 A 发送位码为 syn=1,随机产生 seq =200的数据包到服务器,主机 B 由 SYN=1 知道,A 要求创建联机;
   第二次握手:主机 B 收到请求后要确认联机信息,向 A 发送 ack 确认序列=(主机 A的 seq+1),syn=1,ack=1,随机产生 seq=500 的包;
   第三次握手:主机 A 收到后检查 ack 确认序列是否正确,即第一次发送的 seq number+1,以及位码 ack 是否为 1,若正确,主机 A 会再发送 ack number=(主机 B 的seq+1),ack=1,主机B收到后确认 seq 值与 ack=1 则链接创建成功。
   【三次握手总结】 主机A发 syn 给主机B,主机B回 ack,syn ,主机 A 回 ack ,三次握手,链接成功。
  四次挥手, 第一次挥手:主机A发送一个FIN,用来关闭客户A到服务器B的数据传送。
   第二 次挥手:主机B收到这个FIN,它发回一个ACK,确认序号为收到的序号加 1。和SYN同样,一个FIN将占用一个序号。
   第三 次挥手:主机B关闭与主机A的链接,发送一个 FIN 给主机 A。


0八、线程启动用 start 方法仍是run


  下面是 JDK 中 start()方法的源码,能够看到 start()调用的是 start0(),而start0()是一个 native 方法,经过注释能够知道,它的做用主要是为线程分配系统资源的;而 run 只是一个普通的方法,因此线程的启动是经过 start 方法实现的。缓存

public synchronized void start() {
		if (threadStatus != 0 || this != me)
			throw new IllegalThreadStateException();
		group.add(this);
		start0();
		if (stopBeforeStart) {
			stop0(throwableFromStop);
		}
	}
	private native void start0();
复制代码

0九、ThreadLocal 的基本原理


  下面两个问题是一个同窗面试的时候遇到的,网上也能看到,问题不难但平时不留意也不太容易回答。一、每一个线程的变量副本是存储在哪里的?二、threadlocal 是什么时候初始化的?变量副本是如何为共享的那个变量赋值的?回答这样的问题,建议你们看一下 JDK相关 threadlocal 部分的源码,下面只引用部分源码来解释说明。
  ThreadLocal 并不是是线程的本地实现,而是线程的本地变量,它归附于具体的线程,为每一个使用该变量的线程提供一个副本,每一个线程均可以独立的操做这个副本,在外面看来,貌似每一个线程都有一份变量。线程的变量存在哪里,这里能够结果 ThreadLocal 的源码说明,这里看一下 get 实现安全

public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null)
                return (T)e.value;
        }
        return setInitialValue();
    }
复制代码

  在 get 方法中,会先得到当前线程对象,而后传到 getMap()中获取 ThreadLocalMap对象,咱们要的变量副本是从 ThreadLocalMap 对象中取出来的,可见每一个线程的变量副本是保存在 ThreadLocalMap 对象里,而跟一下代码能够看到 ThreadLocalMap 是在 Thread中声明实现的,因此 每一个线程的变量副本就保 存 在相应线程的 ThreadLocalMap 对象中。
  第二个问题,能够理解 ThreadLocal 如何把变量的副本复制而且初始化的(声明和初始化),这里看一下源码中的 set 方法实现bash

public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }
复制代码

  当第一次调用 set 方法时,获取的 ThreadLocalMap 对象为空,这里会调用 createMap方法建立一个 ThreadLocalMap 对象,而且完成相应的初始,将 value 值存放进去。后面再次调用将会直接从线程中获取ThreadLocalMap 对象,而后将副本保存进去。服务器


十、JVM内存泄露的缘由有哪些


  这个问题看似简单,却用一个问题考察了 JVM 不少个相关的知识点,回答这个问题你首先要了解 JVM 的结构、对象的分配与存储、GC 的原理等,但你看到此的时候若是对上面的知识点还不是很熟悉的话,先翻开其相关知识点,以前都已经谈到过,而后这个问题就容易回答了。
  JVM 结构上通常分为堆内存、栈内存、方法区,内存泄露可能会发生在任何一个位置;在 JVM 划分上方法区一般划给堆,因此这两块能够一块儿。而栈内存一般是用来存放普通变量和对象引用,回收速度速度快,通常不会形成内存泄露,一旦溢出一般是栈内存大小分配不合理,或者可能显示的将对象空间分配到栈内存来追求效率形成的。下面咱们重点探讨堆内存的溢出(根据面试的场景来判断有没有谈栈内存这块)。
  参考: 首先形成 Java JVM 泄露的主要缘由:JVM 未及时的对垃圾进行回收形成的;当对象失去引用且持续占有内存或无用对象的内存得不到及时释放,从而形成内存空间的浪费称为内存泄漏。形成这种对象没法及时释放致使内存泄露的缘由,能够简单的为归分两类。
  一是基 于设计方面 :一、对应用加载数据级别判断失误,从而致使 JVM 内存分配不合理(企业单机部署应用常见到)。二、应用请求的常链接设计,常链接会一直占用后台资源,不能及时释放。三、数据库操做时,存在不少耗时链接,致使大量资源不能释放。四、大量的监听设计等。
  二 是基于开发方面:一、大量静态变量的使用(静态变量的生成周期与应用一致),若是静态引用指向的是集合或者数据,会一直占用资源。二、不合理的方法使用,好比 jdk6以前的 substring 就可能致使内存泄露。三、数据库链接未能及时关闭,刚工做不久的同窗容易忽略。四、单例模式使用,单例一般用来加载资源信息,但若是加载信息里有大量的集合、数组等对象,这些资源会一直驻留内存中,不易释放。五、在循环中建立复杂对象、一次性读取加载大量信息到内存中,都有可能形成内存泄露。cookie


十一、OO的设计原则


  面向对象设计原则一般归结为五大类,
  第 一 “单 一职责原则” (SRP):一个设计元素只作一件事,不要随意耦合,多管闲事;
  第二 “开 放 封闭 原则” (OCP):对变动关闭、对扩展开放,提倡基于接口设计,新的须要最好不要去变动已经完成的功能,能够灵活经过接口扩展新功能;
  第三 “里氏 替换原则” (LSP):子类能够替换父类而且出如今父类可以出现的任何地方,这个也是提倡面向接口编程思想的;
  第四 “依赖 倒置原则” (DIP):要依赖于抽象,不要依赖于具体,简单的说就是面对抽象编程,不要过于依赖于细节;
  第 五 “接口 隔离原则” (ISP):使用多个专门的接口比使用单个接口要好,在设计中不要把各类业务类型的东西放到一个接口中形成臃肿。


更多更新请关注........