Java面试题汇总与解答

2018最新Java面试题整理(持续完善中…)

声明:面试题(无参考答案)收集自公众号服务端思惟,有不足的地方还请指正,谢谢~html

文章目录

基础篇

基本功

面向对象特征

封装,继承,多态和抽象前端

  1. 封装
    封装给对象提供了隐藏内部特性和行为的能力。对象提供一些能被其余对象访问的方法来改
    变它内部的数据。在 Java 当中,有 3 种修饰符: public, private 和 protected。每一种修饰符
    给其余的位于同一个包或者不一样包下面对象赋予了不一样的访问权限。
    下面列出了使用封装的一些好处:
  • 经过隐藏对象的属性来保护对象内部的状态。
  • 提升了代码的可用性和可维护性,由于对象的行为能够被单独的改变或者是扩展。
  • 禁止对象之间的不良交互提升模块化
  1. 继承
    继承给对象提供了从基类获取字段和方法的能力。继承提供了代码的重用行,也能够在不修改类的状况下给现存的类添加新特性。java

  2. 多态
    多态是编程语言给不一样的底层数据类型作相同的接口展现的一种能力。一个多态类型上的操做能够应用到其余类型的值上面。mysql

  3. 抽象
    抽象是把想法从具体的实例中分离出来的步骤,所以,要根据他们的功能而不是实现细节来建立类。 Java 支持建立只暴漏接口而不包含方法实现的抽象的类。这种抽象技术的主要目的是把类的行为和实现细节分离开。nginx

final, finally, finalize 的区别

  1. final
    修饰符(关键字)若是一个类被声明为final,意味着它不能再派生出新的子类,不能做为父类被继承。所以一个类不能既被声明为 abstract的,又被声明为final的。将变量或方法声明为final,能够保证它们在使用中不被改变。被声明为final的变量必须在声明时给定初值,而在之后的引用中只能读取,不可修改。被声明为final的方法也一样只能使用,不能重载。git

  2. finally
    在异常处理时提供 finally 块来执行任何清除操做。若是抛出一个异常,那么相匹配的 catch 子句就会执行,而后控制就会进入 finally 块(若是有的话)。程序员

  3. finalize
    方法名。Java 技术容许使用 finalize() 方法在垃圾收集器将对象从内存中清除出去以前作必要的清理工做。这个方法是由垃圾收集器在肯定这个对象没有被引用时对这个对象调用的。它是在 Object 类中定义的,所以全部的类都继承了它。子类覆盖 finalize() 方法以整理系统资源或者执行其余清理工做。finalize() 方法是在垃圾收集器删除对象以前对这个对象调用的。github

int 和 Integer 有什么区别

int 是基本数据类型
Integer是其包装类,注意是一个类。
为何要提供包装类呢???
一是为了在各类类型间转化,经过各类方法的调用。不然 你没法直接经过变量转化。
好比,如今int要转为Stringweb

int a=0;
String result=Integer.toString(a);

在java中包装类,比较多的用途是用在于各类数据类型的转化中。
我写几个demo
//经过包装类来实现转化的面试

int num=Integer.valueOf("12");
int num2=Integer.parseInt("12");
double num3=Double.valueOf("12.2");
double num4=Double.parseDouble("12.2");
//其余的相似。经过基本数据类型的包装来的valueOf和parseXX来实现String转为XX
String a=String.valueOf("1234");//这里括号中几乎能够是任何类型
String b=String.valueOf(true);
String c=new Integer(12).toString();//经过包装类的toString()也能够
String d=new Double(2.3).toString();

再举例下。好比我如今要用泛型

List<Integer> nums;

这里<>须要类。若是你用int。它会报错的。

重载和重写的区别

  • override(重写)

1. 方法名、参数、返回值相同。

2. 子类方法不能缩小父类方法的访问权限。

3. 子类方法不能抛出比父类方法更多的异常(但子类方法能够不抛出异常)。

4. 存在于父类和子类之间。

5. 方法被定义为final不能被重写。

  • overload(重载)

1. 参数类型、个数、顺序至少有一个不相同。

2. 不能重载只有返回值不一样的方法名。

3. 存在于父类和子类、同类中。

区别点 重载 重写(覆写)
英文 Overloading Overiding
定义 方法名称相同,参数的类型或个数不一样 方法名称、参数类型、返回值类型所有相同
权限 对权限没要求 被重写的方法不能拥有更严格的权限
范围 发生在一个类中 发生在继承类中

抽象类和接口有什么区别

接口是公开的,里面不能有私有的方法或变量,是用于让别人使用的,而抽象类是能够有私有方法或私有变量的,
另外,实现接口的必定要实现接口里定义的全部方法,而实现抽象类能够有选择地重写须要用到的方法,通常的应用里,最顶级的是接口,而后是抽象类实现接口,最后才到具体类实现。
还有,接口能够实现多重继承,而一个类只能继承一个超类,但能够经过继承多个接口实现多重继承,接口还有标识(里面没有任何方法,如Remote接口)和数据共享(里面的变量全是常量)的做用。

说说反射的用途及实现

Java反射机制主要提供了如下功能:在运行时构造一个类的对象;判断一个类所具备的成员变量和方法;调用一个对象的方法;生成动态代理。反射最大的应用就是框架

Java反射的主要功能:

  • 肯定一个对象的类
  • 取出类的modifiers,数据成员,方法,构造器,和超类.
  • 找出某个接口里定义的常量和方法说明.
  • 建立一个类实例,这个实例在运行时刻才有名字(运行时间才生成的对象).
  • 取得和设定对象数据成员的值,若是数据成员名是运行时刻肯定的也能作到.
  • 在运行时刻调用动态对象的方法.
  • 建立数组,数组大小和类型在运行时刻才肯定,也能更改数组成员的值.

反射的应用不少,不少框架都有用到

spring 的 ioc/di 也是反射…
javaBean和jsp之间调用也是反射…
struts的 FormBean 和页面之间…也是经过反射调用…
JDBC 的 classForName()也是反射…
hibernate的 find(Class clazz) 也是反射…

反射还有一个不得不说的问题,就是性能问题,大量使用反射系统性能大打折扣。怎么使用使你的系统达到最优就看你系统架构和综合使用问题啦,这里就很少说了。

来源:http://uule.iteye.com/blog/1423512

说说自定义注解的场景及实现

(此题自由发挥,就看你对注解的理解了!==)登录、权限拦截、日志处理,以及各类Java框架,如Spring,Hibernate,JUnit 提到注解就不能不说反射,Java自定义注解是经过运行时靠反射获取注解。实际开发中,例如咱们要获取某个方法的调用日志,能够经过AOP(动态代理机制)给方法添加切面,经过反射来获取方法包含的注解,若是包含日志注解,就进行日志记录。

HTTP 请求的 GET 与 POST 方式的区别

GET方法会把名值对追加在请求的URL后面。由于URL对字符数目有限制,进而限制了用在客户端请求的参数值的数目。而且请求中的参数值是可见的,所以,敏感信息不能用这种方式传递。

POST方法经过把请求参数值放在请求体中来克服GET方法的限制,所以,能够发送的参数的数目是没有限制的。最后,经过POST请求传递的敏感信息对外部客户端是不可见的。

参考:https://www.cnblogs.com/wangli-66/p/5453507.html

session 与 cookie 区别

cookie 是 Web 服务器发送给浏览器的一块信息。浏览器会在本地文件中给每个 Web 服务
器存储 cookie。之后浏览器在给特定的 Web 服务器发请求的时候,同时会发送全部为该服
务器存储的 cookie。下面列出了 session 和 cookie 的区别:
不管客户端浏览器作怎么样的设置,session都应该能正常工做。客户端能够选择禁用 cookie,
可是, session 仍然是可以工做的,由于客户端没法禁用服务端的 session。

JDBC 流程

一、 加载JDBC驱动程序:
在链接数据库以前,首先要加载想要链接的数据库的驱动到JVM(Java虚拟机),
这经过java.lang.Class类的静态方法forName(String className)实现。
例如:

try{
//加载MySql的驱动类
Class.forName("com.mysql.jdbc.Driver") ;
}catch(ClassNotFoundException e){
System.out.println("找不到驱动程序类 ,加载驱动失败!");
e.printStackTrace() ;
}

成功加载后,会将Driver类的实例注册到DriverManager类中。

二、 提供JDBC链接的URL

  • 链接URL定义了链接数据库时的协议、子协议、数据源标识。
  • 书写形式:协议:子协议:数据源标识

协议:在JDBC中老是以jdbc开始 子协议:是桥链接的驱动程序或是数据库管理系统名称。
数据源标识:标记找到数据库来源的地址与链接端口。
例如:
jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=gbk;useUnicode=true;(MySql的链接URL)
表示使用Unicode字符集。若是characterEncoding设置为 gb2312或GBK,本参数必须设置为true 。characterEncoding=gbk:字符编码方式。

三、建立数据库的链接

  • 要链接数据库,须要向java.sql.DriverManager请求并得到Connection对象, 该对象就表明一个数据库的链接。
  • 使用DriverManager的getConnectin(String url , String username , String password )方法传入指定的欲链接的数据库的路径、数据库的用户名和 密码来得到。

例如: //链接MySql数据库,用户名和密码都是root

String url = "jdbc:mysql://localhost:3306/test" ;
String username = "root" ;
String password = "root" ;
try{
Connection con = DriverManager.getConnection(url , username , password ) ;
}catch(SQLException se){
System.out.println("数据库链接失败!");
se.printStackTrace() ;
}

四、 建立一个Statement
•要执行SQL语句,必须得到java.sql.Statement实例,Statement实例分为如下3 种类型:
一、执行静态SQL语句。一般经过Statement实例实现。
二、执行动态SQL语句。一般经过PreparedStatement实例实现。
三、执行数据库存储过程。一般经过CallableStatement实例实现。
具体的实现方式:
Statement stmt = con.createStatement() ; PreparedStatement pstmt = con.prepareStatement(sql) ; CallableStatement cstmt = con.prepareCall("{CALL demoSp(? , ?)}") ;
五、执行SQL语句
Statement接口提供了三种执行SQL语句的方法:executeQuery 、executeUpdate 和execute
一、ResultSet executeQuery(String sqlString):执行查询数据库的SQL语句 ,返回一个结果集(ResultSet)对象。
二、int executeUpdate(String sqlString):用于执行INSERT、UPDATE或 DELETE语句以及SQL DDL语句,如:CREATE TABLE和DROP TABLE等
三、execute(sqlString):用于执行返回多个结果集、多个更新计数或两者组合的 语句。 具体实现的代码:
ResultSet rs = stmt.executeQuery(“SELECT * FROM …”) ; int rows = stmt.executeUpdate(“INSERT INTO …”) ; boolean flag = stmt.execute(String sql) ;
六、处理结果
两种状况:
一、执行更新返回的是本次操做影响到的记录数。
二、执行查询返回的结果是一个ResultSet对象。
• ResultSet包含符合SQL语句中条件的全部行,而且它经过一套get方法提供了对这些 行中数据的访问。
• 使用结果集(ResultSet)对象的访问方法获取数据:
while(rs.next()){
String name = rs.getString(“name”) ;
String pass = rs.getString(1) ; // 此方法比较高效
}
(列是从左到右编号的,而且从列1开始)

