集合
对象的容器,定义了对多个对象进行操作的常用方法,可用于实现数组的功能
和数组的区别:
数组的长度固定,集合长度不固定
数组可以储存基本类型和引用类型,集合只能存储引用类型
Collection父接口 无序、无下标、不能重复
List 特点 有序 有下标 元素可以重复 有序是指 存入顺序与读取顺序一致
访问 增强for循环 迭代器 get(i)
listIterator 可以反向遍历,但是只有头迭代器
向集合中添加基本类型 会自动装箱
整数缓存 在list中使用new Inter 删除所包含的数据
arrayList 数组 查询快 增删慢 连续存储 效率快 线程不安全
默认容量大小 10,没有添加任何元素容量为0,存放对象的数组 elementdata size 实际的对象个数 扩容1.5倍
vector 查询快 增删慢 运行效率慢 线程安全
LinkedList 增删快 查询慢 双向链表
泛型 方法 接口 类
好处 代码重用性 防止类型转换异常 提高代码安全性
泛型集合 参数化类型 类型安全的集合,强制集合元素类型一致
特点 编译时即可检查 而非运行时抛出异常,访问时不必类型转换 拆箱 不同泛型之间引用不能相互赋值 泛型不存在多态
Set 无序,无下标,元素不可重复 继承collection 添加顺序与遍历不一致
Hashset 基于hashcode计算元素存放位置
1.7 数组+链表
1.8之后 数组+链表+红黑树
1.根据hashcode计算保存位置,如果此位置为空,则直接保存
2.如果不为空执行equals,如果为true则重复,拒绝添加,否则形成链表
重写hashcode和equals (1) 31 是一个素数,减少散列冲突,2.31提高运行效率
31*i=(i<<5)-i
当存入元素的哈希码相同时,会调用equals进行确认 如果为true 则拒绝插入
Treeset 自动排序 红黑树 自定义类型需要实现comparable接口或者创建集合时指定比较规则 匿名内部类
Map 键值对 键:无序 无下标 不允许重复 值:无序 无下标 允许重复
Hashmap
Treemap 实现 sortedmap接口
Put get set 返回所有key; values 返回所有的值collection集合,
Hashmap 线程不安全 效率快 允许用null作为key或value
默认初始容量16 最大1<<30 加载因子 0.75
jdk1.8之后,当链表长度超过8.且数组长度超过64 则链表将转换为红黑树 当链表长度小于6 将红黑树转化为链表 1.8以前 链表时头插入 1.8以后尾插入
刚创建hashmap 只有加载因子赋值 table=null,size=0,在添加第一个元素是size=16,超过加载因子 ,每次增加一倍
使用key 的hashcode和equals 判定重复
Hashset 使用hashmap的key值进行存储
Hashtable 线程安全 效率慢 不允许null作为key或value
Properties 继承hashtable 要求key和value为String 通常用于读取配置文件
TreeMap 实现sortedmap接口 key值自动排序
Collections 工具类
那数组转集合 ,受限集合 不能修改 删除和添加
基本类型转数组 需要先转为包装类型
关键字:
Final 用来修饰类、方法和常量
Final修饰类:不能被继承 final类中的成员方法会被隐式指定为final方法
Final修饰的方法不能重写
Final修饰的变量是常量 基本类型则初始化后不可更改 引用类型 则初始化后不可指向其他对象
类中所有的private方法都隐式地指定为final
反射:动态代理依赖于反射 将类的各个组成部分封装为其他对象
Java代码三个过程:
- 源代码阶段 将java变成class文件
- Class 类对象阶段 类加载器将class文件中各组成部分封装成对象,fields,constructior,methods
- Runtimes 运行阶段
好处:可以在程序的运行过程中,操作这些对象
可以解耦 提高可扩展性
获取class对象的方式:
- Class.forName(“全类名”) :将字节码文件加载进内存 返回class对象 多用于配置文件
- 类名.class 通过类名属性class获取 多用于参数传递
- 对象.getclass() getClass方法在Object中定义
同一个字节码文件,在一次程序的运行中,只会加载一次,
age.setAccessible(true); 暴力反射
代理:理解
为其他对象提供一种代理以控制对这个对象的访问
作用:功能增强增加额外的功能 控制访问:代理类不让你访问目标
静态代理:代理类是自己手工实现,手动创建一个java类表示代理类,同时要代理的目标类是确定的 特点 实现简单 容易理解 客户端类 目标类 代理类
目标类中方法的调用 功能增强管
缺点:当目标类增多,代理类数量过多。接口功能增加,改代码比较麻烦,
动态代理:动态代理中代理类即使很多,代理类的数量可以很少,当增加接口中的功能时,不会影响代理类
在程序执行过程中,使用jdk的反射机制,创建代理类对象,并动态指定要代理的目标类
实现方式:jdk:必须有接口,和cglib:能继承即可
Invocation·Handler 接口,就一个invoke()
代理类的功能写在invoke中 包括:调用目标类和功能增强
public Object invoke(Object proxy, Method method, Object[] args)
Object proxy:jdk创建的代理对象, Method method:目标类中的方法jdk提供方法对象, Object[] args:方法的参数 jdk提供
Method类:目标类中的方法
通过Method可以执行目标类的方法,Method.invoke()
Proxy类:核心的对象 创建代理对象 之前创建使用new类的构造方法,现在使用proxy类的方法
静态方法:newProxyInstance()
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
可以在不改变原有目标方法,可以在代理中增强自己的功能
Jvm:
- jvm的位置
jvm相当于一个软件,运行在操作系统之上
3.类加载器
作用:加载Class文件 引用在栈 具体的实例对象在堆区
- 虚拟机自带的加载器
- 启动类(根)加载器
- 扩展类加载器
- 应用程序(系统类)加载器
双亲委派机制 - 类加载器收到类加载的请求
- 将这个请求向上委派给父类加载器完成,一直向上委托,直到启动类加载器
- 启动类加载器检查,是否能够直接加载当前这个类,能加载就加载,否则抛出异常,通知子类加载器加载
关键字 native:说明java的作用范围达不到了,回去调用底层c语言的库,会进入本地方法栈,然后调用本地方法本地接口 JNI,
方法区:
方法区是被所有线程共享,所有字段和方法字节码,以及一些特殊的方法,如构造函数,接口代码也在此定义,所有定义的方法的信息都保存在该区域,此区域属于共享区间
静态变量、常量、类信息(构造、接口定义)、运行时的常量池存在方法区,但是实例变量存在堆内存中,和方法区无关
方法区:static final Class 常量池
栈:栈内存,主管程序的运行,生命周期和线程同步
线程结束,栈内存释放, 不存在垃圾回收问题
栈:8大基本类型 +对象引用+实例方法
Java只有值传递,基本类型传值,引用类型传引用值
栈帧
堆 heap 一个jvm只有一个堆内存,堆内存的大小是可以调节的
类加载器读取类文件后,一般会把类,方法,常量,变量~,真实对象
分为三个区:
新生区(伊甸园):包括:伊甸园、幸存区0区、幸存区1区 经过GC之后会保留的内容进去幸存区,
养老区:经过好多次GC后,还存在的内容进去养老区
永久区:
GC垃圾回收,主要是在伊甸园区(轻GC)和养老区 (重GC)、
Jdk8之后,永久区改为元空间
永久区 常驻内存,用于存放jdk自身携的Class对象,interface元数据,存储的是java运行时的一些环境变量或类信息~,这个区域不存在来及回收,关闭虚拟机就会释放这个内存
Jdk1.6之前:永久代,常量池是在方法区
Jdk1.7 永久代,但是慢慢退化了,去永久代,常量池在堆中
Jdk1.8 之后,无永久代,常量池在元空间
常量池在方法区中 方法区在元空间 元空间 逻辑上存在 物理上不存在
-Xms1024m -Xms1024m -XX:+PrintGCDetails
在一个项目中,突然出现OOm故障,首先调整堆内存大小,如果还爆错就使用内存快照分析工具MAT,jprofiler,或者Debug
-Xms1m -Xmx8m -XX:+HeapDumpOutOfMemoryError
-Xms 设置初始化内存 默认1/64 -Xmx 设置最大分配内存 默认1/4
GC算法:15gc会进入养老区
标记清除法:两次扫描比较浪费时间,产生内存碎片- 不需要额外的内存空间
标记压缩:再次扫描,移动存活对象,防止内存碎片
复制算法:好处没有内存碎片,坏处浪费了内存,to区永远为空,最佳使用场景在新生区
引用计数器:计数器统计引用的次数
Java并发:
进程:进程可以看成一个程序的实例,进程是用来加载指令、管理内存、管理io的
线程:一个进程可以分为一到多个线程 一个线程就是一条指令流,线程为最小调度单位,进程为资源分配的最小单位,在Windows中进程是不活动的,只是线程的容器
同一时刻线程轮流使用cpu的做法称为并发,同一时间应对多件事件的能力
并行 多核 同一时间动手做多件事情
同步:需要等待结果返回,才能继续运行
异步:不需要等待结果返回,就能继续运行
Thread 与 runnable
Thread把线程和任务合并在一起,runnable 把线程和任务分开了
用runnable更容易与线程池等高级API配合
用runnable让任务脱离了Thread继承体系,更灵活
FutureTask 接受Callable 用来处理又返回结果的情况
一个线程有一个栈,一个栈中包含一个活动栈帧和若干个非活动栈帧,一个栈帧对应一个方法
线程上下文切换
线程的start与run方法都可以执行run 方法 但是直接调用run方法,是主线程在执行,并没有启动新线程,start才会启动多线程
线程状态:new, runnable,TIMED_WAITING,running
Interrupt 打断线程睡眠
Yield,让出cpu使用权 从running进入runnable 就绪状态
线程优先级:1, default5, 10
线程在sleep时打断,isInterrunpt标记会被重置false
Pack
Interrupted掉用该方法 会修改打断标记
两阶段终止方法
守护线程,主线程终止,其守护线程也会终止 setDaemon(true)
线程状态:
从操作系统层面来说:
初始状态,可运行状态,运行状态,阻塞状态,终止状态
阻塞状态意思为当线程调用阻塞API,如BIO读写文件,线程进入阻塞状态,读取完成后,进入可运行状态
从java来说
New,TERMINATED,WAITING(join),TIMED_WAITING(sleep),BLOCKED(锁,等待获取对象锁),RUNNABLE
一段代码块中如果存在对共享资源的多线程操作,称这段代码块为临界区
Synchronized: 对象锁。同一时刻至多只有一个线程 持有对象锁,
Synchronized实际是用对象锁保证了临界区内的代码的原子性
对访问同一共享资源的临界区,使用同一锁对象,且都加锁
Synchronized只能锁对象,加在成员函数上锁this。加在静态成员函数上锁类名.class
局部变量不存在线程安全问题,存在栈帧中,线程私有
线程安全是指 多个线程调用同一个实例的方法时,
每一个方法可以保证原子性,但是将两个原子性方法组合将不能保证线程安全
String Integer 不可变类 保证线程安全
对于转账的线程安全问题,属于共享两个对象间的金额,需要锁住两个对象,简单的解决方法是对类别加锁,但是这样会锁住所有对象,效率太低
Java对象头
对象由两部分组成,一部分对象头,一部分成员变量
在32位 普通对象:对象头由64位组成,32位Mark word、 32位klass word(主要用来标识类名)
数组对象额外有一个32位来说明数组长度
Synchronized的锁住的对象会关联一个monitor,monitor包含owner、entrylist、waitset
偏向锁 轻量锁 重量级锁
膨胀锁 向上升级锁
自旋优化
Cas compare and swap
对象默认为偏向锁,但是会有几秒延迟,对象调用hashcode 偏向锁会被禁用
偏向锁没有额外的存储空间,所以调用hashcode会禁用偏向锁 可偏向101, 不可偏向001
重量级锁时,hashcode会存到monitor里面 结束时会交换
轻量级锁的hashcode 会存在线程栈帧的锁记录里 结束时会交换
批量重偏向 默认偏向锁,之后改为轻量锁,撤销20次后重偏向
批量撤销
锁消除,jit优化锁消除
Lock.wait(), notify(), notifyall()
Wait会释放锁,sleep 不释放锁,只释放cpu时间片
他们的状态都是TIME_WAITING
两个线程间的交互,使用中间类 保护暂停 guarded,只需要等待到结果就可以
而join需要等待线程结束,join的需要的结果变量必须是全局的, 而保护暂停是局部的
保护暂停 生产者与消费者一一对应 送信 同步模式
生产者消费者 消息队列、异步模式
Pack unpack
细粒度的锁,增强程序的并发度
活跃性
死锁:t1线程获得A对象锁,接下来想获得B对象锁
T2线程获得B对象锁,接下来想获得A对象锁
一个线程需要同时获取多把锁,这时就容易发生死锁
定位死锁 使用第三方工具 jconsole
活锁:出现在两个线程互相改变对方的结束条件 最后都无法结束
修改睡眠时间
线程饥饿:线程的优先级太低,始终得不到cpu的调度执行
按一定顺序加锁 可以解决死锁问题 但是会出现饥饿问题
ReentrantLock 特点:可中断、设置超时时间、公平锁、支持多个条件变量
与synchronized一样支持可重入 重复加锁 在同一个线程中,同一个锁被夹了几层锁
Reentrantlock.lock();
Try{
//临界区
} finally{
Reentrantlock.unlock();
}
lock.lockInterruptibly();
lock.tryLock()
lock.tryLock(1, TimeUnit.SECONDS)加入时间限制的锁可打断
公平锁:构造函数 true 会降低并发度
ReentrantLock lock = new ReentrantLock(true);
条件变量:创建不同的condition
Condition condition = lock.newCondition();
lock.lock();
condition.await();
condition.signal();
condition.signalAll();