知识点复习1


1/3/2015 JavaSE

总结一下常用知识点

serializable 的意义

  1. 比如说你的内存不够用了, 那计算机就要将内存里面的一部分对象暂时的保存到硬盘中, 等到要用的时候再读入到内存中, 硬盘的那部分存储空间就是所谓的虚拟内存. 在比如过你要将某个特定的对象保存到文件中, 我隔几天在把它拿出来用, 那么这时候就要实现Serializable接口;
  2. 在进行java的Socket编程的时候, 你有时候可能要传输某一类的对象, 那么也就要实现Serializable接口;最常见的你传输一个字符串, 它是JDK里面的类, 也实现了Serializable接口, 所以可以在网络上传输.
  3. 如果要通过远程的方法调用(RMI)去调用一个远程对象的方法, 如在计算机A中调用另一台计算机B的对象的方法, 那么你需要通过JNDI服务获取计算机B目标对象的引用, 将对象从B传送到A, 就需要实现序列化接口.

例如:

在web 开发中, 如果对象被保存在了Session 中, tomcat 在重启时要把Session 对象序列化到硬盘, 这个对象就必须实现Serializable接口.

如果对象要经过分布式系统 进行网络传输或通过rmi 等远程调用, 这就需要在网络上传输对象, 被传输的对象就必 须实现Serializable接口.

单例模式

  1. 懒汉模式
  2. 饿汉模式
  3. 同步锁
  4. 双锁机制
  5. 枚举实现
  6. 静态内部类实现

因为加载外部类时,是不会加载内部类的

//一个延迟实例化的内部类的单例模式
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;
    }
 }
1
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(); 
1
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;
    }
}
1
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());
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

防止序列化反序列化破坏单例的方法: 添加 readResolve 方法

private Object readResolve() {
        return singleton;
    }
1
2
3

利用枚举创建单例

/**
* Singleton pattern example using Java Enumj
*/
public enum EasySingleton{
    INSTANCE;
}
1
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();  
}  
1
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");  
} 
1
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 :

  1. NIO会将数据准备好后, 再交由应用进行处理, 数据的读取/写入过程依然在应用线程中完成, 只是将等待的时间剥离到单独的线程中去.
  2. 节省数据准备时间(因为Selector可以复用)

AIO:

  1. 读完了再通知我
  2. 不会加快IO, 只是在读完后进行通知
  3. 使用回调函数, 进行业务处理

序列化和反序列化

  • 只有实现了 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));
1
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)); // 比内容
1
2
3
4

false,true

强引用 软引用 弱引用 虚引用

强引用

  • 如果一个对象具有强引用, GC绝不会回收它;
  • 当内存空间不足, JVM宁愿抛出OutOfMemoryError错误.
  • 一般new出来的对象都是强引用, 如下
//强引用 
User strangeReference=new User();  
1
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;
  }
}
1
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方法获得强引用
1
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);
  }
}
1
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());
1
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);
  }
}
1
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(),
1
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();
    }
}
1
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是在什么时候, 对什么东西, 做了什么事情?

一.回答: 什么时候?

  1. 系统空闲的时候. 分析: 这种回答大约占30%, 遇到的话一般我就会准备转向别的话题, 譬如算法、譬如SSH看看能否发掘一些他擅长的其他方面.
  2. **系统自身决定, 不可预测的时间/调用System.gc()的时候. ** 分析: 这种回答大约占55%, 大部分应届生都能回答到这个答案, 起码不能算错误是吧, 后续应当细分一下到底是语言表述导致答案太笼统, 还是本身就只有这样一个模糊的认识.
  3. 能说出新生代、老年代结构, 能提出minor gc/full gc 分析: 到了这个层次, 基本上能说对GC运作有概念上的了解, 譬如看过《深入JVM虚拟机》之类的. 这部分不足10%.
  4. 能说明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的时间延迟, 以延长对象生存期.

二.回答: 对什么东西?

  1. 不使用的对象. 分析: 相当于没有回答, 问题就是在问什么对象才是“不使用的对象”. 大约占30%. 2 .超出作用域的对象/引用计数为空的对象. 分析: 这2个回答站了60%, 相当高的比例, 估计学校教java的时候老师就是这样教的. 第一个回答没有解决我的疑问, gc到底怎么判断哪些对象在不在作用域的?至于引用计数来判断对象是否可收集的, 我可以会补充一个下面这个例子让面试者分析一下
  2. **从gc root开始搜索, 搜索不到的对象. ** 分析: 根对象查找、标记已经算是不错了, 小于5%的人可以回答道这步, 估计是引用计数的方式太“深入民心”了. 基本可以得到这个问题全部分数. PS: 有面试者在这个问补充强引用(类似new Object(), 只要强引用还在就不会被回收)、弱引用(还有用但并非必须的对象, 在系统将要发生OOM之前, 才会将这些对象回收)、软引用(只能生存到下一次垃圾收集之前)、幻影引用(无法通过幻影引用得到对象, 和对象的生命周期无关, 唯一目的就是能在这个对象被回收时收到一个系统通知)区别等, 不是我想问的答案, 但可以加分.
  3. 从root搜索不到, 而且经过第一次标记、清理后, 仍然没有复活的对象. 分析: 我期待的答案. 但是的确很少面试者会回答到这一点, 所以在我心中回答道第3点我就给全部分数.

超出了作用域或引用计数为空的对象;从gc root开始搜索找不到的对象, 而且经过一次标记、清理, 仍然没有复活的对象.

三.回答: 做什么

  1. 删除不使用的对象, 腾出内存空间. 分析: 同问题2第一点. 40%.
  2. 补充一些诸如停止其他线程执行、运行finalize等的说明. 分析: 起码把问题具体化了一些, 如果像答案1那样我很难在回答中找到话题继续展开, 大约占40%的人.
  3. 能说出诸如新生代做的是复制清理、from survivor、to survivor是干啥用的、老年代做的是标记清理、标记清理后碎片要不要整理、复制清理和标记清理有有什么优劣势等. 分析: 也是看过《深入JVM虚拟机》的基本都能回答道这个程度, 其实到这个程度我已经比较期待了. 同样小于10%.
  4. 除了3外, 还能讲清楚串行、并行(整理/不整理碎片)、CMS等搜集器可作用的年代、特点、优劣势, 并且能说明控制/调整收集器选择的方式.

总结: 删除不使用的对象, 回收内存空间;运行默认的finalize, JVM用from survivor、to survivor对它进行标记清理, 对象序列化后也可以使它复活.

Last Updated: 7/3/2019, 6:17:56 PM