七、关闭JDBC对象
操做完成之后要把全部使用的JDBC对象全都关闭,以释放JDBC资源,关闭顺序和声 明顺序相反:
一、关闭记录集
二、关闭声明
三、关闭链接对象

if(rs != null){ // 关闭记录集
    try{
      rs.close() ;
    }catch(SQLException e){
      e.printStackTrace() ;
    }
}
if(stmt != null){ // 关闭声明
    try{
      stmt.close() ;
    }catch(SQLException e){
      e.printStackTrace() ;
    }
}
if(conn != null){ // 关闭链接对象
    try{
      conn.close() ;
    }catch(SQLException e){
      e.printStackTrace() ;
    }
}

MVC 设计思想

MVC就是
M:Model 模型
V:View 视图
C:Controller 控制器
模型就是封装业务逻辑和数据的一个一个的模块,控制器就是调用这些模块的(java中一般是用Servlet来实现,框架的话不少是用Struts2来实现这一层),视图就主要是你看到的,好比JSP等.
当用户发出请求的时候,控制器根据请求来选择要处理的业务逻辑和要选择的数据,再返回去把结果输出到视图层,这里多是进行重定向或转发等.

equals 与 == 的区别

值类型(int,char,long,boolean等)都是用 == 判断相等性。对象引用的话,== 判断引用所指的对象是不是同一个。equals是Object的成员函数,有些类会覆盖(override)这个方法,用于判断对象的等价性。例如String类,两个引用所指向的String都是"abc",但可能出现他们实际对应的对象并非同一个(和jvm实现方式有关),所以用==判断他们可能不相等,但用equals判断必定是相等的。

集合

List 和 Set 区别

List,Set都是继承自Collection接口

List特色:元素有放入顺序,元素可重复

Set特色:元素无放入顺序,元素不可重复,重复元素会覆盖掉

(注意:元素虽然无放入顺序,可是元素在set中的位置是有该元素的HashCode决定的,其位置实际上是固定的,加入Set 的Object必须定义equals()方法 ,另外list支持for循环,也就是经过下标来遍历,也能够用迭代器,可是set只能用迭代,由于他无序,没法用下标来取得想要的值。)

Set和List对比:

Set:检索元素效率低下,删除和插入效率高,插入和删除不会引发元素位置改变。

List:和数组相似,List能够动态增加,查找元素效率高,插入删除元素效率低,由于会引发其余元素位置改变。

List 和 Map 区别

List是对象集合,容许对象重复。

Map是键值对的集合,不容许key重复。

Arraylist 与 LinkedList 区别

Arraylist:

优势:ArrayList是实现了基于动态数组的数据结构,由于地址连续,一旦数据存储好了,查询操做效率会比较高(在内存里是连着放的)。

缺点:由于地址连续, ArrayList要移动数据,因此插入和删除操做效率比较低。

LinkedList:

优势:LinkedList基于链表的数据结构,地址是任意的,因此在开辟内存空间的时候不须要等一个连续的地址,对于新增和删除操做add和remove,LinedList比较占优点。LinkedList 适用于要头尾操做或插入指定位置的场景

缺点:由于LinkedList要移动指针,因此查询操做性能比较低。

适用场景分析:

当须要对数据进行对此访问的状况下选用ArrayList,当须要对数据进行屡次增长删除修改时采用LinkedList。

ArrayList 与 Vector 区别

public ArrayList(int initialCapacity)//构造一个具备指定初始容量的空列表。
public ArrayList()//构造一个初始容量为10的空列表。
public ArrayList(Collection<?  extends E> c)//构造一个包含指定 collection 的元素的列表

Vector有四个构造方法:

public Vector()//使用指定的初始容量和等于零的容量增量构造一个空向量。
public Vector(int initialCapacity)//构造一个空向量,使其内部数据数组的大小,其标准容量增量为零。
public Vector(Collection<? extends E> c)//构造一个包含指定 collection 中的元素的向量
public Vector(int initialCapacity,int capacityIncrement)//使用指定的初始容量和容量增量构造一个空的向量

ArrayList和Vector都是用数组实现的,主要有这么三个区别:

  1. Vector是多线程安全的,线程安全就是说多线程访问同一代码,不会产生不肯定的结果。而ArrayList不是,这个能够从源码中看出,Vector类中的方法不少有synchronized进行修饰,这样就致使了Vector在效率上没法与ArrayList相比;

  2. 两个都是采用的线性连续空间存储元素,可是当空间不足的时候,两个类的增长方式是不一样。

  3. Vector能够设置增加因子,而ArrayList不能够。

  4. Vector是一种老的动态数组,是线程同步的,效率很低,通常不同意使用。

适用场景分析:

  1. Vector是线程同步的,因此它也是线程安全的,而ArrayList是线程异步的,是不安全的。若是不考虑到线程的安全因素,通常用ArrayList效率比较高。

  2. 若是集合中的元素的数目大于目前集合数组的长度时,在集合中使用数据量比较大的数据,用Vector有必定的优点。

HashMap 和 Hashtable 的区别

1.hashMap去掉了HashTable 的contains方法,可是加上了containsValue()和containsKey()方法。

2.hashTable同步的,而HashMap是非同步的,效率上逼hashTable要高。

3.hashMap容许空键值,而hashTable不容许。

注意:
TreeMap:非线程安全基于红黑树实现。TreeMap没有调优选项,由于该树总处于平衡状态。

Treemap:适用于按天然顺序或自定义顺序遍历键(key)。

参考:http://blog.csdn.net/qq_22118507/article/details/51576319

HashSet 和 HashMap 区别

set是线性结构,set中的值不能重复,hashset是set的hash实现,hashset中值不能重复是用hashmap的key来实现的。

map是键值对映射,能够空键空值。HashMap是Map接口的hash实现,key的惟一性是经过key值hash值的惟一来肯定,value值是则是链表结构。

他们的共同点都是hash算法实现的惟一性,他们都不能持有基本类型,只能持有对象

HashMap 和 ConcurrentHashMap 的区别

ConcurrentHashMap是线程安全的HashMap的实现。

(1)ConcurrentHashMap对整个桶数组进行了分割分段(Segment),而后在每个分段上都用lock锁进行保护,相对于HashTable的syn关键字锁的粒度更精细了一些,并发性能更好,而HashMap没有锁机制,不是线程安全的。

(2)HashMap的键值对容许有null,可是ConCurrentHashMap都不容许。

HashMap 的工做原理及代码实现

参考:https://tracylihui.github.io/2015/07/01/Java集合学习1:HashMap的实现原理/

ConcurrentHashMap 的工做原理及代码实现

HashTable里使用的是synchronized关键字,这实际上是对对象加锁,锁住的都是对象总体,当Hashtable的大小增长到必定的时候,性能会急剧降低,由于迭代时须要被锁定很长的时间。

ConcurrentHashMap算是对上述问题的优化,其构造函数以下,默认传入的是16,0.75,16。

public ConcurrentHashMap(int paramInt1, float paramFloat, int paramInt2)  {
//…
int i = 0;
int j = 1;
while (j < paramInt2) {
  ++i;
  j <<= 1;
}
this.segmentShift = (32 - i);
this.segmentMask = (j - 1);
this.segments = Segment.newArray(j);
//…
int k = paramInt1 / j;
if (k * j < paramInt1)
  ++k;
int l = 1;
while (l < k)
  l <<= 1;

for (int i1 = 0; i1 < this.segments.length; ++i1)
  this.segments[i1] = new Segment(l, paramFloat);
 }
public V put(K paramK, V paramV)  {
if (paramV == null)
  throw new NullPointerException();
int i = hash(paramK.hashCode()); //这里的hash函数和HashMap中的不同  
return this.segments[(i >>> this.segmentShift & this.segmentMask)].put(paramK, i, paramV, false);
}

ConcurrentHashMap引入了分割(Segment),上面代码中的最后一行其实就能够理解为把一个大的Map拆分红N个小的HashTable,在put方法中,会根据hash(paramK.hashCode())来决定具体存放进哪一个Segment,若是查看Segment的put操做,咱们会发现内部使用的同步机制是基于lock操做的,这样就能够对Map的一部分(Segment)进行上锁,这样影响的只是将要放入同一个Segment的元素的put操做,保证同步的时候,锁住的不是整个Map(HashTable就是这么作的),相对于HashTable提升了多线程环境下的性能,所以HashTable已经被淘汰了。

线程

建立线程的方式及实现

Java中建立线程主要有三种方式:

1、继承Thread类建立线程类

(1)定义Thread类的子类,并重写该类的run方法,该run方法的方法体就表明了线程要完成的任务。所以把run()方法称为执行体。

(2)建立Thread子类的实例,即建立了线程对象。

(3)调用线程对象的start()方法来启动该线程。

package com.thread;

public class FirstThreadTest extends Thread{
    int i = 0;
    //重写run方法,run方法的方法体就是现场执行体
    public void run()
    {
        for(;i<100;i++){
          System.out.println(getName()+"  "+i);
        }
    }
    public static void main(String[] args)
    {
        for(int i = 0;i< 100;i++)
        {
            System.out.println(Thread.currentThread().getName()+"  : "+i);
            if(i==20)
            {
                new FirstThreadTest().start();
                new FirstThreadTest().start();
            }
        }
    }
}

上述代码中Thread.currentThread()方法返回当前正在执行的线程对象。getName()方法返回调用该方法的线程的名字。

2、经过Runnable接口建立线程类

(1)定义runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体一样是该线程的线程执行体。

(2)建立 Runnable实现类的实例,并依此实例做为Thread的target来建立Thread对象,该Thread对象才是真正的线程对象。

