知识点复习1
总结一下常用知识点
serializable 的意义
- 比如说你的内存不够用了, 那计算机就要将内存里面的一部分对象暂时的保存到硬盘中, 等到要用的时候再读入到内存中, 硬盘的那部分存储空间就是所谓的虚拟内存. 在比如过你要将某个特定的对象保存到文件中, 我隔几天在把它拿出来用, 那么这时候就要实现Serializable接口;
- 在进行java的Socket编程的时候, 你有时候可能要传输某一类的对象, 那么也就要实现Serializable接口;最常见的你传输一个字符串, 它是JDK里面的类, 也实现了Serializable接口, 所以可以在网络上传输.
- 如果要通过远程的方法调用(RMI)去调用一个远程对象的方法, 如在计算机A中调用另一台计算机B的对象的方法, 那么你需要通过JNDI服务获取计算机B目标对象的引用, 将对象从B传送到A, 就需要实现序列化接口.
例如:
在web 开发中, 如果对象被保存在了Session 中, tomcat 在重启时要把Session 对象序列化到硬盘, 这个对象就必须实现Serializable接口.
如果对象要经过分布式系统 进行网络传输或通过rmi 等远程调用, 这就需要在网络上传输对象, 被传输的对象就必 须实现Serializable接口.
单例模式
- 懒汉模式
- 饿汉模式
- 同步锁
- 双锁机制
- 枚举实现
- 静态内部类实现
因为加载外部类时,是不会加载内部类的
//一个延迟实例化的内部类的单例模式
public final class Singleton {
//一个内部类的容器, 调用getInstance时, JVM加载这个类
private static final class SingletonHolder {
static final Singleton singleton = new Singleton();
}
private Singleton() {}
public static Singleton getInstance() {
return SingletonHolder.singleton;
}
}
2
3
4
5
6
7
8
9
10
11
防止反射实例化对象 利用反射生成对象
//使用反射破坏单例模式
Class c = Class.forName(Singleton.class.getName());
Constructor constructor = c.getDeclaredConstructor();
constructor.setAccessible(true);
Singleton singleton = (Singleton)ct.newInstance();
2
3
4
5
调用私有构造方法抛出异常 防止反序列化实例化对象
import java.io.Serializable;
/**
* Created by hollis on 16/2/5.
* 使用双重校验锁方式实现单例
*/
public class Singleton implements Serializable{
private volatile static Singleton singleton;
private Singleton (){}
public static Singleton getSingleton() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class SerializableDemo1 {
//为了便于理解, 忽略关闭流操作及删除文件操作. 真正编码时千万不要忘记
//Exception直接抛出
public static void main(String[] args) throws IOException, ClassNotFoundException {
//Write Obj to file
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("tempFile"));
oos.writeObject(Singleton.getSingleton());
//Read Obj from file
File file = new File("tempFile");
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
Singleton newInstance = (Singleton) ois.readObject();
//判断是否是同一个对象
System.out.println(newInstance == Singleton.getSingleton());
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
防止序列化反序列化破坏单例的方法: 添加 readResolve 方法
private Object readResolve() {
return singleton;
}
2
3
利用枚举创建单例
/**
* Singleton pattern example using Java Enumj
*/
public enum EasySingleton{
INSTANCE;
}
2
3
4
5
6
使用反射破解枚举单例:
运行结果是抛出异常: Exception in thread "main" java.lang.NoSuchMethodException: cn.xing.test.Weekday.<init>()
明明Weekday有一个无参的构造函数, 为何不能通过暴力反射访问?
最新的Java Language Specification (§8.9)规定: Reflective instantiation of enum types is prohibited. 这是java语言的内置规范.
使用 clone 破解枚举单例 所有的枚举类都继承自java.lang.Enum类, 而不是Object类. 在java.lang.Enum类中clone方法如下:
protected final Object clone() throws CloneNotSupportedException {
throw new CloneNotSupportedException();
}
2
3
调用该方法将抛出异常, 且final意味着子类不能重写clone方法, 所以通过clone方法获取新的对象是不可取的.
使用序列化破解枚举单例 java.lang.Enum类的readObject方法如下:
private void readObject(ObjectInputStream in) throws IOException,
ClassNotFoundException {
throw new InvalidObjectException("can't deserialize enum");
}
private void readObjectNoData() throws ObjectStreamException {
throw new InvalidObjectException("can't deserialize enum");
}
2
3
4
5
6
7
同暴力反射一样, Java Language Specification (§8.9)有着这样的规定: the special treatment by the serialization mechanism ensures that duplicate instances are never created as a result of deserialization.
fork/join
fork/join类似MapReduce算法, 两者区别是: Fork/Join 只有在必要时如任务非常大的情况下才分割成一个个小任务, 而 MapReduce总是在开始执行第一步进行分割. 看来, Fork/Join更适合一个JVM内线程级别, 而MapReduce适合分布式系统.
NIO 和 AIO
NIO :
- NIO会将数据准备好后, 再交由应用进行处理, 数据的读取/写入过程依然在应用线程中完成, 只是将等待的时间剥离到单独的线程中去.
- 节省数据准备时间(因为Selector可以复用)
AIO:
- 读完了再通知我
- 不会加快IO, 只是在读完后进行通知
- 使用回调函数, 进行业务处理
序列化和反序列化
- 只有实现了 Serializable 和 Externalizable 接口的类的对象才能被序列化. Externalizable接口继承自 Serializable接口, 实现Externalizable接口的类完全由自身来控制序列化的行为, 而仅实现Serializable接口的类可以采用默认的序列化方式 .
- 默认实现Serializable接口的序列化是对于一个类的非static, 非transient的实例变量进行序列化与反序列化. 刚刚上面也说了, 如果要对static实例变量进行序列化就要使用Externalizable接口, 手动实现.
- serialVersionUID的作用
- 父类的序列化
- 要想将父类对象也序列化, 就需要让父类也实现Serializable 接口. 如果父类不实现的话的, 就需要有默认的无参的构造函数. 在父类没有实现 Serializable 接口时, 虚拟机是不会序列化父对象的, 而一个 Java 对象的构造必须先有父对象, 才有子对象, 反序列化也不例外. 所以反序列化时, 为了构造父对象, 只能调用父类的无参构造函数作为默认的父对象. 因此当我们取父对象的变量值时, 它的值是调用父类无参构造函数后的值. 如果你kao虑到这种序列化的情况, 在父类无参构造函数中对变量进行初始化, 否则的话, 父类变量值都是默认声明的值, 如 int 型的默认是 0, string 型的默认是 null.
- 关键字transient
- 当持久化对象时, 可能有一个特殊的对象数据成员, 我们不想用serialization机制来保存它. 为了在一个特定对象的一个域上关闭serialization, 可以在这个域前加上关键字transient. transient是Java语言的关键字, 用来表示一个域不是该对象序列化的一部分. 当一个对象被序列化的时候, transient型变量的值不包括在序列化的表示中, 然而非transient型的变量是被包括进去的
Integer
int i = 0;
Integer j = new Integer(0);
System.out.println(i==j);
System.out.println(j.equals(i));
2
3
4
在 JDK1.5以前,会报错 在 JDK1.5后,由于引入了自动装箱和拆箱,会输入 true,true
进制
如果下列的公式成立: 78+78=123.则采用的是()进制表示的? 解析一: 设进制数为x, 根据题设公式展开为7x+8+7x+8=1x^2+2x+3, 由于进制数必须为正整数, 得到x=13. 解析二: 等式左边个位数相加为16, 等式右边个位数为3, 即16 mod x=3, x=13.
== 和 equals
String i = "0";
String j = new String("0");
System.out.println(i==j); // 比值
System.out.println(j.equals(i)); // 比内容
2
3
4
false,true
强引用 软引用 弱引用 虚引用
强引用
- 如果一个对象具有强引用, GC绝不会回收它;
- 当内存空间不足, JVM宁愿抛出OutOfMemoryError错误.
- 一般new出来的对象都是强引用, 如下
//强引用
User strangeReference=new User();
2
以前我们使用的大部分引用实际上都是强引用, 这是使用最普遍的引用. 如果一个对象具有强引用, 那就类似于必不可少的生活用品, 垃圾回收器绝不会回收它. 当内存空 间不足, Java虚拟机宁愿抛出OutOfMemoryError错误, 使程序异常终止, 也不会靠随意回收具有强引用的对象来解决内存不足问题.
软引用
如果一个对象具有软引用, 当内存空间不足, GC会回收这些对象的内存, 使用软引用构建敏感数据的缓存.
在JVM中, 软引用是如下定义的, 可以通过一个时间戳来回收, 下面引自JVM:
public class SoftReference<T> extends Reference<T> {
/**
* Timestamp clock, updated by the garbage collector
*/
static private long clock;
/**
* Timestamp updated by each invocation of the get method. The VM may use
* this field when selecting soft references to be cleared, but it is not
* required to do so.
*/
private long timestamp;
/**
* Creates a new soft reference that refers to the given object. The new
* reference is not registered with any queue.
*
* @param referent object the new soft reference will refer to
*/
public SoftReference(T referent) {
super(referent);
this.timestamp = clock;
}
/**
* Creates a new soft reference that refers to the given object and is
* registered with the given queue.
*
* @param referent object the new soft reference will refer to
* @param q the queue with which the reference is to be registered,
* or <tt>null</tt> if registration is not required
*
*/
public SoftReference(T referent, ReferenceQueue<? super T> q) {
super(referent, q);
this.timestamp = clock;
}
/**
* Returns this reference object's referent. If this reference object has
* been cleared, either by the program or by the garbage collector, then
* this method returns <code>null</code>.
*
* @return The object to which this reference refers, or
* <code>null</code> if this reference object has been cleared
*/
public T get() {
T o = super.get();
if (o != null && this.timestamp != clock)
this.timestamp = clock;
return o;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
软引用的声明的借助强引用或者匿名对象, 使用泛型SoftReference;可以通过get方法获得强引用. 具体如下:
//软引用
SoftReference<User>softReference=new SoftReference<User>(new User());
strangeReference=softReference.get();//通过get方法获得强引用
2
3
如果一个对象只具有软引用, 那就类似于可有可物的生活用品. 如果内存空间足够, 垃圾回收器就不会回收它, 如果内存空间不足了, 就会回收这些对象的内存. 只要垃圾回收器没有回收它, 该对象就可以被程序使用. 软引用可用来实现内存敏感的高速缓存. 软引用可以和一个引用队列(ReferenceQueue)联合使用, 如果软引用所引用的对象被垃圾回收, JAVA虚拟机就会把这个软引用加入到与之关联的引用队列中.
弱引用
如果一个对象具有弱引用, 在GC线程扫描内存区域的过程中, 不管当前内存空间足够与否, 都会回收内存, 使用弱引用 构建非敏感数据的缓存.
在JVM中, 弱引用是如下定义的, 下面引自JVM:
public class WeakReference<T> extends Reference<T> {
/**
* Creates a new weak reference that refers to the given object. The new
* reference is not registered with any queue.
*
* @param referent object the new weak reference will refer to
*/
public WeakReference(T referent) {
super(referent);
}
/**
* Creates a new weak reference that refers to the given object and is
* registered with the given queue.
*
* @param referent object the new weak reference will refer to
* @param q the queue with which the reference is to be registered,
* or <tt>null</tt> if registration is not required
*/
public WeakReference(T referent, ReferenceQueue<? super T> q) {
super(referent, q);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
弱引用的声明的借助强引用或者匿名对象, 使用泛型 WeakReference<T>
, 具体如下:
//弱引用
WeakReference<User>weakReference=new WeakReference<User>(new User());
2
如果一个对象只具有弱引用, 那就类似于可有可物的生活用品. 弱引用与软引用的区别在于: 只具有弱引用的对象拥有更短暂的生命周期. 在垃圾回收器线程扫描它 所管辖的内存区域的过程中, 一旦发现了只具有弱引用的对象, 不管当前内存空间足够与否, 都会回收它的内存. 不过, 由于垃圾回收器是一个优先级很低的线程, 因此不一定会很快发现那些只具有弱引用的对象.
弱引用可以和一个引用队列(ReferenceQueue)联合使用, 如果弱引用所引用的对象被垃圾回收, Java虚拟机就会把这个弱引用加入到与之关联的引用队列中.
虚引用
如果一个对象仅持有虚引用, 在任何时候都可能被垃圾回收, 虚引用与软引用和弱引用的一个区别在于: 虚引用必须和引用队列联合使用, 虚引用主要用来跟踪对象被垃圾回收的活动.
在JVM中, 虚引用是如下定义的, 下面引自JVM:
public class PhantomReference<T> extends Reference<T> {
/**
* Returns this reference object's referent. Because the referent of a
* phantom reference is always inaccessible, this method always returns
* <code>null</code>.
*
* @return <code>null</code>
*/
public T get() {
return null;
}
/**
* Creates a new phantom reference that refers to the given object and
* is registered with the given queue.
*
* <p> It is possible to create a phantom reference with a <tt>null</tt>
* queue, but such a reference is completely useless: Its <tt>get</tt>
* method will always return null and, since it does not have a queue, it
* will never be enqueued.
*
* @param referent the object the new phantom reference will refer to
* @param q the queue with which the reference is to be registered,
* or <tt>null</tt> if registration is not required
*/
public PhantomReference(T referent, ReferenceQueue<? super T> q) {
super(referent, q);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
虚引用 PhantomReference<T>
的声明的借助强引用或者匿名对象,结合泛型 ReferenceQueue<T>
初始化, 具体如下:
//虚引用
PhantomReference<User> phantomReference=new PhantomReference<User>(new User(),
2
"虚引用"顾名思义, 就是形同虚设, 与其他几种引用都不同, 虚引用并不会决定对象的生命周期. 如果一个对象仅持有虚引用, 那么它就和没有任何引用一样, 在任何时候都可能被垃圾回收. 虚引用主要用来跟踪对象被垃圾回收的活动. 虚引用与软引用和弱引用的一个区别在于: 虚引用必须和引用队列(ReferenceQueue)联合使用. 当垃 圾回收器准备回收一个对象时, 如果发现它还有虚引用, 就会在回收对象的内存之前, 把这个虚引用加入到与之关联的引用队列中. 程序可以通过判断引用队列中是 否已经加入了虚引用, 来了解 被引用的对象是否将要被垃圾回收. 程序如果发现某个虚引用已经被加入到引用队列, 那么就可以在所引用的对象的内存被回收之前采取必要的行动.
import java.lang.ref.*;
import java.util.HashSet;
import java.util.Set;
class User {
private String name;
public User()
{}
public User(String name)
{
this.name=name;
}
@Override
public String toString() {
return name;
}
public void finalize(){
System.out.println("Finalizing ... "+name);
}
}
/**
* Created by jinxu on 15-4-25.
*/
public class ReferenceDemo {
private static ReferenceQueue<User> referenceQueue = new ReferenceQueue<User>();
private static final int size = 10;
public static void checkQueue(){
/* Reference<? extends User> reference = null;
while((reference = referenceQueue.poll())!=null){
System.out.println("In queue : "+reference.get());
}*/
Reference<? extends User> reference = referenceQueue.poll();
if(reference!=null){
System.out.println("In queue : "+reference.get());
}
}
public static void testSoftReference()
{
Set<SoftReference<User>> softReferenceSet = new HashSet<SoftReference<User>>();
for (int i = 0; i < size; i++) {
SoftReference<User> ref = new SoftReference<User>(new User("Soft " + i), referenceQueue);
System.out.println("Just created: " + ref.get());
softReferenceSet.add(ref);
}
System.gc();
checkQueue();
}
public static void testWeaKReference()
{
Set<WeakReference<User>> weakReferenceSet = new HashSet<WeakReference<User>>();
for (int i = 0; i < size; i++) {
WeakReference<User> ref = new WeakReference<User>(new User("Weak " + i), referenceQueue);
System.out.println("Just created: " + ref.get());
weakReferenceSet.add(ref);
}
System.gc();
checkQueue();
}
public static void testPhantomReference()
{
Set<PhantomReference<User>> phantomReferenceSet = new HashSet<PhantomReference<User>>();
for (int i = 0; i < size; i++) {
PhantomReference<User> ref =
new PhantomReference<User>(new User("Phantom " + i), referenceQueue);
System.out.println("Just created: " + ref.get());
phantomReferenceSet.add(ref);
}
System.gc();
checkQueue();
}
public static void main(String[] args) {
testSoftReference();
testWeaKReference();
testPhantomReference();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
谈谈, Java GC 是在什么时候, 对什么东西, 做了什么事情
地球人都知道, Java有个东西叫垃圾收集器, 它让创建的对象不需要像c/cpp那样delete、free掉, 你能不能谈谈, GC是在什么时候, 对什么东西, 做了什么事情?
一.回答: 什么时候?
- 系统空闲的时候. 分析: 这种回答大约占30%, 遇到的话一般我就会准备转向别的话题, 譬如算法、譬如SSH看看能否发掘一些他擅长的其他方面.
- **系统自身决定, 不可预测的时间/调用System.gc()的时候. ** 分析: 这种回答大约占55%, 大部分应届生都能回答到这个答案, 起码不能算错误是吧, 后续应当细分一下到底是语言表述导致答案太笼统, 还是本身就只有这样一个模糊的认识.
- 能说出新生代、老年代结构, 能提出minor gc/full gc 分析: 到了这个层次, 基本上能说对GC运作有概念上的了解, 譬如看过《深入JVM虚拟机》之类的. 这部分不足10%.
- 能说明minor gc/full gc的触发条件、OOM的触发条件, 降低GC的调优的策略.
分析: 列举一些我期望的回答: eden满了minor gc, 升到老年代的对象大于老年代剩余空间full gc, 或者小于时被HandlePromotionFailure参数强制full gc;gc与非gc时间耗时超过了GCTimeRatio(GC时间占总时间的比率, 默认值为99, 即允许1%的GC时间, 仅在使用Parallel Scavenge收集器时生效)的限制引发OOM, 调优诸如通过NewRatio控制新生代老年代比例, 通过MaxTenuringThreshold控制进入老年前生存次数等……能回答道这个阶段就会给我带来比较高的期望了, 当然面试的时候正常人都不会记得每个参数的拼写, 我自己写这段话的时候也是翻过手册的. 回答道这部分的小于2%. 总结: 程序员不能具体控制时间, 系统在不可预测的时间调用System.gc()函数的时候;当然可以通过调优, 用NewRatio控制newObject和oldObject的比例, 用MaxTenuringThreshold 控制进入oldObject的次数, 使得oldObject 存储空间延迟达到full gc,从而使得计时器引发gc时间延迟OOM的时间延迟, 以延长对象生存期.
二.回答: 对什么东西?
- 不使用的对象. 分析: 相当于没有回答, 问题就是在问什么对象才是“不使用的对象”. 大约占30%. 2 .超出作用域的对象/引用计数为空的对象. 分析: 这2个回答站了60%, 相当高的比例, 估计学校教java的时候老师就是这样教的. 第一个回答没有解决我的疑问, gc到底怎么判断哪些对象在不在作用域的?至于引用计数来判断对象是否可收集的, 我可以会补充一个下面这个例子让面试者分析一下
- **从gc root开始搜索, 搜索不到的对象. ** 分析: 根对象查找、标记已经算是不错了, 小于5%的人可以回答道这步, 估计是引用计数的方式太“深入民心”了. 基本可以得到这个问题全部分数. PS: 有面试者在这个问补充强引用(类似new Object(), 只要强引用还在就不会被回收)、弱引用(还有用但并非必须的对象, 在系统将要发生OOM之前, 才会将这些对象回收)、软引用(只能生存到下一次垃圾收集之前)、幻影引用(无法通过幻影引用得到对象, 和对象的生命周期无关, 唯一目的就是能在这个对象被回收时收到一个系统通知)区别等, 不是我想问的答案, 但可以加分.
- 从root搜索不到, 而且经过第一次标记、清理后, 仍然没有复活的对象. 分析: 我期待的答案. 但是的确很少面试者会回答到这一点, 所以在我心中回答道第3点我就给全部分数.
超出了作用域或引用计数为空的对象;从gc root开始搜索找不到的对象, 而且经过一次标记、清理, 仍然没有复活的对象.
三.回答: 做什么
- 删除不使用的对象, 腾出内存空间. 分析: 同问题2第一点. 40%.
- 补充一些诸如停止其他线程执行、运行finalize等的说明. 分析: 起码把问题具体化了一些, 如果像答案1那样我很难在回答中找到话题继续展开, 大约占40%的人.
- 能说出诸如新生代做的是复制清理、from survivor、to survivor是干啥用的、老年代做的是标记清理、标记清理后碎片要不要整理、复制清理和标记清理有有什么优劣势等. 分析: 也是看过《深入JVM虚拟机》的基本都能回答道这个程度, 其实到这个程度我已经比较期待了. 同样小于10%.
- 除了3外, 还能讲清楚串行、并行(整理/不整理碎片)、CMS等搜集器可作用的年代、特点、优劣势, 并且能说明控制/调整收集器选择的方式.
总结: 删除不使用的对象, 回收内存空间;运行默认的finalize, JVM用from survivor、to survivor对它进行标记清理, 对象序列化后也可以使它复活.