笔记
2023-11-28 10:40:19 0 举报
AI智能生成
个人笔记
作者其他创作
大纲/内容
JAVA
JAVA基础知识
四种对象引用
强引用
弱引用
软引用
幻像引用
集合
Collections工具类
常见考点
ArrayList和Vector区别
并发集合有哪些
CopyOnWriteArrayList
ConcurrentHashMap
CopyOnWriteArraySet
Map
HashMap
8前后区别
7:使用数组+链表
8:使用数组+链表+红黑树
JDK8原理
初始容量16
扩容2倍
当桶位大于装填因子*总量时扩容
如果链表长度大于8且桶为超过64,则链表转为红黑树。
否则扩容2倍,重新hash
否则扩容2倍,重新hash
桶位置index = (n-1)&hash = hash%n(总桶必须是2的倍数)
节点小于6的红黑树退化为 链表
线程不安全
可以存NULL
源码解读
HashMap实现原理以及源码分析
先通过哈希函数计算出实际存储地址
如果两个不同的元素,通过哈希函数的胡的实际存储地址相同,产生hash冲突
开放定址法
再散列函数法
链地址法
HashMap 几采用了链地址法,也就是数组+链表
HashMap 的主干是一个Entry数组。Entry是HashMap的基本组成单元,每个Entry包含一个key-value键值对
Java hashCode() 和 equals()
两个对象equals为true,但hashCode不一定相同
由于hashMap是哈希表,通过计算得到HashCode,根据hashCode计算出实际存储地址
hashMap要求equals为true,hashCode也要相同,因此重写了equals方法也同时需要修改hashCode方法
hsahCode不同的对象不会在同个key桶里
hashCode方法就是对象存储位置的映像,因此HashCode能够快速定位到对象的地址。
HashMap实现原理浅析
主要参数
子主题
主要方法
put
添加元素
先计算hash
hash与table.length取模 得到index值
table[index]位置有值,则会在此位置形成链表
如果链表长度与table长度符合扩容则扩容,或者转化为红黑树
get
得到元素
先计算hash
hash与table.length 取模得到 index值
遍历链表或者红黑树
remove
删除元素
计算hash值,得到index位置,便利查找,删除元素
resize
扩容方法
生成一个new_table[old_table.length*2]
遍历旧HashMqp将值通过put方法插入新HashMap
clear
遍历将table制空
containsKey
与get类似,计算hash值,得到index值
contaionsValue
遍历整个HashMap
所以不要依赖这个方法做事情
hash
h ^= (h >>> 20 )^(h >>>12);
return h ^(h>>>7)^(h>>>4);
return h ^(h>>>7)^(h>>>4);
HashMap 线程不安全问题
hash碰撞于扩容导致
在多线程情况下,出现同时put同个index的情况下,会导致某个值被取代
HashMap线程不安全的体现
如果在扩容时,在数据从旧数据复制到新数组的过程中,这时候某个线程插入一条数据,这时候插入的时新数组。
但是复制过程不做数据判断,就会导致新插入的值被覆盖了。
但是复制过程不做数据判断,就会导致新插入的值被覆盖了。
HashMap 线程不安全的表现
HashMap并发导致死循环 CurrentHashMap
深入理解HashMap (原理,查找,扩容)
hashMap 数组扩容之后,最消耗性能的点就出现了。需要遍历旧数组中的数据,添加到新数组中去。resize
当hashMap桶位占用个数达到0.75*table.length
扩容将桶位数扩充为2倍
为了减少过多的扩容,使用HashMap时,需要尽可能的创建符合业务逻辑的长度
并且长度为2的n次方
为什么要保证长度为2的n次幂
因为 hash&(table.length-1) = hash%length
使用位运算符,可以提高计算效率
HashTable
线程安全
实现方法都加了synchronize
默认初始容量11,装填因子也是0.75
每次扩容2倍+1
直接使用hashCode作为hash值
TreeMap
红黑树实现
key自动排序
LinkerdHashMap
链表实现
需要有顺序的去存储key-value时
线程同样不安全
ConcurrentHashMap
JDK1.8
取消了分段锁,使用的Node锁+cas与synchronized来保证并发安全
synchroized只锁住当前链表或者红黑二叉树的首节点,只要hash不冲突,就锁住,提高效率
synchroized只锁住当前链表或者红黑二叉树的首节点,只要hash不冲突,就锁住,提高效率
Colletion
List
ArrarList
动态数组实现
初始容量为10
在添加数据时进行初始化,扩容时1.5倍
线程不安全
实现RandomAccess接口,支持快速随机访问(只是标识接口)
多线程问题
数在扩容时,同时add数据,导致线程中的数据会被覆盖
线程安全的list
CopyOnWriteArrayList
LinkedList
双向链表
线程不安全
插入和删除时间复杂度为O(1)
不支持快速随机访问
空间消耗更大一些,多两个引用
Vector
动态数组实现
线程安全
初始容量为10
每次扩容2倍
底层加synchroized和ArrayList
CopyOnWriteArrayList
原理
写入时复制
特点
适合大量读操作,不适合大量写操作
可以读写分离,并不是读写锁实现
线程安全
初始容量为0,写入进行扩容,新容量=插入容量+就容量
Set
TreeSet
TreeMap换皮
Comparator放入对象实现此接口,会使用比较器进行排序,否则使用升序排序
底层使用二叉树(红黑树)
HashSet
HashMap换皮
主要使用HashMap实现,key不可重复,Value保持null的Object对象
LinkedHashSet
LinkedHashMap换皮
CopyOnWriteSet
和CopyOnWriteArrayList原理一样
Queue
Deque
ArrayDeque
BlockingQueue
常用子类
LinkedBlockingQueue
ArrayBlockingQueue
四组常用API
抛出异常
add
remove
element
有返回值,不会抛出异常
offer
poll
peek
阻塞等待
put
take
超时等待
offect
poll
AbstractQueue
数据类型
8种基本数据类型
boolean
byte 1个字节
char 2个字节
short 2个字节
int 4个字节
long 8个字节
fload 4个字节
double 8个字节
8种包装类型
名称
Byte
Short
Interger
Long
Double
Boolean
Character
面试点
以下几个有缓冲池[-128,127],
也就是一个byte单位,
除了Character。
也就是一个byte单位,
除了Character。
Byte,Short,Integer,Long,Characher
String 类型
String
JAVA8与JAVA9的区别,8使用char数组,而9使用byte数组,通过coder来确定使用哪种编码
内部有final修饰,String类不可修改。字符串不可修改。
StringBuilder
线程不可变,可以被该改变
StringBuffer
线程安全,内部有synchronized关键字修饰
抽象类与接口
接口
在JAVA8 中接口可以有default方法,也可以有静态方法
静态方法不能被重写,default方法可以被重写
接口中方法都是默认public的,并且不允许定义为其他的
接口中的字段都是static和final的
抽象类
抽象类和方法都是使用abstract修饰,如果类中包含抽象方法,那么类必须被声明为抽象类
抽象类不能被实例化,只能被继承
异常
分类
Exception
RuntimeException
非运行时异常
区别
运行时异常是不可处理的,交给JVM处理。例如空指针异常。
对于非运行时异常则需要使用try-catch处理。
否则编译不能同通过,例如SQLException。
对于非运行时异常则需要使用try-catch处理。
否则编译不能同通过,例如SQLException。
Error
系统级别的异常,不可处理异常
OutOfMemoryError 内存溢出异常
StackOverflowError 站溢出
ThreadDeath 线程问题
Exception 和 Error 的区别?
Error 一般由JVM 抛出,Error 是Java 程序不能够处理的错误。Exception是java程序能够处理的异常。
关键字
final
可以声明的位置
类
该类不能被继承
方法
该方法不能被重写
数据
基本类型
使得数值不能被修改
引用类型
引用不能变,但是被引用变量的对象本身被修改
final修饰的变量可以被修改
使用java提供的反射技术,并且关闭安全校验也可以修改其值
static
可以声明的位置
类
方法
成员变量
静态代码块
初始化顺序
1.父类(静态变量,静态代码块)
2.子类(静态变量,静态代码块)
3.父类(实例变量,普通代码块)
4.父类构造方法
5.子类(实力变量,普通代码块)
6.子类构造方法
泛型
简介
Java泛型设计原则:只是在编译时期没有出现警告,那么运行时期就不会出现ClassCasException异常
泛型:把类型明确的工作推迟到创建对象或者调用方法的时候才去明确的特殊的类型
使用泛型
泛型类:在编译后泛型类就确定了类型
泛型方法,某个方法上使用了泛型,外界只看该方法。
泛型类派生出的子类
子类泛型类型确定
子类泛型类型不确定:继续使用父类定义的泛型
使用通配符
在泛型类中并没有像我们面向对象的继承结构,想要使用任意的泛型类型,我们都可以使用通配符。
?通配符可以表示匹配任意类型,任意JAVA类型都可以
PECS原则
通配符设置上限,?extends Type
下限
?super Type
子类可以从泛型读取
?extends Type
超类可以从泛型写入
?super Type
通配符和泛型方法
如果参数之间的类型有依赖关系,或者返回值与参数之间有依赖关系,就可以使用泛型
如果没有依赖关系,就使用通配符,通配符会灵活一些
泛型好处
代码简洁,更可靠,编译期间通过,意味着运行时不会出现ClassCasException异常
类型擦拭
泛型类型在编译期起作用,编译后在运行时将类型擦除,编译期保证数据安全,运行时提高效率。
应用
参考BeanDao实现
反射
概念
1.反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法
2.反射可以在一个类运行的时候获取类的信息的机制,可以获取在编译期不可能获得的类的信息
3.对于任意一个对象,都能调用它的任意一个方法和属性
4.因为类的信息是保存在Class对象中的;而这个Class对象是在程序运行时被类加载器(ClassLoader)动态加载
5.当类加载器装载运行了类后,动态获取Class对象的信息,操作Class对象的属性,方法的功能称为反射
作用
反编译:.class->.java
通过反射机制访问Java对象中的属性,方法,构造方法
涉及到的类
Class-类的创建;
获取Class对象的方法
Class c3 = new Refect().getClass();
ClassLoader 装载入内存。
ClassLoader 装载入内存。
Class c2 = Reflect.class;
返回类对象运行时真正所指的对象,所属类型
返回类对象运行时真正所指的对象,所属类型
Class c1 = Class.forName("com.mxm.Reflect")
会让ClassLoader装载类,并运行
会让ClassLoader装载类,并运行
无参数创建对象
Class c4 = Class.forName("com.mxm.Reflect")
Object o = c4.newInstance();
newInstance 使用类的加载机制
Object o = c4.newInstance();
newInstance 使用类的加载机制
有参数创建对象
Constructor<?> csr = c4.getConstructor(String.class,in,class);
Object o = csr.newInstance("王",25);
getConstructor 方法返回一个 Constructor 对象,它反映了此 Class 对象所表示的类的指定的公共构造
Object o = csr.newInstance("王",25);
getConstructor 方法返回一个 Constructor 对象,它反映了此 Class 对象所表示的类的指定的公共构造
Constructor
反射类中的构造方法
Field
反射方法
获取属性
Field field = class.getDeclaredField("name");
使用setAccseible取消封装,特别是可以取消私有字段的访问权限。
field.setAccessible(true);
field.set(Object,"老王");
使用setAccseible取消封装,特别是可以取消私有字段的访问权限。
field.setAccessible(true);
field.set(Object,"老王");
Field类描述
Field类描述的时属性对象,其中可以获取到很多属性信息,包括名字,属性类型,属性注解
安全管理
在安全管理器中会使用checkPermission方法来检查权限,而setAccessible(true)并不是将
方法权限改为public,二十取消Java的权限控制检查,所以public方法的accessible属性也是false
方法权限改为public,二十取消Java的权限控制检查,所以public方法的accessible属性也是false
修改属性的修饰符
Field field = class.getDeclaredField("name")
String prive = Modeifier.toString(field.getModofoers());
getModofoers() 放回的是一个 代表类,成员变量,方法修饰符
String prive = Modeifier.toString(field.getModofoers());
getModofoers() 放回的是一个 代表类,成员变量,方法修饰符
Method
反射方法
Method m = class.getDeclaredMethod("setName",String.class);
m.setAccessible(true);// 同样需要忽略访问权限限制
m.invoke(class,"老王");
m.setAccessible(true);// 同样需要忽略访问权限限制
m.invoke(class,"老王");
Modifier
访问修饰符的信息
反射进阶
获取不到Class
当Class.forName() 中路径获取不到对应的Class时,会抛出异常。
获取不到Field
确实不存在这个Field,抛出异常
修饰符导致的权限问题,抛出相同异常
获取父类修饰符
getField只能获取对象和父类的public 修饰的属性
getDeclaredField 获取对象中的各种修饰符属性,但是不能获取父类的任何属性
获取不到父类的非public的方法
获取不到父类的构造方法
newInstance方法创建类对象的两种方法
Class.newInstance()
使用收到限制,对应的Class中必须存在一个无参数构造器,且必须有访问权限
Contructor.newInstance()
适应任何类型的构造器方法,无论是否有参数都可以调用,只需要setAccessible()方法控制访问权限。
放射静态方法
例子:Public class TestMethod{
static void test(){}
}
Class class = Class.forName("TestMethod");
Method m = class.getDeclaredMethod("test");
m.invoke(null);
关键Method.invoke的第一个 static 反复噶因为属于类本身,所以不需要填写对象,填写null就可以
static void test(){}
}
Class class = Class.forName("TestMethod");
Method m = class.getDeclaredMethod("test");
m.invoke(null);
关键Method.invoke的第一个 static 反复噶因为属于类本身,所以不需要填写对象,填写null就可以
反射的泛型参数方法
Java的泛型擦除概念,泛型T在编译后会被擦除,自动向上转型为Object
public class Test<T>{
public void test(T t){}
}
Class class = Test.class;
Method m = class.getDeclaredMethod("test",Object.class);
m.invoke(new Test<Integer>(),1);
public void test(T t){}
}
Class class = Test.class;
Method m = class.getDeclaredMethod("test",Object.class);
m.invoke(new Test<Integer>(),1);
反射框架:JOOR
注解
介绍
注解其实就是代码中的特殊标记,这些标记可以在编译,类加载,运行时被读取,并执行相对应的处理
自定义Annotation
注入普通属性其实就三步:
反射出该类的方法
通过方法得到注解上的具体信息
将注解上的信息注入到方法上
注入对象
得到想要类中注入的属性,得到该属性的对象
得到属性对应的写方法,通过写方法得到注解
获取注解详细的信息,将注解的信息注入到对象上
调用属性写方法,将已填充数据的对象注入到方法中
元注解
@Retention
表示注解周期
RetentionPolicy.SOURCE:Java文件时期
RetentionPolicy.CLASS:Class文件时期
RetentionPolicy.RUNTIME:运行时期
通常使用RUNTIME
@Target
用于指定注解修饰程序单元
TYPE
FIELD
METHOD
PARAMETER
CONSTRUCTOR
LOCAL_VARIABLE
ANNOTATION_TYPE
RACKAGE
@Documented
将被javadoc工具提取成文档
@Inherited
使得注解具有继承性
常见注解
@Overried
避免低级错误,检查是继承父类的方法有效性
@Deprecated
标记为过时的
@SuppressWarnings
去除编译器警告
@SafeVarargs
当把一个不是泛型的集合赋值给一个带泛型的集合时候,这种情况就容易出现堆污染
@FunctionalInterface
指定接口为函数式接口
Lamada
函数接口
Consumer void accept(T t) 将T 类型的参数引用与该方法
Supplier T get(); 返回类型为T 的对象
Function<T,R> R apply(T t):输出T类型的参数返回R类型的结果
Predicate boolean test (T t): 确定类型为T 的参数是否满足test逻辑
@FunctionalInterface(接口注解)
被该注解注解的是函数式接口,表示该接口只能定义一个方法。
Steam(parallelStream):懒加载方式,运行时才会生成数据
操作类型
Intermediate
concat
distinct
peek
parallel
sequential
unoradered
map
有状态
filter
distinct
sorted
limit
skip
Terminal
min
max
count
sum
通用
collect
常用场景
某个功能在Stream接口中没有,多半就在collect方法中实现
子主题
reduce
生成唯一值
Function.identity()
t->t
子主题
多线程
并发工具
如何实现一个流程控制
基础知识
实现方式
继承Thread类创建线程
实现Runnable接口创建线程
使用Callable和FutureTask创建线程
Callable定义的方法是call
Runnable定义的方法时run
Callable的call是有返回值
Runnable是没有返回值的
Callable的call是可以抛出异常的
Runnable的run是不能抛出异常的
停止线程
使用stop方法强行停止,但是Java已经标记为过期方法,不推荐使用
使用退出标识,正常退出,run执行完成
使用interrupt方法中断线程
notify()和notifyAll()有什么区别
notify();
只唤醒一个等待线程
无法指定唤醒哪个线程
notifyAll();
notifyAll();
唤醒所有处于等待的线程
唤醒线程进入就绪队列,得到锁才能运行
wait();方法,那么线程便会处于该对象等待池中,等待池不会去竞争该对象的锁
优先级高的线程竞争得到锁的概率大,但是不能保证一定是优先级高的先得到锁
sleep()与wait()有什么区别
sleep() 是线程类的方法,不涉及通信,调用时会暂停此线程指定的时间,但是监控依旧保持,不会释放锁资源。
wait() 时Object的对象,用于线程通信,调用时会放弃对象锁,进入等待队列,待调用notify()/notifyAll()唤醒指定的线程或者所有线程,
才进入对象锁线程池准备获得对象锁的运行状态
才进入对象锁线程池准备获得对象锁的运行状态
wait,notify,notifyAll只能在同步控制方法或者同步控制块里使用,而sleep可以在任何地方使用
sleep()方法必须捕获异常InterruptedException,而wait()\notify()\notifyAll()不需要捕获异常
sleep方法只让出CPU,而并不会释放同步资源锁
线程执行sleep()方法后转入阻塞状态
sleep()方法指定的时间为线程不会运行的最短时间,因此,sleep()方法不能保证该线程睡眠到期后开始执行。
notify的作用相当于唤醒睡着的人,但是不包括帮他分配任务,只是叫醒。
Daemom线程,它有什么意义?
守护线程与普通线程的唯一区别是:当JVM中所有的线程都是守护线程的时候,JVM就可以退出了;
如果还有一个或者以上的非守护线程则不会退出。(以上针对正常退出,调用System.exit则必定会退出)
JVM的垃圾回收线程就是Daemon线程,Finalizer也是守护线程
Thread的SetDaemon方法设置称为守护线程,必须在线程启动之前调用
当你在一个守护线程中产生了其他线程,那么这些新产生的线程不用设置Daemon属性,都将是守护线程,用户线程同样。
意义 JVM 不需要等待它退出,让JVM喜欢什么就退出,不用管它。
多线程如何通讯协作
等待,通知,唤醒
锁机制
同步机制
ThreadLocal类
通讯
使用全局变量
共享内存
java线程中的6个状态
NEW
新生
RUNNABLE
运行
BLOCKED
阻塞
WAITING
等待
TIMED_WAITING
超时等待
TERMINATED
终止
锁
ReentrantLock
可重入锁
synchronized与RentrantLock都是可重入锁
最大作用避免死锁
synchronized与java.util.concurrent.lock.Lock
Lock能完成synchronized 所有实现的所有功能
ReentrantLock
ReadWriteLock
ReentrantReadWritedLock
Lock有比synchronized更精确的线程语义和更好的性能,而且不强制要求一定要获得锁
synchronized自动释放锁,而Lock一定要求程序员手动释放,最好在finally释放
乐观锁与悲观锁
理解
乐观锁和悲观锁是两种思想,用于解决并发场景下的线程竞争问题
乐观锁:乐观锁在操作数据时非常乐观,认为别人不会同时修改数据,因此乐观锁不会上锁,只是在执行更新的时候,如果别人修改了数据,则放弃操作。
悲观锁,则在修改数据之前,加锁,防止别人共同操作
实现
悲观锁
加锁
乐观锁
CAS机制
CAS操作包括3个操作数:
需要读写的内存位置(V)
进行比较的预期值(A)
拟写入的新值(B)
需要读写的内存位置(V)
进行比较的预期值(A)
拟写入的新值(B)
如果内存位置V的值等于预期A的值,则将该位置更新为新值B,否则不进行任何操作。许多CAS的操作是自旋锁到成功为止。
CAS是由CPU支持的原子性操作,其原子性是在硬件层面进行保证的。
缺点
高竞争下的开销问题
CAS循环等待需要不断调用CPU,资源消耗大
功能局限性,只能针对当个变量进行原子性操作。一般需要结合volatile配合线程安全
ABA问题
A状态,先被修改成B状态,然后又改回来A状态
导致已经被修改过,但是线程不知道
可以加入版本号,时间戳的方式实现。
公平锁与非公平锁
公平锁
遵循先来先用
非公平锁
FIFO原则,线程进入等待线程
队列开头的回合
公平和非公平锁的队列都基于锁内部维护的一个双向链表,表结点Node的值就是每一个请求当前锁的线程。公平锁则在于每次都是依次从队首取值
非公平锁在等待锁的过程中, 如果有任意新的线程妄图获取锁,都是有很大的几率直接获取到锁的
(在ReentrantLock中很明显可以看到其中同步包括两种,分别是公平的FairSync和非公平的NonfairSync。公平锁的作用就是严格按照线程启动的顺序来执行的,不允许其他线程插队执行的;而非公平锁是允许插队的。
默认情况下ReentrantLock是通过非公平锁来进行同步的,包括synchronized关键字都是如此,因为这样性能会更好。因为从线程进入了RUNNABLE状态,可以执行开始,到实际线程执行是要比较久的时间的。而且,在一个锁释放之后,其他的线程会需要重新来获取锁。其中经历了持有锁的线程释放锁,其他线程从挂起恢复到RUNNABLE状态,其他线程请求锁,获得锁,线程执行,这一系列步骤。如果这个时候,存在一个线程直接请求锁,可能就避开挂起到恢复RUNNABLE状态的这段消耗,所以性能更优化)
非公平锁在等待锁的过程中, 如果有任意新的线程妄图获取锁,都是有很大的几率直接获取到锁的
(在ReentrantLock中很明显可以看到其中同步包括两种,分别是公平的FairSync和非公平的NonfairSync。公平锁的作用就是严格按照线程启动的顺序来执行的,不允许其他线程插队执行的;而非公平锁是允许插队的。
默认情况下ReentrantLock是通过非公平锁来进行同步的,包括synchronized关键字都是如此,因为这样性能会更好。因为从线程进入了RUNNABLE状态,可以执行开始,到实际线程执行是要比较久的时间的。而且,在一个锁释放之后,其他的线程会需要重新来获取锁。其中经历了持有锁的线程释放锁,其他线程从挂起恢复到RUNNABLE状态,其他线程请求锁,获得锁,线程执行,这一系列步骤。如果这个时候,存在一个线程直接请求锁,可能就避开挂起到恢复RUNNABLE状态的这段消耗,所以性能更优化)
互斥锁
互斥锁时同步状态的一种特殊情况,相当于只存在一种临界资源,因此·1同时只能给一个线程提供服务。在复杂的多线程应用程序中,可能存在多个临界资源,这时候我们可以借助Semaphore信号量来完成多个临界资源的访问。
线程安全
servlet线程不安全
线程安全就是只,多个线程访问同一段代码,产生结果一致
同步有几种方式实现
JAVA基础
多线程
线程安全
线程同步
代码重排序
jvm的优化操作
volatile有什么用
禁止代码重排序
保证线程可见性
无法保证原子性
保证线程读取到的值是内存真实值,而不是缓存
TreadPoolExecutor
线程池
corePoolSize
核心线程数
maximumPoolSize
最大线程数
keepAliveTime
当线程池中线程数量超过最corePoolSize时,空闲线程的存活时间
unit
keepAliveTime单位
threadFactory
线程工厂
handler
拒绝策略
new ThreadPoolExecutor.CallerRunsPolicy();
只要线程池没有关闭,该策略直接在调用者的线程中,运行当前被丢弃的任务
只要线程池没有关闭,该策略直接在调用者的线程中,运行当前被丢弃的任务
new ThreadPoolExecute.AbortPolicy();
直接抛出异常,阻止系统正常功能
直接抛出异常,阻止系统正常功能
new ThreadPoolExecutor.DiscardPolicy();
该策略默默丢弃无法处理的任务,不处理
该策略默默丢弃无法处理的任务,不处理
new ThreadPoolExecutor.DiscardOldestPolicy();
该策略丢弃最老的一个请求
该策略丢弃最老的一个请求
workQueue
任务队列
一般自己创建有界队列实现
JUC
java.util.concurrent工具包,并发大师Doug Lea的杰作
AQS队列同步器
时JUC的核心
原理
围绕一个同步队列和park还有自旋锁实现
获取公平锁和非公平锁的区别
公平锁时在唤醒的时候不能插队,非公平锁可以插队
详解
lock加锁
lock方法真正调用的是acquire
unlock解锁
调用sync.release方法
ReentrantLock
可重入可中断锁,他是Lock最重要的实现类之一
FairSync()这个方法时公平锁,调用NonfairSync()方法时非公平锁
CountDownLatch
减法计数器
减到0执行await方法
CyclicBarrier
加法计数器
触发线程运行
Semaphore
信号量
acquire会减少一个资源,release释放锁
读写锁
ReentrantReadWriteLock
读写互斥
写写互斥
读读并存
阻塞队列
参考BlockingQueue
SynchronousQueue
线程池
ForkJoin
Future
IO
BIO
传统BIO
网络编程的几本C/S模型,即连个进程的通信
服务端提供IP监控端口,客户端通过连接操作想服务端监听的地址发起连接请求,通过三次握手连接,如果连接成功建立,双向通信
传统的同步阻塞模型开发中,ServerSocket负责绑定IP地址,启动监听端口,Socket负责起连接操作,连接成功后,双方通过输入和输出流进行同步阻塞
BIO服务通信模型
采用BIO通信模型服务端,通常由一个独立的Acceptor线程负责监听客户端的连接
它接收到的客户端连接请求之后为每个客户端创建一个新的线程进行链路处理完成后,通过输出流返回答应客户端,线程销毁
典型一个请求一个答应通信模型
最大问题
并发高,CPU、IO等待的冲突问题导致性能急剧下降
伪异步I/O
如果使用CachedThreadPool线程池,其实除了能自动帮我们管理线程,本质还是BIO
使用FixedThreadPool我们就有效的控制了线程最大数量,保证了系统有限资源的控制,实现了N:M模型
最大问题
读数据时比较慢,大量并发的情况下,其他接入消息只能一直等待,这是最大的弊端
常见使用
磁盘操作:file
字节操作:inputStream和OutputStream
字符操作:Reader和Writer
对象操作:Serializable
网格操作:Socket
NIO
非阻塞面向缓冲区的高性能IO
NIO与BIO
NIO面向块,BIO面向流
NIO非阻塞,BIO阻塞的
非阻塞作用
以前的BIO模型是阻塞的,也就是说有多少个流就必须要有多少个线程,因为在等待接收或者发送的时候该线程是阻塞的。如果IO是非阻塞的,那么我们就可以让一个线程管理多个IO或者,多个线程管理多个IO
直接缓冲区和非直接缓冲区
直接缓冲区需要经过一个Copy的阶段从内核空间Copy到用户空间
直接缓冲区不需要经过copy阶段
使用直接缓冲区的两种方式
使用ByteBuffer的AllocateDirect方法
使用FileChannel的map方法
NIO的三个核心部分
buffer缓冲区
本质
数组
状态变量
capacity最大容量
position当前读写的字节数
还可以读写的字节数
常用实现
ByteBuffer
CharBuffer
ShortBuffer
IntBuffer
LongBuffer
FloatBuffer
BoubleBuffer
Channel管道
管道理解
通过Channel是对原I/O中的流模拟的
可以理解为管道就是火车道,而缓冲区就是火车
常用管道
FileChannel从文件中读写数据
DatagramChannel能通过UDP读写网络中的数据
SocketChannel能通过TCP读写网络中的数据
ServerSocketChannel可以监听新进来的TCP连接,像web服务器那样,对每个新进来的连接都创建一个SocketChannel
管道和流的区别
管道可以同时进行读写,流只能读或者只能写
管道可以实现异步读写数据
管道可以从缓冲中读取数据,也可以写数据到缓冲中
Selector选择器
一个组件,可以检测盒多个Nio channel,看卡读或者写时间是否就绪
多个Channel事件的方式可以注册到同一个Selector,从而达到用一个线程处理多个请求成为可能。
Java IO 全解
创建NIO服务端的步骤
子主题
网络
HTTP
就是客户端和服务器的一种交互的一种通信格式
告知服务器意图
HTTP提供的方式
GET
PUT
HEAD
DELETE
POST
OPTIONS
RESTful风格就是基于HTTP生成的
持久连接
HTTP1.0 每次进行HTTP通信都会断开
HTTP1.1版本后,就是持久连接了,一次HTTP请求可以持续处理多个请求
持久化连接为官线化方式发送创造了条件,再一次HTTP连接里面,不需要等待服务器响应请求,就可以继续发送第二次请求
常用状态码
2xxx,一般都是成功处理
200正常响应
204成功处理,但是服务器没有新数据返回,页面不更新
206 对服务器进行范围请求,只返回一部分数据
3xx
301请求的资源已经重新分配到新的URI中,URL地址已经改变了【永久重定向】
302请求资源临时被分配到新的URI中,URL地址没有变
303与302相同功能,但明确客户端应该采用GET方式获取资源
304发送了附带请求,但是不符合条件【返回未过期的缓存数据】
307与302相同,但不会把POST请求变成GET
4xx
400请求报文语法错误
401需要认证身份
403没有权限访问
404服务器没有这个资源
5xx
500 内部资源错误
503服务正在忙
HTTPS简述
HTTPS就是披着SSL的HTTP
HTTP在建立通信线路的时候使用公开的私有秘钥,当建立完成连接后,随后就使用共享秘钥进行加密和解密
HTTPS 是基于第三方认证机构来获取受认可得证书
过程
1.用户向web服务器发起一个安全连接的请求
2.服务器返回经过CA认证的数字证书,证书里包含了服务器的public key(公钥)
3.用户拿到数字证书,用自己浏览器内置的CA证书解密得到服务器的public key
4.用户服务器的public key 加密一个用于接下来的对称加密算法的秘钥,传给web服务器
5.因为只有服务器有private key 可以解密,所以不用担心中间人拦截这个加密的秘钥,服务器拿到这个加密的秘钥,解密获取秘钥,再使用对称加密算法,和客户端完成接下来的通信
网站通信初略的过程
DNS:负责解析域名
HTTP:产生请求报文数据
TCP协议:分割HTTP数据,保证数据运输
IP协议:传输数据包,找到通信的目的地址
HTTP是不保证状态的协议
HTTP是的无状态的,也就是说,它是不对通信状态进行保存的。它并不知道之前通信的对方是谁
由于我们很多时候都需要知道对方是谁,于是我们欧了Cookie来解决问题
提升传输效率
使用压缩技术把实体主体压小,在客户端再把数据解析
使用分块传输码,将实体主体分块传输,当浏览器解析到实体主体就能够显示了
这种技术可以实现断点续传
服务器与客户端之间的应用程序
代理
网关
能够提非HTTP请求的操作,访问数据库什么的
隧道
建立一条安全的通信路径,可以使用SSL等加密手段进行通信
HTTP请求和报文组成
请求报文
请求行:包含请求方法,URI,HTTP版本信息
请求首部字段
请求内容实体
空行
响应报文
一个住状态行【用于描述服务器对请求的处理结果】
首部字段【用于描述服务器的基本信息,以及数据的描述,服务器通过这些数据的描述信息,可以通知客户端如何处理等一会儿它会送回来的数据】
一个空行
实体内容【服务器向客户端回送数据】
HTTP1.1新版特性
默认使用长链接,只要客户端与服务端没有断开TCP请求,则可以一直保持链接,可以发送多次HTTP请求
管线化,客户端可以同时发出多个HTTP请求,而不是一个个的等待响应
断点续传,实际上就是利用HTTP消息头使用分块传输编码,将实体主体分块传输
HTTP2
子主题
多路复用
二进制分帧层
其他改动
HPACK对HTTP2头部压缩
服务器推送
流量控制
流优先
OSI七层模型
Open System Interconnection
操控奥模型是国际标准化组织定制的一个用于计算机或通信系统间的标准体系,一般OSI参考模型或者七层模型
只要遵循这个七层协议就可以实现就可以实现计算机互联
OSI 七层模型各层作用
物理层
定义物理设备标准
所有网络有关
数据链路层
STP
网卡,交换机
将物理层接收的数据进行MAC地址的封装和解分装,也可以简单的理解为物理寻址
网络层
IP
控制子网的运行,如逻辑编址
分组传输,路由
传输层
定义一些传输数据的协议端口
TCP
UDP
会话层
负责在玩那个罗中两个节点建立,维持和终止通信
SMTP,DNS
表示层
确保一个系统的应用层发送的消息可以被另外一个系统的应用层读取
TeInet
应用层
文件传输,文件管理,电子邮件信息处理
HTTP,FTP,TFTP,WAIS,SMTP
TCP/IP协议
TCP和UDP
TCP是面向连接的,可靠的流协议,通过三次握手建立连接,通信完成时要拆除连接。
UDP是面向无连接通信协议,UDP通信时不需要接收方确定,属于不可靠传输,可能出现丢包现象
三次握手和四次挥手
通俗易懂的讲解TCP/IP三次握手与四次挥手姿势
连接需要三次握手,断开需要四次挥手
ACK:TCP协议规定,只有ACK=1时有效,也规定连接建立后所有发送的报文的ACK必须为1
SYN:在连接建立时用来同步序号,当SYN=1而ACK=0时,表明这是连接请求报文。
对方若同意建立连接,则应在响应报文中使SYN=1和ACK=1.因此,SYN置1就表示这是一个连接请求或者连接接受报文。
对方若同意建立连接,则应在响应报文中使SYN=1和ACK=1.因此,SYN置1就表示这是一个连接请求或者连接接受报文。
FIN即完,终结的意思,用来释放一个连接。当FIN=1时,表明此报文段的发送方的数据已经完毕,并要求释放连接。
过程
第一次握手
客户端将标示SYN=1,随机产生一个值seq=j,并将该数据包发送给服务器端,客户端进入SYN_SENT状态,等待服务器端确认。
第二次握手
服务单收到数据包后由标志SYN=1知道客户端请求建立连接,服务端将标志SYN和ACK都设置为1
ack=J+1,随机参数一个值seq=k,并将数据包发送给客户端确定连接请求,服务器进入SYN_RCVD状态
第三次握手
客户端收到确认后,检查ack是否为J+1,ACK是否为1,如果正确则将标志ACK置为1,ack=K+1
则开始将数据包发送给服务端,服务端检查ack是否为K+1,ACk是否为1,如果正确连接成功,客户端和服务端进入ESTABLISHED状态,完成三次握手,随后客户端与服务端之间可以开始传输数据。
挥手
第一次挥手:
Client发送一个FIN,用来关闭Client到Server的数据传送,Client进入FIN WAIT 1 状态
第二次挥手:
Server收到FIN后,发送以恶ACK给Client,确定序号为收到序号+1(与 SYN相同,一个FIN占用一个序号),Server进入CLOSE_WAIT状态。
第三次挥手:
Server发送一个FIN,用来关闭Server到Client的数据传送,Server进入LAST_ACK状态。
第四次挥手:
Client收到FIN后,Client进入TIME_WAIT状态,接着发送一个ACK给Server,确认序号为收到序号+1,Server进入CLOSED状态,完成四次挥手
TCP/IP中的数据包
TCP中通过序列号与确认应答提高可靠性
网络编程常见术语
网络编程基础(网络基础知识)
Socket套接字
Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面。
主机A的应用程序要能和主机B的应用程序通信,必须通过Socket建立连接,而建立Socket连接必须需要底层TCP/IP协议来建立TCP连接。
建立TCP连接需要底层IP协议来寻址网络中的主机。
短连接
连接=传输=关闭连接
HTTP是无状态,浏览器和服务器进行一次HTTP操作,就建立一次连接,但是任务结束就中断
短连接是指SOCKET连接后发送后接收完数据后马上断开连接
使用场景
WEB网站的http服务一般都是短连接,因为长连接对于服务端来说会消耗一定的资源。
长连接
连接=传输数据=保持连接=传输数据=关闭连接
长连接指建立SOCKET连接后不管是否使用都保持连接,但是安全性较差
使用场景
数据库的连接用长连接,如果短连接频繁的通信会造成Socket错误,而且频繁的Socket创建也是对资源的浪费。
简略版
五层协议体系结构
应用层
应用层的任务就是通过应用进程间的交互来完成特定的网络应用。
应用层协议定义是应用进程间通信和交互的规则。
对于不同的网络应用需要不同的应用协议。
例如:HTTP,SMTP
运输层
运输层的主要任务就是负责向两台主机进程之间的通信提供通用的数据传输服务。
主要是一下两种协议
TCP
面向连接
可靠传输
UDP
面向报文
不可靠传输
网络层
在计算机网络中进行通信的两个计算机之间可能会经过多个数据链路,也可能还要经过很多通信子网。
网络层的任务就是选择合适的网间路由和交换节点,确保数据及时传送。
数据链路层
数据链路层,通常简称链路层。
两台主机之间的数据传输,总是在一段一段的链路上传送的,这就需要使用专门的链路层协议。
两个节点之间传送数据时,数据链路层将网络层交下来的IP数据报组装成帧。
物理层
物理层上只以透明的比特流进行传送,尽可能的屏蔽掉具体的介质和物理设备的差异。
TCP可靠传输
应用数据被切割成TCP认为最适合发送的数据块
TCP给发送的每个包进行编号,接收方对数据包进行排序,把有序数据传送给应用层
校验和:TCP将保持它的首部和数据的校验和。
TCP的接收端会丢弃重复的数据
流量控制:TCP连接的每方都有固定大小的缓冲空间,TCP的接收端只允许发送端发送接收端缓冲能接纳的数据。
拥塞控制:当网络拥塞时,就减少数据的发送。
ARQ协议:
也是为了实现可靠传输的,它的基本原理就是每发完一个分组就停止发送,等待对方确认,在收到确认后再发下一个分组
超时重传:
当TCP发出一个段后,它启动一个定时器,等待目的端确认收到这个报文。
并发
并发编程实践
基础知识
结论
如果当多个线程访问同一个可变的状态变量时没有使用合适的同步,那么程序就会出现错误。
避免错误措施
不在线程之间共享该变量
将状态变量修改为不可变的变量
在访问状态变量时使用同步
当设计线程安全的类时,良好的面向对象技术、不可修改性,以及明晰的不变性规范都能起到一定的帮助作用。
线程安全
当多个线程访问某个类时,不管运行时环境采用何种调度方式或者这些线程将如何交替执行,并且在主调代码中不需要任何额外的同步或协同,这个类都能表现出正确的行为,那么就称这个类是线程安全的。
在线程安全类中封装了必要的同步机制,因此客户端无须进一步采取同步措施。
个人积累
从一开始就考虑线程安全问题,并做好设计工作。会比线程不安全的类使用后,当出现问题再去处理要容易的多。
代码首先要能正常运行,然后才是性能,其次是,在满足需求的条件下,尽可能的简单,在没有性能问题前,尽量不去优化性能。
当多个线程访问某个类时,这个类始终都能表现出正确的行为,那么就称这个类是线程安全的。
UML
图例
理解用例图
概念
理解1
用例的一个最主要的特征是它是相对独立的
业务用例
业务用例的定义是业务执行者希望通过和所研究组织交互获得的价值。
系统用例
系统能够为执行者提供的、涉众可以接受的价值。
MQ
消费者消息处理模板代码
try {
// 业务逻辑。
}catch (Exception e){
// 输出错误日志。
}finally {
// 消息签收。
}
// 业务逻辑。
}catch (Exception e){
// 输出错误日志。
}finally {
// 消息签收。
}
无论如何倒要在finally里去签收消息,避免异常消息再放入队列中,导致持续的异常死循环(产生原因:由于Spring默认requeue-rejected配置为true,默认情况下,消息异常后会重新入队,入队后又会被消费,从而导致了死循环)。
日志
单体日志
分布式日志
OpenTracing
中文翻译:https://wu-sheng.gitbooks.io/opentracing-io/content/
JVM
架构
指令集少,需要指令多,基于栈实现,可以跨平台
执行性能差
生命周期
通过类加载器加载类
执行JAVA程序
JAVA进程
退出
使用Runtime 或者 System 的exit方法或Runtime的halt方法
历史版本
Sun Classic VM
1996
第一台商用虚拟机
1.0~1.4
只有解释器
Exact VM
1.2
只在Solaris短暂使用
JIT 与 解释型混合使用
热点代码的探测
HotSpot VM
1997
1.3默认虚拟机
GC
热点代码的探测
在最佳响应时间与最佳执行性能中取得平衡
BEA 的 Jrockit
专注服务端应用
只使用即时编译器
性能高,程序启动时间慢
IBM的 J9
多用途
三大商用虚拟机之一
在IBM产品上很快
Azul VM
软硬绑定
商用
Liquid VM
软硬绑定
Microsoft JVM
Taobao JVM
内存结构
简图
ClassFiles 字节码文件
Class Loader 类加载器
Loading 加载
过程
通过类全限定名获取定义此类的二进制流
这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
在内存中生成一个代表这个类的java.lang.Class 对象,作为方法区这个类的各种数据的访问入口
实现方式
BootStrap ClassLoader
引导类加载器
引导类加载器
启动类加载器
使用C/C++实现,嵌套在JVM内部
加载核心类库
JAVA_HOME/jre/lib/rt.jar
不继承java.lang.ClassLoader,无父类加载器
加载应用类和应用类加载器,并指定为他妈呢的父类加载器
只加载Java,javax,sum开头的类
Extension ClassLoader
扩展类加载器
扩展类加载器
JAVA编写
派生于ClassLoader类
父类加载器为启动类加载器
加载java.ext.dirs或者jre/lib/ext
如果用户创建的JAR放在此目录下,也会自动由扩展类加载器加载。
Application ClassLoader
系统类加载器
系统类加载器
JAVA编写
派生于ClassLoader类
父类加载器为扩展类加载器
负责加载环境变量classpath或者系统熟悉java.class.path指定类库
该类加载是默认的类加载器,一般来说,Java应用的类都可以由它来完成加载
通过ClassLoader#getSystemClassLoader()方法可以获取该类加载器。
应用类加载器
由上至下,包含关系。(非继承)
引用加载器
BootStrap ClassLoader
引导类加载器
引导类加载器
非JAVA实现
自定义加载器
继承与ClassLoader的加载器
Extension ClassLoader
扩展类加载器
扩展类加载器
Application ClassLoader
系统类加载器
系统类加载器
JAVA实现
ClassLoader
抽象类,其后所有的类加载器都继承自ClassLoader
不包括启动类加载器
常见方法
getParent()
返回该类的超类加载器
loadClass(String name)
加载器名称为name的类,返回java.lang.Class类的实例
findClass(String name)
查找
findLoadedClass(String name)
查找被加载过
define
从内存中获取
resolveClass
连接指定一个Java类
获取方式
Class.forName("className").getClassLoader();
当前类
Thread.currentThread().getContextClassLoader();
获取线程上下文
ClassLoader.getSystemClassLoader();
系统
DriverManger.getClaserClassLoader();
调用者
双亲委派机制
按需加载方式
类加载时,先去查看父类加载器是否能加载此类
父类在递归
直到引导类
父类都不能加载,子类才加载
Linking 连接
Verify 验证
字节头标识
CA FE BA BE
意义
确保Class文件的字节流的合法性,保证被加载的类的正确性,不会破坏虚拟机本身安全
四种校验方式
文件格式校验
元数据校验
字节码校验
符号引用验证
Prepare 准备
意义
为类变量分配内存并设置默认值,0值
这里不包括使用final修饰的static 因为final在编译时就分配了,准备阶段会显式初始化
不会为实例变量分配初始化,类变量会分配在方法区中,而实例变量时会随着对象一起分配到堆中的
Resolve 解析
意义
将常量池里的符号引用装换为直接引用的过程
事实上,解析操作往往会伴随着JVM的执行完成初始化之后再执行
解析动作主要是对类,接口,字段,类方法,接口方法,方法类型等。
Initialization 初始化
意义
初始化阶段就是执行类构造器方法<clinit>()的过程
此方法不需要定义,是javac编译器自动收集类中的所有类变量的赋值动作和静态代码块中的语句合并而来
构造器方法中指令按语句在源文件中出现的顺序执行
<clinit>()不同于类构造器(类的构造器在虚拟机视角为<init>())
该类具有父类,JVM会保证子类的<clinit>()执行前,父类<clinit>()已经执行完成
虚拟机必须保证一个类的<clinit>()方法在多线程下被同步加锁
作用
负责将Class文件加载到内存当中。(只负责加载,不负责运行)(媒婆)
Runtime Data Area 运行时数据区
Method Area 方法区/Metaspace 元空间
存储类型
类型信息
域信息
方法信息
JIT代码缓冲
运行时常量池
静态变量
概述
尽管所有的方法区在逻辑上属于堆的一部分,但是一些简单的
实现可能不会选择进行垃圾收集或者进行压缩。
HotSpotJVM将方法区又称作非堆(Non-Heap)
实现可能不会选择进行垃圾收集或者进行压缩。
HotSpotJVM将方法区又称作非堆(Non-Heap)
可以看做独立于Java堆的内存空间
于堆一样,时线程共享区域
物理不连续,逻辑连续
可固定大小,也可扩展
同样会抛出OOM异常
建议将MetaspaceSize
设置成较高值,避免过多的FullGC
内存结构
类型信息
类型
类
枚举
注解
接口
包括
完整有效名称 包.类名
直接父类完整有效名称
修饰符
有序列表
域
域名称
域类型
域修饰符
方法
方法名称
方法返回值类型
参数表
修饰符
字节码,操作数栈,局部变量表
异常表
运行时常量池
用于存放编译期生成的各种字面量与符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中
具备动态性
将符号表的符号装载成真实的引用
类似菜谱->真的食材
字符串常量池
JIT编译内容
为什么要用常量池
减少class的文件大小
替换永久代原因
永久代很难确定
GC
条件很苛刻
但是又很有必要
常量池
无引用就回收
类
无派生子类实例
实例被回收
类加载器被回收
java.lang.Class
无引用
Heap 堆
堆
一对一关系
JVM实例
堆内存
堆是物理上不连续,但是逻辑上连续的一块内存空间
概述
几乎所有的对象实例都存储在堆内存中
数组和对象永远不会出现在栈上,因为栈帧中保持引用。引用指向对象或者数组中堆中位置
方法结束后,堆中的对象不会马上被移除,仅仅在垃圾收集时才会被移除
堆,是GC的重点区域
内存细分
现代GC大多数都是针对分代收集理念设计的
Youg Generation Space
新生代
Eden
Survivor
Tenure Generation Spce
养老代
1.7永久区/1.8 元空间
Permanent Space
永久代
Mate Space
元空间
指令参数
-Xms
用于设置堆的起始内存,等价于 -XX:InitialHeapSize
-Xmx
bi表示最大内存,等价于-XX:MaxHeapSize
一般将两个值设置成一样的,这样GC后不需要重新计算分配堆的大小,从而提高性能
-XX:NewRatio
设置新生代与老年代的比例
例如-XX:NewRatio=2
新生代1,老年代2
-XX:SurvivorRatio
设置Eden与Survivor的比例
默认时8:1:1
事实上是6:1:1
-XX:-UserAdaptiveSizePolicy
自适应的分配策略
-Xmn
设置新生代的空间大小
一般不使用
-XX:MaxTenuringThreshdld
设置GC晋升年龄
-XX:PrintFlagsInitint
查看默认值
-XX:PrintFlagsFinal
最终值
jps 进程号
jinfo -flag 参数名 进程号
-XX:+PrintEscapeAnalysis
查看逃逸分析的筛选结果
-XX:DoEscapeAnalysis
显示开启逃逸分析
一旦堆超过了-Xmx的最大内存值,就会抛出异常OutOfMemoryError异常
图
对象创建过程
对象分配策略
TLAB
对象创建于回收过程
对象创建在Eden区
Eden区满了
Minor GC
YGC
出现STW
Eden 存活的放到
Survivor区
计算年龄
Survivor
s0,s1 有个一定是空的
谁空是to
另外一个是from
Minor GC 时也需要判断是否存活
判断GC年龄是否到晋升年龄15
到了就晋升到老年代
只有Eden满了才会触发MinorGC/YGC
Survivor 满了
触发特殊规则
晋升
内存大于新生代
直接晋升到老年代
总结
s0,s1,复制后有交换,谁空谁是to
GC频繁在新生区,很少在老年区,几乎不在永久代或者元空间
分代原因
对象的生命周期不同
大部分的对象是临时对象
优化GC的性能,对于不同生命周期的对象,进行分代回收
不用每次都把堆里的所有对象进行GC
TLAB
可以避免多线程内存争夺问题
快速分配策略
空间不大,只占Eden的1%,大对象无法分配
-XX:UseTLAB
设置是否开启
默认开启
-XX:TLABWasteTargetPercent
设置占比
如果在TLAB分配失败,JVM会采用加锁机制确保数据原子性
逃逸分析
栈上分配,标量替换优化技术
如果逃逸分析后,对象没有出方法,就在栈上分配
锁消除
标量替换
聚合量
对象就是聚合量
Class person{
id,name
}
id,name
}
person.id ,person.name
Program Counter Register 程序计数器(PC寄存器)
含义
PC寄存器对物理PC寄存器的一种抽象模拟
作用
PC寄存器用来存储指向下一条指令的地址,也即将要执行的指令代码。由执行引擎读取下一条指令。
含义
运行最快的存储区域
线程私有的
上下文切换时,记录当前运行的指令位置
唯一不会发生OutOtMemoryError区域
图
PC寄存器存下一步指令地址
Native Method Stack 本地方法栈
本地方法
一般在某些领域使用其他语言能够有更加好的性能,则使用其他语言实现
外部环境
操作系统交互
本地方法大多数都是C写的
Sun java
解释器使用C实现
不能与abstract连用
类似Thread类的中方法
Java调用非Java代码编写的接口
线程私有
本地方法使用的栈
当某个线程调用本地方法时,它就进入了一个全新的并且不
再受到虚拟机限制的世界,它与虚拟机拥有同样的权限
再受到虚拟机限制的世界,它与虚拟机拥有同样的权限
本地方法可以通过本地接口方法接口来访问虚拟机内部的运行时数据区
甚至直接使用本地处理器中的寄存器
直接使用内存等
并非所有JVM都支持本地方法以及本地方法栈
Java Virtual Machine Stack 虚拟机栈
栈与堆
栈管运行
堆管存储
栈与堆类似按菜谱做菜,菜谱时栈,菜时堆
存储的是栈帧
与方法一对一的关系
栈运行的原理
一个活动运行中,只有一个活动栈帧,称之当前栈帧
执行引擎运行只针对当前栈帧
如果调用了新方法,新方法就会称为当前栈帧
内部结构
Local Variables 局部变量表(LocalVariableTable)
局部变量数组
本地变量表
定义一个数字数组,主要用于存储方法参数和定义在方法体内的局部变量
基本数据类型
对象引用
retrunAddress
不存在线程安全问题
局部变量表所需的容量大小是在编译期确定下来的
Slot
局部变量表的存储单元
变量槽
可重复利用,节省资源
this
存在局部变量表index=0 上
静态变量与局部变量的对比
分类
数据类型分类
基本数据类型
引用数据类型
类中声明的位置分
成员变量
类变量
都经过默认初始化赋值LInking的Prepare阶段
initaial阶段显示赋值
实例变量
随着对象的创建在堆空间中分配,并进行默认赋值
局部变量
使用前,必须进行显示赋值,否则编译不通过
子主题
Operand Stack 操作数栈
可以使用数组或者链表实现
作用
在方法执行过程中,根据字节码指令,往栈中写入数据或者提取数据,即入栈或者出栈
用于存储计算过程中中间结果,同时作为计算过程中变量的临时存储空间
方法刚开始执行时,操作数栈时空的
并非采用访问索引的方式来访问数据,只能使用入栈和出栈
如果被调用的方法有返回值,返回值将会被压入到当前栈帧的操作数栈中
解释引擎是基于栈的执行引擎
类型在执行编译时就确定了
栈顶缓存技术
将栈顶元素全部缓存在CPU的寄存器中,以此降低对内存的读写次数,提升执行引擎的执行效率。
缺点
指令多,内存与CPU的交互次数多,导致性能比较低
Dynamic Linking 动态链接
每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用
包含这个引用的目的就是为了实现动态链接Dynamic Linking
包含这个引用的目的就是为了实现动态链接Dynamic Linking
在JAVA源文件被编译到字节码文件中时,所有的变量和方法引用都作为符号引用保存在class文件的常量池中。
作用
动态链接的作用就是为了将这些符号引用转化为调用方法来直接引用
虚方法
别名:指向运行时常量池的方法引用
Return Address 方法返回地址
帧数据区内容
存储了该方法PC寄存器中的值
重点
方法退出后,PC计数器的值作为返回地址,即调用该方法的指定的下一条指令的地址
正常退出
异常退出则由异常表控制
一个方法的返回指令,根据方法的返回值实际数据来确定
ireturn
boolean
byte
char
short
int
lreturn
long
freturn
float
dreturn
dobble
areturn
String
Date
return
void
图
图解
图解2
虚方法
调优
栈帧中调优最密切的部分就是局部变量表
局部变量表中的变量也是重要的垃圾回收的根节点
只要被局部变量表中直接或者间接引用的对象不会被回收
方法的调用
静态链接
编译期间既可以知道其实际值,可以将调用方法的符号引用转化为直接引用的过程称之为静态引用
早期绑定
动态链接
被调用方法在编译期间无法确定值,也就是说需要在运行期间才能将符号引用转化为直接引用的,这种引用转换过程几倍动态性,因此称之动态链接
晚期绑定
空参构造器大多都是早期绑定
final 方法就没有晚期绑定
(虚方法)与非虚方法
编译器就确定了调用版本,这个版本在运行时不可变的,称为非虚方法
静态方法
私有方法
final方法
实例构造器
父类方法
其他都称虚方法
虚拟机中几条方法调用指令
invokestatic
调用静态方法
解析阶段确定唯一方法版本
非虚
invokespecial
调用<init>方法,私有及父类方法,解析阶段确定唯一方法版本
非虚
invokevirtual
调用所有虚方法
除了final之外都是虚方法
invokeinterface
调用接口方法
invokedynamic
动态解析出需要调用的方法,然后执行
JAVA动态语言的体现
具体体现:Lamda
对操作指令集的修改
而不是对语言本身的修改
小记忆
动态语言与静态语言
静态语言
编译期间就确定类型
变量类型
动态语言
运行期间才能确定类型
变量值类型
方法重写
本质
操作数栈顶第一个元素纪录了实际凑在哦的类型
如果在该类型中找到对应操作的名称相符的方法,则进行访问权限的校验
有访问权限
直接引用
无访问权限
Java.lang.IllegalAccessError 异常
经常发生在Maven导包中,重复jar包等
如果没有合适的方法,则继续向上递归,(双亲委派) 重复2操作
如果到最后都没找到合适方法,则抛出java.lang.AbstractMethodError异常
虚方法表
非虚方法编译期就可以确定方法类型了,所以不用
类加载的链接阶段创建并初始化,类的变量初始化准备之后,JVM会把类的方发表也初始化。
作用
提高效率
附加信息
生命周期
线程一致
作用
程序的运行
保持局部变量
8种基本数据类型
对象的引用地址
部分结果
返回值
优缺点
优点
仅此于PC寄存器
只有两种操作方式
入栈
出栈
无GC
缺点
OOM,SOFE
参数
-Xss
设置栈内存大小
默认1024k
关系图解
子主题
子主题
栈,堆,元空间
总结
元空间与堆
后台线程
普通线程
守护线程
GC回收线程
面试题
栈溢出情况
StackOverflowError
栈溢出
调整栈大小,可以保证不出现溢出吗?
不能,但是能竟可能不。
分配的栈内存越大越好吗?
非,浪费资源
垃圾回收是否会涉及到虚拟机栈?
不会
方法中定义的局部变量是否线程安全?
不一定,基本类型则安全,引用类型得看引用类型是不是在方法区内。
扎乱记忆
new出来的对象都在堆里
Execution Engine 执行引擎
概述
JVM核心之一
虚拟机的执行引擎是由软件实现的
能被执行那些不可被硬件直接支持的指令格式
主要任务负责将字节码装到其内部
CAFE BEBA
将字节码翻译成机器语言
解释器
逐行将字节码翻译成机器指令
JIT编译器
将源代码直接编译成机器语言
参数
-Xint:
完全采用解释器模式执行程序
-Xcomp:
完全采用即时编译器的模式执行程序,如果即时编译出现问题,解释器会介入执行
-Xmixed:
采用解释器+即时编译器的混合模式共同执行程序
Native Method Interface 本地方法接口
对象实例化内存布局和访问定位
对象实例化
对象创建的方式
new
最常见
单例模式,生成器
工厂模式
Class
newInstance()
反射方式,只能调用空参数,public
Constructor
newInstance(Xxx)
反射的方式,可以调用空参,带参构造器
clone()
不调用任何构造器,当前类需要实现Cloneable接口
使用反序列化
从文件中,网络中,获取对象的二进制流
第三方库Objenesis
创建步骤
判断对象类是否加载,连接,初始化
为对象分配内存
指针碰撞
空闲内存列表
处理并发安全问题
CAS
TLAB
初始化分配到的空间
每个属性都设置默认值,保证对象实例字段在不赋值时可以直接使用
设置对象的对象头
运行时元数据
哈希值
GC年代
锁状态标识
线程持有的锁
偏向线程id
偏向时间戳
类型指针
执行元数据InstanceKlass,确定对象所属的类型
实例数据
先放父类再放子类
相同宽度在一起
对齐填充
执行init方法进行初始化
对象访问定位
问题
栈帧中的引用如何找到堆中的具体实现的?
实现方式
句柄访问
栈引用->句柄池地址->具体实例
子主题
直接指针(Hotspot实现)
栈引用->具体实例->类信息
直接内存
在涉及到大内存使用时,使用直接内存可以避免内核态和用户态的切换提高性能
图
大汇总
StringTable
不可变序列
final
底层变化
1.8之前
char[]
1.9之前
byte[]
字符串常量池不会存储相同的内容的字符串
String Pool
底层是一个固定大小的Hashtable
默认长度为60013
最小值1009
所以当hash冲突多了链表越来越长就会卡
-XX:StringTableSize
设置StringTable长度
内存分配
在堆中
和普通对象一样
intern()
String str = "1";
String str2= str.intern();
String str2= str.intern();
先去池里找对象是否存在,用equals判断
存在则返回池中地址,不存在则创建新的字符到池里
返回新对象
保证相同的字符对象只会存储一份,节省内存
各版本差异
1.6
如果池里有,则不会放入,返回已有串池中的对象地址
如果没有,则会复制此对象,放入串池汇总,并返回串池中的对象地址
1.7
如果有,则不会放入,返回池中地址
如果过没有,会把对象地址引用复制一份,放入串池,返回串池中的引用地址
注意对象和对象引用地址
因为有GC
所以使用的内存会比没使用的少
字符串拼接操作
常量和常量拼接
直接冲常量池里取值
只要有一个是变量
结果就在堆中,非字符串常量池
原理StringBuilder
等价于new String();
例子 String s3= s1+s2;
StringBuilder s = new StringBuilder()
s.append("a")
s.append("b")
s3=s.toString();
常量池中不会存在相同内容的常量
intern()方法
从常量池中取值,没则创建
GC
垃圾回收概述
垃圾回收算法
引用计数算法
每个对象都保持一个整型的引用计数器属性,用于记录对象被引用的情况
优点
简单,判断效率高
缺点
额外开销
循环引用问题
子主题
可达性分析
追踪性垃圾收集
概述
从GC Roots 从上至下的搜索可到达的对象
可到达的对象间形成引用链
不可到达的对象则称作垃圾对象
能够被根对象直接或间接引用的对象才是存活对象
GC Roots
虚拟机栈中引用对象
线程中的参数,局部变量表
方法区里静态属性引用的对象
类引用的静态变量
方法区常量引用对象
字符串常量池引用
被同步锁synchronized持有的对象
虚拟机内部引用
Class对象,异常对象,类加载器
采用栈方式存储变量或者指针,对象在堆里,但是本身不在堆里的就是Root
如果局部回收的话,未回收区域的对象也可以作为Root
finalize()
定义在Object
对象销毁前的自定义处理逻辑
只能被调用一次
未重写不调用
对象三种状态
可触及
存活
可复活
未调用finalize()
不可触及
调用过finalize()
标记清除算法
过程
STW
标记:存活对象
清除:未被标记的对象被回收
缺点
效率不算高
需要STW
清理出来的内存不连续,碎片化
清除只是把需要被清除的内存存入空闲内存表里,后续使用直接覆盖
复制算法
优点
运行高效
无碎片化
缺点
额外开销
region
G1实现内存和时间开销
特别的
存活对象多,效率就差,因为要全部复制
标记压缩算法
过程
STW
标记:存活对象
清除:未被标记的对象被回收,并进行压缩操作
标记-清楚-压缩
移动式
优点
解决标记算法碎片化问题
避免复制算法内存消耗
缺点
效率上低于复制算法
移动对象同时还需要调整引用地址
移动过程需要STW
分代
只不同区间采用不同的回收算法
分代思想被现有虚拟机广泛应用
增量收集算法
交替执行
STW
应用
缺点
不断的上下文切换
造成系统吞吐量的下降
分区
控制STW时间
G 1
与分区不同的是,他是将堆分成一个个分区Region
垃圾回收相关概念
System.gc()
等同于Runtime.getRuntime().gc()
会触发Full GC
无法保证具体运行时机
内存的溢出与内存泄漏
内存溢出
官方解释
OOM是没有空闲内存了,且垃圾收集器也无法提供更多的内存了
产生原因
设置的堆内存过小
存在大量大对象,并且GC无法即时回收
内存泄漏
存储渗漏
对象不会再被程序使用到了,但是GC又无法回收它们的情况,叫内存泄漏
广义上不合理的生命周期的对象也可以称为内存泄漏
例子
用生命周期关联的一些生命周期比较低的对象
连接未手动断开
STW
Stop The World
停止应用程序的运行
场景
可达性分析算法执行时
确保可达性分析的一致性,准确性
GC都无法避免STW
只能减少STW时间
垃圾回收的并行与并发
并行垃圾回收器
ParNew
Parallel
Scavenge
Parallel Old
串行垃圾回收器
Serial
并发垃圾收集器
CMS
G1
安全点与安全区域
安全点
在特定位置才可以GC
太少,GC时间长
太多,GC太频繁,影响性能
选择执行时间较长的指令作为安全点
主动中断
安全区域
是只在一段代码片段里,对象的引用关系不会发生变化,在此区域中任何位置GC都是安全的。
就是在这段代码里,不会影响GC
所以在运行这段代码时,可以同时GC
GC没结束,先不要脱离这段代码
各种引用
强
正常new出来,最传统的引用
只要存在强引用垃圾收集器永远不回收
软
只要系统在发生内存溢出前,就会把这些对象列入回收范围中进行二次回收。
还有用,但是非必须
会在第二次GC被回收
一般用来做缓存
java.lang.ref.SoftReference
Object obj = new Object();
SoftReference<Object> sf = new SoftReference<Object>(obj);
obj = null;
SoftReference<Object> sf = new SoftReference<Object>(obj);
obj = null;
弱
只能存活到下一次垃圾回收,换句话来说,只要GC它就得死
例子WeakReference
虚
无法通过虚引用来得到对象实例
在垃圾收集器回收时收到系统通知
PhantomRefernce
终结器引用
垃圾回收器
GC分类与性能指标
吞吐量
应用运行时间/总时间(包括垃圾收集时间)
暂停时间
STW,应用停止服务时间
内存占用
堆区内存大小
垃圾收集开销
垃圾收集时间/总时间
收集频率
快速
对象出生到死亡再到回收时间
不同的垃圾回收器概述
新生代收集器
Serial
ParNew
Parallel Scavenge
老年代收集器
Serial Old
Parallel Old
CMS
整体
G1
根据业务场景选择合适垃圾收集器
Serial 回收器,串行回收
最基本最悠久的垃圾回收器
复制算法
STW
Serial Old
老年代GC
CMS 兜底方案
标记整理
单线程运行
优势
单线程下运行高效
简单
Client 模式下不错的选择
图
开启
-XX:+UseSerialGC
会开启Serial GC 与 Serial Old GC
ParNew回收器:并行回收
并行
多线程
复制算法
STW
Serial 多线程版本
适合多核版本
新生代使用
Parallel回收器:吞吐量优先
Parallel Scavenge
复制算法
STW
新生代
可控制的吞吐量
提高CPU利用率,适合后台运算,不需要过多交互任务
自适应的调节策略
Parallel old
标记压缩算法
STW
并行
1.6
多线程版本
参数设置
-XX:+UseParallelGC
开启新生代
-XX:+UseParallelOldGC
开启老年代
-XX:ParallelGCThreads
设置并行数
-XX:MaxGCPauaseMillis
设置最大停顿时间
-XX:GCTimeRatio
垃圾收集时间占总时间比例
-XX:+UseAdaptiveSizePolicy
自适应调节策略
CMS回收器:低延迟
特点
低延迟
实现回收时收集线程与用户线程同时工作
对于响应速度有要求,希望系统STW时间尽可能短的。
采用标记清除算法
过程
初始标记
STW
只是标记出GC Roots能直接关联出来的对象
速度快
并发标记
并发
直接关联对象开始遍历整个对象图过程,耗时,但是不需要STW
重新标记
STW
修正因为并发标记期间,因用户线程继续工作导致标记产生变动的那一部分对象标记来纪录。
并发清理
并发
清理删除掉标记阶段判断为死亡的对象。释放内存空间。
重置线程
并发
图
解释
过程中STW需要的时间比较短,在耗时的并发标记和并发清除阶段。可以并发执行,不会STW
因为并发的过程,所以需要保证在CMS执行时,还有多余的空间来让CMS有时间清除垃圾。
一般设置一个阈值来处理。
如果因为CMS没有能即时清理垃圾,会出现Concurrent Mode Failure
此时会使用Serial Old来处理,那就STW很久了
缺点
采用标记清除算法,会产生碎片化的内存空间
不可避免,因为并发执行过程中,用户线程在运行
总结弊端
产生内存碎片
无法分配大内存对象时, 不得不提前FullGC
CMS对CPU资源非常敏感
并发阶段会占用部分CPU资源
CMS无法收集浮动垃圾
因为只会清除标记阶段标记的垃圾。所以不会清理并发阶段产生的新垃圾
参数
-XX:+UseCMSCompactAtFullCollection
指定FUllGC 后进内存压缩
-XX:CMSFullGCsBeforeCompaction
设置多少次Full GC后进行内存压缩
-XX:ParallelCMSThreads
设置CMS线程数
-XX:+UseConcMarkSweepGC
手动指定使用CMS收集器执行
-XX:CMSInitiatingOccupanyFraction
设置内存使用的阈值
1.6及以上为92%,之前为68%。
如果内存增加的慢,阈值可以大一些。如果快则小一些。
尽可能避免Full GC
G1回收器:区域划分代式
对于服务器端的多核心CPU以及大内存模式下效果比较好
效果
在延迟可控的情况下,尽可能的提高吞吐量
分区算法
Region来表示Eden,幸存者0区,1区,老年代等
避免全区的回收,可以精确到某个区来进行回收工作
会计算每个Region的回收价值
按照回收价值进行回收
优势
并行与并发
并行性:
G1回收时支持多线程回收
此时用户线程STW
并发性:
G1与应用线程交替执行,部分工作可以和应用程序同时执行,一般不会在整合回收阶段完全STW
分代收集
G1任然是分代收集的垃圾回收器。他会区分为老年代和年轻代。同其他GC一样
堆空间分为若干个区域,这些区域中逻辑上是年轻代或者老年代
回收时,它可兼顾老年代和年轻代
空间整合
Region之间,使用复制算法
整体上时标记压缩算法。
可预测的暂停模型
软实时
每次根据允许的收集时间,优先回收价值最大的Region,保证有限时间内尽可能高的提高回收效率。
缺点
不具备完全碾压CMS
会产生额外的内存占用和CPU消耗
参数
-XX:+UseG1GC
指定使用G1
-XX:G1HeapRegionSize
设置Region的大小
2的次方
1~32MB
-XX:MaxGCPauseMillis
最大停顿时间
默认是200ms
-XX:ParallelGCThread
STW工作线程数,最大是8
-XX:ConcGCThreads
并发标记线程数
默认是STW工作数的四分之一
-XX:InitiatingHeapOccupancyPercnet
触发并发GC周期的JAVA堆率阈值
默认45
适用场景
大内存,多处理器
需要低GC延迟
堆6G以上,GC低于0.5
humongous
用于存储大对象
一个装不下,就找连续的H区
还装不下就Full GC
整理后的H区连续的装不下就报OOM
GC过程
YGC
并行独占式
可能冲Eden区到Survivor 也可能是 老年代区
也可能都涉及
OGC
可能会包含YGC同时执行
老年区分配区空闲区域,空闲区域称为新的老年区
不一定会对整合老年代进行回收
Rememberd Set
一个对象可能被不同区域的对象引用
分区间存在相互引用
每个分区都有Rememberd Set
Reference 写操作时,都会产生一个Write Barrier 中断操作
判断写入的引用指向的对象是否和该Reference类型数据在不同Region
如果不同,通过CardTable把相关引用信息记录到引用指向对象所在的Region对相应的Remembered Set中
垃圾收集时,在GC根节点的枚举范围加入Rememberd Set,可以保证不进行全局扫描,也不会遗漏
图
具体过程
年轻代回收
只会回收Eden和Survivor区
STW
扫描根
更新RSet
处理RSet
复制对象
处理引用
初始化标记
根区域扫描
并发标记
再次标记
独占清理
并发清理
混合回收
百分百是垃圾的被回收了
Region的回收价值也 被计算出来了
分8次回收,垃圾回收价值高于65%,切优先回收价值高的
垃圾回收器总结
Serial
串行
新生代
复制算法
响应速度优先
适用单线程
ParNew
并行运行
新生代
复制算法
响应速度优先
多CPU环境雨CMS配合适用
Parallel
并行运行
新生代
复制算法
吞吐量优先
后代运行不需要多交互的场景
Serial Old
串行运行
老年代
标记压缩算法
响应速度优先
单CPU客户端模式
Parallel Old
并行运行
老年代
标记压缩算法
吞吐量优先
后台运行不需要多交互场景
CMS
并发运行
老年代
标记清除算法
响应速度优先
使用与B/S
G1
并发并行
新老
标记压缩,复制
响应速度
面向服务端应用
GC日志分析
参数
图
垃圾收齐器的新发展
图
杂
查看GC指令集
-XX:PrintCommandLineFlags
jinfo -flag 相关参数 进程ID
Class 文件结构
概述
字节码文件的跨平台性
Java语言:跨平台的语言
JVM:跨语言的平台
只要语言遵守JVM字节码格式就可以使用JVM运行
只和Class里的字节码关联
前端编译器的主要任务是将Java代码转化为字节码
javac
词法解析
语法解析
语义解析
生成字节码
Java的前端编译器
ECJ
ajc
透过字节码指令看代码细节
类文件有几个部分?
虚拟机的基石:Class文件
字节码
字节码是一种二进制的类文件,内部是JVM指令,可以直接编译成机器码
字节码指令
一种由一个字节长度,代表某种特定含义的操作码,以及随其后的数字代表操作数
虚拟机中许多指令不包括操作数,只有操作码
astore_1~3
操作码
astore 4
操作码和操作数
bipush 10
存在形式
.class 文件在磁盘中
网络中的流
内存等
本质上就是二进制流
内容
魔数
ca fe ba be
咖啡宝贝
使用魔数而不是基于文件扩展名,因为文件扩展名是可以随意改动的
前4位
Class文件版本
第五第六位 副版本
第七第八位 主版本
00 00 00 34
十进制52
1.8
高版本兼容低版本,低版本无法运行高版本的class
java版本号1.1=45
向上累加
常量池
内容最为丰富的区域之一
基石
第九位第十位
常量池表计数器
举例
00 16
十进制22
只有21项常量池
0下标是空出来的
用来无指定向引用时来引用
从下标1开始有常量项
常量池表
字面量和符号引用
类加载后
进入方法区的运行时常量池存储
下标从1开始
图
字面量
文本字符串
声明final的常量值
符号引用
类和接口的全限定名
java.lang.String全限定名
java/lang/String;
字段的名称和描述符
add()
add
方法的名称和描述符
描述符的作用是用来描述字段的数据类型,方法参数列表(数量,类型,顺序)和返回值
类型解析
byte
B
char
C
double
D
float
F
int
I
Long
J
short
S
boolean
Z
void
V
对象类型
L
Ljava/lang/Object;
数组类型
[
只有加载Class文件时才会动态链接
虚拟机运行时,需要从常量池中获取对应的符号引用
在此类加载过程中解析阶段将其替换为直接引用,并翻译到具体的内存中
符号引用转为直接引用
区别
符号引用
就是符号
直接引用
是真实的地址值
与虚拟机内存结构相关
访问标志
在常量池之后
两位
类索引,父类索引,接口索引集合
当前类索引
在访问标识之后
两位
父类索引
当前类索引之后
两位
接口索引集合
父类索引之后
两位
说明个数
后续每两位标识一个索引
字段表集合
说明
fields
类级别变量
实例变量
不包括局部变量
不包括父类变量
字段无法重载
字段计数器
两位,表示字段个数
访问标识
两位
字段名索引
两位
字段描述符的索引
两位
属性计数器
两位
属性表集合
方发表集合
method_info
对应着一个类或者接口的方法信息
方法计数器
两位
方法体
访问标志
方法名索引
描述索引
属性计数器
属性集合
属性表集合
通用
属性名索引
2
属性长度
4
属性表
1
code
属性表集合
辅助信息
附加属性表计数器
附加属性名的索引
SourceFile
属性名索引
2
属性长度
4
必须是2
源文件索引
2
全限名称
Class文件结构
Java虚拟机的指令
由一个字节长度的,代表某种特定操作含义的数字(称作操作码,Opcode)
可能跟着0个或者多个的操作数Operands
使用javap指令解析Class文件
解析字节码作用
javac -g 操作
生成的字节码包括局部变量表信息
javap
<options>
<classes>
-version
java版本
-public
仅仅显示公共的类和成员
-protected
受到保护的/公共类和成员
-p -private
所有
-package
显示程序包/受到保护的/公共类和成员
-sysinfo
显示正在处理的类系统信息
-constants
显示静态最终常量
-s
输出内部类型签名
-l
输出行号和本地变量表
-c
对代码进行反编译
-v -verbose
附加信息
文件解读
Classfile
字节码所属的路径
Last modified
最后修改时间,大小
MD5
MD5散列值
Compiled from
源文件名称
副版本
主版本
flags
访问标识
Constant pool
常量池
字段表集合信息
字段表集合信息
字段名
字段描述符:字段类型
访问标识
ConstantValue
常量属性值
方法表信息
构造器也在这
方法名称
descriptor
描述符
flags
访问标识
Code
方法的code属性
stack
操作数栈的最大深度
locals
局部变量表所需的存续空间
args_size
方法接收参数个数
字节码指令
偏移量
操作码
操作数
LineNumberTable
行号表
指名字节码指令的偏移量和java源代码中的行号对应关系
localVariableTable
局部变量表
描述内部局部变量的信息
Start
开始
Length
长度
Slot
槽位
Name
名称
Signature
Static()
<clinit>
SourceFile
附加信息
源文件名
字节码指令
概述
执行模型
do{
自动计算PC寄存器的值加1;
根据PC寄存器的指示位置,从字节码流中取出操作码;
if(字节码存在操作数) 从字节码流中取出操作数;
执行操作码所定义的操作;
}while(字节码长度>0);
自动计算PC寄存器的值加1;
根据PC寄存器的指示位置,从字节码流中取出操作码;
if(字节码存在操作数) 从字节码流中取出操作数;
执行操作码所定义的操作;
}while(字节码长度>0);
字节码与数据类型
它们的操作码助记符中都有特殊的字符来表明专门为那种数据类型服务
显示相关
i
int
l
long
s
short
b
byte
c
char
d
double
f
float
隐式相关
没有明确地指明操作类型的字母
arraylength
只能数组类型
无相关
数据类型无关
goto
按指令集用途大致分成9类
加载与存储指令
将一个局部变量加载到操作数栈
xload
xload_<n>
将一个常量加载到操作数栈
指令const系列
inconst_<i>
-1,5
iconst_m1
-1
fconst_<f>
0,2
lconst_<l>
0,1
dconst_<d>
0,1
aconst_null
指令push系列
bipush
8位参数
sipush
16位参数
指令ldc系列
ldc
万能的
ldc_w
8位
ldc2_w
long,double
将一个数值从操作数栈存储到局部变量表
xstore
xstore_<n>
xastore
存储到局部变量表
n标识索引
后续需要byte参数紧跟
x标识类型
扩充局部变量表的访问索引指令
wide
对于这若干组特殊指令来说,它们表面上没有操作数,不需要进行取操作数的动作,但操作数都隐含在指令中。
算术指令
加法
add
减法
sub
乘法
mul
除法
div
求余
rem
取反
neg
自增
inc
位运算
位移
shl
shr
ushr
位或
or
位与
and
异或
xor
比较
dcmpg
dcmpgl
fcmpg
fcmpl
lcmp
类型转化指令
数值类型进行转化
剔除布尔类型
一般用于用户显式类型转换操作
分类
宽话类型转化
转换规则
int->long,float,double
i2l,i2f,i2d
long->float,double
l2f,l2d
float转化为double
f2d
int->long->float->double
精度损失
不会出现
long->float
可能会
long->double
可能会
高精度绝对不能使用基本数据类型进行计算
补充说明
byte,char,short->到int类型的宽化转化类型转换实际不存在。
窄化类型转化
转化规则
int->byte,short,char
i2b,i2s,i2c
long->int
l2i
float->int,long
f2i,f2l
double->int,long,float
d2i,d2l,d2f
特殊举例:double->short
d2i,i2s
其余也一样
精度损失问题
会有精度损失,但是不会报异常
补充说明
NaN,不确定值
NaN->int,long
0
无限大
double->float
小的超过精度-0
大的超过精度+无穷大
NaN不变
对象的创建与访问指令
创建对象指令
new
创建数组指令
newarray
基本数组
anewarray
引用类数组
multianewarray
多维数组
dup
将操作数栈地址复制一份
字段访问指令
访问类字段
getstatic
将值放到栈中
putstatic
从栈中取值
访问类实例字段
getfied
将值放到栈中
putfield
从栈中取值
数组操作指令
xaload
把x类型加载到栈
xastore
把x类型从栈中赋值到堆
arraylength
数组长度
类型检查指令
instanceof
用来判断对象是否属于某个类
将判断结果压入栈
checkcast
用于检查类型是否可以强行装换,如果可以不会改变操作数栈,否则抛出ClassCastException异常
方法调用与返回指令
方法调用
invokevirtual
用于调用对象实例方法
支持多态
invokeinterface
用于调用接口方法
搜索实现接口的方法,并调用合适的实现者
invokespecial
调用需要特殊处理的实例方法
构造器
私有方法
父类方法
都是静态类型绑定的
invokestatic
调用静态方法
invokedynamic
动态绑定的方法
返回值指令
xreturn
void则为return
操作数栈管理指令
pop
pop
弹出一个solt
pop2
弹出两个solt
dup
dup
dup_x1
不带_x
直接复制
带x
复制后插入
swap
交换栈顶两个元素
nop
什么都不做
比较控制指令
比较指令
xcmpg
x:df
xcmpl
x:df
l与g
NaN
g
压入1
l
压入-1
将栈中两个操作数弹出
栈顶为v2,顺位为v1
v2=v1
压入0
v1>v2
压入1
v1<v2
压入-1
条件跳转
ifeq
当栈顶的int类型值等于0时候跳转
ifne
当栈顶int类型值不等于0时跳转
iflt
当栈顶值小于0时跳转
ifle
当栈顶值小于等于0时跳转
ifgt
当栈顶int类型数值大于0
ifge
大于等于0
ifnull
为null
ifnuonnull
不为null
比较条件跳转指令
if_icmpeq
=
if_icmpne
!=
if_icmplt
<
if_icmple
<=
if_icmpgt
>
if_icmpge
>=
if_acmpeq
引用类型等于
if_acmpne
引用类型不相等
多条件分支跳转
tableswitch
case连续
由于连续,所以效率会高一些
lookupswitch
case值不连续
无条件跳转
goto
got_w
goto
用于指令跳转到偏移量,指令执行的目的就是跳转到偏移量指定位置。
异常处理指令
athrow指令
抛出异常
会清空操作数栈的数据,将异常压入栈中
同比控制指令
方法级同步
隐式的
code 里没有体现
accges flags
方法标识里体现
代码块同步
monitorenter
monitorexit
进入监视器同步代码块时,判断是否为0或者是否>0且为自己持有
如果是则进入同步代码块
操作数栈回忆
操作数栈,用来存放计算的操作数以及返回结果。
局部变量表
字节码程序可以将计算结果缓存在局部变量表中,形成一个数组
可以存this指针
可以存字节码中的局部变量
其中long与dubble需要占8个字节,2个槽位
类的生命周期
加载Loading
简单的来说,就是将Java字节码文件加载到内存中,并构建出类模板对象
查找并加载类二进制数据,生成Class的实例。
通过类全名,加载得到二进制流
通过class后缀文件取得
zip,jar包
数据库中
http网络协议传输
动态Class二进制信息
解析二进制流数据结构
生成Class类实例,生成类模板
存储
类模板存储在方法区
1.8之前在永久代
1.8之后再元空间
本地内存里
Class实例对象
堆区中
引用方法区的中的类模板
数组类的加载
数组类本身不是由类加载器负责创建的
引用类型需要准遵从类加载过程
连接Linking
验证Verification
确保字节码的合法,合理和规范的。
过程
格式检查
魔数检查
版本检查
长度检查
语义检查
是否继承final
是否有父类
抽象方法是否有实现
字节码验证
跳转指令是否指向正确位置
操作数是否合理
符号引用验证
符号引用的直接引用是否存在
准备Preparation
静态变量分配内存,并将其初始化为默认值
不包括基本数据类型的字段使用static final修饰的情况,因为final在编译时就会被分配了,准备阶段会显式赋值。
解析Resolution
将类,接口,字段和方法的符号引用转化为直接引用
初始化Initialization
为类的静态变量赋值
执行类的初始化方法<clinit>()方法
包括静态代码块里的内容
由父及子,静态先行。
由编译器生成
哪些场景下不会生成<clinit>
无静态类变量
有静态类变量但是无显式赋值
有静态类常量
static final
String
字面量赋值
且显式赋值中不涉及到方法或者构造器方法调用的基本数据类型或String类型的显式赋值,是在链接阶段的准备环节进行的
<clinit>()线程安全性的问题
多线程环节被加锁同步了
是否是隐式锁,看不到
类的主动使用与被动使用
主动使用
当创建一个类的实例时,比如使用new关键字,或者通过反射、克隆、反序列化。
当调用类的静态方法时,即当使用了字节码invokestatic指令
当使用类、接口的静态字段时(final修饰符特殊考虑),比如getstatic或者putstatic指令。
使用java.lang.reflect包中的方法反射类方法时。
当初初始化子类时,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化。
如果一个接口定义了default方法,那么直接实现或者间接实现该接口的类的初始化,该接口就要在其之前被初始化。
当初次调用MethodHandle
初始化该MethodHandle指向的方法所在的类。
被动使用
除了以上的情况属于主动使用,其他情况属于被动使用,被动使用不会引起类的初始化。
并不是在代码中出现的类,就不一定会被被加载或者初始化,如果不符合主动使用的条件,类就不会初始化。
例如
子类引用父类的静态变量,不会导致子类初始化
数组定义引用类引用,不会触发类的初始化
引用常量不会触发此类或者接口的初始化,因为常量在链接阶段就已经被显式赋值了。
调用ClassLoader类或者loadClass()方法加载一个类,并不是对类的主动使用,不会导致类的初始化。
主动使用会调用<clinit>()
使用Using
卸载Unloading
垃圾回收
类,类的加载器,类的实例
用Java集合来存储类加载器
通过Class对象获取
对象->Class对象->类加载器
Class对象->类模板
图
类的生命周期
当类被加载,链接,初始化后,它的生命周期就开始了。
当Class类对象不再被引用,即不可触及,Class对象生命就结束了。
Sample类在方法区的数据也会被卸载,从而类的生命周期结束。
类的卸载
启动类加载器
不会被回收
扩展类加载器也不太可能
总能间接或直接引用到
自定义加载器
要强制调用虚拟机卸载
不可回收原因
类的加载器
再谈类加载器.
ClassLoader的作用
负责将Class信息的二进制数据流读入到JVM内部,转换称为一个Class对象实例。
然后
交给JVM区连接,初始化等
它不负责
类加载器的分类
显式加载
代码中通过ClassLoader加载class对象
Class.forName(name)
this.getClass().getClassLoader().loadClass()加载class对象
隐式加载
没有直接通过ClassLoader来加载
命名空间
何为类的唯一性
由加载它的加载器和这个类本身称作类在虚拟机中的唯一性
每个类加载器都有自己的命名空间,命名空间由该加载器所有的父类加载器所加载的类组成。
在同一命名空间中,不会出现类的完整名字相同的两个类
在不同命名空间汇总,有可能会出现类的完整名称相同的两个类。
复习
组成
启动类加载 BootStrap ClassLoader
C/C++编写,嵌套JVM内部
加载核心类库
jre/lib/rt,sun.boot.class.path
不继承Java.lang.ClassLoader没有父加载器
加载扩展类和应用程序的类加载器, 并指定为他们的父类加载器
处于安全考虑,其只加载java,javax,sun 开头的类
扩展类加载器Extension ClassLoader
使用Java语言编写
继承Classloader类
父类加载器为启动类加载器
从Java.ext.dirs系统属性所指定的目录中加载类库,或从JDK的安装目录的jre/lib/ext子目录下加载类库
用户创建的JAR在此目录下,也会自动由扩展类加载器加载。
应用类加载器Application ClassLoader
用户自定义类装载器
Java语言编写
继承ClassLoader类
父类加载器为扩展类加载
负责加载环境变量classpath或者系统属性java.class.path
应用程序中的类加载默认是系统类加载
它是用户自定义类加载器的默认父加载器
通过ClassLoader的getSystemClassLoader()方法可以获取到该类加载器
他们不是继承关系,但是可以宽化的说父类
包含关系
得到ClassLoader的途径
clazz.getClassLoader()
得到当前类的ClassLoader
Thread.currentThread().getContextClassLoader()
获取当前线程上下文的ClassLoader
ClasLoader.getSystemClassLoader()
获得系统的ClassLoader
重点知识
ClassLoader与现有类加载器的关系
Abstract ClassLoader
loadClass(String name)
加载名称为name的类,返回Class类实例
遵从双亲委派机制
解析
图
resolveClass(Class<?>)
解析操作
findClass(String)
查找二进制名称为name的类,返回结果为Class类实例,这是一个受保护的方法。JVM鼓励我们重写此方法,需要自定义加载双亲委派机制,该方法会在检查完父类加载器之后被loadClass()调用
在URLClassLoader重写
解析
图
调用defineClass创建类
defineClass(byte[],int,int)
根据二进制流创建类实例
官方注释信息系介绍:
getParent()
获取类加载器的父类加载器
SecureClassLoader
URLClassLoader
findClass(String)
继承
ExtClassLoader
AppClassLoader
loadClass(String,boolean)
系统类加载器与扩展类加载器
Launcher
加载扩展类加载器
加载系统类加载器
可以看到给线程上下文添加了系统类加载器
区别
Class.forName()与ClassLoader.loadClass()
前者将Class文件加载完会创建对应实例并初始化
后者只是加载Class文件到内存里
破坏双亲委派机制
1.2之前不支持双亲委派机制
线程上下文的加载器
顶成类加载时不能知道底层类加载类
SPI
rt.java提供对外服务,可由应用层自行实现的接口称为SPI
热部署
热替换
自定义类加载器
好处
隔离加载类
修改加载方式
扩展加载源
防止源码泄露
实现
简单实现
继承URLClassLoader
继承ClassLoader
重写findClass
推荐
重写loadClass
系统调优
指令
jps(查看正在运行的Java进程)
jps -q
只查看LVMID,即本地虚拟机唯一id,不显示主类名称等。
jps -l
输出应用程序主类的全类名称,或者jar全路径
jps -m
输出虚拟机进程启动时传递给主类mian()的参数
jps -v
列出虚拟机进程启动时的JVM参数。
可以综合使用
jstat(查看JVM统计信息)
基本语法
jstat -<opteion> [-t] [-h<lines>]<vmid>[<interval>[<count>]]
option参数
-class
查看ClassLoader的相关信息:类装载,卸载数量,总空间,类转载所有消耗的时间
JIT编译内容
-compiler
JIT编译内容
-printcompilation
说明编译的方法
垃圾回收
-gc
显示堆信息
-gccapacity
与gc类型,关注最大和最小空间
-gcutil
关注已使用的空间占总空间百分比
-gccause
额外输出最后一次或者当前正在产生GC的原因
-gcnew
新生代GC
-gcnewcapacity
新生代最大最小空间
-geold
老年代GC
-gcoldcapacity
老年代最大最小空间
-gcpermcapacity
永久代最大最小空间
interval参数
用于指定输出的统计数据周期,单位为毫秒
count参数
用于指定查询总数
-t参数
可以在输出信息前加一个Timestamp列,运行时间。
经验
可以比较启动时间与GC时间
推导出GC时间占总运行时间的比例
比例超过20%,表示堆压力比较大,超过90%,表示堆几乎满了
发现ou越来越高
后续可能会OOM
-h参数
可以周期的加表头信息
补充
jinfo(实时查看和修改JVM配置参数)
基本情况
用来查看虚拟机配置参数,并且可以调整虚拟机参数配置
基本语法
jinfo [options] pid
options
no option
输出全部的参数和系统属性
-flag name
输出对应名称的参数
-flag [+-] name
开启对应名称的参数
-flag name = value
设定对应名称参数
-flags
输出全部参数
-sysprops
输出系统全部参数
扩展
java -XX:+PrintFlagsInitial
系统初始化值
java -XX:+PrintFlagFinal
最终值
jmap(导出内存映像文件&内存文件使用情况)
基本情况
获取dump堆快照文件,二进制文件
可以获取Java进程的内存信息,包括Java堆内存各个区域使用情况,堆中对象统计信息,类加载信息等
基本语法
jmap [option] <pid>
-histo
输出堆中对象的统计信息,包括类,实例数量和合计容量
特别的:-histo:live只统计存活的对象
-heap
输出堆空间详细信息,包括GC的使用,堆配置信息,内存等。
-dump
生成Java堆存储快照:dump文件
特别的:-dump:live只保持堆中存活对象
-permstat
以ClassLoader为统计口径输出永久代的内存状态信息
-finalizerinfo
显示在F -Queue中等待Finalizer线程执行finalize方法的对象
-F
强制
使用1:导出内存映像文件
手动导出
jmap -dump:format,file=<filename.hprof> <pid>
jmap -dump:live,format=b,file=<filename.hprof> <pid>
自动导出
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=<filename.hprof>
使用2:显示堆内存相关信息
jmap -heap pid
-jmap -histo
使用3:其他作用
jmap -permstat pid
查看系统的ClassLoader信息
jmap -finalizerinfo
查看堆积在finalizer队列中的对象
小结
需要在安全点执行,不到安全点,会一直等待下去
jhat(jDK自带堆分析工具)
基本情况
用于分析heap dump 文件
基本语法
jstack:打印JVM中线程快照
基本情况
线程快照,虚拟机指定进程中的每一条线程正在执行的方法堆栈集合
留意
死锁:deadlock
等待资源:Waitting on conditon
基本语法
option参数
-F
当正常输出的请求不被响应时,强制输出线程堆栈
-l
除堆栈外,显示关于锁的附加信息
-m
如果调用到本地方法的话,可以显示C/C++的堆栈
-h
帮助操作
jcmd(多功能命令行)
基本情况
多功能工具
可以用来导出堆,内存使用,查看进程,导出线程,执行GC,JVM运行时间等
基本语法
jcmd -l
列出所有的JVM进程
jcmd pid help
针对进程支持的所有指令
jcm pid 具体命令
显示指定进程的指令命令数据
jstatd(远程主机信息收集)
代理服务器,建立与远程服务器的监听
GUI图形界面
工具概述
JDK自带的工具
jconsole
visual VM
JMC
第三方
MAT
JProfler
Arthas
Btrace
JConsole
基本概述
内存,线程,类
启动
jdk/bin
三种连接
local
本地连接
remote
advanced
主要作用
Visual VM
基本概述
功能强大,多合一,故障诊断,性能监控可视化工具
环境变量,cpu,gc,堆,方法区,线程信息
插件安装
连接方式
主要功能
生成dump文件
装入dump文件
实时监听线程状态
抽样区
eclipase MAT
基本概述
强大的JAVA堆内存分析工具,分析dump文件
获取堆dump文件
dump文件内容
hprof文件
可以看到所有的对象信息:对象实例,成员变量,存储与栈中的基本数据类型值和存储于堆中的其他对象的引用值
所有类信息,包括ClassLoder,类名称,父类,静态变量
GCRoot到所有的这些对象的引用路径
线程信息,包括线程的调用栈及此线程的线程局部变量TLS
两点说明
不是万能的,主流的可以识别
可以生成内存泄露的报表
获取dump文件
分析堆dump文件
案例:tomcat堆溢出
支持使用OQL语言查询对象信息
JProfiler
基本概述
IDEA插件使用,收费的。
比MAT更加强大
安装配置
具体使用
数据采集方式
Instrumentation重构方式
消耗大
Sampling抽样方式
消耗小
不能提供特殊功能,方法次数等
遥感监测Telemetries
内存试图LiveMemory
堆遍历 heap walker
cpu视图 cpu views
线程视图 threads
监视器&锁 Monitors & locks
案例分析
案例分析
案例分析
Arthas
基本概述
安装和使用
诊断指令
基础指令
help
查看帮助指令
JVM相关
dashboard
当前系统的实时数据面板
thread
查看当前JVM线程的信息
jvm
其他
class/classloader相关
mc,redefine
sc
sm
jad
classloader
monitor/watch/trace相关
stack
monitor
trace
watch
tt
其他
Java Mission Control
Btrace
Flame Graphs
内存泄露
内存泄露理解与分析
可达分析算法。严格上说,对象不被程序使用了,但是GC不能回收它们,才叫内存泄露
宽泛的泄露,过长的生命周期。
Java内存的8种泄露情况
静态集合类
静态的List,Map等
单例模式
单例对象存有外部对象的引用
内部类持有外部类
内部类长期引用外部类对象
各种连接,例如数据库连接,网络,IO连接
各种数据库连接等
变量不合理的作用域
改变哈希值
缓存泄露
监听器和回调
内存泄露案例分析
案例1
不合理的list使用,旧数据没有置空
案例2
子主题
支持使用OQL语言查询对象信息
JVM运行时参数
JVM参数选项类型
类型1:标准参数选项
特点
比较稳点
以-开头
各种选项
例如-help且下面的指令
补充-server与-client
64位不支持-client
类型2:-X参数选项
特点
非标准化参数
功能还是比较稳点的,但官方说后续会更变
以 -X开头
各种选项
运行 java -X 命令可以查看所有的X选项
-Xmixed 混合执行(默认)
-Xint 解释器
-Xcomp 编译器
-Xms <size> 设置初始Java堆大小
-Xmx <size> 设置最大Java堆大小
-Xint 解释器
-Xcomp 编译器
-Xms <size> 设置初始Java堆大小
-Xmx <size> 设置最大Java堆大小
JVM的JIT编译模式的相关选项
-Xint
解释器模式
-Xcomp
编译器模式
-Xmixed
混合模式
特别地
-Xms<size>
初始堆大小
-Xmx<size>
最大堆大小
-Xss<size>
Java线程堆栈大小
类型3:-XX参数选项
特点
非标准的
使用的最多的参数类型
这类选项属于实验性,不稳定
以-XX开头
作用
用于开发和调试JVM
分类
Boolean类型格式
-XX:+<option> 表示启用option属性
-XX:-<option> 表示禁用option属性
举例
说明:因为有的指令默认是开启的,使用-关闭
非Boolean类型(k-v类型)
子类型1:数值类型-XX:<option>=<number>]
子类型2:非数值类型-XX:<name>=<string>
特别地
-XX:+PrintFlagsFinal
输出所有参数的名称或者默认值
默认不包括Diagnostic和Experimenta的参数
可以配合-XX:+UnIockDiagnosticVMOptions和-XX:UnIockExperimentalVMOptions使用
添加JVM参数选项
Eclipse
IDEA
运行jar包
通过Tomcat运行war包
程序运行过程中
jinfo -flag 设置
常用JVM参数选项
打印设置的XX选项及值
-XX:+PrintFlagsFinal
打印出所有XX选项在运行程序员时的生效值
-XX:PrintCommandLineFlags
可以运行程序前打印出用户手动设置或者JVM自动设置的XX选项
-XX:PrintFlagsInital
打印出所有XX的默认值
-XX:+PrintVMOptions
打印JVM的参数
堆,栈,方法区等内存大小的设置
栈
-Xss
-XX:ThreadStackSize
堆内存
-Xms
初始化堆大小
-Xmx
最大堆大小
-Xmn
年轻代堆大小
建议使用堆3/8官方
-XX:NewSize
年轻代大小
-XX:MaxNewSize
年轻代最大值
-XX:SurvivorRatio
Eden与Survivor比例
-XX:+UseAdaptiveSizePolicy
自动选择各区大小比例
-XX:NewRatio
设置老年代与年轻代比例
-XX:PretenureSIzeThradshold
设置让大于此阈值的直接分配到老年代,大小
-XX:MaxTenuringThreshold
GC跃迁年代
-XX:+PrintTenuringDistribution
让JVM在每次MinorGC之后打印出当前使用的Survivor中对象年龄分布
-XX:TargetSurvivorRatio
表示MinorGC结束后Survivor区域中占用空间的期望比例
方法
永久代
-XX:PermSize
设置永久代初始值
-XX:MaxPermSIze
永久代最大值
元空间
-XX:MetaspaceSize
初始大小
-XX:MaxMeatapaceSIze
最大值
-XX:UseCompressdOops
压缩对象指针
-XX:UseCompressedClassPointers
压缩类指针
-XX:CompressedClassSpaceSize
设置K拉萨市Metapace大小
直接内存
-XX:MaxDIrectMemorySize
指定DirectMemory容量
OutofMemory相关的选项
-XX:HeapDumpOnOutOfMemoryError
表示在内存出现OOM的时候,把Heap转储到Dump到文件一遍后续分析
-XX:+HeapDumpBeforeFulllGC
b表示FullGC之前生成Heap文件转储
-XX:HeapDumpPath=<Path>
指定heap转储文件的存储路径
-XX:OnOutOfMemoryError
指定一个可行性程序或者脚本路径,当发送OOM时候,执行这个文件
垃圾收集器的相关选项
查看默认垃圾收集器
-XX:PrintCommandLineFlags
Seria回收器
-XX:+UseSerialGC
单线程高效垃圾收集器
ParNew回收器
-XX:+UseParNewGC
指定为新生代并行收集器
-XX:ParallelGCThreads=N
限制线程数,默认与CPU数量一致
Paralle回收器
-XX:UserParalleGC
开启Paralle并行垃圾回收器
并行垃圾回收器,在有限时间内提高吞吐量
-XX:+UseParallelOldGC
指定老年代GC
-XX:ParallelGCThreads
并行线程数
-XX:MaxGCPauseMillis
最大停顿时间
-XX:GCTimeRatio
吞吐量
-XX:+UseAdaptiveSizePolicy
开启自适应
CMS回收器
补充参数
特别说明
-XX:+UseConcMarkSweepGC
手动开启CMS
-XX:CMSInittiatingOccupanyFraction
设定内存阈值
-XX:+UseCMSCompactAtFullCollection
用于指定FullGC后对内存进行压缩
-XX:CMSFullGCsBeforeCOmpaction
设置多少次FullGC后进行压缩操作
-XX:ParallelCMSThreads
设置CMS线程数
并发的垃圾收集器
G1回收器
MixedGC调优参数
怎么选择垃圾回收器
GC日志相关选项
常用参数
-verbose:gc
记录GC
-XX:+PrintGC
-XX:PrintGCDetails
记录GC与GC完堆空间内存大小
-XX:+PrintGCTimeStams
GC的时间戳
-XX:PrintGCDateStams
日期
-XX:+PrintHeapAtGC
每次GC前后记录堆空间
-Xlogc:<file>
日志输出到指定卫路径
其他参数
-XX:TraceClassLoading
监控类的加载
-XX:+PrintGCApplicationStoppedTime
打印GC时线程停顿时间
-XX:PrintGCapplicationConcurrentTime
垃圾收集之前打印出应用未中断的执行时间
-XX:PrintReferenceGC
记录回收了多少中不同引用类型的引用
-XX:+PrintTenuringDistribution
让JVM在每次MinorGC后打印出当前使用的Survivor中对象的年龄分布
-XX:+UseGCLogFileRotation
启用GC日志文件自动转储
-XX:NumberOfGClogFiles=1
GC日志文件的循环数
-XX:GCLogFileSize=1M
控制GC日志文件的大小
其他参数
通过Java代码获取JVM参数
分析GC日志
GC日志参数
GC日志格式
GC日志分析工具
设计模式
策略模式
定义了算法族,分别封装起来,让它们之间可以相互替换,此模式让算法的变化独立于使用算法的客户。
定义了算法族,分别封装起来,让它们之间可以相互替换,此模式让算法的变化独立于使用算法的客户。
概述
定义了算法族,分别封装起来,让它们之间可以相互替换,此模式让算法的变化独立于使用算法的客户。
类图
鸭子模型-策略类图
抽象模式-策略类图
内容
总结
由客户端决定具体使用何种策略(业务,算法)来实现。
建议
观察者模式
定义了对象之间的一对多依赖,这样一来,当一个对象改变状态时,它的所有依赖者都会受到通知并自动更新。
定义了对象之间的一对多依赖,这样一来,当一个对象改变状态时,它的所有依赖者都会受到通知并自动更新。
概述
定义了对象之间的一对多依赖,这样一来,当一个对象改变状态时,它的所有依赖者都会受到通知并自动更新。
类图
观察者模式
java.uitl.Observer
内容
当两个对象之间松耦合,它们依旧可以交互,但是不太清楚彼此的细节。
观察者模式提供了一种对象设计,让主题和观察者之间松耦合。
总结
建议
观察者模式定义了对象之间一对多的关系。
主题用一个共同的接口来更新观察者。
观察者和可观察者之间用松耦合方式结合,可观察者不知道观察者的细节,只知道观察者实现了观察者的接口。
使用此模式时,你可以从被观察者处推或拉数据。
有多个观察者时,不可以依赖特定的通知次序。
Java有多种观察者模式的实现,包括了通用的java.util.Observable。
要注意java.util.Observable实现上所带来的一些问题。
如果有必要的话,可以实现自己的Observable,这并不难,不要害怕。
装饰者模式
动态地将责任附加到对象上。若要扩展功能,装饰者提供了比继承更有弹性的替代方案。
动态地将责任附加到对象上。若要扩展功能,装饰者提供了比继承更有弹性的替代方案。
概述
动态地将责任附加到对象上。若要扩展功能,装饰者提供了比继承更有弹性的替代方案。
类图
装饰者类图
内容
使用装饰者,可以在不修改底层代码的前提下,给其补充新的职责。
装饰者可以在所委托被装饰者的行为之前、之后,加上自己的行为,以达到特定的目的。
总结
完全遵循开放-关闭原则
完美的利用组合的方式来处理问题
建议
继承属于扩展形式之一,但不见得是达到弹性设计的最佳方式。
在我们设计中,应该允许行为可以被扩展,而无须修改现有代码。
组合和委托可用于在运行时动态地加上新的行为。
除了继承,装饰者模式也可以让我们扩展行为。
装饰者模式意味着一群装饰者嘞,这些类用来包装具体的组件。
装饰者类反映出被装饰的组件类型。
装饰者可以再被装饰者的行为前面或后面加上自己的行为,甚至将被装饰者的行为整个取代掉,而达到特定的目的。
你可以用无数个装饰者包装一个组件。
装饰者一般对组件的客户是透明的,除非客户程序依赖于组件的具体类型。
装饰者会导致设计中出现许多小对象,如果过度使用,会让程序变得很复杂。
工厂模式
定义了一个创建对象的接口,但由子类决定要实例化的类是哪一个。工厂方法让类把实例化推迟到子类。
定义了一个创建对象的接口,但由子类决定要实例化的类是哪一个。工厂方法让类把实例化推迟到子类。
概述
定义了一个创建对象的接口,但由子类决定要实例化的类是哪一个。工厂方法让类把实例化推迟到子类。
抽象工厂模式
提供一个接口,用于创建相关或者依赖对象的家族,而不需要明确指定具体的类。
类图
工厂模式
简单工厂
工厂方法
抽象工厂
披萨店
内容
通过子类决定该创建的对象时什么,来达到将对象创建的过程封装的目的。
总结
将使用与实现解耦
将创建对象与使用对象的操作解耦合
依赖倒置原则最贴切的实践
高层组件不依赖于底层组件。
举例:披萨店-披萨
披萨店为高层组件
披萨为低层组件
披萨店为高层组件
披萨为低层组件
思维方式转变,不从上到下,而从下至上。
不想考虑披萨店,先考虑有哪些披萨。
然后披萨店可以选择不同的披萨。
然后披萨店可以选择不同的披萨。
建议
变量不可以持有具体类的引用
不要让类派生自具体类
不要覆盖基类中已实现的方法
所有的工厂都是用来封装对象的创建
简单工厂,虽然不是真正的设计模式,但仍不失为一个简单的方法,可以将客户程序从具体的类解耦。
工厂方法使用继承:把对象的创建委托给子类,子类实现工厂方法来创建对象。
抽象工厂使用对象组合:对象的创建被实现在工厂接口所暴露出来的方法中。
所有工厂模式都通过减少应用程序和具体类之间的依赖促进松耦合。
工厂方法允许类将实例化延迟到子类进行。
抽象工厂创建相关的对象家族,而不需要依赖它们的具体的类。
依赖倒置原则,指导我们避免依赖具体类型,而要尽量依赖抽象。
工厂模式是很有威力的技巧,帮助我们针对抽象编程,而不要针对具体类编程。
单例模式
确保一个类只有一个实例,并提供一个全局访问点。
确保一个类只有一个实例,并提供一个全局访问点。
概述
确保一个类只有一个实例,并提供一个全局访问点。
类图
单例模式类图
内容
使用场景举例
线程池、缓存、对话框、处理偏好设置和注册表的对象、日志对象、驱动对象。
创建单例代码流程
单线程
判断创建
多线程
同步方法
类对象
双重检查加锁
总结
建议
单件模式确保程序中一个类最多只有一个事例。
单件模式也提供访问这个事例的全局点。
在Java中实现单件模式需要私有构造器、一个静态方法和一个静态变量。
确定在性能和资源上的限制,然后小心地选择适当的方案来实现单件,已解决多线程的问题。
如果使用多个类加载器,可能会导致单件失效而产生多个实例。
命令模式
将请求封装成对象,以便使用不同的请求、队列或者日志来参数化其他对象。命令模式也支持可撤销的操作。
将请求封装成对象,以便使用不同的请求、队列或者日志来参数化其他对象。命令模式也支持可撤销的操作。
概述
将请求封装成对象,以便使用不同的请求、队列或者日志来参数化其他对象。命令模式也支持可撤销的操作。
类图
命令模式类图
内容
封装调用
允许发出请求的对象
接受与执行这些请求的对象
解耦
流程
创建命令对象
存储命令对象
调用命令对象的命令指令
命令对象给接受者绑定特定的一组动作来封装请求。
NoCommand
空对象,兜底方案对象
队列请求
线程池里的等待队列
总结
一般来说,有两个对象,命令对象,接受者对象。
建议
命令模式将发出请求对象和执行请求的对象解耦。
在被解耦的两者之间是通过命令对象进行沟通的。命令对象封装了接受者和一个或一组动作。
调用者通过调用命令对象的execute()发出请求,这会使得接收者的动作被调用。
调用者可以接受命令当做参数,甚至在运行时动态地进行。
命令可以支持撤销,做法是实现一个undo()方法来回到execute()被执行前的状态。
宏命令是命令的一种简单延伸,允许调用多个命令。宏方法也可以支持撤销。
实际操作时,很常见使用“聪明”命令对象,也就是直接实现了请求,而不是将工作委托给接受者。
命令也可以用来实现日志和事务系统。
适配器模式
将一个类的接口,转换成客户期望的另外一个接口。适配器让原本接口不兼容的类可以合作无间。
将一个类的接口,转换成客户期望的另外一个接口。适配器让原本接口不兼容的类可以合作无间。
概述
将一个类的接口,转换成客户期望的另外一个接口。适配器让原本接口不兼容的类可以合作无间。
类图
适配器类图
类适配器与对象适配器
内容
过程
客户通过目标接口调用适配器的方法对适配器发出请求。
适配器使用被适配者接口把请求转换成被适配者的一个或多个调用接口。
客户接收到调用的结果,但并未察觉这一切是适配器在起装换作用。
Java源码举例
例如Collection适配了Enumeration枚举类
后面迭代器Iterator出来后,适配兼容Enum做了适配器EnumerationIterator
适配器
类适配器与对象适配器
对于java来说,不存在类适配器。
对象类加载器,组合
总结
对象适配器与类适配器
类适配器使用的是多重继承的方式,Java不存在类适配器模型
对象适配器使用的对象组合的方式
装饰者与适配器
从代码上来看,适配器与装饰者是类似的。
意图上有差距。
适配器
主要是处理接口适配。
装饰者
主要是给类新增一个行为。
建议
当需要使用一个现有的类而其接口并不符合你的需要时,就使用适配器。
适配器可以改变接口以符合客户的期望。
实现一个适配器可能需要一番功夫,也可能不费功夫,视目标接口的大小与复杂程度而定。
适配器有两种形式:对象适配器与类适配器。类适配器需要用到多重继承。
适配器将一个对象包装起来以改变其接口;装饰者将一个对象包装起来以增加新的行为和责任;而外观将一群对象“包装”起来以简化其接口。
外观模式
提供了一个统一的接口,用来访问子系统中的一群接口。外观定义了一个高层接口,让子系统更容易使用。
提供了一个统一的接口,用来访问子系统中的一群接口。外观定义了一个高层接口,让子系统更容易使用。
概述
提供了一个统一的接口,用来访问子系统中的一群接口。外观定义了一个高层接口,让子系统更容易使用。
类图
外观模式
内容
这很容易理解,但是请务必记得模式的意图。这个定义清楚地告诉我们,外观的意图是要提供一个简单的接口,好让一个系统更易于使用。从这个模式的类图可以感受到这一点。
总结
建议
当需要简化并统一一个很大的接口或者一群复杂的接口时,使用外观。
外观将客户从一个复杂的子系统中解耦。
实现一个外观,需要将子系统组合进外观中,然后将工作委托给子系统执行。
你可以为一个子系统实现一个以上的外观。
模板方法模式
在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以再不改变算法结构的情况下,重新定义算法中的某些步骤。
在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以再不改变算法结构的情况下,重新定义算法中的某些步骤。
概述
在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以再不改变算法结构的情况下,重新定义算法中的某些步骤。
类图
模板方法
内容
Java数组里的sort方法,让元素实现Compable接口,实现compareTo方法。
定义流程
总结
模板方法与策略模式
模板方法
定义算法流程
继承实现
策略模式
封装不同算法
组合实现
建议
"模板方法"定义了算法的步骤,把这些步骤的实现延迟到子类。
模板方法模式为我们提供了一种代码复用的重要技巧。
模板反方法的抽象类可以定义具体方法、抽象方法和钩子。
抽象方法由子类实现。
钩子是一种方法,它在抽象类中不做事,或者只做默认的事情,子类可以选择要不要去覆盖它。
为了防止子类改变模板方法中的算法,可以将模板方法声明为final。
好莱坞原则告诉我们,将决策权放在高层模块中,以便决定如何以及何时调用底层模块。
你讲在真实世界代码中看到模板方法模式的许多变体,不要期待他们全都是一眼就可以被你认出的。
策略模式和模板方法都封装算法,一个用组合,一个用继承。
工厂方法是模板方法的一种特殊版本。
迭代器模式
提供一种方法顺序访问一个聚合对象中的各个元素,而又不暴露其内部的表示。
提供一种方法顺序访问一个聚合对象中的各个元素,而又不暴露其内部的表示。
概述
提供一种方法顺序访问一个聚合对象中的各个元素,而又不暴露其内部的表示。
类图
迭代器接口
迭代器模式
两部分,封装迭代器装机接口。
封装迭代器元素。
封装迭代器元素。
内容
管理良好的集合
实现遍历我的对象,但是又不可窥见我对象的存储方式。
依赖于迭代器接口
主要两个方法,一个获取下一个元素,判断是否还有下个元素。
客户端只需要知道它是迭代器,迭代器实现的方法
至于迭代器对象如何实现客户端无需关注
迭代器方法封装了遍历过程
迭代器模式让我们能游走于聚合内的每一个元素,而又不暴露其内部的表示。
把游走的任务放在迭代器上,而不是聚合上。这样简化了聚合的接口和实现,也让责任各得其所。
迭代器分类
内部迭代器
不提供next方法,不能外部游走元素
外部迭代器
总结
建议
迭代器允许访问聚合的元素,而不需要暴露它的内部结构。
迭代器将遍历聚合的工作封装进一个对象中。
当使用迭代器的时候,我们依赖聚合提供遍历。
迭代器提供了一个通用的接口,让我们遍历聚合的项时,就可以使用多台机制。
我们应该努力让一个只分配一个责任。
组合模式
允许你将对象组合成树形结构来表现“整体、部分”层次结构。组合能让客户以一致的方式处理个别对象以及对象组合。
允许你将对象组合成树形结构来表现“整体、部分”层次结构。组合能让客户以一致的方式处理个别对象以及对象组合。
概述
允许你将对象组合成树形结构来表现“整体、部分”层次结构。组合能让客户以一致的方式处理个别对象以及对象组合。
类图
组合模式
内容
组合模式让我们能用树形方式创建对象的结构,树里面包含了组合以及个别对象。
使用组合结构,我们能把相同的操作应用在组合和个别对象上。换句话说,在大多数情况下,我们可以忽略对象组合和个别对象之间的差别。
所有的组件都必须实现MenuComponent接口
但是叶子节点与组合节点的角色不同,所以有些方法可能并不适合某种节点
因此你对于不需要的方法最好是抛出运行时异常
因此可以给默认方法设置为抛出异常,子类未实现此方法去调用则报异常。
以单一责任设计原则换区透明性
客户可以将组合与叶子节点一视同仁。
但是与之带来的还有安全性的丢失
空迭代器
兜底方案,默认迭代
当没试下迭代器时有两种处理方式
返回null
使用者需要判断是否为null
返回一个空迭代器
hashNext()返回永远为false
因此对于客户端来说。空迭代器的处理方式更加优雅。可以用对待普通迭代器的方式处理。
总结
当你有数个对象的集合,它们彼此之间有“整体/部分”的关系,并且你想用一致的方式对待这些对象时,那就需要我。
建议
组合模式允许客户对个别对象以及组合对象一视同仁。
组合结构内的任意对象称为组件,组件可以是组合,也可以是叶节点。
在实现组合模式时,有许多设计上的折衷。你要根据需要平衡透明性和安全性。
状态模式
允许对象在内部状态改变时改变它的行为,对象看起来好像修改了它的类。
允许对象在内部状态改变时改变它的行为,对象看起来好像修改了它的类。
概述
允许对象在内部状态改变时改变它的行为,对象看起来好像修改了它的类。
类图
状态模型
类图
内容
策略模式和状态模式的双胞胎,在出生时才分开。
策略模式
通过切换不同的算法来创建业务功能
由客户端自己选择不同的策略
继承之外的一种弹性替代方案
状态模式
通过切换不同的状态来实现业务功能
由状态对象内部自行控制状态切换
不用放置许多条件判断的代替方案
局部化每个状态的行为,封装变化的原则
当某些状态需要改变时,不会影响到其他状态。
设计步骤
定义一个State接口
实现不同状态类,对应机器不同行为
将if else 语句改为委托形式
对修改关闭,对扩展开放
为啥使用赢家类而不是直接修改售出类来实现释放两个糖果的按钮呢?如果促销结束,需要再次修改售出类。调整中奖几率。更换中奖商品等。都需要修改它。
总结
改变行为这个事情是状态内部方案中的控制的,由状态模式内部决定。
建议
状态模式允许一个对象基于内部状态而拥有不同的行为。
和程序状态机(PSM)不同,状态模式用类代表状态。
Context会将行为委托给当前状态对象。
通过将每个状态封装进一个类,我们把以后需要做的任何改变局部化了。
状态模式和策略模式有相同的类图,但是它们的意图不同。
策略模式通常会用行为或算法来配置Context类。
状态模式允许Context随着状态改变而改变行为。
状态装换可以由State类或者Context类控制。
使用过状态模式通常会导致设计中类的数目大量增加。
状态类可以被多个Context实例共享。
代理模式
为另一个对象提供一个替身或占位符以控制对这个对象的访问。
为另一个对象提供一个替身或占位符以控制对这个对象的访问。
概述
为另一个对象提供一个替身或占位符以控制对这个对象的访问。
类图
代理模式
内容
你的客户对象所做的就像是在做远程方法调用,但其实只是调用本地堆中的“代理”对象上的方法,再由代理处理所有网络通信的底层细节。
流程
客户对象
客户辅助对象
服务辅助对象
服务对象
客户对象 doBigThing -> 客户辅助对象
客户辅助对象 远程调用-> 服务辅助对象
服务辅助对象 真实调用-> 服务对象
客户辅助对象又称为桩(stub)
服务辅助对象又称为骨架(skeleton)
远程服务制作步骤
步骤一:制作远程接口
远程接口定义出可以让客户远程调用的方法。客户将用它作为服务的类类型。Stub和是实际的服务都实现此接口。
步骤二:制作远程的实现
为远程接口中定义的远程方法提供了真正的实现。
步骤三:利用rmic产生的stub和skeleton
步骤四:启动RMI registry
步骤五:开始远程服务
案例
远程代理
虚拟代理
处理问题
远程访问对象
控制访问创建开销大的资源
基于权限控制对资源的访问
在日志打印中,控制日志打印内容的开与关
总结
使用代理模式创建代表对象,让代表对象控制某对象的访问,被代理的对象可以是远程对象、创建开销大的对象或需要安全控制的对象。
建议
代理模式为另外一个对象提供代表,以便控制客户对对象的访问,管理访问的方式有许多种。
远程代理管理客户和远程对象之间的交互。
虚拟代理控制访问实例化开销大的对象。
保护代理基于调用者控制对象方法的访问。
代理模式有许多变体,例如:缓存代理、同步代理、防火墙代理和写入时复制代理。
代理在结构上类似于装饰者,但是目的不同。
装饰者模式为对象加上行为,而代理则是控制访问。
Java内置的代理支持,可以根据需要建立动态代理,并将所有调用分配到所选的处理器。
就和其他的包装者一样,代理会造成你的设计中类的数目整加。
复合模式
概述
模式通常被一起使用,并被组合在同一个设计解决方案中。
复合模式在一个解决方案中结合两个或者多个模式,已解决一般或重复发生的问题。
类图
内容
总结
建议
MVC是复合模式,结合了观察模式、策略模式和组合模式。
模型使用观察者模式,以便观察者更新,同时保持两者之间解耦。
控制器是视图的策略,视图可以使用不同的控制器实现,得到不同的行为。
视图使用组合模式实现用户界面,用户界面通常组合了嵌套的组件,像面板、框架和按钮。
这些模式携手合作,把MVC模型的三层解耦,这样可以保存设计干净又有弹性。
适配器模式用来将新的模式适配成已有的视图和控制器。
Model2 是MVC在Web上的应用。
在Model2中,控制器实现成Servlet,而JSP/HTML实现视图。
桥接模式
概述
类图
内容
总结
建议
生成器模式
概述
类图
内容
总结
建议
责任链模式
概述
类图
内容
总结
建议
蝇量模式
概述
类图
内容
总结
建议
解释器模式
概述
类图
内容
总结
建议
中介者模式
概述
类图
内容
总结
建议
备忘录模式
概述
类图
内容
总结
建议
原型模式
概述
类图
内容
总结
建议
访问者模式
概述
类图
内容
总结
建议
总结
概述
设计原则
封装变化
多用组合,少用继承
针对接口编程,不针对实现编程
为了交互对象之间的松耦合设计而努力
类应该对扩展开放,对修改关闭
我们的目标是允许类容易扩展,在不修改现有代码的情况下,可以搭配新的行为。这样的设计具有弹性可以应对改变,可以接受新的功能来应对改变的需求。
要依赖抽象,不要依赖具体的类。
依赖倒置原则
不能让高层组件依赖底层组件。无论高低组件,都应当依赖于抽象。
高层组件与低层组件鉴别
由低层组件定义了其行为的类,就叫高层组件
案例,披萨店由披萨风味觉得它的类型
因此,披萨是低层组件,披萨店是高层组件
因此,披萨是低层组件,披萨店是高层组件
最少知识原则:只和你的密友谈话。
如何不要赢得太多的朋友和影响太多的对象。
只调用以下范围的方法。
该对象本身
被当做方法的参数而传递进来的对象
此方法所创建或实例化的任何对象
对象的任何组件
好莱坞原则
别调用我,我会调用你。
单一责任原则
一个类应该只有一个引起变化的原因
类的每个责任都有改变的潜在区域。超过一个责任,意味着超过一个改变的区域。
这个原则告诉我们,尽量让每个类保持单一责任。
高内聚的体现
类图
内容
OO思想
良好的OO设计必须具备可复用、可扩充、可维护三个特性。
模式不是代码,而是针对设计问题的通用解决方案。你可把它们应用到特定的应用中。
大多数的模式都允许系统局部改变独立于其他部分。
通常我们会把变化部分抽取出来封装。
总结
如果你发现自己处于某个情境下,面对着所裕达到的目标被一群约束影响着的问题,
然而,你能够应用某个设计,克服这些约束并达到该目标,将你领向某个解决方案。
然而,你能够应用某个设计,克服这些约束并达到该目标,将你领向某个解决方案。
设计模式
模式是在某情境下,针对某问题的某种解决方案。
情境就是应用某个模式的情况。这应该是会不断出现的情况。
问题就是你想在某个情境下达到的目标,但也可以是某情境下的约束。
解决方案就是你锁追求的:一个通用的设计,用来解决约束,达到目标。
解决方案有两个方向
目标
约束
子主题
建议
阅读理解
开发
Effective Java
1.用静态工厂方法代替构造器
描述
优点
1.最主要:他有名称,使用者能知道它是做什么的。
2.可以不必在每次调用它们的时候都创建一个新对象。
3.可以返回原返回类型的任何子类型的对象。
4.所返回的对象的类可以随着每次调用而发生变化,这取决于静态工厂方法的参数值。
5.方法返回的对象所属的类,在编写包含该静态工厂方法的类是可以不存在。
缺点
1.类如果不包含公有的或者受保护的构造器,就不能被子类实例化。
2.Javadoc不支持,程序员很难发现它们。
例子
Bigintege me = Bigintege .valueOf(Intege .MAX_VALUE);
2.遇到多个构造器参数时要考虑使用构建器
描述
简而言之,重构构造器模式可行,但是当有许多参数的时候,客户端代码会很难编写,并且可阅读性差。
使用getset模式,无法把类做成不可变。
优点
客户端代码可阅读性强,可较容易的强化被构建的对象。
缺点
增加了创建构建器的额外开销,客户端构建代码比较冗长。
例子
NutitionFacts cocaCola = new NutritionFacts.Builder(240, 8)
.caloes(100).sodium(35).cabohydate(27). build();
.caloes(100).sodium(35).cabohydate(27). build();
3.用私有构造器或者枚举类型强化单例(Singleton)属性
描述
单例会使得测试变得困难。
对象可序列化时,要注意重写readResolve反复噶,否则每次反序列化时,都会创建一个新对象。
反射可以破坏单例。
单元素的枚举类型经常成为实现单例的最佳方法。
优点
缺点
例子
4.通过私有够朝气强化不可实例化的能力
描述
当某些工具类不希望被实例化,因为该工具类只有静态方法。
企图通过将类做成抽象类来强制该类不可被实例化时行不通的。
该类的子类任然可以被实例化。会误导客户端使用者。
让这个类包含一个私有构造器,它就不能被实例化
优点
客户端不会误用,去实例化该类
缺点
无法被子类化,无法使用构造器
例子
public class UtiltityClass{
private UtiltityClass(){
throw new AssertionError();
}
}
private UtiltityClass(){
throw new AssertionError();
}
}
5.优先考虑依赖注入来引用资源
描述
静态工具类和单例类不适合于需要引用底层资源的类。
当创建一个新的实例时,就将该资源传到构造器中。(简单的依赖注入)
资源工厂传给构造器
优点
极大地提升了类的灵活性,可重用性和可测试性。
缺点
可能会使得大型项目凌乱不堪,但是这种凌乱可以用依赖注入框架解决,例如Spring
例子
public class SpellChecker{
private final Lexicon dictionary;
public SpellChecker(Lexicon dictitonary){
this.dictionary = dictionnary;
}
}
private final Lexicon dictionary;
public SpellChecker(Lexicon dictitonary){
this.dictionary = dictionnary;
}
}
6.避免创建不必要的对象
描述
自动装箱使得基本类型和装箱类型之间的差别变得模糊起来,但是并没有完全消除。
要优先使用基本类型而不是装箱基本类型,要当心无意识的自动装箱。
优点
减少创建对象的开销,减少额外对象的内存占用。
缺点
在某些场景下,例如拷贝对象,则避免使用重复对象,会有bug和安全漏洞。
例子
7.消除过期的对象引用
描述
清空对象引用应该是一种例外,而不是一种规范行为。
只要类是自己管理内存,程序员就应该警惕内存泄漏问题。
内存泄漏的另外一个常见来源是缓存。
内存泄漏的第三个常见来源是监听器和其他回调。
优点
缺点
例子
8.避免使用终结方法和清除方法
描述
终结方法(finalizer)通常是不可预测的,也是很危险的,一般情况下是不必要的。
清除方法(cleaner)没有终结反方法那么危险,但是仍然是不可预测、运行缓慢,一般情况下也是不必要的。
由于终结方法与清除方法是否执行是不可预知的,所以无法保证其执行时间。
永远不应该依赖终结方法或者清除方法来更新重要的持久状态。
其二者非常消耗性能。
终结方法的安全漏洞,打开了类的大门。
它可以阻止对象被回收,可以破坏原有的结构,例如私有构造器创建对象。
所以为了防止final类受到终结方法的攻击,要编写一个空的final的finalize方法。
特殊情况,可以作为兜底操作,例如兜底的close,过很久释放资源和不释放资源还是有一定影响的。
总结:除非作为安全网或者终止非关键的本地资源。否则不要使用它!并且java9前尽可能不要使用终结反复噶。
优点
缺点
例子
9.try-with--resource优先于try-finally
描述
资源需要实现 AutoCloseable ,提供单个返回viod的close接口
优点
代码简洁、清晰,产生的异常更加有价值。更加容易编写正确代码。
缺点
例子
static String firstLineOfFile(String path) throws IOException{
try(BufferedReader br = new BufferedReader(
new FileReader(path))){
return br.readLine();
}
}
try(BufferedReader br = new BufferedReader(
new FileReader(path))){
return br.readLine();
}
}
重构:改善既有代码的设计
第一章:入门实例
例子
租客,影片,订单
影片计费,影片分类扩展
重构的第一步
构建可靠的测试环境
知识点
如果你发现自己需要为程序添加一个特性,
而代码结构使你无法很方便地达成目的,
那就先重构那个程序,
使特性的添加比较容易进行,
然后再添加特性。
而代码结构使你无法很方便地达成目的,
那就先重构那个程序,
使特性的添加比较容易进行,
然后再添加特性。
重构之前,
首先检查自己是否有一套可靠的测试机制。
这些测试必须有自我检验的能力。
首先检查自己是否有一套可靠的测试机制。
这些测试必须有自我检验的能力。
重构技术就是以微小的步伐修改程序。
如果你犯下错误,很容易便可以发现它。
如果你犯下错误,很容易便可以发现它。
任何一个傻瓜都能写出计算机可以理解的代码。
唯有写出人类容易理解的代码,
才是优秀的程序员。
唯有写出人类容易理解的代码,
才是优秀的程序员。
运用多态取代条件语句
第二章:重构原则
何为重构
重构:对软件内部结构的一种调整,
目的是在不改变软件可观察行为对的前提下,
提高其可理解性,
降低其修改成本。
目的是在不改变软件可观察行为对的前提下,
提高其可理解性,
降低其修改成本。
重构:使用一系列重构手法,
在不改变软件可观察行为的前提下,
调整其结构。
在不改变软件可观察行为的前提下,
调整其结构。
为何重构
改进软件设计
更加易于理解
帮助找到BUG
提供编程速度
何时重构
三次法则
事不过三,三则重构
添加功能使重构
修改错误时重构
复审代码时重构
何时不该重构
不要过早发布接口。
请修改你的代码所有权策略,
使重构更顺畅。
请修改你的代码所有权策略,
使重构更顺畅。
第三章:代码的坏味道
如果尿布臭了,就换掉它。
重构这件事情,比较主观,依赖于coder的经验和嗅觉。
坏味道
1.Duplicated Code (重复代码)
设法将它们合二为一,程序变得更好。
类出现相同的表达式
子类拥有功能一致的算法
函数模式
算法中部分流程一致
模版方法
2.Long Method (过长的函数)
拥有短函数的对象会活得比较好、比较长。
面向对象编程,短函数的解释能力、共享能力、选择能力。极大提高了代码后续的可维护性。
需要注意,短函数,需要一个好名字,让开发者通过函数名就可以直观认识到业务内容。
通常条件表达式、循环处理结构,应该被提炼到新函数中去。
3.Large Class (过大的类)
类业务太多的话,往往其内部拥有太多实例。往往此时也会出现重复代码。
需要将类拆分
4.Long Parameter List (过长参数列)
5.Divergent Change (发散式变化)
一次引入新模块,导致需要多处修改
6.坏名字
7.全局数据(Global Data)
8.可变数据(Mutable Data)
9.散弹式修改(Shotgun Surgery)
每遇到变化,你都要在不同类做许多小改动。
利用内联函数或者内联类
避免产生大类,模块划分可以更加清晰
10.依恋情结(Feature Envy)
所谓模块化,就是将代码分区,最大化内部交互,最小化跨区交互
如果模块数据过度依赖其他模块函数处理。可能说明模块拆分的不合理。
策略模式和访问模式
将变化的行为和不变的行为隔离,但是会有多一层间接性的代价
11数据泥团(Data Clumps)
相同属性,字段经常一起出现是,应当将他们归类。创建一个新类管理他们。
12.基本类型偏执(Primitive Obsession)
含糊不清的类型,例如手机号码,使用的是String字符串类。
13.重复的switch(Repeated Switches)
重复的switch会让我们修改时需要多重考虑才能新增case类型数据。推荐使用多态的形式来解决这个问题。
14.循环语句(Loops)
循环一直是程序设计的核心要素。
java里可以使用管道操作来减少使用循环操作。
15.冗赘的元素(Lazy Element)
对于过度抽象出来的类,函数。应该慎重,如果不能保证其后续会有扩大情况,应该使用内联函数或者内联类去实现。
16.夸夸其谈通用性(Speculative Generality)
过早的提前准备,提前的钩子方法,特殊情况处理等。
依旧可以内联类或者内联函数处理。
17.临时字段(Temporary Field)
当类中某些字段只有特殊情况下才会被使用,请搬离它们,给他们从新找个家。
18.过长的消息链(Message Chains)
对象引用过多的业务流程
应该使用隐藏委托关系
更好的选择是,尝试提炼函数,将过多的函数提炼到独立函数中。
但是函数链不一定都是坏家伙,需要冷静思考其是不是已经将责任依赖划分的足够合理了。
19.中间人(Middle Man)
对象特征之一封装
如果某个类接口一半函数都是委托给其他类,过度使用委托。
这时候要考虑是否能移除中间人,直接操作其他对象了。
可以将他们用内联函数代替
委托取代超类或者委托取代子类
20.内幕交易(Insider Trading)
软件提倡高内聚低耦合,实际上交互数据的耦合是不可避免的。只能减少。
可以搬移函数或者搬移字段减少它们私下交流。
如果它们有共同兴趣,可以为他们单独抽离到一个新模块,提供管理良好的地方去。或者使用应酬委托关系,把另外一个模块编程两者中介。
21.异曲同工的类(Alternative Classes with Different Interfaces)
使用类的好处就是后面方便替换。
22.纯数据类(Data Class)
23.被拒绝的遗赠(Refused Bequest)
24.过多的注释(Comments)
当你感觉要撰写注释时,请先尝试重构,试着让所有注释都变得多余。
第四章:构建测试体系
第五章:重构列表
重构的记录格式
名称
概要
动机
做法
范例
第六章:第一组重构
提炼函数(Extract Method)
反向操作:内联函数
做法:新函数要说明做什么,而不是怎么做
内联函数(Inline Method)
反向操作:提炼函数
动机:如果函数内部实现已经非常直观,无需抽离成函数了
做法
检查函数,确保其不具备多态性
找出所有调用点
将所有调用点替换为函数本体
测试!!!
提炼变量(Extract Variable)
反向操作:内联变量
动机:表达式阅读性太差!
做法
确保提炼的表达式没有副作用
确定作用域
新变量取代原函数表达式
测试!!!
内联变量(Inline Variable)
反向操作:提炼变量
动机:某些情况下,表达足够精简,
变量可能会妨碍代码重构
变量可能会妨碍代码重构
做法
变量的表达式没有副作用
确定作用域
找到所有声明使用的地方
替换
删除变量声明
测试!!!
改变函数声明(Change Function Declaration)
动机:函数是我们将程序
拆分成小块的主要方式
拆分成小块的主要方式
函数名很重要
参数列表需要细心选择,
减少不必要的耦合
减少不必要的耦合
没有简单可循的规则来定义参数
所以最好的名字和参数就是结合业务,
及时调整!!!
及时调整!!!
Change是最好的!
做法
对于函数的修改,名称以及参数列表的改动
其影响范围都是比较大的,遵循小步慢跑的原则
渐进式地逐步修改
其影响范围都是比较大的,遵循小步慢跑的原则
渐进式地逐步修改
简单做法:
移除参数,确保函数体已经不再使用
修改函数声明,达到表达的最佳状态
找出所有调用点,调整为新函数声明
测试!!!
迁移式做法:
可以将新函数在进行拆分
提炼函数本身
新增参数,参考简单做法
测试!!!
对旧函数改用内联函数
调整代码
测试!!!
对于已经发布的API,需要保留旧接口,给客户端时间调整。
封装变量(Encapsulate Variable)
动机:重构的作用就是调整程序中的元素。
降低调整组织数据的难度
提高对大作用域(多个函数内使用)的数据的管控能力
动态添加字段,修改校验规则等。
封装数据很重要,不过,不可变数据更重要
降低调整组织数据的难度
提高对大作用域(多个函数内使用)的数据的管控能力
动态添加字段,修改校验规则等。
封装数据很重要,不过,不可变数据更重要
做法:
创建封装函数,在其中访问和更新变量值
执行静态检查,java可以选择重新编译代码
逐步知道使用该变量的代码,改用何时的封装函数
替换后及时测试!!!
替换后及时测试!!!
限制变量的可见性
测试!!!
如果变量的值是一个记录,考虑使用封装记录
有时还需要控制对参数的修改权限。
设定为不可修改类型。
设定为不可修改类型。
可采用拷贝复制的方式
复制对于性能的影响通常是可以忽略不计
数据封装很有价值,但往往并不简单。到底应该封装什么,以及
如何封装,取决于数据被使用的方式,以及我想要修改数据的方式。不过,一言
以蔽之,数据被使用得越广,就越是值得花精力给它一个体面的封装。
如何封装,取决于数据被使用的方式,以及我想要修改数据的方式。不过,一言
以蔽之,数据被使用得越广,就越是值得花精力给它一个体面的封装。
变量改名(Rename Variable)
动机:好的命名是整洁编程的核心
名言,如果你发现有更加好的名称,别吝啬,及时调整。
使用范围越广,名字的好坏就越重要。
机制:如果变量被广泛使用,考虑运用封装变量将其封装起来。
找出素有使用该变量的代码,逐一修改。
测试!!!
找出素有使用该变量的代码,逐一修改。
测试!!!
引入参数对象(Introduce Parameter Object)
动机:当一群数据泥团经常一起出现时,
将他们柔到一个类统一管理很重要
将他们柔到一个类统一管理很重要
做法:
目前没有一个合适的数据结构
那就重新创建一个
那就重新创建一个
通常使用类来承载
测试!!!
使用改变函数声明给原来的函数新增一个参数
类型是新建的数据结构
类型是新建的数据结构
测试
调整所有调用方代码,传入新的数据结构的适当实例
测试
逐步替换原函数中每项参数,然后删除原参数。
测试!!!
函数组合成类(Combine Functions into Class)
动机:类,在大多数现代编程语音中都是基本的构造。
如果发现一组数据总是离不开某个函数,那么是时候为它们创建一个家了。
如果发现一组数据总是离不开某个函数,那么是时候为它们创建一个家了。
做法:
运用封装记录对多个函数共用的数据记录加以封装。
对于使用该记录结构的每个函数,运用搬移函数将其移入新类。
用以处理该数据记录的逻辑可以用提炼函数提炼出来,并移入新类。
函数组合成变换(Combine Functions into Transform)
动机:在软件中,经常需要把数据“喂”给程序,
让它再计算出各种派生信息。
让它再计算出各种派生信息。
单纯的提炼函数也可以处理派生类中重复代码
但是后续要使用和管理就不那么方便,我需要
找到相关逻辑需要花费的时间就比较长。
但是后续要使用和管理就不那么方便,我需要
找到相关逻辑需要花费的时间就比较长。
做法:
创建一个变换函数,输入参数是需要变换的记录,并直接返回该记录的值。
挑选一块逻辑,将其主体移入变换函数中,把结果作为字段添加到输出记录中。
修改客户端代码,将其使用这个新字段。
修改客户端代码,将其使用这个新字段。
测试
拆分阶段(Split Phase)
动机:看到一段代码同时处理两件不同的事情,
就会想着拆分成两个模块来处理。
就会想着拆分成两个模块来处理。
做法:
将第二阶段的代码提炼成独立的函数
测试
引入一个中转数据结构,将其作为参数添加到提炼出的新函数的参数列表中。
测试
逐一检查提炼出的第二阶段函数的每个参数。
如果第一阶段有使用,就将其移入到中转数据结构中。
如果第一阶段有使用,就将其移入到中转数据结构中。
测试
对第一阶段代码运用提炼函数,将提炼函数返回中转数据结构
第七章:封装
1.封装记录(Encapsuate Record)
动机
特例是不可变的数据结构。
通常建议使用类去聚合这些数据结构。给他一个家
简单数据结构,会给后续维护者带来维护困难。
做法
对持有记录的变量使用封装变量,将其封装到一个函数中。
创建一个类,将记录包装起来,并将记录变量的值替换为该类的一个实例。
然后在类上定义一个访问函数,用于返回原始的记录。
修改封装变量的函数,令其使用这个访问函数。
然后在类上定义一个访问函数,用于返回原始的记录。
修改封装变量的函数,令其使用这个访问函数。
测试。
新建一个函数,让它返回该类的对象,而非那条原始的记录。
对于该记录的每处使用点,将原先返回记录的函数调用替换为那个返回实例对象的函数调用。
使用对象上的访问函数来获取数据的字段,如果该字段的访问函数还不存在,那创建一个。
每次更改之后运行测试。
使用对象上的访问函数来获取数据的字段,如果该字段的访问函数还不存在,那创建一个。
每次更改之后运行测试。
如果该记录比较复杂,例如是一个嵌套结构,那么先重点关注客户端对数据的更新操作,
对于读操作可以考虑返回一个数据副本或者只读的数据代理。
对于读操作可以考虑返回一个数据副本或者只读的数据代理。
移除类对原始记录的访问函数,那个容易搜索的返回原始数据的函数也要一并删除。
测试。
如果记录中的字段本身也是复杂结构,考虑对其再次应用封装记录或者封装集合手法。
实践经验
更加关注更新操作。
2.封装集合(Encapsuate Collection)
动机
封装程序中所有可变的数据。
一般来说,封装函数需要包括:修改、添加、删除、建议返回的函数是拷贝体,而不是本体。
最常见做法,提供一个取值函数,返回集合的副本。
做法
如果集合的引用尚未被封装起来,先用封装变量封装它。
在类上添加用于“添加集合元素”和“移除集合元素”的函数。
如果存在对该集合的设值函数,尽可能先用移除设值函数移除它。
如果不能移除该设值函数,至少让它返回集合的一份副本。
如果不能移除该设值函数,至少让它返回集合的一份副本。
执行静态检查。
找到集合的引用点。如果有调用者直接修改集合,令该处调用使用新的添加、删除元素的函数。测试。
修改集合的取值函数,使其返回一份只读的数据,可以使用只读代理或数据副本。
3.以对象取代基本类型(Replace Primitive with Object)
动机
需要对数据进行复杂处理的基本类型
做法
如果变量尚未被封装起来,先使用封装变量封装它。
为这个数据值创建一个简单的类。类的结构函数应该保存这个数据值,并为它提供一个取值函数。
执行静态检查。
修改第一步得到的设值函数,令其创建一个新类的对象并将其存入字段,
如果有必要的话,同时修改字段的类型声明。
如果有必要的话,同时修改字段的类型声明。
修改取值函数,令其调用新类的取值函数,并返回结果。
测试。
考虑对第一步得到的访问函数使用函数改名,以便更好反映其用途。
考虑应用将引用对象改为值对象或将值对象改为引用对象,明确指出新对象的角色是值对象还是引用对象。
4.已查询取代临时变量(Replace Temp with Query)
动机
代码重复,提高可读性,避免函数内过多局部变量
适用于:计算结果后续不会有改变的变量。
做法
检查变量在使用前是否已经完全计算完毕,检查计算它的那段代码是否每次都能得到一样的值。
如果变量目前不是只读的,但是可以改造成只读变量,那就先改造它。
测试。
将为变量赋值的代码提炼成函数。
如果变量和函数不能使用同样名字,那么先为函数取个临时名字。|
确保待待提炼函数没有副作用。若有,先应用将查询函数和修改函数分离手法隔离副作用。
确保待待提炼函数没有副作用。若有,先应用将查询函数和修改函数分离手法隔离副作用。
测试。
内联变量手法移除临时变量。
5.提炼类(Extract Class)
动机
如果一个类有大量函数和数据,那么需要仔细查看,
观察是否能将不同职责进行拆分成小类。
观察是否能将不同职责进行拆分成小类。
做法
决定如何分解类所负的责任。
创建一个新的类,用以表现从旧类中分离出来的责任。
如果旧类剩下的责任与旧类的名称不符,为旧类改名。
构造旧类时创建一个新类的实例,建立“从旧类访问新类”的连接关系。
对于你想搬移的每个字段,运用搬移字段搬移之。每次更改后运行测试。
使用搬移函数将必要函数搬移到新类。先搬移较低层函数。每次更改后进行测试。
检查两个类的结构,去掉不再需要的函数,必要时为函数重新取个合适新环境名字。
决定是否公开新的类。如果确实需要,考虑对新类应用引用对象改为值对象使其成为一个值对象。
6.内联类(Inline Class)
动机
一个类不再承担足够责任,不再有单独存在的理由,可以萎缩它,将它移到另外一个类中去。
做法
对于待内联类中所有public 函数,在目标类上创建一个对应的函数,新创建的所有函数应该直接委托至源类。
修改源类public方法的所有引用点,令它们调用目标类对应的委托方法。每次
更改后运行测试。
更改后运行测试。
将源类中的函数与数据全部搬移到目标类,每次修改之后进行测试,直到源类
变成空壳为止。
变成空壳为止。
删除源类,为它举行一个简单的“丧礼”
7.隐藏委托关系(Hide Delegate)
动机
如果某些客户端先通过服务对象的字段得到另一个对象(受托类),然后调
用后者的函数,那么客户就必须知晓这一层委托关系。万一受托类修改了接口,
变化会波及通过服务对象使用它的所有客户端。我可以在服务对象上放置一个简
单的委托函数,将委托关系隐藏起来,从而去除这种依赖。这么一来,即使将来
委托关系发生变化,变化也只会影响服务对象,而不会直接波及所有客户端。
用后者的函数,那么客户就必须知晓这一层委托关系。万一受托类修改了接口,
变化会波及通过服务对象使用它的所有客户端。我可以在服务对象上放置一个简
单的委托函数,将委托关系隐藏起来,从而去除这种依赖。这么一来,即使将来
委托关系发生变化,变化也只会影响服务对象,而不会直接波及所有客户端。
做法
对于每个委托关系中的函数,在服务对象端建立一个简单的委托函数。
调整客户端,令它只调用服务对象提供的函数。每次调整后运行测试。
如果将来不再有任何客户端需要取用Delegate(受托类),便可移除服务对象
中的相关访问函数。
中的相关访问函数。
测试。
8.移除中间人(Remove Midde Man)
动机
随着受托类的特性(功能)越来越多,更多的转
发函数就会使人烦躁。服务类完全变成了一个中间人(81),此时就应该让客户
直接调用受托类。
发函数就会使人烦躁。服务类完全变成了一个中间人(81),此时就应该让客户
直接调用受托类。
做法
为受托对象创建一个取值函数。
对于每个委托函数,让其客户端转为连续的访问函数调用。每次替换后运行测
试
试
替换完委托方法的所有调用点后,你就可以删掉这个委托方法了。
这能通过可自动化的重构手法来完成,你可以先对受托字段使用封装变量
(132),再应用内联函数(115)内联所有使用它的函数。
(132),再应用内联函数(115)内联所有使用它的函数。
9.替换算法(Substitute Algorithm)
动机
使用这项重构手法之前,我得确定自己已经尽可能分解了原先的函数。替换
一个巨大且复杂的算法是非常困难的,只有先将它分解为较简单的小型函数,我
才能很有把握地进行算法替换工作。
一个巨大且复杂的算法是非常困难的,只有先将它分解为较简单的小型函数,我
才能很有把握地进行算法替换工作。
做法
整理一下待替换的算法,保证它已经被抽取到一个独立的函数中。
先只为这个函数准备测试,以便固定它的行为。
准备好另一个(替换用)算法。
执行静态检查。
运行测试,比对新旧算法的运行结果。如果测试通过,那就大功告成;否则,
在后续测试和调试过程中,以旧算法为比较参照标准。
在后续测试和调试过程中,以旧算法为比较参照标准。
第八章:搬移特性
1.搬移函数(Move Function)
动机
模块化是优秀软件设计的核心。
任何函数都需要具备上下文环境才能存活。
如果函数与上下文已经不符合、或者经常和其他模块联动,
那么这个函数可能需要有个新的家。
那么这个函数可能需要有个新的家。
做法
检查函数在当前上下文里引用的所有程序元素(包括变量和函数),
考虑是否需要将它们一并搬移。
考虑是否需要将它们一并搬移。
如果发现有些被调用的函数也需要搬移,我通常会先搬移它们。这样可以
保证移动一组函数时,总是从依赖最少的那个函数入手。
保证移动一组函数时,总是从依赖最少的那个函数入手。
如果该函数拥有一些子函数,并且它是这些子函数的唯一调用者,那么你
可以先将子函数内联进来,一并搬移到新家后再重新提炼出子函数。
可以先将子函数内联进来,一并搬移到新家后再重新提炼出子函数。
检查待搬移函数是否具备多态性。
在面向对象的语言里,还需要考虑该函数是否覆写了超类的函数,或者为
子类所覆写。
子类所覆写。
将函数复制一份到目标上下文中。调整函数,使它能适应新家。
如果函数里用到了源上下文(source context)中的元素,我就得将这些元
素一并传递过去,要么通过函数参数,要么是将当前上下文的引用传递到新的
上下文那边去。
搬移函数通常意味着,我还得给它起个新名字,使它更符合新的上下文。
素一并传递过去,要么通过函数参数,要么是将当前上下文的引用传递到新的
上下文那边去。
搬移函数通常意味着,我还得给它起个新名字,使它更符合新的上下文。
执行静态检查。
设法从源上下文中正确引用目标函数。
修改源函数,使之成为一个纯委托函数。
测试。
考虑对源函数使用内联函数(115)
也可以不做内联,让源函数一直做委托调用。但如果调用方直接调用目标
函数也不费太多周折,那么最好还是把中间人移除掉。
函数也不费太多周折,那么最好还是把中间人移除掉。
2.搬移字段(Move Field)
动机
编程活动中你需要编写许多代码,为系统实现特定的行为,但往往数据结构
才是一个健壮程序的根基。
才是一个健壮程序的根基。
代码凌乱,势必
难以理解;不仅如此,坏的数据结构本身也会掩藏程序的真实意图。
难以理解;不仅如此,坏的数据结构本身也会掩藏程序的真实意图。
做法
Architecture of a Database System
数据库系统架构
数据库系统架构
概述
结构
客户端通信管理器
本地客户端协议
远程客户端协议
查询管理器
查询解析和授权
查询重写
查询优化
执行查询计划
数据定义与表结构
存储管理器
访问方法
缓存区管理
锁管理
日志管理
进程管理器
入场控制
调度与排期
共享组件和实用工具
目录管理
内存管理
管理、监控、工具
备份与恢复
批量工具
图解
图
内容
为调用者(客户端或者中间件)建立连接并记录其连接地址,对客户端的 sql 语句做出回应,
并在适当的时候返回数据以及控制信息。
并在适当的时候返回数据以及控制信息。
进程模型
目前主流的三种模型
一个DBMS工作者对应一个进程
一个DBMS工作者对应一个线程
进程池、线程池方式
一些定义
一个操作系统进程
包含了一个正在执行的程序活动单元(线程)以及专属的地址空间。
一个操作系统线程
是操作系统程序执行单元,他没有私有的地址空间和上下文。
轻量级线程包
是一个应用层次上的结构,它支持单系统进程中的多线程。
应用层实现。任何中断操作会导致整体中断。
两个解决方式
只支持异步的I/O操作。
不调用会导致中断的系统操作。
DBMS客户端
是应用程序实现与数据库系统通信API的软件组件。
JDBC
ODBC
属于是大家遵从一个大的方向,但是又各家各有特色。
DBMS工作者
是指DBMS中为客户端工作的线程。
一般是与DBMS客户端一一对应的。
单处理机和轻量级线程
理解前两个假设
系统线程支持
系统内核支持线程,且进程支持多线程
单处理机
仅有一个CPU的机器。(目前不现实)
三种模型
每个DBMS工作者拥有一个进程
图解
图解1
DBMS工作者直接映射到系统进程,实现简单。
由系统调度管理控制运行时间、提供保护措施来派出标准错误、
较好支持锁机制、缓冲池等。
较好支持锁机制、缓冲池等。
对于并发连接来说,这种模式不太高效。
进程切换需要切换安全的上下文环境、存储空间变量、文件和网络句柄列表以
及其他一些进程上下文
及其他一些进程上下文
支持的常见DB
IBM DB2、PostgreSQL 和 Oracle
每个DBMS工作者拥有一个线程
图解
图解1
拥有一个调度线程负责监听客户端的连接
问题:
操作系统不提供溢出、指针保护,调试困难
移植性差
移植性差
优点:
很好地扩展到高并发系统中
支持的DB
IBM DB2、微软 SQL Server、MySql、Informix 和 Sybase。
进程池
图解
图解1
对于每个DBMS工作者拥有一个进程缺点,进行的变体
池化思维减小创建进程与回收进程的开销
共享数据和进程空间
缓冲区
磁盘I/O缓冲区
读写共享的数据。
数据库请求
数据库I/O中断请求:缓冲池
线程模型里,缓冲池主要是堆数据存在
进程的共享空间。
存查询结果,将查询结果落库
日志请求
日志 I/O 请求:日志尾部
周期性的按FIFO顺序存储日志内容到磁盘
对于线程模型,任然是堆数据结构
进程模型则是由独立的进程负责管理日志。
提交事务前需要等待日志刷新
客户端通信缓冲区
为支持预提取功能,DBMS 工作者使用客户端通信套接字作为元组队列
锁表
锁表由所有的 DBMS 工作者共享,由锁管理器实现数据库锁机制。
DBMS线程
这个架构为我们提供了快速的任务切换和易移植性,它的代
价是需要在 DBMS 中重复实现许多操作系统逻辑
价是需要在 DBMS 中重复实现许多操作系统逻辑
标准实践
每个 DBMS 工作者拥有一个进程:
Oracle默认方式
支持进程池
PostgreSQL
每个 DBMS 工作者拥有一个线程:
IBM DB2
默认使用
MySQL
进程/线程池:
DBMS 工作者共用进程池
Oracle
DBMS 工作者共用线程池
微软 SQL Server
准入控制
当资源环境(CPU、内存)负载高时、需要由准入控制器,
合理调控系统性能衰退,在负载低时合理增加。
合理调控系统性能衰退,在负载低时合理增加。
实现方式
1:通过进程来确保客户端链接数在临界值以下。
2:关系查询处理器上实现。
由优化器查询计划决定
1:所需磁盘IO
2:操作、元组数据的CPU负载
3:内存使用情况,包括连接、排序、哈希等。
因此,许多DBMS 把内存使用情况和活跃的 DBMS 工作者的数量作为主要的准入控制标准。
并行架构:进程与内存协调
共享内存
图解
图解1
概述
共享内存机器的进程模型很自然地遵循单处理机的方式。
其主要思路是,多个CPU共同使用同块内存区域。
三种进程模式在这种架构下都能很好的兼容
无共享
图解
图解
概念
一个无共享的并行系统是由多个独立计算机的集群组成的,这些计算机可以高速地通过
网络进行互连通信,或者逐渐频繁地在商业网络组件上通信。对于给定的一个系统,无法直
接访问另一个系统的内存和硬盘。
网络进行互连通信,或者逐渐频繁地在商业网络组件上通信。对于给定的一个系统,无法直
接访问另一个系统的内存和硬盘。
无共享系统并没有提供抽象的硬件共享,协调不同机器的任务完全留给了 DBMS。
共享磁盘
非均衡内存访问
线程和多处理器
标准的操作规程
讨论和附加材料
关系查询处理器
存储管理
事务:并发控制和恢复
共享组件
数据库
概念
数据库概念1
结构
全局
核心组件
工具
数据查询流程
客户端管理器
客户端管理器->
查询管理器->
数据管理器->
客户端管理器
查询管理器->
数据管理器->
客户端管理器
查询管理器
查询解析器
查询重写器
统计
查询优化器
查询执行器
数据管理器
缓存管理器
事务管理器
并发控制
锁管理器
日志管理器
ACID
原子性(Atomicity)
要么全成功,要么全失败
eg: A转50给B
(1)A没扣,B没得
(2)A扣了,B得了
不会出现
(1)A没扣,B得了
(2)A扣了,B没得
eg: A转50给B
(1)A没扣,B没得
(2)A扣了,B得了
不会出现
(1)A没扣,B得了
(2)A扣了,B没得
一致性(Consistency)
合法数据约束
数据不会凭空产生,也不会凭空消失。
数据不会凭空产生,也不会凭空消失。
隔离性(Isolation)
事务间同时执行,不会相互干扰
eg: A转50给B,C转100给B
二者不会干扰,
例如A与C均扣钱了,但是B只加了50或100
eg: A转50给B,C转100给B
二者不会干扰,
例如A与C均扣钱了,但是B只加了50或100
持久性(Durability)
事务提交,就一定会保存到数据库。
0 条评论
下一页