(3)调用线程对象的start()方法来启动该线程。

package com.thread;

public class RunnableThreadTest implements Runnable
{

    private int i;
    public void run()
    {
        for(i = 0;i <100;i++)
        {
            System.out.println(Thread.currentThread().getName()+" "+i);
        }
    }
    public static void main(String[] args)
    {
        for(int i = 0;i < 100;i++)
        {
            System.out.println(Thread.currentThread().getName()+" "+i);
            if(i==20)
            {
                RunnableThreadTest rtt = new RunnableThreadTest();
                new Thread(rtt,"新线程1").start();
                new Thread(rtt,"新线程2").start();
            }
        }
    }
}

3、经过Callable和Future建立线程

(1)建立Callable接口的实现类,并实现call()方法,该call()方法将做为线程执行体,而且有返回值。

(2)建立Callable实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值。

(3)使用FutureTask对象做为Thread对象的target建立并启动新线程。

(4)调用FutureTask对象的get()方法来得到子线程执行结束后的返回值

package com.thread;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class CallableThreadTest implements Callable<Integer>
{
    public static void main(String[] args)
    {
        CallableThreadTest ctt = new CallableThreadTest();
        FutureTask<Integer> ft = new FutureTask<>(ctt);
        for(int i = 0;i < 100;i++)
        {
            System.out.println(Thread.currentThread().getName()+" 的循环变量i的值"+i);
            if(i==20)
            {
                new Thread(ft,"有返回值的线程").start();
            }
        }
        try
        {
            System.out.println("子线程的返回值:"+ft.get());
        } catch (InterruptedException e)
        {
            e.printStackTrace();
        } catch (ExecutionException e)
        {
            e.printStackTrace();
        }
    }
    @Override
    public Integer call() throws Exception
    {
        int i = 0;
        for(;i<100;i++)
        {
            System.out.println(Thread.currentThread().getName()+" "+i);
        }
        return i;
    }
}

建立线程的三种方式的对比

采用实现Runnable、Callable接口的方式创见多线程时,优点是:

线程类只是实现了Runnable接口或Callable接口,还能够继承其余类。

在这种方式下,多个线程能够共享同一个target对象,因此很是适合多个相同线程来处理同一份资源的状况,从而能够将CPU、代码和数据分开,造成清晰的模型,较好地体现了面向对象的思想。

劣势是:

编程稍微复杂,若是要访问当前线程,则必须使用Thread.currentThread()方法。

使用继承Thread类的方式建立多线程时优点是:

编写简单,若是须要访问当前线程,则无需使用Thread.currentThread()方法,直接使用this便可得到当前线程。

劣势是:

线程类已经继承了Thread类,因此不能再继承其余父类。

sleep() 、join()、yield()有什么区别

一、sleep()方法

在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操做受到系统计时器和调度程序精度和准确性的影响。 让其余线程有机会继续执行,但它并不释放对象锁。也就是若是有Synchronized同步块,其余线程仍然不能访问共享数据。注意该方法要捕获异常

好比有两个线程同时执行(没有Synchronized),一个线程优先级为MAX_PRIORITY,另外一个为MIN_PRIORITY,若是没有Sleep()方法,只有高优先级的线程执行完成后,低优先级的线程才能执行;但当高优先级的线程sleep(5000)后,低优先级就有机会执行了。
总之,sleep()能够使低优先级的线程获得执行的机会,固然也可让同优先级、高优先级的线程有执行的机会。

二、yield()方法

yield()方法和sleep()方法相似,也不会释放“锁标志”,区别在于,它没有参数,即yield()方法只是使当前线程从新回到可执行状态,因此执行yield()的线程有可能在进入到可执行状态后立刻又被执行,另外yield()方法只能使同优先级或者高优先级的线程获得执行机会,这也和sleep()方法不一样。

三、join()方法

Thread的非静态方法join()让一个线程B“加入”到另一个线程A的尾部。在A执行完毕以前,B不能工做。

Thread t = new MyThread();
       t.start();
       t.join();

保证当前线程中止执行,直到该线程所加入的线程完成为止。然而,若是它加入的线程没有存活,则当前线程不须要中止。

说说 CountDownLatch 原理

参考:

分析CountDownLatch的实现原理

何时使用CountDownLatch

Java并发编程:CountDownLatch、CyclicBarrier和Semaphore

说说 CyclicBarrier 原理

参考:

JUC回顾之-CyclicBarrier底层实现和原理

说说 Semaphore 原理

JAVA多线程–信号量(Semaphore)

JUC回顾之-Semaphore底层实现和原理

说说 Exchanger 原理

java.util.concurrent.Exchanger应用范例与原理浅析

说说 CountDownLatch 与 CyclicBarrier 区别

CountDownLatch CyclicBarrier
减计数方式 加计数方式
计算为0时释放全部等待的线程 计数达到指定值时释放全部等待线程
计数为0时,没法重置 计数达到指定值时,计数置为0从新开始
调用countDown()方法计数减一,调用await()方法只进行阻塞,对计数没任何影响 调用await()方法计数加1,若加1后的值不等于构造方法的值,则线程阻塞
不可重复利用 可重复利用

尽可能把CyclicBarrier和CountDownLatch的区别说通俗点

ThreadLocal 原理分析

Java并发编程:深刻剖析ThreadLocal

讲讲线程池的实现原理

主要是ThreadPoolExecutor的实现原理

Java并发编程:线程池的使用

线程池的几种方式

newFixedThreadPool(int nThreads)
建立一个固定长度的线程池,每当提交一个任务就建立一个线程,直到达到线程池的最大数量,这时线程规模将再也不变化,当线程发生未预期的错误而结束时,线程池会补充一个新的线程

newCachedThreadPool()
建立一个可缓存的线程池,若是线程池的规模超过了处理需求,将自动回收空闲线程,而当需求增长时,则能够自动添加新线程,线程池的规模不存在任何限制

newSingleThreadExecutor()
这是一个单线程的Executor,它建立单个工做线程来执行任务,若是这个线程异常结束,会建立一个新的来替代它;它的特色是能确保依照任务在队列中的顺序来串行执行

newScheduledThreadPool(int corePoolSize)
建立了一个固定长度的线程池,并且以延迟或定时的方式来执行任务,相似于Timer。

举个栗子

private static final Executor exec=Executors.newFixedThreadPool(50);

Runnable runnable=new Runnable(){
    public void run(){
        ...
    }
}
exec.execute(runnable);

Callable<Object> callable=new Callable<Object>() {
    public Object call() throws Exception {
        return null;
    }
};

Future future=executorService.submit(callable);
future.get(); // 等待计算完成后,获取结果
future.isDone(); // 若是任务已完成,则返回 true
future.isCancelled(); // 若是在任务正常完成前将其取消,则返回 true
future.cancel(true); // 试图取消对此任务的执行,true中断运行的任务,false容许正在运行的任务运行完成

参考:

建立线程池的几种方式

线程的生命周期

新建(New)、就绪(Runnable)、运行(Running)、阻塞(Blocked)和死亡(Dead)5种状态

(1)生命周期的五种状态

新建(new Thread)
当建立Thread类的一个实例(对象)时,此线程进入新建状态(未被启动)。
例如:Thread t1=new Thread();

就绪(runnable)
线程已经被启动,正在等待被分配给CPU时间片,也就是说此时线程正在就绪队列中排队等候获得CPU资源。例如:t1.start();

运行(running)
线程得到CPU资源正在执行任务(run()方法),此时除非此线程自动放弃CPU资源或者有优先级更高的线程进入,线程将一直运行到结束。

死亡(dead)
当线程执行完毕或被其它线程杀死,线程就进入死亡状态,这时线程不可能再进入就绪状态等待执行。

天然终止:正常运行run()方法后终止

异常终止:调用**stop()**方法让一个线程终止运行

堵塞(blocked)
因为某种缘由致使正在运行的线程让出CPU并暂停本身的执行,即进入堵塞状态。

正在睡眠:用sleep(long t) 方法可以使线程进入睡眠方式。一个睡眠着的线程在指定的时间过去可进入就绪状态。

正在等待:调用wait()方法。(调用motify()方法回到就绪状态)

被另外一个线程所阻塞:调用suspend()方法。(调用resume()方法恢复)

参考:

线程的生命周期

锁机制

说说线程安全问题

线程安全是指要控制多个线程对某个资源的有序访问或修改,而在这些线程之间没有产生冲突。
在Java里,线程安全通常体如今两个方面:
一、多个thread对同一个java实例的访问(read和modify)不会相互干扰,它主要体如今关键字synchronized。如ArrayList和Vector,HashMap和Hashtable(后者每一个方法前都有synchronized关键字)。若是你在interator一个List对象时,其它线程remove一个element,问题就出现了。
二、每一个线程都有本身的字段,而不会在多个线程之间共享。它主要体如今java.lang.ThreadLocal类,而没有Java关键字支持,如像static、transient那样。

volatile 实现原理

聊聊并发(一)——深刻分析Volatile的实现原理

悲观锁 乐观锁

乐观锁 悲观锁
是一种思想。能够用在不少方面。

好比数据库方面。
悲观锁就是for update(锁定查询的行)
乐观锁就是 version字段(比较跟上一次的版本号,若是同样则更新,若是失败则要重复读-比较-写的操做。)

JDK方面:
悲观锁就是sync
乐观锁就是原子类(内部使用CAS实现)

本质来讲,就是悲观锁认为总会有人抢个人。
乐观锁就认为,基本没人抢。

CAS 乐观锁

乐观锁是一种思想,即认为读多写少,遇到并发写的可能性比较低,因此采起在写时先读出当前版本号,而后加锁操做(比较跟上一次的版本号,若是同样则更新),若是失败则要重复读-比较-写的操做。

CAS是一种更新的原子操做,比较当前值跟传入值是否同样,同样则更新,不然失败。
CAS顶多算是乐观锁写那一步操做的一种实现方式罢了,不用CAS本身加锁也是能够的。

ABA 问题

ABA:若是另外一个线程修改V值假设原来是A,先修改为B,再修改回成A,当前线程的CAS操做没法分辨当前V值是否发生过变化。

参考:

Java CAS 和ABA问题

乐观锁的业务场景及实现方式

