1. public, protected, default, private 以及开放性
见博客文章
2. 不带后缀的字面量小数默认是double型,带f后缀的是float型,所以float f = 3.4
是错误的,正确的是float f = 3.4f
3. 包装类
为什么要有包装类?
因为泛型中的类型参数不能使用基本类型.
为什么泛型中的类型参数不能使用基本类型?
因为java中的泛型实现实际上是将保存的泛型当做Object,然后在需要的地方向下转型到相应的类型,举个例子
List list = new ArrayList();
list.add(new ClassA());
ClassA a = list.get(0);
gets turned into (roughly):
List list = new ArrayList();
list.add(new ClassA());
ClassA a = (ClassA)list.get(0);
因为List中保存的是Object,并需要在使用时向下转型成正确的类型,所以泛型中的类型参数必须为包装类型(基本类型并不继承自Object)
5. override与overload
博客
6. getClass() 与 instanceof的区别
可以这样说, instanceof考虑到了多态, 一个对象也是其父类的实例, 是是一个的关系, 而getClass只考虑其本身
class A { }
class B extends A { }
Object o1 = new A();
Object o2 = new B();
o1 instanceof A => true
o1 instanceof B => false
o2 instanceof A => true // <================ HERE
o2 instanceof B => true
o1.getClass().equals(A.class) => true
o1.getClass().equals(B.class) => false
o2.getClass().equals(A.class) => false // <===============HERE
o2.getClass().equals(B.class) => true
7. StringBuilder, StringBuffer, String
见博文
9. 类加载器
类的加载是由类加载器完成的,类加载器包括:根加载器(BootStrap)、扩展加载器(Extension)、系统加载器(System)和用户自定义类加载器(java.lang.ClassLoader的子类)。从Java 2(JDK 1.2)开始,类加载过程采取了父亲委托机制(PDM)。PDM更好的保证了Java平台的安全性,在该机制中,JVM自带的Bootstrap是根加载器,其他的加载器都有且仅有一个父类加载器。类的加载首先请求父类加载器加载,父类加载器无能为力时才由其子类加载器自行加载。JVM不会向Java程序提供对Bootstrap的引用。下面是关于几个类加载器的说明:
- Bootstrap:一般用本地代码实现,负责加载JVM基础核心类库(rt.jar);
- Extension:从java.ext.dirs系统属性所指定的目录中加载类库,它的父加载器是Bootstrap;
- System:又叫应用类加载器,其父类是Extension。它是应用最广泛的类加载器。它从环境变量classpath或者系统属性java.class.path所指定的目录中记载类,是用户自定义加载器的默认父加载器。
顾名思义, 类加载器就是决定要加载哪一个类, 由上描述, 类加载器之间的关系是一种树型关系, 而PDM保证了, 用户写的同名类(全限定名)不会覆盖系统自己的类如Object, 因此保证了程序的基础行为。
10.为什么抽象的方法不可以是static的
Because "abstract" means: "Implements no functionality", and "static" means: "There is functionality even if you don't have an object instance". And that's a logical contradiction.
abstract说明该方法是空的, 无功能的, 需要继承实现其功能, 而static说明, 即使没有对象也可以使用, 可以使用说明其是有功能的, 矛盾。
12. Integer的parseInt valueOf
public static Integer valueOf(String s) throws NumberFormatException {
return Integer.valueOf(parseInt(s, 10));
}
public static int parseInt(String s) throws NumberFormatException {
return parseInt(s,10);
}
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
由源码可知,valueOf就是把parseInt的结果装了个箱
13. Comparable和Comparator的区别
其实从名字上就可以区别两者
Comparable(可比较的):这说的是对象的固有性质,往往可以有这样的说法,一个对象是可比较的.
Comparator(比较器):比较器是一个外部的东西,往往是利用比较器来比较对象,它不是对象固有的性质.
由以上两者的含义不同,其用法也不同
Comparable是被继承使用,这样,这个类的对象就是可比较的,并且其实现往往提供了对象的自然顺序, 什么是自然顺序呢? 就是符合人们直觉的顺序,如数的从小到大,字母的a,b,c,d
而Comparator往往是作为一个参数传给一个方法的,因为Java中方法并不能直接传给一个方法,为了实现c语言中的利用不同的策略来进行排序就只能传递一个Comparator.
一个比较直观的例子见
Java Sorting: Comparator vs Comparable Tutorial
AOP
关键是讲清AOP中的术语, 以及和代理模式的关系, 详见以下文章
從代理機制初探 AOP
動態代理
AOP 觀念與術語
Spring AOP 学习笔记-引子
AOP那点事儿
AOP主要是将业务无关的东西分隔开来,这样即提到业务模块的复用性,用提到非业务模块的复用性
18. 锁的优化策略
读写分离 : 使用读写锁
分段加锁 : 减少锁的粒度 concurrentHashMap
减少锁持有的时间
多个线程尽量以相同的顺序去获取资源。
无锁设计
结合自己博客文章
20. CopyOnWrite系列
博客文章
21. ArrayList, LinkedList, Vector, HashMap, HashTable的原理
博客文章
22. Java提供的四种线程池
详见
http://www.infoq.com/cn/articles/java-threadPool
Java中线程池就一个实现类ThreadPoolExecutor
Executors提供的几种就是对其设置不同参数而已
23. Synchronized 与 ReentrantLock的区别
见博客
25. Object的clone方法与深拷贝与浅拷贝
见博客
26. finalize方法
博客文章, 能做什么, 不能做什么
27. finally return
见博客
28. Java 容器
博客文章
29. Java Web
讲一下Servlet, Tomcat, JSP, Spring等的作用与角色, 能与Laravel做一些对比
Tomcat是一个Servlet容器, 所谓容器就是负责管理Servlet的生命周期的工具.另外Tomcat本身也包含一个Http服务器, 但并不是一个功能强大完全的服务器
Servlet是服务器端程序, 用来处理请求, 本生成动态的Web内容. 理论上说, Servlet可以处理各种类型的请求, 但绝大多数情况下是用来处理Http请求的.
JSP: 因为Servlet处理Http请求, 要生成HTML, XML等响应, 不可避免的涉及到大量HTML内容, 这给servlet的书写效率和可读性带来很大障碍,JSP便是在这个基础上产生的。其功能是使用HTML的书写格式,在适当的地方加入Java代码片段,将程序员从复杂的HTML中解放出来,更专注于servlet本身的内容。
JSP在首次被访问的时候被应用服务器转换为servlet,在以后的运行中,容器直接调用这个servlet,而不再访问JSP页面。JSP的实质仍然是servlet。
Spring是一种更一般化的DI容器.
30. Map针对Value进行排序
TreeMap只是对Key进行排序
http://stackoverflow.com/questions/109383/sort-a-mapkey-value-by-values-java
31. ThreadLocal使用和原理
见博文
33. Happen_before原则
见博文
34. Java锁的种类
如果要对某一事物分类, 必须按照一下标准, 但对Java锁的分类按照什么标准呢? 似乎找不到什么标准, 以下是自己遇到过的一些锁
- 隐式锁 Synchronized
- 显示可重入 ReentrantLock
- 读定锁 ReadWriteLock
- 自旋锁 利用CAS
37. BIO, NIO系统总结一下
参见
https://segmentfault.com/a/1190000003063859#articleHeader17
38. String 类为什么是final的
String基本约定中最重要的一条是immutable。
的确声明String为final 和immutable是没有必然关系,但是假如String没有声明为final, 那么你的StringChilld就有可能是被复写为mutable的,这样就打破了成为共识的基本约定。
举个例子:一个方法可能本来接受String类型并返回其大写方式
public static String uppperString(String s){
return s.toUpperCase();
}
你传入String 的s="test", 他不会修改字符串池中"test", 而是直接新建立一个实例"TEST"返回。但如果你的StringChild的toUpperCase()被你重写(override)为mutable的方式,然后你调用这个方法的时候传入的是StringChild实例, 那么整体(依赖于(过)方法uppperString的所有类)的行为就有可能出现错乱。
要知道,String是几乎每个类都会使用的类,特别是作为Hashmap之类的集合的key值时候,mutable的String有非常大的风险。而且一旦发生,非常难发现。
声明String为final一劳永逸。
40. 参数传递
- 对于基本类型就是传值
- 对于对象, 就是复制了一份引用, 这可以对照C语言中传递指针
详见http://blog.sina.com.cn/s/blog_4b622a8e0100c1bo.html
41. 子类为什么不能缩小父类的访问权限
Lisvo替换原则, 子类必须可以当做一个父类使用
但是子类可以扩大父类的访问权限, 但这种行为是不好的, 这样子类的接口相对于父类就扩大了, 当替换子类时, 可能另一个子类并没有扩大父类接口, 所以子类的接口最好和父类是一样的。
17. concurrentHashMap原理
CocurrentHashMap可以看成由多个HashMap组成, 每一个Segment可以看作是一个HashMap
其高并发性来自于以下两个方面
- 用分离锁实现多个线程间的更深层次的共享访问。
- 用 volatile 型变量协调线程间的内存可见性,使得 大多数时候,读操作不需要加锁就可以正确获得值。
https://www.ibm.com/developerworks/cn/java/java-lo-concurrenthashmap/
39. Java内存泄露定位与调查
利用工具观察内存的使用情况, 看是不是一直在增加, 利用工具分析内存快照。
因为没有经验, 想个大致思路即可。
35. 动态代理与cglib的区别, 动态代理的原理
代理的作用
- 添加日志功能
- 添加缓存功能
- 添加统计功能, 如统计函数的运行时间
静态代理, 代理类的实现由程序员负责
动态代理,在程序的运行过程中来生成代理类, 需要指定被代理的对象
动态代理的好处
- 简化了代理类的生成, 减少程序员的工作量, 否则需要为每一个被代理类手动生成代理
- 因为是动态生成的, 可以将代理类的指定放到程序外, 如配置文件中, 这样可以在不改变代码的情况下更换代理的行为。
JDK动态代理与cglib的区别
JDK动态代理是需要接口的, 也就是被代理类必须实现某个接口,
cglib是不需要接口的, 原理是对指定的目标类生成一个子类,并覆盖其中方法实现增强,但因为采用的是继承,所以不能对final修饰的类进行代理。
36. DirectMemory与HeapByteBuffer的区别
MyCAT中Buffer机制分析
ByteBuffer.allocate() vs. ByteBuffer.allocateDirect()
16. 如何设计一个高并发的系统
这个问题太大, 自己没有经验回答不好。 所以要想办法将问题具体化, 缩小化, 什么样的问题促进什么样的思考, 可缩小如下
-
如何使单台服务器支持数十万路链接, 对于聊天应用是慢速, 长连接
这要从, 设计IO模型, 来尽量减少线程以及防止读数据过慢导致的阻塞IO线程, 阻塞了其它有数据的Socket的读写
-
如何优化对数据库的访问, 使得数据库的处理最快
以一个点赞功能为例, 从请求的发起到执行完毕后响应功能有哪些步骤, 每一步又能如何优化去考虑。
具体来说, 可以从
- 表的设计, 分表
- 缓存(不熟)
- 是否使用索引
- SQL语句优化(不熟)
- 隔离级别的选择
- 存储引擎的选择, MVVC而不是锁
- 合并请求(也可以过滤)
- 进一步可使用MySql的主从复制保证高可用性, 也可实现读写分离
17. super 与 this 同时使用问题
- 普通函数中调用父类函数
class Fathor {
void foobar(int a) {
System.out.println("fathor");
}
}
class Son extends Fathor {
void foobar() {
super.foobar(10);
System.out.println("son");
}
}
super与this与调用构造方法
this代表对象的构造方式, 当一个对象有多个构造方法时, 一般会这样设计
有一个构造方法可以做为完全构造方法, 负责构造这个对象的所有属性
其它的构造方法只是对其进行包装方便使用, 如为某些属性设置默认值
而在完全构造方法中, 需要使用super来构造父类对象, 至于其它构造方法, 因为最终都委托给了完全构造方法,
所以不需要调用super.
在这个意义上来讲, super和this都要作为第一行因此不能导致同时调用在设计上是完全合情合理的。
18. WeakReference, WeakHashMap, ThreadLocalMap
- WeakReference的一个应用场景, 同时也是WeakHashMap的应用场景
http://www.jianshu.com/p/a7aaaf1bd7be
Why——为什么使用弱引用?
考虑下面的场景:现在有一个Product类代表一种产品,这个类被设计为不可扩展的,而此时我们想要为每个产品增加一个编号。一种解决方案是使用HashMap
引用队列
下面我们来简单地介绍下引用队列的概念。实际上,WeakReference类有两个构造函数:
//创建一个指向给定对象的弱引用
WeakReference(T referent)
//创建一个指向给定对象并且登记到给定引用队列的弱引用
WeakReference(T referent, ReferenceQueue super T> q)
我们可以看到第二个构造方法中提供了一个ReferenceQueue类型的参数,通过提供这个参数,我们便把创建的弱引用对象注册到了一个引用队列上,这样当它被垃圾回收器清除时,就会把它送入这个引用队列中,我们便可以对这些被清除的弱引用对象进行统一管理。
当不使用某一对象后, 用于自动删除容器中的对象。
这和Cache还不太一样, 一般是自己来实现Cache中替换策略
19. ThreadLocalMap与ClassLoader内存泄露
http://wiki.xiaohansong.com/java/class_lifecycle.html
http://blog.xiaohansong.com/2016/08/06/ThreadLocal-memory-leak/
http://blog.xiaohansong.com/2016/08/09/ThreadLocal-leak-analyze/
20. HashMap rehash过程, 在并发环境下可能导致死循环
http://coolshell.cn/articles/9606.html
21. JVM调优
明确问题
JVM调优的目的是什么? 让程序运行的更好更快。
暂时先抛开JVM, 想一想怎样让程序运行的更快更好
可以从两个方面考虑
- 程序本身:如调整算法之类
- 程序所运行的硬件设备:如更改内存之类
而JVM调优应该类似与第二个方面。
原理
即然调优涉及到JVM, 所以要了解JVM的原理。
这里的原理涉及到
- JVM运行时的数据区域
- 垃圾收集以及内存分配策略
可考虑
- 每一类分区的大小(根据程序的行为)
- 选择不同的垃圾回收器(根据程序的要求, 如是否是交互型的, 是否是高吞吐量的等等)
策略
利用工具监视找到问题-解决问题
注意
大多时, 优化并非只限于JVM, 更重要的是理解程序本身的行为并做优化, 程序本身与JVM优化不能割裂来看
举例
1.优化程序的空间复杂度
2. StringBuilder代替String: 防止常量池有大量临时的字符串
3. String.intern()使用:防止产生大量无用的String对象(不是常量池对象),见自己的笔记
4. Netty中利用直接内存与缓冲池技术,避免在Heap大量分配与回收内存造成内存抖动以及减少复制次数
Netty中怎样处理短时间内大量内存的申请和释放
考虑一个客户端和一个服务器
典型的交互是, 客户端发来一个Request, 这是一个有结构的对象, 服务器返回一个Response, 这
也是一个有结构的对象。
虽然, 都是有结构的对象, 但从socket读和写时, 其实都是字节流。
所以, 要首先有一个Buffer, 将一个Request对象所对应的字节缓存下来, 然后从该缓存中解码生成
Request对象。
这里就涉及到缓存的拷贝, 如果该Buffer为堆上分配, 则需要从内核socket缓冲区复制到直接缓冲区,
再从直接缓冲区复制到堆缓冲区。
这就涉及到多次复制。
现在考虑多个客户端,假如1s中处理10W个客户端的请求。
那么在1s中内就要分配和释放10W个Buffer, 这会导致什么问题呢?
- 过多不必要的缓冲区的复制
- 如果都在堆上分配,由于短时间内大量的分配与释放, 可以造成内存的抖动。为GC增加压力。
那么怎么办呢?
- 为了减少复制, 可以使用off heap memory
- 利用对象池技术, 减少短时间内大量对象的分配与释放。