文章目录
参考
jdk8源码
java 版本 java version “1.8.0_191”
Unsafe类
在看JDK源码或者一些开源软件时,会是不是经常看到着这个类的使用,
这个类做什么?有什么用?
首先我们看一下源码。源码中这个类的绝大多数都是native 方法。有几个不是native方法的方法,内部调用的也是native方法,而且都是公共的方法,但是如果你直接调用里面的方法会发现报如下异常
Exception in thread "main" java.lang.SecurityException: Unsafe
at sun.misc.Unsafe.getUnsafe(Unsafe.java:90)
这是为什么呢?
public static Unsafe getUnsafe() {
Class var0 = Reflection.getCallerClass();
//这里会首先获取调用这个方法的Class 对象
//然后获取这个类的类加载器判断是不是根加载器
if (!VM.isSystemDomainLoader(var0.getClassLoader())) {
throw new SecurityException("Unsafe");
} else {
return theUnsafe;
}
}
这个类的对象方法的使用只能是是jre/lib/rt.jar包里面的类调用。
难道真的就没有办法调用了么?也不是可以通过反射来实现,但是这样做会增加程序的风险。Java 文档不建议直接操作这个方法,除非你非常清除你要做什么,否则很有可能。内存溢出。
Field field = Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
Unsafe unsafe = (Unsafe) field.get(null);
//分配一块内存
Long id = unsafe.allocateMemory(1024);
System.out.println(id);
//释放一块内存
unsafe.freeMemory(id);
field.setAccessible(false);
double-check 写法
今天查看代码,从同事那里学到一种写法。
private volatile Map<String, ThreadLocal<RunStatDto>> runStatThreadLocalMap = new ConcurrentHashMap<>();
/**这段代码的精髓在这里,
原先这个地方我以为只需要这样写就可以了。
synchronized (runStatThreadLocalMap) {
if (!runStatThreadLocalMap.containsKey(processKey)) {
runStatThreadLocalMap.put(processKey, new ThreadLocal<>());
}
}
后来经过同事的讲解,我这种写法只保证了现成安全,但是没有考虑性能。
为什么?
因为,如果向上面那样写的话,不论map中是否包含key都要获取锁,
而double-check,就很好避免这个问题。同时利用了ConcurrentHashMap的并发特性。只要包含key就不需要竞争锁资源。最大可能保证并发性。
**/
if (!runStatThreadLocalMap.containsKey(processKey)) {
// double-check
synchronized (runStatThreadLocalMap) {
if (!runStatThreadLocalMap.containsKey(processKey)) {
runStatThreadLocalMap.put(processKey, new ThreadLocal<>());
}
}
}
空对象调用静态方法
class Person{
public static void testA(){
System.out.print("testA");
}
public void testB(){
System.out.print("testB");
}
}
public static void main(String[] args) {
Person person = new Person();
person = null;
person.testA();//这一行不会报空指针异常,原因是静态方法不依对象而存在。
}
空对象instanceof
class Person{
public static void testA(){
System.out.print("testA");
}
public void testB(){
System.out.print("testB");
}
}
public static void main(String[] args) {
Person person = new Person();
person = null;
//或persion = null;
persion = (Person) null;
if (persion instanceof Person){
System.out.println(true);
}else {
System.out.println(false);//总是打印false
}
}
以上打印false
ScheduledThreadPoolExecutor
Java远程调试
-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=5005
String.intern
String s1 = new StringBuilder("go").append("od").toString();
System.out.println(s1.intern() == s1);
//2:
//String s0 = "good";
String s2 = new StringBuilder("go").append("od").toString();
System.out.println(s2.intern() == s2);
//3:
String s3 = new StringBuilder("ja").append("va").toString();
System.out.println(s3.intern() == s3);
不同jar完全相同的包名和类名的加载问题
类的加载与jar的导入顺序有关。只会加载第一个。第二个包会在classloader加载类时判断重复而忽略。
java 获取泛型的实际类
Class<K> ss = ((Class<K>) (((ParameterizedType)(this.getClass().getGenericSuperclass())).getActualTypeArguments()[0]));
java为什么有些异常throw出去需要在函数头用throws声明,一些就不用。
Excepiton分两类:checked exception、runtime exception;
直接继承自Exception就是checked exception,继承自RuntimeException就是runtime的exception。
你可以简单地理解checked exception就是要强制你去处理这个异常(不管你throws多少层,你终归要在某个地方catch它);而runtime exception则没有这个限制,你可以自由选择是否catch。
那些强制异常处理的代码块,必须进行异常处理,否则编译器会提示“Unhandled exception type Exception”错误警告。
clone
浅clone
被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象。换言之,浅复制仅仅复制所考虑的对象,而不复制它所引用的对象。
public class Bird implements Cloneable,Serializable{
/**bird种类*/
String type;
/**age*/
int age;
/**羽毛*/
Feather feather;
public Bird(String type, int age, Feather feather) {
this.type = type;
this.age = age;
this.feather = feather;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
/**
* 深度克隆:通过递归的把内嵌的对象复制实现深度克隆。
* @return
*/
public Bird clone(){
Bird o = null;
Feather f = null;
try{
o = (Bird)super.clone();
o.feather = (Feather) this.feather.clone();
}catch (CloneNotSupportedException e){}
return o;
}
@Override
public String toString() {
return "type= " + type + ", age= " + age + ", " + feather.toString() ;
}
}
/**羽毛*/
class Feather implements Cloneable,Serializable{
/**color*/
String color;
/**length*/
int length;
public Feather(String color, int length) {
this.color = color;
this.length = length;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
public int getLength() {
return length;
}
public void setLength(int length) {
this.length = length;
}
/**
* 浅克隆
* @return
*/
public Object clone(){
Object o = null;
try{
o = super.clone();
}catch(CloneNotSupportedException e){}
return o;
}
@Override
public String toString() {
return "color= " + color + ", length= " + length ;
}
}
深clone
被复制对象的所有变量都含有与原来的对象相同的值,除去那些引用其他对象的变量。那些引用其他对象的变量将指向被复制过的新对象,而不再是原有的那些被引用的对象。换言之,深复制把要复制的对象所引用的对象都复制了一遍。
实现深度克隆有两种方式:
- 实现Cloneable接口并重写Object类中的clone()方法;(递归的把内嵌的对象复制)如果引用类型里面还
包含很多引用类型,或者内层引用类型的类里面又包含引用类型,使用clone方法就会很麻烦。这时我们可以
用序列化的方式来实现对象的深克隆。 - 实现Serializable接口,通过对象的序列化和反序列化实现克隆,可以实现真正的深度克隆。
通过序列化实现深度复制。
Feather f = new Feather("red",10);
Bird bird = new Bird("布谷鸟",2,f);
System.out.println(bird.toString());
//Bird copybird = bird.clone();
Bird copybird = null;
try {
ByteArrayOutputStream out = new ByteArrayOutputStream();
ObjectOutputStream o = new ObjectOutputStream(out);
o.writeObject(bird);
ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
ObjectInputStream oo = new ObjectInputStream(in);
copybird = (Bird)oo.readObject();
}catch (IOException e){
System.out.println(e.getMessage().toString());
}catch(ClassNotFoundException e){
System.out.println(e.getMessage().toString());
}
System.out.println(copybird.toString());
bird.feather.setColor("black");//原对象修改引用变量指向的对象的属性。
System.out.println("############################");
System.out.println(bird.toString());
System.out.println(copybird.toString());
serialVersionUID
参考
https://www.cnblogs.com/duanxz/p/3511695.html
serialVersionUID适用于Java的序列化机制。简单来说,Java的序列化机制是通过判断类的serialVersionUID来验证版本一致性的。在进行反序列化时,JVM会把传来的字节流中的serialVersionUID与本地相应实体类的serialVersionUID进行比较,如果相同就认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的异常,即是InvalidCastException。
具体的序列化过程是这样的:
- 序列化操作的时候系统会把当前类的serialVersionUID写入到序列化文件中;
- 反序列化时系统会去检测文件中的serialVersionUID,判断它是否与当前类的serialVersionUID一致,如果一致就说明序列化类的版本与当前类版本是一样的,可以反序列化成功,否则失败。
serialVersionUID有两种显示的生成方式:
一是默认的1L,比如:
private static final long serialVersionUID = 1L;
二是根据类名、接口名、成员方法及属性等来生成一个64位的哈希字段,比如:
private static final long serialVersionUID = xxxxL;
当实现java.io.Serializable接口的类没有显式地定义一个serialVersionUID变量时候,Java序列化机制会根据编译的Class自动生成一个serialVersionUID作序列化版本比较用,这种情况下,如果Class文件(类名,方法明等)没有发生变化,就算再编译多次,serialVersionUID也不会变化的。
这里需要注意的是:对对象方法的变更,不会导致Class文件的变更,也就是改变对象方法,不会导致serialVersionUID变化
如果我们不希望通过编译来强制划分软件版本,即实现序列化接口的实体能够兼容先前版本,就需要显式地定义一个名为serialVersionUID,类型为long的变量,不修改这个变量值的序列化实体都可以相互进行串行化和反串行化。
下面用代码说明一下serialVersionUID在应用中常见的几种情况。
序列化实体类
package com.sf.code.serial;
import java.io.Serializable;
public class Person implements Serializable {
private static final long serialVersionUID = 123456789L;
public int id;
public String name;
public Person(int id, String name) {
this.id = id;
this.name = name;
}
public String toString() {
return "Person: " + id + " " + name;
}
}
序列化功能:
package com.sf.code.serial;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
public class SerialTest {
public static void main(String[] args) throws IOException {
Person person = new Person(1234, "wang");
System.out.println("Person Serial" + person);
FileOutputStream fos = new FileOutputStream("Person.txt");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(person);
oos.flush();
oos.close();
}
}
反序列化功能:
package com.sf.code.serial;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
public class DeserialTest {
public static void main(String[] args) throws IOException, ClassNotFoundException {
Person person;
FileInputStream fis = new FileInputStream("Person.txt");
ObjectInputStream ois = new ObjectInputStream(fis);
person = (Person) ois.readObject();
ois.close();
System.out.println("Person Deserial" + person);
}
}
情况一:假设Person类序列化之后,从A端传输到B端,然后在B端进行反序列化。在序列化Person和反序列化Person的时候,A端和B端都需要存在一个相同的类。如果两处的serialVersionUID不一致,会产生什么错误呢?
【答案】可以利用上面的代码做个试验来验证:
先执行测试类SerialTest,生成序列化文件,代表A端序列化后的文件,然后修改serialVersion值,再执行测试类DeserialTest,代表B端使用不同serialVersion的类去反序列化,结果报错:
Exception in thread "main" java.io.InvalidClassException: com.sf.code.serial.Person; local class incompatible: stream classdesc serialVersionUID = 1234567890, local class serialVersionUID = 123456789
at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:621)
at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1623)
at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1518)
at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1774)
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1351)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:371)
at com.sf.code.serial.DeserialTest.main(DeserialTest.java:13)
情况二:假设两处serialVersionUID一致,如果A端增加一个字段,然后序列化,而B端不变,然后反序列化,会是什么情况呢?
package com.sf.code.serial;
import java.io.Serializable;
public class Person implements Serializable {
private static final long serialVersionUID = 1234567890L;
public int id;
public String name;
public int age;
public Person(int id, String name) {
this.id = id;
this.name = name;
}
public Person(int id, String name, int age) {
this.id = id;
this.name = name;
this.age = age;
}
public String toString() {
return "Person: " + id
+ ",name:" + name
+ ",age:" + age;
}
}
public class SerialTest {
public static void main(String[] args) throws IOException {
Person person = new Person(1234, "wang", 100);
System.out.println("Person Serial" + person);
FileOutputStream fos = new FileOutputStream("Person.txt");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(person);
oos.flush();
oos.close();
}
}
Person DeserialPerson: 1234,name:wang
【答案】新增 public int age; 执行SerialTest,生成序列化文件,代表A端。删除 public int age,反序列化,代表B端,最后的结果为:执行序列化,反序列化正常,但是A端增加的字段丢失(被B端忽略)。
情况三:假设两处serialVersionUID一致,如果B端减少一个字段,A端不变,会是什么情况呢?
package com.sf.code.serial;
import java.io.Serializable;
public class Person implements Serializable {
private static final long serialVersionUID = 1234567890L;
public int id;
//public String name;
public int age;
public Person(int id, String name) {
this.id = id;
//this.name = name;
}
public String toString() {
return "Person: " + id
//+ ",name:" + name
+ ",age:" + age;
}
}
Person DeserialPerson: 1234,age:0
【答案】序列化,反序列化正常,B端字段少于A端,A端多的字段值丢失(被B端忽略)。
情况四:假设两处serialVersionUID一致,如果B端增加一个字段,A端不变,会是什么情况呢?
验证过程如下:
先执行SerialTest,然后在实体类Person增加一个字段age,如下所示,再执行测试类DeserialTest.
package com.sf.code.serial;
import java.io.Serializable;
public class Person implements Serializable {
private static final long serialVersionUID = 1234567890L;
public int id;
public String name;
public int age;
public Person(int id, String name) {
this.id = id;
this.name = name;
}
/*public Person(int id, String name, int age) {
this.id = id;
this.name = name;
this.age = age;
}*/
public String toString() {
return "Person: " + id
+ ",name:" + name
+ ",age:" + age;
}
}
结果:Person DeserialPerson: 1234,name:wang,age:0
说明序列化,反序列化正常,B端新增加的int字段被赋予了默认值0。
graph LR
Person类A端-->|生成序列化文件|Person.txt
Person.txt-->|读取序列化文件|Person类B端
Person类B端-->Person对象
graph LR
Person类增加字段-->|生成序列化文件|Person.txt
Person.txt-->|读取序列化文件|Person类B端
Person类B端-->Person对象读取不到新增字段
graph LR
Person类A端-->|生成序列化文件|Person.txt
Person.txt-->|读取序列化文件|Person类B端减少字段
Person类B端减少字段-->Person对象读取不到减少字段
graph LR
Person类A端-->|生成序列化文件|Person.txt
Person.txt-->|读取序列化文件|Person类B端增加字段
Person类B端增加字段-->Person对象新增字段为默认值