乐观锁(Optimistic Lock):
每次获取数据的时候,都不会担忧数据被修改,因此每次获取数据的时候都不会进行加锁,可是在更新数据的时候须要判断该数据是否被别人修改过。若是数据被其余线程修改,则不进行数据更新,若是数据没有被其余线程修改,则进行数据更新。因为数据没有进行加锁,期间该数据能够被其余线程进行读写操做。

乐观锁:比较适合读取操做比较频繁的场景,若是出现大量的写入操做,数据发生冲突的可能性就会增大,为了保证数据的一致性,应用层须要不断的从新获取数据,这样会增长大量的查询操做,下降了系统的吞吐量。

核心篇

数据存储

MySQL 索引使用的注意事项

参考:

mysql索引使用技巧及注意事项

说说反模式设计

参考:

每一个程序员要注意的 9 种反模式

说说分库与分表设计

分表与分库使用场景以及设计方式

分库与分表带来的分布式困境与应对之策

服务端指南 数据存储篇 | MySQL(09) 分库与分表带来的分布式困境与应对之策

说说 SQL 优化之道

sql优化的几种方法

MySQL 遇到的死锁问题

参考:

Mysql并发时经典常见的死锁缘由及解决方法

存储引擎的 InnoDB 与 MyISAM

1)InnoDB支持事务,MyISAM不支持,这一点是很是之重要。事务是一种高级的处理方式,如在一些列增删改中只要哪一个出错还能够回滚还原,而MyISAM就不能够了。

2)MyISAM适合查询以及插入为主的应用,InnoDB适合频繁修改以及涉及到安全性较高的应用

3)InnoDB支持外键,MyISAM不支持

4)从MySQL5.5.5之后,InnoDB是默认引擎

5)InnoDB不支持FULLTEXT类型的索引

6)InnoDB中不保存表的行数,如select count() from table时,InnoDB须要扫描一遍整个表来计算有多少行,可是MyISAM只要简单的读出保存好的行数便可。注意的是,当count()语句包含where条件时MyISAM也须要扫描整个表

7)对于自增加的字段,InnoDB中必须包含只有该字段的索引,可是在MyISAM表中能够和其余字段一块儿创建联合索引

8)清空整个表时,InnoDB是一行一行的删除,效率很是慢。MyISAM则会重建表

