Java知识荟萃(为什么实现Serializable接口&volatile关键字解析&对象生死状态)
Java知识荟萃(2019年5月14日)
无敌码农
Java对象为什么要实现Serializable接口
Serializable接口没有任何方法或者字段,只是用于标识可系列化的语义.实现了Serializable接口的类可以被ObjectOutputStream转换为字节流,同时也可以通过ObjectInputStream再将其解析为对象.下面通过代码来讲一个对象保存到文件,并读取的过程:
package com.xiaojian.serializable;
import java.io.Serializable;
public class User implements Serializable {
private static final long serialVersionUID = 6021512867207299175L;
private String name;
private String password;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
测试代码:
package com.xiaojian.serializable;
import java.io.*;
public class TestSerializable {
public static void main(String[] args) {
//writerObject();
readerObject();
}
private static void readerObject() {
try{
ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("E:\\user.txt"));
Object object = inputStream.readObject();
User user = (User) object;
System.out.println(user.getName() + "---" + user
.getPassword());
inputStream.close();
}catch(IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
private static void writerObject() {
User user = new User();
user.setName("张三");
user.setPassword("123456");
try{
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("E:\\user.txt"));
outputStream.writeObject(user);
outputStream.close();
}catch(IOException e) {
e.printStackTrace();
}
}
}
如果我们在保存的时候,User类不实现Serializable接口,那么将会抛出一个异常,同样的,在读取文件转换为对象的时候,如果我们修改了User类的Serializable值,同样也会转换错误.
常见的序列化场景:
- 把内存中的数据保存到文件或者数据库
- 网络通信时需要用套接字在网络中传送对象时,如我们使用RPC协议进行网络通信时;
volatile关键字
首先,volatile关键字只能修饰类变量和实例变量.方法参数、局部变量、实例常量以及类常量都是不能用volatile修饰的.
原因:volatile关键字是要解决多线程间共享变量的可见性问题的,只有类变量和实例变量才是java中的共享变量的类型,方法参数因为是线程私有,并不存在共享的问题,而常量本身的值是固定的,所以不需要被其修饰
如果volatile修饰的是一个引用数据类型的对象变量,那么该对象作为类变量或实例变量时,其对象中携带的类变量和实例变量也相当于被volatile修饰了.
在并发编程中有三个很重要的特性:
-
原子性
在一次操作或者多次操作中,要么所有的操作全部执行,要么全部不执行
-
可见性
当一个线程对共享变量进行了修改,那么其他线程可以立刻看到修改的最新值
-
有序性
程序代码在执行过程中要确保有数据依赖关系的代码要有先后顺序.
而volatile关键字,就是保证了其中的可见性和有序性,但是不能保证操作的原子性.
在有序性上,它通过增加内存屏障的方式禁止指令重排序,在可见性上,volatile使得它修饰的变量,在被修改以后,会立马刷新同步至主内存,同时将其他线程工作内存中的副本变量置为无效.
石彬的架构笔记
跟面试官聊到JVM,他99%会让你谈谈这个问题!
如何判断一个对象的生死状态?
- 引用计数器算法
给每一个对象设置一个引用计数器,每当有一个地方引用这个对象的时候,计数器就加1,每当引用失效的时候就减1.
优点:实现简单、性能高
缺点:增减处理频繁消耗CPU计算,最主要是无法解决循环引用的问题.
- 可达性分析算法
通过一系列的"GC Roots"对象作为起始点,从这些对象开始往下搜索,搜索所经过的路径称之为"引用链".
当一个对象到GC Roots没有任何引用链相连的时候,证明此对象是可以被回收的.
在java中,可作为GC Roots对象的列表:
- java虚拟机栈中的引用对象
- 本地方法中JNI引用的对象
- 方法区中类静态常量的引用对象
- 方法区中常量的引用对象
对象生死与引用的关系
上面的两种算法,不管是引用计数还是可达性分析,都和对象的"引用"有关,这说明对象的引用决定了对象的生死.
在JDK1.2以后对对象引用进行了扩充,分为:
- 强引用
- 软引用
- 弱引用
- 虚引用
强引用: 在代码中普遍存在的,类似Object obj = new Object()
这种,只要强引用还在,垃圾回收器永远不会回收掉被引用的对象.
软引用: 是一种相对强引用弱化一些的引用,可以让对象豁免一些垃圾回收.只有当jvm认为内存不足时,才会去试图回收软引用指向的对象.jvm会确保在抛出OutOfMemoryError之前,清理软引用指向的对象.
弱引用: 非必须对象,但它的强度比软引用弱,被弱引用关联的对象只能生存到下一次垃圾回收之前.
虚引用: 也成为幽灵引用或幻影引用,是最弱的一种引用关系,无法通过虚引用来获取一个对象实例,为对象设置虚引用的目的只有一个,就是当这个对象被垃圾回收器回收时收到一条系统通知.
死亡标记与拯救
在可达性算法中,不可达的对象,并不是非死不可的,要真正宣告一个对象死亡,至少要经历两次标记的过程.
如果对象在进行可达性分析之后,没有与GC Roots相连接的引用链,它会被第一次标记,并进行筛选,筛选的条件是此对象是否有必要执行finalize()方法.
而执行finalize()方法的两个条件:
- 重写了finalize()方法
- finalize()方法之前没有被调用过,因为对象的finalize()方法只能被执行一次.
如果满足以上两个条件,这个对象将会放置在F-Queue的队列中,并在稍后由一个虚拟机自建的、低优先级FInalizer线程来执行它.
finalize()方法是对象脱离死亡命运最后的机会,如果对象在finalize()方法中重写与引用链上的任何一个对象建立关联,就可以存活.如把自己复制给某个类变量或对象的成员变量.
示例代码:
执行结果:
执行finalize方法
我还活着
我已经死了
从结果可以看出,任何对象的finalize()方法只会被系统调用一次.