Java复习资料
Java基础
1. 八种基本数据类型的大小,以及他们的封装类
double ---Double 64位 0.0d
long ---Long 64位 0L
float ---Float 32位 0.0f
int ---Integer 32位 0
short ---Short 16位 (short)0
char ---Character 16位 null\u0000
byte ---byte 8位 (byte)0
boolean ---Boolean -- false
boolean 只有两个值:true、false,可以使用 1 bit 来存储,但是具体大小没有明确规定。JVM 会在编译时期将 boolean 类型的数据转换为 int,使用 1 来表示 true,0 表示 false。
JVM规范里说,
- 单个的boolean 类型变量在编译的时候是使用的int 类型。
boolean a=true;//这个a在JVM中占4个字节即:32位。
- ,在Oracle的JVM实现中,boolean 类型的数组时,在编译的时候是作为byte array来编译的所以boolean 数组里面的每一个元件占一个字节,
boolean[] b = new boolean[10];//数组时,每一个boolean在JVM中占一个字节
参考Primitive Data Types和The Java® Virtual Machine Specification
2. Switch能否用string做参数
以前只能支持byte、short、char、int,可以强转;Jdk7.0以后可以,整型、枚举类型、boolean、字符串都可以
3. equals与==的区别
java中的数据类型,可分为两类:
- 基本数据类型,也称原始数据类型。byte,short,char,int,long,float,double,boolean
它们之间的比较,应用双等号(==),比较的是它们的值。
- 复合数据类型(类)
当它们用(==)进行比较的时候,比较的是它们在内存中的存放地址,所以,除非是同一个new出来的对象,比较后的结果为true,否则比较后结果为false。 JAVA当中所有的类都是继承于Object这个基类的,在Object中的基类中定义了一个equals的方法,这个方法的初始行为是比较对象的内存地址(==),但在一些类库当中这个方法被覆盖掉了,如String,Integer,Date在这些类当中equals有其自身的实现,而不再是比较类在堆内存中的存放地址了。
对于复合数据类型之间进行equals比较,在没有覆写equals方法的情况下,他们之间的比较还是基于他们在内存中的存放位置的地址值的,因为Object的equals方法也是用双等号(==)进行比较的,所以比较后的结果跟双等号(==)的结果相同。
4. 自动装箱,常量池
5. Object有哪些公用方法
clone(),hashCode(),equals(),notify(),notifyAll(),wait(),getClass(),toString,finalize()
6. Java的四种引用,强弱软虚,用到的场景
- 强引用:使用普遍的引用,内存空间不足了,一般垃圾回收器绝不会回收它
- 软引用:软引用可用来实现内存敏感的高速缓存,内存空间不足了,就会回收这些对象的内存。
- 弱引用:具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存
- 虚引用:虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。
7. Hashcode的作用
Java中的hashCode方法就是根据一定的规则将与对象相关的信息(比如对象的存储地址,对象的字段等)映射成一个数值,这个数值称作为散列值,降低equals的调用,实现存放的值不会重复。
- 重写equals必须重写hashcode方法,equals相等,hashcode也必须相等,反正不一定。
- 两个对象相等,其HashCode一定相同;
- 两个对象不相等,其HashCode有可能相同;
- HashCode相同的两个对象,不一定相等;
- HashCode不相同的两个对象,一定不相等;
8. HashMap的hashcode的作用
HashMap中需要保证插入的元素的key值唯一,所以每插入一个key值,都要判断key值是不是重复,但是如果直接调用key的euqals()方法的话,效率会比较低,所以会先通过hash(key)来调用一下key的hashCode()方法获得hash值,然后使用hash值对数组长度进行取模,就可以找到需要插入的位置。如果该位置没有元素,就可以直接插入了,如果已经有了一个链表,就需要遍历链表,调用key的equals()方法判断key值是否重复网了。
9. ConcurrentHashMap能完全替代HashTable吗?
Hash table虽然性能上不如ConcurrentHashMap,但并不能完全被取代,两者的迭代器的一致性不同的,ConcurrentHashMap由于分段锁,弱一致性主要是为了提升效率。
强一致性就如hashtable一样,锁整个map。
10. Java的四个基本特性(抽象、封装、继承,多态),对多态的理解(多态的实现方式)以及在项目中那些地方用到多态
Java的四个基本特性
- 抽象:抽象是将一类对象的共同特征总结出来构造类的过程,包括数据抽象和行为抽象两方面。抽象只关注对象有哪些属性和行为,并不关注这些行为的细节是什么。
- 继承:继承是从已有类得到继承信息创建新类的过程。提供继承信息的类被称为父类(超类、基类);得到继承信息的类被称为子类(派生类)。继承让变化中的软件系统有了一定的延续性,同时继承也是封装程序中可变因素的重要手段。
- 封装:通常认为封装是把数据和操作数据的方法绑定起来,对数据的访问只能通过已定义的接口。面向对象的本质就是将现实世界描绘成一系列完全自治、封闭的对象。我们在类中编写的方法就是对实现细节的一种封装;我们编写一个类就是对数据和数据操作的封装。可以说,封装就是隐藏一切可隐藏的东西,只向外界提供最简单的编程接口。
- 多态:是指允许不同子类型的对象对同一消息作出不同的响应。
多态的理解(多态的实现方式)
- 方法重载(overload)实现的是编译时的多态性(也称为前绑定)。
- 方法重写(override)实现的是运行时的多态性(也称为后绑定)。运行时的多态是面向对象最精髓的东西。
要实现多态需要做两件事:1) 方法重写(子类继承父类并重写父类中已有的或抽象的方法);2) 对象造型(用父类型引用引用子类型对象,这样同样的引用调用同样的方法就会根据子类对象的不同而表现出不同的行为)。
项目中对多态的应用
举一个简单的例子,在物流信息管理系统中,有两种用户:订购客户和卖房客户,两个客户都可以登录系统,他们有相同的方法Login,但登陆之后他们会进入到不同的页面,也就是在登录的时候会有不同的操作,两种客户都继承父类的Login方法,但对于不同的对象,拥有不同的操作。
面向对象和面向过程的区别?用面向过程可以实现面向对象吗?那是不是不能面向对象?
面向对象和面向过程的区别
- 面向过程就像是一个细心的管家,事无具细的都要考虑到。而面向对象就像是个家用电器,你只需要知道他的功能,不需要知道它的工作原理。
- 面向过程”是一种是事件为中心的编程思想。就是分析出解决问题所需的步骤,然后用函数把这些步骤实现,并按顺序调用。面向对象是以“对象”为中心的编程思想。
简单的举个例子:汽车发动、汽车到站
- 这对于“面向过程”来说,是两个事件,汽车启动是一个事件,汽车到站是另一个事件,面向过程编程的过程中我们关心的是事件,而不是汽车本身。针对上述两个事件,形成两个函数,之后依次调用。
- 然而这对于面向对象来说,我们关心的是汽车这类对象,两个事件只是这类对象所具有的行为。而且对于这两个行为的顺序没有强制要求。
重载和重写,如何确定调用哪个函数
- 重载:重载发生在同一个类中,同名的方法如果有不同的参数列表(参数类型不同、参数个数不同或者参数顺序不同)则视为重载。
- 重写:重写发生在子类与父类之间,重写要求子类被重写方法与父类被重写方法有相同的返回类型,比父类被重写方法更好访问,不能比父类被重写方法声明更多的异常(里氏代换原则)。根据不同的子类对象确定调用的那个方法。
面向对象开发的六个基本原则(单一职责、开放封闭、里氏替换、依赖倒置、合成聚合复用、接口隔离),迪米特法则。在项目中用过哪些原则
- 单一职责:一个类只做它该做的事情(高内聚)。在面向对象中,如果只让一个类完成它该做的事,而不涉及与它无关的领域就是践行了高内聚的原则,这个类就只有单一职责。
- 开放封闭:软件实体应当对扩展开放,对修改关闭。要做到开闭有两个要点:①抽象是关键,一个系统中如果没有抽象类或接口系统就没有扩展点;②封装可变性,将系统中的各种可变因素封装到一个继承结构中,如果多个可变因素混杂在一起,系统将变得复杂而换乱。
- 里氏替换:任何时候都可以用子类型替换掉父类型。子类一定是增加父类的能力而不是减少父类的能力,因为子类比父类的能力更多,把能力多的对象当成能力少的对象来用当然没有任何问题。
- 依赖倒置:面向接口编程。(该原则说得直白和具体一些就是声明方法的参数类型、方法的返回类型、变量的引用类型时,尽可能使用抽象类型而不用具体类型,因为抽象类型可以被它的任何一个子类型所替代)
- 合成聚和复用:优先使用聚合或合成关系复用代码。
- 接口隔离:接口要小而专,绝不能大而全。臃肿的接口是对接口的污染,既然接口表示能力,那么一个接口只应该描述一种能力,接口也应该是高度内聚的。
迪米特法则
迪米特法则又叫最少知识原则,一个对象应当对其他对象有尽可能少的了解。
项目中用到的原则
单一职责、开放封闭、合成聚合复用(最简单的例子就是String类)、接口隔离
static和final的区别和用途
- Static
- 修饰变量:静态变量随着类加载时被完成初始化,内存中只有一个,且JVM也只会为它分配一次内存,所有类共享静态变量。
- 修饰方法:在类加载的时候就存在,不依赖任何实例;static方法必须实现,不能用abstract修饰。
- 修饰代码块:在类加载完之后就会执行代码块中的内容。按照
父类静态代码块->子类静态代码块->父类非静态代码块->父类构造方法->子类非静态代码块->子类构造方法
的顺序
- Final
- 修饰变量:
- 编译期常量:类加载的过程完成初始化,编译后带入到任何计算式中。只能是基本类型。
- 运行时常量:基本数据类型或引用数据类型。引用不可变,但引用的对象内容可变。
- 修饰方法:不能被子类重写。
- 修饰类:不能被继承。
- 修饰形参:final形参不可变
Hash Map和Hash Table的区别,Hash Map中的key可以是任何对象或数据类型吗?HashTable是线程安全的么?
Hash Map和Hash Table的区别
- Hashtable的方法是同步的,HashMap未经同步,所以在多线程场合要手动同步HashMap这个区别就像Vector和ArrayList一样。
- Hashtable不允许 null 值(key 和 value 都不可以),HashMap允许 null 值(key和value都可以)。
- 两者的遍历方式大同小异,Hashtable仅仅比HashMap多一个elements方法。
- 两者也都可以通过 entrySet() 方法返回一个 Set , 然后进行遍历处理。
Hashtable 和 HashMap 都能通过values()方法返回一个 Collection ,然后进行遍历处理。
- HashTable使用Enumeration,HashMap使用Iterator。
- 哈希值的使用不同,Hashtable直接使用对象的hashCode。而HashMap重新计算hash值,而且用于代替求模。
- Hashtable中hash数组默认大小是11,增加的方式是 old*2+1。HashMap中hash数组的默认大小是16,而且一定是2的指数。
- HashTable基于Dictionary类,而HashMap基于AbstractMap类
Hash Map中的key可以是任何对象或数据类型吗
- 可以为null,但不能是可变对象,如果是可变对象的话,对象中的属性改变,则对象HashCode也进行相应的改变,导致下次无法查找到已存在Map中的数据。
- 如果可变对象在HashMap中被用作键,那就要小心在改变对象状态的时候,不要改变它的哈希值了。我们只需要保证成员变量的改变能保证该对象的哈希值不变即可。
HashTable是线程安全的么
HashTable是线程安全的,其实现是在对应的方法上添加了synchronized关键字进行修饰,由于在执行此方法的时候需要获得对象锁,则执行起来比较慢。所以现在如果为了保证线程安全的话,使用CurrentHashMap。
HashMap和Concurrent HashMap区别, Concurrent HashMap 线程安全吗, Concurrent HashMap如何保证线程安全?
HashMap和Concurrent HashMap区别?
- HashMap是非线程安全的,CurrentHashMap是线程安全的。
- ConcurrentHashMap将整个Hash桶进行了分段segment,也就是将这个大的数组分成了几个小的片段segment,而且每个小的片段segment上面都有锁存在,那么在插入元素的时候就需要先找到应该插入到哪一个片段segment,然后再在这个片段上面进行插入,而且这里还需要获取segment锁。
- ConcurrentHashMap让锁的粒度更精细一些,并发性能更好。
Concurrent HashMap 线程安全吗, Concurrent HashMap如何保证 线程安全?
- HashTable容器在竞争激烈的并发环境下表现出效率低下的原因是所有访问HashTable的线程都必须竞争同一把锁,那假如容器里有多把锁,每一把锁用于锁容器其中一部分数据,那么当多线程访问容器里不同数据段的数据时,线程间就不会存在锁竞争,从而可以有效的提高并发访问效率,这就是ConcurrentHashMap所使用的锁分段技术,首先将数据分成一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问。
- get操作的高效之处在于整个get过程不需要加锁,除非读到的值是空的才会加锁重读。get方法里将要使用的共享变量都定义成volatile,如用于统计当前Segement大小的count字段和用于存储值的HashEntry的value。定义成volatile的变量,能够在线程之间保持可见性,能够被多线程同时读,并且保证不会读到过期的值,但是只能被单线程写(有一种情况可以被多线程写,就是写入的值不依赖于原值),在get操作里只需要读不需要写共享变量count和value,所以可以不用加锁。
- Put方法首先定位到Segment,然后在Segment里进行插入操作。插入操作需要经历两个步骤,第一步判断是否需要对Segment里的HashEntry数组进行扩容,第二步定位添加元素的位置然后放在HashEntry数组里。
String、StringBuffer、StringBuilder以及对String不变性的理解
String、StringBuffer、StringBuilder
- 都是 final 类, 都不允许被继承;
- String 长度是不可变的, StringBuffer、StringBuilder 长度是可变的;
- StringBuffer 是线程安全的, StringBuilder 不是线程安全的,但它们两个中的所有方法都是相同的,StringBuffer在StringBuilder的方法之上添加了synchronized修饰,保证线程安全。
- StringBuilder比StringBuffer拥有更好的性能。
- 如果一个String类型的字符串,在编译时就可以确定是一个字符串常量,则编译完成之后,字符串会自动拼接成一个常量。此时String的速度比StringBuffer和StringBuilder的性能好的多。
String不变性的理解
- String 类是被final进行修饰的,不能被继承。
- 在用+号链接字符串的时候会创建新的字符串。
- String s = new String("Hello world"); 可能创建两个对象也可能创建一个对象。如果静态区中有“Hello world”字符串常量对象的话,则仅仅在堆中创建一个对象。如果静态区中没有“Hello world”对象,则堆上和静态区中都需要创建对象。
- 在 java 中, 通过使用 "+" 符号来串联字符串的时候, 实际上底层会转成通过 StringBuilder 实例的 append() 方法来实现。
String有重写Object的hashcode和toString吗?如果重写equals不重写hashcode会出现什么问题?
String有重写Object的hashcode和toString吗?
String重写了Object类的hashcode和toString方法。
当equals方法被重写时,通常有必要重写hashCode方法,以维护hashCode方法的常规协定,该协定声明相对等的两个对象必须有相同的hashCode
- object1.euqal(object2)时为true, object1.hashCode() == object2.hashCode() 为true
- object1.hashCode() == object2.hashCode() 为false时,object1.euqal(object2)必定为false
- object1.hashCode() == object2.hashCode() 为true时,但object1.euqal(object2)不一定定为true
重写equals不重写hashcode会出现什么问题
在存储散列集合时(如Set类),如果原对象.equals(新对象),但没有对hashCode重写,即两个对象拥有不同的hashCode,则在集合中将会存储两个值相同的对象,从而导致混淆。因此在重写equals方法时,必须重写hashCode方法
可以参考这里。
如果你定义一个类,包括学号,姓名,分数,如何把这个对象作为key?要重写equals和hashcode吗
需要重写equals方法和hashcode,必须保证对象的属性改变时,其hashcode不能改变。
所了解的设计模式,单例模式的注意事项,jdk源码哪些用到了你说的设计模式
所了解的设计模式
- 工厂模式:定义一个用于创建对象的接口,让子类决定实例化哪一个类, Factory Method 使一个类的实例化延迟到了子类。
- 单例模式:保证一个类只有一个实例,并提供一个访问它的全局访问点;
- 适配器模式:将一类的接口转换成客户希望的另外一个接口,Adapter 模式使得原本由于接口不兼容而不能一起工作那些类可以一起工作。
- 装饰者模式:动态地给一个对象增加一些额外的职责,就增加的功能来说, Decorator 模式相比生成子类更加灵活。
- 代理:为其他对象提供一种代理以控制对这个对象的访问
- 迭代器模式:提供一个方法顺序访问一个聚合对象的各个元素,而又不需要暴露该对象的内部表示。
单例模式的注意事项
- 尽量使用懒加载
- 双重检索实现线程安全
- 构造方法为private
- 定义静态的Singleton instance对象和getInstance()方法
jdk源码中用到的设计模式
- 装饰器模式:IO流中
- 迭代器模式:Iterator
- 单例模式: java.lang.Runtime
-代理模式:RMI
Java序列化
Java并发
什么情况下会死锁?
产生死锁的四个必要条件,如果发生死锁,以下四个条件必然成立。只要有一个不满足就不会发生死锁。
- 互斥条件。 一个资源每次只能被一个进程使用。
- 不可抢占条件。 进程已获得的资源,在末使用完之前,不能强行剥夺。
- 占有且申请条件。 一个进程因请求资源而阻塞时,对已获得的资源保持不放。
- 循环等待条件。存在一个进程等待序列{P1,P2,...,Pn},其中P1等待P2所占有的某一资源,P2等待P3所占有的某一源,......,而Pn等待P1所占有的的某一资源,形成一个进程循环等待环。就像前面的过独木桥问题,甲等待乙占有的桥面,而乙又等待甲占有的桥面,从而彼此循环等待。
死锁预防
- 打破互斥条件。即允许进程同时访问某些资源。但是,有的资源是不允许被同时访问的,像打印机等等,这是由资源本身的属性所决定的。所以,这种办法并无实用价值。
- 打破不可抢占条件。即允许进程强行从占有者那里夺取某些资源。
- 打破占有且申请条件。可以实行资源预先分配策略。即进程在运行前一次性地向系统申请它所需要的全部资源。如果某个进程所需的全部资源得不到满足,则不分配任何资源,此进程暂不运行。只有当系统能够满足当前进程的全部资源需求时,才一次性地将所申请的资源全部分配给该进程。这种策略也有如下缺点:
- 在许多情况下,一个进程在执行之前不可能知道它所需要的全部资源。
- 资源利用率低。无论所分资源何时用到,一个进程只有在占有所需的全部资源后才能执行。
- 降低了进程的并发性。
- 打破循环等待条件,实行资源有序分配策略。存在以下缺点:
- 限制了进程对资源的请求,同时给系统中所有资源合理编号也是件困难事,并增加了系统开销;
- 为了遵循按编号申请的次序,暂不使用的资源也需要提前申请,从而增加了进程对资源的占用时间。
死锁避免
- 加锁顺序(线程按照一定的顺序加锁)
- 当多个线程需要相同的一些锁,但是按照不同的顺序加锁,死锁就很容易发生。如果能确保所有的线程都是按照相同的顺序获得锁,那么死锁就不会发生。
- 加锁时限(线程尝试获取锁的时候加上一定的时限,超过时限则放弃对该锁的请求,并释放自己占有的锁)
- 死锁检测
- 死锁检测是一个更好的死锁预防机制,它主要是针对那些不可能实现按序加锁并且锁超时也不可行的场景。每当一个线程获得了锁,会在线程和锁相关的数据结构中(map、graph等等)将其记下。除此之外,每当有线程请求锁,也需要记录在这个数据结构中。
- 那么当检测出死锁时,这些线程该做些什么呢?
- 一个可行的做法是释放所有锁,回退,并且等待一段随机的时间后重试。这个和简单的加锁超时类似,不一样的是只有死锁已经发生了才回退,而不会是因为加锁的请求超时了。虽然有回退和等待,但是如果有大量的线程竞争同一批锁,它们还是会重复地死锁(编者注:原因同超时类似,不能从根本上减轻竞争)。
- 一个更好的方案是给这些线程设置优先级,让一个(或几个)线程回退,剩下的线程就像没发生死锁一样继续保持着它们需要的锁。如果赋予这些线程的优先级是固定不变的,同一批线程总是会拥有更高的优先级。为避免这个问题,可以在死锁发生的时候设置随机的优先级。
进程间通信有哪几种方式?
- 管道(PIPE):管道可用于具有亲缘关系进程间的通信,允许一个进程和另一个与它有共同祖先的进程之间进行通信。
- 命名管道(FIFO):命名管道克服了管道没有名字的限制,因此,除具有管道所具有的功能外,它还允许无亲缘关系进程间的通信。命名管道在文件系统中有对应的文件名。命名管道通过命令mkfifo或系统调用mkfifo来创建。
- 信号(Signal):信号是比较复杂的通信方式,用于通知接受进程有某种事件发生,除了用于进程间通信外,进程还可以发送信号给进程本身。
- 消息队列(MessageQueue):消息队列是消息的链接表,包括Posix消息队列system V消息队列。有足够权限的进程可以向队列中添加消息,被赋予读权限的进程则可以读走队列中的消息。消息队列克服了信号承载信息量少,管道只能承载无格式字节流以及缓冲区大小受限等缺限。
- 共享内存(SharedMemory):使得多个进程可以访问同一块内存空间,是最快的可用IPC形式。是针对其他通信机制运行效率较低而设计的。往往与其它通信机制,如信号量结合使用,来达到进程间的同步及互斥。
- 内存映射(mapped memory):内存映射允许任何多个进程间通信,每一个使用该机制的进程通过把一个共享的文件映射到自己的进程地址空间来实现它。
- 信号量(semaphore):主要作为进程间以及同一进程不同线程之间的同步手段。
- 套接口(Socket):更为一般的进程间通信机制,可用于不同机器之间的进程间通信。起初是由Unix系统的BSD分支开发出来的,但现在一般可以移植到其它类Unix系统上:Linux和System V的变种都支持套接字。
数据库
在什么情况下适合建立索引
- 为经常出现在关键字order by、group by、distinct后面的字段,建立索引。
- 在union等集合操作的结果集字段上,建立索引。其建立索引的目的同上。
- 为经常用作查询选择的字段,建立索引。
- 在经常用作表连接的属性上,建立索引。
- 考虑使用索引覆盖。对数据很少被更新的表,如果用户经常只查询其中的几个字段,可以考虑在这几个字段上建立索引,从而将表的扫描改变为索引的扫描。
数据表设计:一对一、一对多、多对多
数据库实体间有三种对应关系:一对一,一对多,多对多。
- 一对一关系示例:一个学生都有唯一的身份证编号。
- 一对多关系示例:一个学生只属于一个班,但是一个班级有多名学生。
- 多对多关系示例:一个学生可以选择多门课,一门课也有多名学生。
不同的对应关系有不同的处理方式:
- 一对一关系。这个比较简单,只需要建立一张表,将这个关系进行关联就行。比如学生和身份证编号,只需要建立一张学生表,在表中插入一个身份证列就行。
- 一对多关系。这种关系有两种处理方法:
- 新增一个关系表作为映射表。比如学生和班级的问题,新建一张映射表用来表示学生与班级的属于关系,该关系表包含字段(学生号,班级号)。通过学生号与班级号的对应关系表示学生属于的班级。
- 在表中新增字段。一般情况下,一对多的两个实体间,在“多”的实体表中新增一个字段,该字段是“一”实体表的主键。比如可以在“班级”表中增加一个“学生”字段,这样如果有类似于
select * from '班级';
这种需要查询某个班级中所有学生的需求的时候比较简单。
- 多对多关系。这种情况一般考虑建立中间表。比如上面的学生选课的关系,可以建立三张表:“学生”表、“课程”表、“学生和课程的映射”表。
参考:
数据库表设计(一对多,多对多)
数据库设计(一对一、一对多、多对多)
JVM
用什么工具可以查出内存泄漏
- MemoryAnalyzer:一个功能丰富的 JAVA 堆转储文件分析工具,可以帮助你发现内存漏洞和减少内存消耗
- EclipseMAT:是一款开源的JAVA内存分析软件,查找内存泄漏,能容易找到大块内存并验证谁在一直占用它,它是基于Eclipse RCP(Rich Client Platform),可以下载RCP的独立版本或者Eclipse的插件
- JProbe:分析Java的内存泄漏。
JVM线程死锁,你该如何判断是因为什么?如果用VisualVM,dump线程信息出来,会有哪些信息
常常需要在隔两分钟后再次收集一次thread dump,如果得到的输出相同,仍然是大量thread都在等待给同一个地址上锁,那么肯定是死锁了。
用什么工具调试程序?JConsole,用过吗?
JConsole 能够监视 JVM 内存的使用情况、线程堆栈跟踪、已装入的类和 VM 信息以及 CE MBean。
了解过JVM调优没,基本思路是什么
调优总结
- 合理设置各个代的大小。避免新生代设置过小(不够用,经常minor gc并进入老年代)以及过大(会产生碎片),同样也要避免Survivor设置过大和过小。
- 选择合适的GC策略。需要根据不同的场景选择合适的gc策略。这里需要说的是,cms并非全能的。除非特别需要再设置,毕竟cms的新生代回收策略parnew并非最快的,且cms会产生碎片。此外,G1直到jdk8的出现也并没有得到广泛应用,并不建议使用。
- jvm启动参数配置-XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:[log_path],以记录gc日志,便于排查问题。
对于第一点,具体如下:
- 年轻代大小选择
- 响应时间优先的应用:尽可能设大,直到接近系统的最低响应时间限制(根据实际情况选择)。在此种情况下,年轻代收集发生的频率也是最小的。同时,减少到达年老代的对象。
- 吞吐量优先的应用:也尽可能的设置大,可能到达Gbit的程度。因为对响应时间没有要求,垃圾收集可以并行进行,一般适合8CPU以上的应用。
- 年老代大小选择
- 响应时间优先的应用:年老代使用并发收集器,所以其大小需要小心设置,一般要考虑并发会话率和会话持续时间等一些参数。如果堆设置小了,可以会造成内存碎片、高回收频率以及应用暂停而使用传统的标记清除方式;如果堆大了,则需要较长的收集时间。最优化的方案,一般需要参考以下数据获得:
- 并发垃圾收集信息
- 持久代并发收集次数
- 传统GC信息
- 花在年轻代和年老代回收上的时间比例
- 减少年轻代和年老代花费的时间,一般会提高应用的效率
- 吞吐量优先的应用:一般吞吐量优先的应用都有一个很大的年轻代和一个较小的年老代。原因是,这样可以尽可能回收掉大部分短期对象,减少中期的对象,而年老代尽存放长期存活对象。
- 较小堆引起的碎片问题
- 因为年老代的并发收集器使用标记、清除算法,所以不会对堆进行压缩。当收集器回收时,他会把相邻的空间进行合并,这样可以分配给较大的对象。但是,当堆空间较小时,运行一段时间以后,就会出现“碎片”,如果并发收集器找不到足够的空间,那么并发收集器将会停止,然后使用传统的标记、清除方式进行回收。如果出现“碎片”,可能需要进行如下配置:
- XX:+UseCMSCompactAtFullCollection:使用并发收集器时,开启对年老代的压缩。
- XX:CMSFullGCsBeforeCompaction=0:上面配置开启的情况下,这里设置多少次Full GC后,对年老代进行压缩
中间件
redis
redis 是什么?都有哪些使用场景?
redis是一种key-value型的no-sql数据库,支持string、list、set、zset和hash类型数据。
redis使用场景大概如下:
- 缓存。redis是内存数据库,拥有强大的qps和稳定性。同时支持AOF和RDB持久化机制和6中缓存淘汰策略。
- 分布式锁。
- 队列。相当于消息系统,ActiveMQ,RocketMQ等。但稳定性不如后者。
- 位操作(大数据处理)。用于数据量上亿的场景下,例如几亿用户系统的签到,去重登录次数统计,某用户是否在线状态等等。比如要检测10亿个用户中的某个是否在线,可以使用redis构建一个足够长的数组,每个数组元素只能是0和1两个值,然后这个数组的下标index用来表示用户id,那么很显然,这个几亿长的大数组就能通过下标和元素值(0和1)来构建一个记忆系统,上面几个场景也就能够实现。用到的命令是:setbit、getbit、bitcount。
- 排行榜。使用zset,命令是ZADD。
参考:Redis的7个应用场景
redis 有哪些功能?
- 速度快,支持的类型多。redis是内存数据库,读写速度可达每秒10完。同时支持string、list、set、zset和hash五种数据结构。
- 持久化:对数据的更新采用Copy-on-write技术,可以异步地保存到磁盘上,主要有两种策略,一是根据时间,更新次数的快照(rdb,Redis DataBase)二是基于语句追加方式(Append-only file,aof) 。
- 快速的主--从复制。使用“主从链”从方式,完成大数据的快速复制。
- 哨兵。可以监听集群中的服务器,并在主服务器进入下线状态时,自动从从服务器中选举出新的主服务器。
- 分片。支持分片功能,扩展很容易。
- 事务。redis支持简单事务,一个事务包含多个命令,一个事务是原子的。Redis 最简单的事务实现方式是使用 MULTI 和 EXEC 命令将事务操作包围起来。
参考:
Redis是什么?什么作用?优点和缺点
Redis的各项功能解决了哪些问题?
redis 和 memecache 有什么区别?
两者都是非关系型内存键值数据库,主要有以下不同:
- 数据类型。Memcached 仅支持字符串类型,而 Redis 支持五种不同的数据类型,可以更灵活地解决问题。
- 数据持久化:Redis 支持两种持久化策略:RDB 快照和 AOF 日志,而 Memcached 不支持持久化。
- 分布式:Memcached 不支持分布式,只能通过在客户端使用一致性哈希来实现分布式存储,这种方式在存储和查询时都需要先在客户端计算一次数据所在的节点。Redis Cluster 实现了分布式的支持。
- 内存管理机制:
- 在 Redis 中,并不是所有数据都一直存储在内存中,可以将一些很久没用的 value 交换到磁盘,而 Memcached 的数据则会一直在内存中。
- Memcached 将内存分割成特定长度的块来存储数据,以完全解决内存碎片的问题。但是这种方式会使得内存的利用率不高,例如块的大小为 128 bytes,只存储 100 bytes 的数据,那么剩下的 28 bytes 就浪费掉了。
redis 为什么是单线程的?
先说一下什么时候用多线程。多线程一般用在某个任务需要花费很长时间的时候,比如,现在有两件事情要做:烧水和洗衣服。烧水一般要花费十几分钟,在烧水的时候不可能一直等待,肯定是会让水烧着,然后去洗衣服,这种情况肯定用多线程比较好。
而redis是内存数据库,操作内存是很快的,不会产生如上面所说的类似于“烧水”这样需要很久才能做完的任务,所以根本没有必要用多线程。用多线程可能更慢,因为线程之间的切换也要花费时间。所以,redis的瓶颈不再内存。
redis使用I/O复用的方式处理网络层的多个socket连接,避免了锁的使用,提高了并发速度,但是,网络带宽和网络传输速率的瓶颈还是避免不了的,所以redis的瓶颈在于网络。
参考:
redis为什么要设计成单线程
为什么单线程的Redis这么快?
为什么说Redis是单线程的以及Redis为什么这么快!
什么是缓存的二八定律
"二八定律":80%的业务访问集中在20%的数据上。这时为了减轻数据的压力和提高网站的数据访问速度,则可以使用缓存机制来优化网站。
什么是冷数据,什么是热数据
冷数据和热数据是根据访问频次来判定的。
- 热数据:是需要被计算节点频繁访问的在线类数据。
- 冷数据:是对于离线类不经常访问的数据,比如企业备份数据、业务与操作日志数据、话单与统计数据。
什么是缓存穿透?怎么解决?
缓存穿透
指的是对某个一定不存在的数据进行请求,该请求将会穿透缓存到达数据库。
解决方案:
- 对这些不存在的数据缓存一个空数据;
- 对这类请求进行过滤。
什么是缓存雪崩,怎么解决?
指的是由于数据没有被加载到缓存中,或者缓存数据在同一时间大面积失效(过期),又或者缓存服务器宕机,导致大量的请求都到达数据库。
在有缓存的系统中,系统非常依赖于缓存,缓存分担了很大一部分的数据请求。当发生缓存雪崩时,数据库无法处理这么大的请求,导致数据库崩溃。
解决方案:
- 为了防止缓存在同一时间大面积过期导致的缓存雪崩,可以通过观察用户行为,合理设置缓存过期时间来实现;
- 为了防止缓存服务器宕机出现的缓存雪崩,可以使用分布式缓存,分布式缓存中每一个节点只缓存部分的数据,当某个节点宕机时可以保证其它节点的缓存仍然可用。
- 也可以进行缓存预热,避免在系统刚启动不久由于还未将大量数据进行缓存而导致缓存雪崩。
什么是缓存击穿,怎么解决?
是指一个key非常热点,在不停的扛着大并发,大并发集中对这一个点进行访问,当这个key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,就像在一个屏障上凿开了一个洞。
解决方案:
- 在访问key之前,采用SETNX(set if not exists)来设置另一个短期key来锁住当前key的访问,访问结束再删除该短期key,也就是访问时加锁。
- 设置缓存用不失效。
redis支持的java客户端都有哪些?jedis 和 redisson 有哪些区别?
Redis的Java客户端很多,官方推荐的有三种:Jedis、Redisson和lettuce。
在这里对Jedis和Redisson进行对比介绍
Jedis:
- 轻量,简洁,便于集成和改造
- 支持连接池
- 支持pipelining、事务、LUA Scripting、Redis Sentinel、Redis Cluster
- 不支持读写分离,需要自己实现
- 文档差(真的很差,几乎没有……)
Redisson:
- 基于Netty实现,采用非阻塞IO,性能高
- 支持异步请求
- 支持连接池
- 支持pipelining、LUA Scripting、Redis Sentinel、Redis Cluster
- 不支持事务,官方建议以LUA Scripting代替事务
- 支持在Redis Cluster架构下使用pipelining
- 支持读写分离,支持读负载均衡,在主从复制和Redis Cluster架构下都可以使用
- 内建Tomcat Session Manager,为Tomcat 6/7/8提供了会话共享功能
- 可以与Spring Session集成,实现基于Redis的会话共享
- 文档较丰富,有中文文档
对于Jedis和Redisson的选择,同样应遵循前述的原理,尽管Jedis比起Redisson有各种各样的不足,但也应该在需要使用Redisson的高级特性时再选用Redisson,避免造成不必要的程序复杂度提升。
参考:
Redis Java客户端的选择
怎么保证缓存和数据库数据的一致性?
http://blog.sina.com.cn/s/blog_bd5db8370102xbj6.html
https://www.oschina.net/question/259473_167708
https://www.jianshu.com/p/a532962cb9e9
https://blog.csdn.net/qq_28740207/article/details/80877079
redis 持久化有几种方式?
redis 怎么实现分布式锁?
redis 分布式锁有什么缺陷?
redis 如何做内存优化?
redis 淘汰策略有哪些?
redis 常见的性能问题有哪些?该如何解决?
Spring
什么时候用get什么时候用post
get方式的安全性较Post方式要差些,包含机密信息的话,建议用Post数据提交方式。在做数据查询时,建议用Get方式;而在做数据添加、修改或删除时,建议用Post方式;