9)InnoDB支持行锁(某些状况下仍是锁整表,如 update table set a=1 where user like ‘%lee%’

参考:

MySQL存储引擎之MyIsam和Innodb总结性梳理

数据库索引的原理

参考:

http://blog.csdn.net/suifeng3051/article/details/52669644

为何要用 B-tree

鉴于B-tree具备良好的定位特性,其常被用于对检索时间要求苛刻的场合,例如:
一、B-tree索引是数据库中存取和查找文件(称为记录或键值)的一种方法。
二、硬盘中的结点也是B-tree结构的。与内存相比,硬盘必须花成倍的时间来存取一个数据元素,这是由于硬盘的机械部件读写数据的速度远远赶不上纯电子媒体的内存。与一个结点两个分支的二元树相比,B-tree利用多个分支(称为子树)的结点,减小获取记录时所经历的结点数,从而达到节省存取时间的目的。

汇集索引与非汇集索引的区别

参考:

快速理解汇集索引和非汇集索引

limit 20000 加载很慢怎么解决

LIMIT n 等价于 LIMIT 0,n

此题总结一下就是让limit走索引去查询,例如:order by 索引字段,或者limit前面根where条件走索引字段等等。

参考:

MYSQL分页limit速度太慢优化方法

选择合适的分布式主键方案

参考:

分布式系统惟一ID生成方案汇总

选择合适的数据存储方案

  • 关系型数据库 MySQL

MySQL 是一个最流行的关系型数据库,在互联网产品中应用比较普遍。通常状况下,MySQL 数据库是选择的第一方案,基本上有 80% ~ 90% 的场景都是基于 MySQL 数据库的。由于,须要关系型数据库进行管理,此外,业务存在许多事务性的操做,须要保证事务的强一致性。同时,可能还存在一些复杂的 SQL 的查询。值得注意的是,前期尽可能减小表的联合查询,便于后期数据量增大的状况下,作数据库的分库分表。

  • 内存数据库 Redis

随着数据量的增加,MySQL 已经知足不了大型互联网类应用的需求。所以,Redis 基于内存存储数据,能够极大的提升查询性能,对产品在架构上很好的补充。例如,为了提升服务端接口的访问速度,尽量将读频率高的热点数据存放在 Redis 中。这个是很是典型的以空间换时间的策略,使用更多的内存换取 CPU 资源,经过增长系统的内存消耗,来加快程序的运行速度。

在某些场景下,能够充分的利用 Redis 的特性,大大提升效率。这些场景包括缓存,会话缓存,时效性,访问频率,计数器,社交列表,记录用户断定信息,交集、并集和差集,热门列表与排行榜,最新动态等。

使用 Redis 作缓存的时候,须要考虑数据不一致与脏读、缓存更新机制、缓存可用性、缓存服务降级、缓存穿透、缓存预热等缓存使用问题。

  • 文档数据库 MongoDB

MongoDB 是对传统关系型数据库的补充,它很是适合高伸缩性的场景,它是可扩展性的表结构。基于这点,能够将预期范围内,表结构可能会不断扩展的 MySQL 表结构,经过 MongoDB 来存储,这就能够保证表结构的扩展性。

此外,日志系统数据量特别大,若是用 MongoDB 数据库存储这些数据,利用分片集群支持海量数据,同时使用汇集分析和 MapReduce 的能力,是个很好的选择。

MongoDB 还适合存储大尺寸的数据,GridFS 存储方案就是基于 MongoDB 的分布式文件存储系统。

  • 列族数据库 HBase

HBase 适合海量数据的存储与高性能实时查询,它是运行于 HDFS 文件系统之上,而且做为 MapReduce 分布式处理的目标数据库,以支撑离线分析型应用。在数据仓库、数据集市、商业智能等领域发挥了愈来愈多的做用,在数以千计的企业中支撑着大量的大数据分析场景的应用。

  • 全文搜索引擎 ElasticSearch

在通常状况下,关系型数据库的模糊查询,都是经过 like 的方式进行查询。其中,like “value%” 能够使用索引,可是对于 like “%value%” 这样的方式,执行全表查询,这在数据量小的表,不存在性能问题,可是对于海量数据,全表扫描是很是可怕的事情。ElasticSearch 做为一个创建在全文搜索引擎 Apache Lucene 基础上的实时的分布式搜索和分析引擎,适用于处理实时搜索应用场景。此外,使用 ElasticSearch 全文搜索引擎,还能够支持多词条查询、匹配度与权重、自动联想、拼写纠错等高级功能。所以,能够使用 ElasticSearch 做为关系型数据库全文搜索的功能补充,将要进行全文搜索的数据缓存一份到 ElasticSearch 上,达处处理复杂的业务与提升查询速度的目的。

ElasticSearch 不只仅适用于搜索场景,还很是适合日志处理与分析的场景。著名的 ELK 日志处理方案,由 ElasticSearch、Logstash 和 Kibana 三个组件组成,包括了日志收集、聚合、多维度查询、可视化显示等。

ObjectId 规则

参考:

MongoDB学习笔记~ObjectId主键的设计

mongodb中的_id的ObjectId的生成规则

聊聊 MongoDB 使用场景

参考:

什么场景应该用 MongoDB ?

倒排索引

参考:

什么是倒排索引?

聊聊 ElasticSearch 使用场景

在通常状况下,关系型数据库的模糊查询,都是经过 like 的方式进行查询。其中,like “value%” 能够使用索引,可是对于 like “%value%” 这样的方式,执行全表查询,这在数据量小的表,不存在性能问题,可是对于海量数据,全表扫描是很是可怕的事情。ElasticSearch 做为一个创建在全文搜索引擎 Apache Lucene 基础上的实时的分布式搜索和分析引擎,适用于处理实时搜索应用场景。此外,使用 ElasticSearch 全文搜索引擎,还能够支持多词条查询、匹配度与权重、自动联想、拼写纠错等高级功能。所以,能够使用 ElasticSearch 做为关系型数据库全文搜索的功能补充,将要进行全文搜索的数据缓存一份到 ElasticSearch 上,达处处理复杂的业务与提升查询速度的目的。

缓存使用

Redis 有哪些类型

Redis支持五种数据类型:string(字符串),hash(哈希),list(列表),set(集合)及zset(sorted set:有序集合)。

参考:
Redis 数据类型

Redis 内部结构

参考:

redis内部数据结构深刻浅出

聊聊 Redis 使用场景

随着数据量的增加,MySQL 已经知足不了大型互联网类应用的需求。所以,Redis 基于内存存储数据,能够极大的提升查询性能,对产品在架构上很好的补充。例如,为了提升服务端接口的访问速度,尽量将读频率高的热点数据存放在 Redis 中。这个是很是典型的以空间换时间的策略,使用更多的内存换取 CPU 资源,经过增长系统的内存消耗,来加快程序的运行速度。

在某些场景下,能够充分的利用 Redis 的特性,大大提升效率。这些场景包括缓存,会话缓存,时效性,访问频率,计数器,社交列表,记录用户断定信息,交集、并集和差集,热门列表与排行榜,最新动态等。

使用 Redis 作缓存的时候,须要考虑数据不一致与脏读、缓存更新机制、缓存可用性、缓存服务降级、缓存穿透、缓存预热等缓存使用问题。

Redis 持久化机制

参考:

redis的持久化和缓存机制

Redis 如何实现持久化

参考:

Redis如何实现持久化

Redis 集群方案与实现

参考:

redis集群主流架构方案分析

Redis 为何是单线程的

单纯的网络IO来讲,量大到必定程度以后,多线程的确有优点——但并非单纯的多线程,而是每一个线程本身有本身的epoll这样的模型,也就是多线程和multiplexing混合。

通常这个开头咱们都会跟一个“可是”。
可是。

还要考虑Redis操做的对象。它操做的对象是内存中的数据结构。若是在多线程中操做,那就须要为这些对象加锁。最终来讲,多线程性能有提升,可是每一个线程的效率严重降低了。并且程序的逻辑严重复杂化。
要知道Redis的数据结构并不全是简单的Key-Value,还有列表,hash,map等等复杂的结构,这些结构有可能会进行很细粒度的操做,好比在很长的列表后面添加一个元素,在hash当中添加或者删除一个对象,等等。这些操做还能够合成MULTI/EXEC的组。这样一个操做中可能就须要加很是多的锁,致使的结果是同步开销大大增长。这还带来一个恶果就是吞吐量虽然增大,可是响应延迟可能会增长。
Redis在权衡以后的选择是用单线程,突出本身功能的灵活性。在单线程基础上任何原子操做均可以几乎无代价地实现,多么复杂的数据结构均可以轻松运用,甚至能够使用Lua脚本这样的功能。对于多线程来讲这须要高得多的代价。

并非全部的KV数据库或者内存数据库都应该用单线程,好比ZooKeeper就是多线程的,最终仍是看做者本身的意愿和取舍。单线程的威力实际上很是强大,每核心效率也很是高,在今天的虚拟化环境当中能够充分利用云化环境来提升资源利用率。多线程天然是能够比单线程有更高的性能上限,可是在今天的计算环境中,即便是单机多线程的上限也每每不能知足须要了,须要进一步摸索的是多服务器集群化的方案,这些方案中多线程的技术照样是用不上的,因此单线程、多进程的集群不失为一个时髦的解决方案。

做者:灵剑
连接:https://www.zhihu.com/question/23162208/answer/142424042
来源:知乎
著做权归做者全部。商业转载请联系做者得到受权,非商业转载请注明出处。

缓存奔溃

参考:

Redis持久化

缓存降级

服务降级的目的,是为了防止Redis服务故障,致使数据库跟着一块儿发生雪崩问题。所以,对于不重要的缓存数据,能够采起服务降级策略,例如一个比较常见的作法就是,Redis出现问题,不去数据库查询,而是直接返回默认值给用户。

使用缓存的合理性问题

参考:

Redis实战(一) 使用缓存合理性

消息队列

消息队列的使用场景

主要解决应用耦合,异步消息,流量削锋等问题

消息队列使用的四种场景介绍

消息的重发补偿解决思路

参考:

JMS消息传送机制

消息的幂等性解决思路

参考:

MQ之如何作到消息幂等

消息的堆积解决思路

参考:

Sun Java System Message Queue 3.7 UR1 管理指南

本身如何实现消息队列

参考:

本身动手实现消息队列之JMS

如何保证消息的有序性

参考:

消息队列的exclusive consumer功能是如何保证消息有序和防止脑裂的

框架篇

Spring

BeanFactory 和 ApplicationContext 有什么区别

beanfactory顾名思义,它的核心概念就是bean工厂,用做于bean生命周期的管理,而applicationcontext这个概念就比较丰富了,单看名字(应用上下文)就能看出它包含的范围更广,它继承自bean factory但不只仅是继承自这一个接口,还有继承了其余的接口,因此它不只仅有bean factory相关概念,更是一个应用系统的上下文,其设计初衷应该是一个一应俱全的对外暴露的一个综合的API。

Spring Bean 的生命周期

参考:

Spring Bean生命周期详解

Spring IOC 如何实现

参考:

Spring:源码解读Spring IOC原理

说说 Spring AOP

参考:

Spring AOP详解

Spring AOP 实现原理

参考:

Spring AOP 实现原理

动态代理(cglib 与 JDK)

java动态代理是利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。

而cglib动态代理是利用asm开源包,对代理对象类的class文件加载进来,经过修改其字节码生成子类来处理。

一、若是目标对象实现了接口,默认状况下会采用JDK的动态代理实现AOP
二、若是目标对象实现了接口,能够强制使用CGLIB实现AOP
三、若是目标对象没有实现了接口,必须采用CGLIB库,spring会自动在JDK动态代理和CGLIB之间转换

如何强制使用CGLIB实现AOP?
(1)添加CGLIB库,SPRING_HOME/cglib/*.jar
(2)在spring配置文件中加入<aop:aspectj-autoproxy proxy-target-class=“true”/>

JDK动态代理和CGLIB字节码生成的区别?
(1)JDK动态代理只能对实现了接口的类生成代理,而不能针对类
(2)CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法
由于是继承,因此该类或方法最好不要声明成final

参考:

Spring的两种代理JDK和CGLIB的区别浅谈

Spring 事务实现方式

参考:

Spring事务管理实现方式之编程式事务与声明式事务详解

Spring 事务底层原理

参考:

深刻理解 Spring 事务原理

如何自定义注解实现功能

能够结合spring的AOP,对注解进行拦截,提取注解。

大体流程为:

  1. 新建一个注解@MyLog,加在须要注解申明的方法上面
  2. 新建一个类MyLogAspect,经过@Aspect注解使该类成为切面类。
  3. 经过@Pointcut 指定切入点 ,这里指定的切入点为MyLog注解类型,也就是被@MyLog注解修饰的方法,进入该切入点。
  4. MyLogAspect中的方法经过加通知注解(@Before、@Around、@AfterReturning、@AfterThrowing、@After等各类通知)指定要作的业务操做。

Spring MVC 运行流程

先用文字描述

1.用户发送请求到DispatchServlet

2.DispatchServlet根据请求路径查询具体的Handler

3.HandlerMapping返回一个HandlerExcutionChain给DispatchServlet

HandlerExcutionChain:Handler和Interceptor集合

4.DispatchServlet调用HandlerAdapter适配器

5.HandlerAdapter调用具体的Handler处理业务

6.Handler处理结束返回一个具体的ModelAndView给适配器

ModelAndView:model–>数据模型,view–>视图名称

7.适配器将ModelAndView给DispatchServlet

8.DispatchServlet把视图名称给ViewResolver视图解析器

9.ViewResolver返回一个具体的视图给DispatchServlet

10.渲染视图

11.展现给用户

2、画图解析

SpringMvc的配置

Spring MVC 启动流程

参考:

SpringMVC启动过程详解(li)

Spring 的单例实现原理

参考:

Spring的单例模式底层实现

Spring 框架中用到了哪些设计模式

Spring框架中使用到了大量的设计模式,下面列举了比较有表明性的:

代理模式—在AOP和remoting中被用的比较多。
单例模式—在spring配置文件中定义的bean默认为单例模式。
模板方法—用来解决代码重复的问题。好比. RestTemplate, JmsTemplate, JpaTemplate。
工厂模式—BeanFactory用来建立对象的实例。
适配器–spring aop
装饰器–spring data hashmapper
观察者-- spring 时间驱动模型
回调–Spring ResourceLoaderAware回调接口
前端控制器–spring用前端控制器DispatcherServlet对请求进行分发

Spring 其余产品(Srping Boot、Spring Cloud、Spring Secuirity、Spring Data、Spring AMQP 等)

参考:

说一说Spring家族

Netty

为何选择 Netty

Netty 是业界最流行的 NIO 框架之一,它的健壮性、功能、性能、可定制性和可扩展性在同类框架中都是数一数二的,它已经获得成百上千的商用项目验证,例如 Hadoop 的 RPC 框架 Avro 使用 Netty 做为通讯框架。不少其它业界主流的 RPC 和分布式服务框架,也使用 Netty 来构建高性能的异步通讯能力。

Netty 的优势总结以下:

  • API 使用简单,开发门槛低;
  • 功能强大,预置了多种编解码功能,支持多种主流协议;
  • 定制能力强,能够经过 ChannelHandler 对通讯框架进行灵活的扩展;
  • 性能高,经过与其它业界主流的 NIO 框架对比,Netty 的综合性能最优;
  • 社区活跃,版本迭代周期短,发现的 BUG 能够被及时修复,同时,更多的新功能会被加入;
  • 经历了大规模的商业应用考验,质量获得验证。在互联网、大数据、网络游戏、企业应用、电信软件等众多行业获得成功商用,证实了它彻底知足不一样行业的商用标准。

正是由于这些优势,Netty 逐渐成为 Java NIO 编程的首选框架。

说说业务中,Netty 的使用场景

有关“为什么选择Netty”的11个疑问及解答

原生的 NIO 在 JDK 1.7 版本存在 epoll bug

它会致使Selector空轮询,最终致使CPU 100%。官方声称在JDK1.6版本的update18修复了该问题,可是直到JDK1.7版本该问题仍旧存在,只不过该BUG发生几率下降了一些而已,它并无被根本解决。该BUG以及与该BUG相关的问题单能够参见如下连接内容。

http://bugs.java.com/bugdatabase/view_bug.do?bug_id=6403933

http://bugs.java.com/bugdatabase/view_bug.do?bug_id=2147719

异常堆栈以下:

java.lang.Thread.State: RUNNABLE  
        at sun.nio.ch.EPollArrayWrapper.epollWait(Native Method)  
        at sun.nio.ch.EPollArrayWrapper.poll(EPollArrayWrapper.java:210)  
        at sun.nio.ch.EPollSelectorImpl.doSelect(EPollSelectorImpl.java:65)  
        at sun.nio.ch.SelectorImpl.lockAndDoSelect(SelectorImpl.java:69)  
        - locked <0x0000000750928190> (a sun.nio.ch.Util$2)  
        - locked <0x00000007509281a8> (a java.util.Collections$ UnmodifiableSet)  
        - locked <0x0000000750946098> (a sun.nio.ch.EPollSelectorImpl)  
        at sun.nio.ch.SelectorImpl.select(SelectorImpl.java:80)  
        at net.spy.memcached.MemcachedConnection.handleIO(Memcached Connection.java:217)  
        at net.spy.memcached.MemcachedConnection.run(MemcachedConnection. java:836)

什么是TCP 粘包/拆包

参考:

TCP粘包,拆包及解决方法

TCP粘包/拆包的解决办法

参考:

TCP粘包,拆包及解决方法

Netty 线程模型

参考:

Netty4实战第十五章:选择正确的线程模型

说说 Netty 的零拷贝

参考:

理解Netty中的零拷贝(Zero-Copy)机制

Netty 内部执行流程

参考:

Netty:数据处理流程

Netty 重连实现

参考:

Netty Client 重连实现

微服务篇

微服务

先后端分离是如何作的

参考:

实现先后端分离的心得

微服务哪些框架

Spring Cloud、Dubbo、Hsf等

你怎么理解 RPC 框架

RPC的目的是让你在本地调用远程的方法,而对你来讲这个调用是透明的,你并不知道这个调用的方法是部署哪里。经过RPC能解耦服务,这才是使用RPC的真正目的。

说说 RPC 的实现原理

参考:

你应该知道的 RPC 原理

从零开始实现RPC框架 - RPC原理及实现

说说 Dubbo 的实现原理

dubbo提供功能来说, 提供基础功能-RPC调用 提供增值功能SOA服务治理
dubbo启动时查找可用的远程服务提供者,调用接口时不是最终调用本地实现,而是经过拦截调用(又用上JDK动态代理功能)过程通过一系列的的序列化、远程通讯、协议解析最终调用到远程服务提供者

参考:

Dubbo解析及原理浅析

你怎么理解 RESTful

REST是 一种软件架构风格、设计风格,它是一种面向资源的网络化超媒体应用的架构风格。它主要是用于构建轻量级的、可维护的、可伸缩的 Web 服务。基于 REST 的服务被称为 RESTful 服务。REST 不依赖于任何协议,可是几乎每一个 RESTful 服务使用 HTTP 做为底层协议,RESTful使用http method标识操做,例如:

http://127.0.0.1/user/1 GET 根据用户id查询用户数据
http://127.0.0.1/user POST 新增用户
http://127.0.0.1/user PUT 修改用户信息
http://127.0.0.1/user DELETE 删除用户信息

说说如何设计一个良好的 API

参考:

如何设计一个良好的API?

如何理解 RESTful API 的幂等性

参考:

如何理解RESTful的幂等性

如何保证接口的幂等性

参考:

后端接口的幂等性

说说 CAP 定理、 BASE 理论

参考:

CAP原理和BASE思想

怎么考虑数据一致性问题

参考:

分布式系统事务一致性解决方案

说说最终一致性的实现方案

能够结合MQ实现最终一致性,例如电商系统,把生成订单数据的写操做逻辑经过事务控制,一些可有可无的业务例如日志处理,通知,经过异步消息处理,最终到请求落地。

参考:

系统分布式状况下最终一致性方案梳理

你怎么看待微服务

  • 小:微服务体积小
  • 独:可以独立的部署和运行。
  • 轻:使用轻量级的通讯机制和架构。
  • 松:为服务之间是松耦合的。

微服务与 SOA 的区别

能够把微服务当作去除了ESB的SOA。ESB是SOA架构中的中心总线,设计图形应该是星形的,而微服务是去中心化的分布式软件架构。

参考:

SOA 与 微服务的区别

如何拆分服务

参考:

微服务架构(二): 如何把应用分解成多个服务

微服务如何进行数据库管理

参考:

在微服务中如何管理数据

如何应对微服务的链式调用异常

参考:

踢开绊脚石:微服务难点之服务调用的解决方案

对于快速追踪与定位问题

参考:

微服务架构下,如何实现分布式跟踪?

微服务的安全

参考:

论微服务安全

分布式

谈谈业务中使用分布式的场景

1、解决java集群的session共享的解决方案:
1.客户端cookie加密。(通常用于内网中企业级的系统中,要求用户浏览器端的cookie不能禁用,禁用的话,该方案会失效)。
2.集群中,各个应用服务器提供了session复制的功能,tomcat和jboss都实现了这样的功能。特色:性能随着服务器增长急剧降低,容易引发广播风暴;session数据须要序列化,影响性能。
3.session的持久化,使用数据库来保存session。就算服务器宕机也没事儿,数据库中的session照样存在。特色:每次请求session都要读写数据库,会带来性能开销。使用内存数据库,会提升性能,可是宕机会丢失数据(像支付宝的宕机,有同城灾备、异地灾备)。
4.使用共享存储来保存session。和数据库相似,就算宕机了也没有事儿。其实就是专门搞一台服务器,所有对session落地。特色:频繁的进行序列化和反序列化会影响性能。
5.使用memcached来保存session。本质上是内存数据库的解决方案。特色:存入memcached的数据须要序列化,效率极低。

2、分布式事务的解决方案:
1.TCC解决方案:try confirm cancel。

参考:
为何说传统分布式事务再也不适用于微服务架构?

Session 分布式方案

1.客户端cookie加密。(通常用于内网中企业级的系统中,要求用户浏览器端的cookie不能禁用,禁用的话,该方案会失效)。
2.集群中,各个应用服务器提供了session复制的功能,tomcat和jboss都实现了这样的功能。特色:性能随着服务器增长急剧降低,容易引发广播风暴;session数据须要序列化,影响性能。
3.session的持久化,使用数据库来保存session。就算服务器宕机也没事儿,数据库中的session照样存在。特色:每次请求session都要读写数据库,会带来性能开销。使用内存数据库,会提升性能,可是宕机会丢失数据(像支付宝的宕机,有同城灾备、异地灾备)。
4.使用共享存储来保存session。和数据库相似,就算宕机了也没有事儿。其实就是专门搞一台服务器,所有对session落地。特色:频繁的进行序列化和反序列化会影响性能。
5.使用memcached来保存session。本质上是内存数据库的解决方案。特色:存入memcached的数据须要序列化,效率极低。

分布式锁的场景

好比交易系统的金额修改,同一时间只能又一我的操做,好比秒杀场景,同一时间只能一个用户抢到,好比火车站抢票等等

分布式锁的实现方案

  1. 基于数据库实现分布式锁
  2. 基于缓存实现分布式锁
  3. 基于Zookeeper实现分布式锁

参考:

分布式锁的多种实现方式

分布式事务

参考:

深刻理解分布式事务,高并发下分布式事务的解决方案

集群与负载均衡的算法与实现

参考:

负载均衡算法及手段

说说分库与分表设计

参考:

分表与分库使用场景以及设计方式

分库与分表带来的分布式困境与应对之策

参考:

服务端指南 数据存储篇 | MySQL(09) 分库与分表带来的分布式困境与应对之策

安全&性能

安全问题

安全要素与 STRIDE 威胁

参考:http://blog.720ui.com/2017/security_stride/

防范常见的 Web 攻击

XSS攻击

  • 跨站脚本攻击;
  • 是什么:攻击者向有XSS漏洞的网站中输入恶意的HTML代码,当其浏览器浏览该网站时,这段HTML代码会自- - 动执行。(理论上全部能够输入的地方没有对输入的数据进行处理,都会存在XSS攻击);
  • 危害: 盗取用户cookie,破坏页面结构,重定向到其余网站;
  • 防护:对用户输入的信息进行处理,只容许合法的值;

CSRF攻击

  • 跨站请求伪造
  • 是什么:攻击者盗用了你的身份,以你的名义发送恶意请求;
  • 危害:以你的名义发送邮件,盗取账号,购买东西等;
  • 原理: 首先个登陆某网站,并在本地生成cookie;而后在不登出的状况下,访问危害网站。
  • 防护: 能够从服务端和客户端两方面进行考虑。可是在服务端的效果好。
    1. 随机的cookie
    2. 添加验证码
    3. 不一样的表单包含一个不一样的伪随机值
  • 注意:若是用户在一个站点上同时打开了两个不一样的表单。CSRF保护措施不该该影响到他对任何表单的提交

SQL注入

  • 是什么:经过sql命令假装成正常的http请求参数,传递到服务端,服务器执行sql命令形成对数据库进行攻击
  • 原理:sql语句伪造参数,而后在对参数机型拼接后造成破坏性的sql语句,最后致使数据库收到攻击
  • 防护:
    1. 对参数进行转义
    2. 数据库中的密码不该明文存储,能够对密码使用md5进行加密。

DDOS攻击(分布式拒绝服务攻击)

  • 是什么:简单来讲就是ifasong大量的请求使服务器瘫痪。
  • 被攻击的缘由:服务器带宽不足,不能挡住攻击者的攻击流量。
  • 防护:
    1. 最直接的方法就是增长带宽;
    2. 使用硬件防火墙;
    3. 优化资源使用提升 web server 的负载能力

来源:http://www.javashuo.com/article/p-eezljnnr-hu.html

服务端通讯安全攻防

Base64加密传输
Base64是网络上最多见的用于传输8Bit字节代码的编码方式之一,可是它其实并非一种用于安全领域的加密解密算法。

可是,Base64编码的数据并不会被人用肉眼所直观的理解,因此也有人使用Base64来进行加密解密,这里所说的加密与解密实际是指编码和解码的过程。

这种,加密传输的安全性是很是低的,Base64加密很是容易被人识别并解码。

DES对称加密
DES也是一种很是经常使用的加密方案,咱们会将敏感的信息在通讯过程当中经过DES进行加密传输,而后在客户端和服务端直接进行解码。

此时,做为读者的你,可能会有个疑问,那如何保管密钥呢?其实,想一想,答案就复出水面了,由于客户端和服务端都须要进行解码,因此二者都要存一份密钥。其实,还有一种方案是经过服务端下发,可是下发的时候通讯的安全性也是没有很好的保障。

因此,DES对称加密也是存在必定的安全隐患:密钥可能会泄漏。这边,举个真实的案例,某个APP的资源不错,同事想抓包分析下其服务端通讯的信息结构,可是发现它既然所有采用了DES加密方案,原本想放弃了,可是咱们又回头想一想客户端确定须要密钥对接口的加密的内容作解码才能正常展示,那么密钥确定在app包中,所以咱们又对app进行了反编译,结果成功的获取到了密钥,对服务端通讯的加密信息进行了解码。

AES对称加密
AES和DES相似,相较于DES算法而言,AES算法有着更高的速度和资源使用效率,安全级别也较之更高。通常状况下,用于文件的加密。咱们以前作个不许确测试,AES和DES分别对一个大文件加密,AES的速度大概是DES的5倍。(由于基于工具和环境问题,这个数据不是很准确哟)。

仍然存在一个相同的问题:密钥可能会泄漏。所以,保管好密钥很关键。

升级到HTTPS
这个能够参考上篇博客《HTTPS原理剖析与项目场景》的内容。

HTTPS的价值在于:

内容加密,第三方没法窃听。
身份认证,一旦被篡改,通讯双方会马上发现。
数据完整性。防止内容冒充或者篡改。
这个方案,无法保护敏感数据,若是须要对敏感数据进行加密,仍是须要考虑加密方案。

URL签名
基于OAuth2协议,进行URL签名。这个方案,有不少话题能够分享,后面另开一篇来详细讲解。

值得注意的是,URL签名只能垂直权限管理,但无法保护敏感数据,若是须要对敏感数据进行保护,仍是须要考虑加密方案。

双向RSA加密
RSA双向认证,就是用对方的公钥加密是为了保密,这个只有对方用私钥能解密。用本身的私钥加密是为了防抵赖,能用个人公钥解开,说明这是我发来的。

例如,支付宝的支付接口就是很是典型的RSA双向认证的安全方案。此外,咱们以前的教育资源、敏感验证码出于安全性考虑都借鉴了这个方案。

来源:http://blog.720ui.com/2016/security_data_transmission/

HTTPS 原理剖析

参考:http://www.javashuo.com/article/p-pcavyrrg-dd.html

HTTPS 降级攻击

参考:http://blog.720ui.com/2016/security_https_tls/

受权与认证

参考:https://blog.csdn.net/gdp12315_gu/article/details/79905424

基于角色的访问控制

参考:https://blog.csdn.net/yin767833376/article/details/64907383

基于数据的访问控制

基于角色的访问控制,只验证访问数据的角色,可是没有对角色内的用户作细分。举个例子,用户甲与用户乙都具备用一个角色,可是若是只创建基于角色的访问控制,那么用户甲能够对用户乙的数据进行任意操做,从而发生了越权访问。所以,在业务场景中仅仅使用基于角色的访问控制是不够的,还须要引入基于数据的访问控制。若是将基于角色的访问控制视为一种垂直权限控制,那么,基于数据的访问控制就是一种水平权限控制。在业务场景中,每每对基于数据的访问控制不够重视,举个例子,评论功能是一个很是常见的功能,用户能够在客户端发起评论,回复评论,查看评论,删除评论等操做。通常状况下,只有本人才能够删除本身的评论,若是此时,业务层面没有创建数据的访问控制,那么用户甲能够试图绕过客户端,经过调用服务端RESTful API 接口,猜想评论 ID 并修改评论 ID 就能够删除别人的评论。事实上,这是很是严重的越权操做。除此以外,用户之间每每也存在一些私有的数据,而这些私有的数据在正常状况下,只有用户本身才能访问。

基于数据的访问控制,须要业务层面去处理,可是这个也是最为常常遗落的安全点,须要引发重视。这里,再次使用删除评论的案例,经过 Java 语言进行介绍。在这个案例中,核心的代码片断在于,判断当前用户是不是评论的建立者,若是是则经过,不是则报出没有权限的错误码。那么,这样就能够很好地防止数据的越权操做。

@RestController
@RequestMapping(value = {"/v1/c/apps"})
public class AppCommentController{
    @Autowired
    private AppCommentService appCommentService;
 
@RequestMapping(value = "/{appId:\\d+}/comments/{commentId:\\d+}", 
method = RequestMethod.DELETE)
public void deleteAppCommentInfo(@PathVariable Long appId, 
    @PathVariable Long commentId,
        @AuthenticationPrincipal UserInfo userInfo) {
        AppComment appComment = this.appCommentService.checkCommentInfo(commentId);
 
// 判断当前用户是不是评论的建立者,若是是则经过,不是则报出没有权限的错误码。
        if(!appComment.getUserId().equals(Long.valueOf(userInfo.getUserId()))){
            throw new BusinessException(ErrorCode.ACCESS_DENIED);
        }
 
        this.appCommentService.delete(commentId);
    }
}

总结下,基于角色的访问控制是一种垂直权限控制,经过创建用户与角色的对应关系,使得不一样角色之间具备高低之分。用户根据拥有的角色进行操做与资源访问。基于数据的访问控制是一种水平权限控制,它对角色内的用户作细分,确保用户的数据不能越权操做。基于数据的访问控制,须要业务层面去处理,可是这个也是最为常常遗落的安全点,须要引发重视。

性能优化

性能指标有哪些

1.响应时间(Response time)
响应时间就是用户感觉软件系统为其服务所耗费的时间,对于网站系统来讲,响应时间就是从点击了一个页面计时开始,到这个页面彻底在浏览器里展示计时结束的这一段时间间隔,看起来很简单,但其实在这段响应时间内,软件系统在幕后通过了一系列的处理工做,贯穿了整个系统节点。根据“管辖区域”不一样,响应时间能够细分为:
(1)服务器端响应时间,这个时间指的是服务器完成交易请求执行的时间,不包括客户端到服务器端的反应(请求和耗费在网络上的通讯时间),这个服务器端响应时间能够度量服务器的处理能力。
(2)网络响应时间,这是网络硬件传输交易请求和交易结果所耗费的时间。
(3)客户端响应时间,这是客户端在构建请求和展示交易结果时所耗费的时间,对于普通的瘦客户端Web应用来讲,这个时间很短,一般能够忽略不计;可是对于胖客户端Web应用来讲,好比Java applet、AJAX,因为客户端内嵌了大量的逻辑处理,耗费的时间有可能很长,从而成为系统的瓶颈,这是要注意的一个地方。
那么客户感觉的响应时间实际上是等于客户端响应时间+服务器端响应时间+网络响应时间。细分的目的是为了方便定位性能瓶颈出如今哪一个节点上(何为性能瓶颈,下一节中介绍)。

2.吞吐量(Throughput)
吞吐量是咱们常见的一个软件性能指标,对于软件系统来讲,“吞”进去的是请求,“吐”出来的是结果,而吞吐量反映的就是软件系统的“饭量”,也就是系统的处理能力,具体说来,就是指软件系统在每单位时间内能处理多少个事务/请求/单位数据等。但它的定义比较灵活,在不一样的场景下有不一样的诠释,好比数据库的吞吐量指的是单位时间内,不一样SQL语句的执行数量;而网络的吞吐量指的是单位时间内在网络上传输的数据流量。吞吐量的大小由负载(如用户的数量)或行为方式来决定。举个例子,下载文件比浏览网页须要更高的网络吞吐量。

3.资源使用率(Resource utilization)
常见的资源有:CPU占用率、内存使用率、磁盘I/O、网络I/O。
咱们将在Analysis结果分析一章中详细介绍如何理解和分析这些指标。

4.点击数(Hits per second)
点击数是衡量Web Server处理能力的一个颇有用的指标。须要明确的是:点击数不是咱们一般理解的用户鼠标点击次数,而是按照客户端向Web Server发起了多少次http请求计算的,一次鼠标可能触发多个http请求,这须要结合具体的Web系统实现来计算。

5.并发用户数(Concurrent users)
并发用户数用来度量服务器并发容量和同步协调能力。在客户端指一批用户同时执行一个操做。并发数反映了软件系统的并发处理能力,和吞吐量不一样的是,它大可能是占用套接字、句柄等操做系统资源。

另外,度量软件系统的性能指标还有系统恢复时间等,其实凡是用户有关资源和时间的要求均可以被视做性能指标,均可以做为软件系统的度量,而性能测试就是为了验证这些性能指标是否被知足。

参考:https://www.douban.com/note/168911628/

如何发现性能瓶颈

参考:https://www.testwo.com/blog/8207

性能调优的常见手段

  1. 尽可能在合适的场合使用单例

使用单例能够减轻加载的负担,缩短加载的时间,提升加载的效率,但并非全部地方都适用于单例,简单来讲,单例主要适用于如下三个方面:
第一,控制资源的使用,经过线程同步来控制资源的并发访问;
第二,控制实例的产生,以达到节约资源的目的;
第三,控制数据共享,在不创建直接关联的条件下,让多个不相关的进程或线程之间实现通讯。

  1. 尽可能避免随意使用静态变量

要知道,当某个对象被定义为stataic变量所引用,那么gc一般是不会回收这个对象所占有的内存,如

Java代码 复制代码
public class A{
static B b = new B();
}
[java] view plain copy

public class A{
static B b = new B();
}

此时静态变量b的生命周期与A类同步,若是A类不会卸载,那么b对象会常驻内存,直到程序终止。

  1. 尽可能避免过多过常的建立Java对象

尽可能避免在常常调用的方法,循环中new对象,因为系统不只要花费时间来建立对象,并且还要花时间对这些对象进行垃圾回收和处理,在咱们能够控制的范围内,最大限度的重用对象,最好能用基本的数据类型或数组来替代对象。

  1. 尽可能使用final修饰符

带有final修饰符的类是不可派生的。在Java核心API中,有许多应用final的例子,例如java.lang.String。为String类指定final防止了使用者覆盖length()方法。另外,若是一个类是final的,则该类全部方法都是final的。Java编译器会寻找机会内联(inline)全部的final方法(这和具体的编译器实现有关)。此举可以使性能平均提升50%。

  1. 尽可能使用局部变量

调用方法时传递的参数以及在调用中建立的临时变量都保存在栈(Stack)中,速度较快。其余变量,如静态变量、实例变量等,都在堆(Heap)中建立,速度较慢。

  1. 尽可能处理好包装类型和基本类型二者的使用场所

虽然包装类型和基本类型在使用过程当中是能够相互转换,但它们二者所产生的内存区域是彻底不一样的,基本类型数据产生和处理都在栈中处理,包装类型是对象,是在堆中产生实例。
在集合类对象,有对象方面须要的处理适用包装类型,其余的处理提倡使用基本类型。

  1. 慎用synchronized,尽可能减少synchronize的方法

都知道,实现同步是要很大的系统开销做为代价的,甚至可能形成死锁,因此尽可能避免无谓的同步控制。synchronize方法被调用时,直接会把当前对象锁 了,在方法执行完以前其余线程没法调用当前对象的其余方法。因此synchronize的方法尽可能小,而且应尽可能使用方法同步代替代码块同步。

  1. 尽可能使用StringBuilder和StringBuffer进行字符串链接

这个就很少讲了。

  1. 尽可能不要使用finalize方法

实际上,将资源清理放在finalize方法中完成是很是很差的选择,因为GC的工做量很大,尤为是回收Young代内存时,大都会引发应用程序暂停,因此再选择使用finalize方法进行资源清理,会致使GC负担更大,程序运行效率更差。

  1. 尽可能使用基本数据类型代替对象

String str = “hello”;
上面这种方式会建立一个“hello”字符串,并且JVM的字符缓存池还会缓存这个字符串;
String str = new String(“hello”);
此时程序除建立字符串外,str所引用的String对象底层还包含一个char[]数组,这个char[]数组依次存放了h,e,l,l,o

  1. 单线程应尽可能使用HashMap、ArrayList

HashTable、Vector等使用了同步机制,下降了性能。

  1. 尽可能合理的建立HashMap

当你要建立一个比较大的hashMap时,充分利用另外一个构造函数
public HashMap(int initialCapacity, float loadFactor)
避免HashMap屡次进行了hash重构,扩容是一件很耗费性能的事,在默认中initialCapacity只有16,而loadFactor是 0.75,须要多大的容量,你最好能准确的估计你所须要的最佳大小,一样的Hashtable,Vectors也是同样的道理。

  1. 尽可能减小对变量的重复计算


for(int i=0;i<list.size();i++)
应该改成
for(int i=0,len=list.size();i<len;i++)
而且在循环中应该避免使用复杂的表达式,在循环中,循环条件会被反复计算,若是不使用复杂表达式,而使循环条件值不变的话,程序将会运行的更快。

  1. 尽可能避免没必要要的建立


A a = new A();
if(i==1){list.add(a);}
应该改成
if(i==1){
A a = new A();
list.add(a);}

  1. 尽可能在finally块中释放资源

程序中使用到的资源应当被释放,以免资源泄漏。这最好在finally块中去作。无论程序执行的结果如何,finally块老是会执行的,以确保资源的正确关闭。

  1. 尽可能使用移位来代替’a/b’的操做

"/"是一个代价很高的操做,使用移位的操做将会更快和更有效

int num = a / 4;
int num = a / 8;
应该改成
int num = a >> 2;
int num = a >> 3;
但注意的是使用移位应添加注释,由于移位操做不直观,比较难理解

17.尽可能使用移位来代替’a*b’的操做

一样的,对于’*'操做,使用移位的操做将会更快和更有效

int num = a * 4;
int num = a * 8;
应该改成
int num = a << 2;
int num = a << 3;

  1. 尽可能肯定StringBuffer的容量

StringBuffer 的构造器会建立一个默认大小(一般是16)的字符数组。在使用中,若是超出这个大小,就会从新分配内存,建立一个更大的数组,并将原先的数组复制过来,再 丢弃旧的数组。在大多数状况下,你能够在建立 StringBuffer的时候指定大小,这样就避免了在容量不够的时候自动增加,以提升性能。
如:StringBuffer buffer = new StringBuffer(1000);

  1. 尽可能早释放无用对象的引用

大部分时,方法局部引用变量所引用的对象 会随着方法结束而变成垃圾,所以,大部分时候程序无需将局部,引用变量显式设为null。
例如:

Java代码 复制代码
Public void test(){
Object obj = new Object();
……
Obj=null;
}
[java] view plain copy

Public void test(){
Object obj = new Object();
……
Obj=null;
}

上面这个就不必了,随着方法test()的执行完成,程序中obj引用变量的做用域就结束了。可是若是是改为下面:

Java代码 复制代码
Public void test(){
Object obj = new Object();
……
Obj=null;
//执行耗时,耗内存操做;或调用耗时,耗内存的方法
……
}
[java] view plain copy

Public void test(){
Object obj = new Object();
……
Obj=null;
//执行耗时,耗内存操做;或调用耗时,耗内存的方法
……
}

这时候就有必要将obj赋值为null,能够尽早的释放对Object对象的引用。

  1. 尽可能避免使用二维数组

二维数据占用的内存空间比一维数组多得多,大概10倍以上。

  1. 尽可能避免使用split

除非是必须的,不然应该避免使用split,split因为支持正则表达式,因此效率比较低,若是是频繁的几十,几百万的调用将会耗费大量资源,若是确实需 要频繁的调用split,能够考虑使用apache的StringUtils.split(string,char),频繁split的能够缓存结果。

  1. ArrayList & LinkedList

一 个是线性表,一个是链表,一句话,随机查询尽可能使用ArrayList,ArrayList优于LinkedList,LinkedList还要移动指 针,添加删除的操做LinkedList优于ArrayList,ArrayList还要移动数据,不过这是理论性分析,事实未必如此,重要的是理解好2 者得数据结构,对症下药。

  1. 尽可能使用System.arraycopy ()代替经过来循环复制数组

System.arraycopy() 要比经过循环来复制数组快的多

  1. 尽可能缓存常用的对象

尽量将常用的对象进行缓存,能够使用数组,或HashMap的容器来进行缓存,但这种方式可能致使系统占用过多的缓存,性能降低,推荐能够使用一些第三方的开源工具,如EhCache,Oscache进行缓存,他们基本都实现了FIFO/FLU等缓存算法。

  1. 尽可能避免很是大的内存分配

有时候问题不是由当时的堆状态形成的,而是由于分配失败形成的。分配的内存块都必须是连续的,而随着堆愈来愈满,找到较大的连续块愈来愈困难。

  1. 慎用异常

当建立一个异常时,须要收集一个栈跟踪(stack track),这个栈跟踪用于描述异常是在何处建立的。构建这些栈跟踪时须要为运行时栈作一份快照,正是这一部分开销很大。当须要建立一个 Exception 时,JVM 不得不说:先别动,我想就您如今的样子存一份快照,因此暂时中止入栈和出栈操做。栈跟踪不仅包含运行时栈中的一两个元素,而是包含这个栈中的每个元素。
如 果您建立一个 Exception ,就得付出代价。好在捕获异常开销不大,所以能够使用 try-catch 将核心内容包起来。从技术上讲,您甚至能够随意地抛出异常,而不用花费很大的代价。招致性能损失的并非 throw 操做——尽管在没有预先建立异常的状况下就抛出异常是有点不寻常。真正要花代价的是建立异常。幸运的是,好的编程习惯已教会咱们,不该该无论三七二十一就 抛出异常。异常是为异常的状况而设计的,使用时也应该牢记这一原则。

7.慎用synchronized,尽可能减少synchronize的方法,使用synchronized关键字并不必定都是锁定当前对象的,要看具体的锁是什么。若是是在方法上加的synchronized,则是以对象自己为锁的,若是是静态方法则锁的粒度是类。
9.尽可能不要使用finalize方法,其实不推荐用finalize方法的根本缘由在于,JVM的规范并不保证什么时候执行该方法,因此用这个方法来释放资源很不合适,有可能形成长时间资源得不到释放。

16.尽可能使用移位来代替’a/b’的操做;
17.尽可能使用移位来代替’a*b’的操做
re:我的不太赞成这两条。这样作确实有更好的性能,可是却牺牲了可读性。这两个操做符对不少程序员来讲并不直观。我认为在现在硬件价格不那么昂贵的状况下,略微牺牲一些性能,换来更好的可读性和可维护性是好的选择。

19.尽可能早释放无用对象的引用
大部分时,方法局部引用变量所引用的对象 会随着方法结束而变成垃圾,所以,大部分时候程序无需将局部,引用变量显式设为null。
例如:
Public void test(){
Object obj = new Object();
……
Obj=null;
}
上面这个就不必了,随着方法test()的执行完成,程序中obj引用变量的做用域就结束了。可是若是是改为下面:
Public void test(){
Object obj = new Object();
……
Obj=null;
//执行耗时,耗内存操做;或调用耗时,耗内存的方法
……
}
若是Object obj = new Object(); 若是这对象并非大对象,这有必要吗?Obj=null;只是告诉jvm这个对象已经成为垃圾,至于何时回收,还不能肯定! 这可读性也很差!

说说你在项目中如何进行性能调优

  1. 尽可能使用缓存,这里不是指的好比ORM框架HIBERNATE的一级缓存和二级缓存,而是独立的缓存服务器,它是存储于内存中的,
    好比用户缓存,基本配置信息缓存等,它通常是在系统中常常要查的一些信息,在这里咱们能够使用缓存,
    咱们项目中经常使用的好比redis memcache,这样能够大量减小与数据库的交互,提升性能。

  2. 统计的功能尽可能作缓存,或按天天一统计或定时统计相关报表,避免须要时进行统计的功能。

  3. 能使用静态页面的地方尽可能使用,减小容器的解析(尽可能将动态内容生成静态html来显示)

  4. 对于一个系统内若是有过多图片加载显示时咱们最好设计成用一台单独的服务器来存詂
    这样就会减小应用服务器的压力 提升性能 若是用了负载均衡更是弥补了图片不能同步的问题

  5. 能够对WEB容器进行优化 开发中咱们经常使用的有tomcat weblogic 对于WEB容器咱们要考虑的有 JVM的使用率 空闲线程数 队列长度 和吞吐量这些方面,
    因此咱们加大内存使用率 调整线程数的值能够提升系统性能

  6. 优化数据库查询语句,好比尽可能不要用通配符* 少用in和not in where条件内尽可能不要用!= <> 这样会致使放弃使用索引

  7. 优化数据库结构,多作索引,主键尽可能不要用自增的 减小冗余字段 提升查询效率。对于大数据量咱们还能够进表拆分 ORACLE超过100W条查询效率就会很慢

  8. 负载均衡,咱们经常使用的并发能力较强的前置代理服务器好比nginx、apache 这里nginx并发量是高于apache的 它能够实现动静分离 反向代理 并且体积相对来讲较小

  9. 采用多台服务器集群的方式来解决单台的瓶颈问题

  10. 分布式部署 咱们把不一样的应用部署在不一样的服务器 运用相关机制统一调度全部的应用程序 这样就能够提升系统性能也解决了单存储服务器的瓶颈问题

工程篇

需求分析

你如何对需求原型进行理解和拆分

说说你对功能性需求的理解

说说你对非功能性需求的理解

你针对产品提出哪些交互和改进意见

你如何理解用户痛点

设计能力

说说你在项目中使用过的 UML 图

你如何考虑组件化

你如何考虑服务化

你如何进行领域建模

你如何划分领域边界

说说你项目中的领域建模

说说概要设计

设计模式

你项目中有使用哪些设计模式

说说经常使用开源框架中设计模式使用分析

说说你对设计原则的理解

23种设计模式的设计理念

设计模式之间的异同,例如策略模式与状态模式的区别

设计模式之间的结合,例如策略模式+简单工厂模式的实践

设计模式的性能,例如单例模式哪一种性能更好。

业务工程

你系统中的先后端分离是如何作的

说说你的开发流程

你和团队是如何沟通的

你如何进行代码评审

说说你对技术与业务的理解

说说你在项目中常常遇到的 Exception

说说你在项目中遇到感受最难Bug,怎么解决的

说说你在项目中遇到印象最深困难,怎么解决的

你以为大家项目还有哪些不足的地方

你是否遇到过 CPU 100% ,如何排查与解决

你是否遇到过 内存 OOM ,如何排查与解决

说说你对敏捷开发的实践

说说你对开发运维的实践

介绍下工做中的一个对本身最有价值的项目,以及在这个过程当中的角色

软实力

说说你的亮点

说说你最近在看什么书

说说你以为最有意义的技术书籍

工做之余作什么事情

说说我的发展方向方面的思考

说说你认为的服务端开发工程师应该具有哪些能力

说说你认为的架构师是什么样的,架构师主要作什么

说说你所理解的技术专家