java面试基础知识点总结
2023-05-28 09:30:00 4 举报
AI智能生成
主要总结了Java日常面试的基础知识点,分模块展示方便复习。
作者其他创作
大纲/内容
为什么boolean没有规定字节数?
boolean ~
byte 1个字节
char 2个字节
shot 2个字节
int 4个字节
long 8个字节
fload 4个字节
double 8个字节
java8种基本数据类型
Boolean
Byte
Character
Short
Integer
Long
Double
分类
详情
什么是缓冲池?
那些包装类具有缓冲池?
缓冲池
8种包装类型
在java8中String使用char数组存储数据
在java9中String使用byte数组存储字符串,并且同时使用coder来标识使用了哪种编码
改变
String为什么被声明为final(声明为final的好处?)
详情请查看
什么是StringPool(StringTable)
String
线程不安全,可以被改变
StringBuilder
线程安全,内部被synchronized同步
StringBuffer
String相关
数据类型
byte和Byte
char和Character
short和Short
int和Integer
enum
注意:从java7开始才能使用char(Character)和String
支持类型
switch
运算
该类不能被继承
类
该方法不能被重写
方法
使数值不能够被修改
基本类型
引用不能变,但是被引用变量的对象本身可以被修改
引用类型
数据
可以声明的位置
详细请看
使用java提供的反射技术,并且关闭安全校验也可以修改其值
final修饰的变量也可以被修改
final
成员变量
1.父类(静态变量,静态代码块)
2.子类(静态变量,静态代码块)
3.父类(实例变量,普通代码块)
4.父类构造方法
5.子类(实例变量,普通代码块)
6.子类构造方法
初始化顺序
静态代码块
static
关键字
private 类访问
default 包访问
这个修饰符在继承体系中成员对于子类可见,但是这个修饰符对于类没有意义
protected 继承访问
public
访问权限
子类只能够重写父类非私有的的,非静态的方法
子类重写父类的方法可以扩大访问权限
子类方法抛出的异常类型必须是父类抛出异常类型或为其子类型。
重写
继承
在java8中接口可以有default方法,也可以有静态方法
但是静态方法不能够被重写,default方法可以被重写
新特性
接口中的方法默认都是public的,并且不允许定义为其他的
接口中的字段都是static和final的
接口
抽象类和抽象方法都使用 abstract 关键字进行声明。如果一个类中包含抽象方法,那么这个类必须声明为抽象类。
抽象类不能被实例化,只能被继承。
抽象类
抽象类和接口
RuntimeException
非运行时异常
运行时异常和非运行时异常区别?
Exception
OutOfMemoryError
StackOverflowError
ThreadDeath
...
Error
Exception和Error的区别?
异常
如果一个类继承了一个泛型类,要么在子类的泛型声明上上声明父类泛型参数,要么指定父类泛型的具体类型。
泛型类
泛型方法
泛型接口
泛型抽象类
在使用”?“只能接收,不能修改。
上边界通配符会导致修改失效,但是获取有效
下边界通配符会导致获取有效,但是修改失效
原因
泛型的通配符和泛型的上限和下限
泛型(参数化类型)
动态数组实现
初始容量10(只有在第一次添加的时候才会真正分配初始容量),每次扩容1.5倍,如果扩容1.5倍还不够就会使用最小容量
线程不安全
实现了RandomAccess接口,支持快速随机访问
ArrarList
双向链表
插入和删除时间复杂度几乎为O(1)
不支持快速随机访问
空间消耗比ArrayList消耗更大(因为每个节点都比ArrayList多了两个引用)
LinkedList
线程安全
可以理解为加了synchronized的ArrayList
Vector
写入时复制
原理
适合大量读操作,但是不适合大量的写操作
可以读写分离,但并不是读写锁实现的
特点
初始容量0,每次写入的时候容量扩容,新的容量=插入容量+旧容量
CopyOnWriteArrayList
List
TreeMap换皮
TreeSet
HashMap换皮
HashSet
LinkedHashMap换皮
LinkedHashSet
和CopyOnWriteArrayList原理一样
CopyOnWriteSet
Set
ArrayDeque
常用子类
Deque
LinkedBlockingQueue
ArrayBlockingQueue
add
remove
element
抛出异常
offer
poll
peek
有返回值,不会抛出异常
put
take
阻塞等待
超时等待
四组常用API
使用阻塞队列实现生产者和消费者模型
BlockingQueue
AbstractQueue
Queue
Collation
在JDK1.7中HashMap使用数组加链表来实现
在JDK1.8中HashMap使用数组+链表+红黑树实现
升级
初始容量16(第一次插入才会真正分配)
每次扩容2倍
如果当前桶的容量大于装填因子*总容量就触发扩容
如果一个链表的节点个数超过8个,并且桶的长度超过64个则链表升级为红黑树,如果没有超过64个则单纯扩容2倍
首先先把hash值高16位和低16位按位与计算,然后计算桶位置算法是 index = (n - 1) & hash相当于index=hash%n
如果一颗红黑树的节点小于6个则红黑树退化为链表
JDK1.8原理
可以存NULL
HashMap
默认初始容量11,装填因子0.75
每次扩容容量是2倍+1
直接使用hasoCode的值作为hash值
HashTable
红黑树实现
key自动排序
TreeMap
链表实现
HashMap是无序的,当我们希望有顺序地去存储key-value时,就需要使用LinkedHashMap
应用场景
LinkedHashMap
分段锁 segment,锁的粒度更加精细分段的数组+链表的形式
实现
get不需要加锁,采取volatile修饰共享变量这样每次get的时候都可以获取最新的结构更新由于遍历不需要加锁的原因是因为next是final。要么是不为空返回,为空的话就加锁重读
get
1.二阶段hash,第一阶段是定位到哪个segment第二阶段是定位到哪个entry
2.entry中,除了value,还有key,hash和next都是final修饰意味着不能从中心或者尾部添加或者删除节点,一律添加到头部
3.通过count来统计段内的数据个数,只有新增和删除会修改这个值,每次都是在上述俩个步骤的最后一步进行修改
由于是final,所以删除节点的时候会删除某个节点然后那个节点之上都复制,然后最后一个节点指向被删节点的下一个节点
扩容只会对段扩容而非整个桶跟HashMap不同的是,段是先判断是否需要扩容再put,而hashmap是先put再判断是否要扩容
resize
先尝试不锁住segment的方式来统计segment的大小如果统计过程中,容器的count发生变化则采取加锁的方式
size
JDK1.7
取消了分段锁,而是采取了cas和synchronized来保证并发安全,synchronized只锁住当前链表或者红黑二叉树的首节点,只要hash不冲突,就不会产生并发,效率很高
可以认为是JDK1.8版本你的HashMap+CAS和synchronized实现的
JDK1.8
ConcurrentHashMap
Map
容器
java基础
进程是资源分配的最小单位,线程是CPU调度的最小单位
一个进程可以有多个线程,也就是进程是线程的容器,每个进程都至少有一个线程
进程和线程的区别
线程之间如何通信
线程之间如何同步
并发编程模型中的两个关键问题
共享内存
消息传递
通信的两种机制
Java的并发采用的是共享内存模型
处理器上的寄存器的读写比内存快几个数量级,为了解决这种,加入了高速缓存
缓存一致性问题
带来的问题?
主内存和工作内存
JMM定义了Java 虚拟机(JVM)在计算机内存(RAM)中的工作方式,它试图屏蔽各种操作系统的内存访问差异,以实现java程序在各种平台下都能达到一致的内存访问效果
read 把一个变量的值从主内存传输到工作内存
load 在read之后执行,把read得到的值放入工作内存的变量副本中
use 把工作内存中一个变量的值传递给执行引擎。
assign 作用于工作内存的变量,它把一个从执行引擎收到的值赋值给工作内存的变量
store 作用于工作内存的变量,把工作内存中的一个变量的值传送到主内存中,以便随后的write的操作
write 作用于主内存的变量,它把store操作从工作内存中的一个变量的值传送到主内存的变量中
lock 作用于主内存的变量,把一个变量标识为一条线程独占状态
unlock 作用于主内存的变量,把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定
8总内存间交互操作
从JDK5开始,java使用happens-before概念来阐述操作间的内存可见性。
在JMM中,如果一个操作执行的结果需要对另一个操作可见,那么这两个操作之间必须要存在happens-before关系 。
两个操作之间具有happens-before关系,并不意味着前一个操作必须要在后一个操作之前执行!happens-before仅仅要求前一个操作(执行的结果)对后一个操作可见,且前一个操作按顺序排在第二个操作之前。
程序顺序规则:一个线程中的每个操作,happens-before于该线程中的任意后续操作。
监视器锁规则:对一个锁的解锁,happens-before于随后对这个锁的加锁。
volatile变量规则:对一个volatile域的写,happens-before于任意后续对这个volatile域的读。
传递性:如果A happens-before B,且B happens-before C,那么A happens-before C。
start()规则:如果线程A执行操作ThreadB.start()(启动线程B),那么A线程的ThreadB.start()操作happens-before于线程B中的任意操作。
join()规则:如果线程A执行操作ThreadB.join()并成功返回,那么线程B中的任意操作happens-before于线程A从ThreadB.join()操作成功返回。
线程中断规则:对线程interrupt方法的调用happens-before于被中断线程的代码检测到中断事件的发生。
规则
happens-before
因为指令重排序能够提高性能
为什么需要指令重排序?
编译器优化的重排序
指令级并行的重排序
内存系统的重排序
指令重排序的种类
as-if-serial 不管如何重排序,都必须保证代码在单线程下的运行正确。
带来的问题
在临界区内的代码可以重排序,但是由于互斥特性,其他线程感受不到重排序带来的影响
使用锁同步
基于happens-Before规则编程
解决方案
可以使用内存屏障的方法禁止重排序,我们遵守happens-Before规则底层就是使用了内存屏障
java内存模型中的重排序
JMM(java内存模型)
缺点:java中类只能支持单继承。
继承Thread类重写run方法
缺点:不能获取返回值,还不能抛出异常
实现Runnable接口
特点:可以获取线程的返回值,可以抛出异常,并且get方法在获取返回值的时候会被阻塞,而且结果还会被缓存
实现Callable接口以及call方法,通过FutureTask包装器来创建Thread线程
实现线程的方式
线程的启动方式只有一种,将实现接口或者被继承的类放入Thread类中通过start启动
线程的启动方式
主线程
GC线程
JAVA程序运行的时候至少有两个线程
NEW 新生
RUNNABLE 运行
BLOCKED 阻塞
WAITING 等待
TIMED_WAITING 超时等待
TERMINATED 终止
java中线程的6个状态
设置为守护线程
当所有非守护线程结束时,程序也就终止,同时会杀死所有守护线程。
必须在开启线程前使用setDaemon方法
new Thread().setDaemon(boolean on)
休眠当前线程,单位为毫秒
sleep() 可能会抛出 InterruptedException,因为异常不能跨线程传播回
sleep不会释放锁
如果线程睡眠时间到了,该线程也不会立即执行,只是从睡眠状态变成了可运行状态
Thread.sleep(long millis)
设置线程的优先级
优先级高的任务并不一定先执行
new Thread().setPriority(int newPriority)
线程让步,当前线程会释放CPU的执行权,转入就绪状态。
yield不会释放锁
Thread.yield()
阻塞调用此方法的线程进入 TIMED_WAITING 状态,直到目标线程完成,此线程再继续;
让线程进入等待
会释放锁,但是不会释放所有的锁,只会释放当前对象的锁
wait
notify随机唤醒一个线程,如果被唤醒的线程之前放弃的锁被其他对象所有,那么这个线程会进入阻塞状态,他必须等待其他线程释放这个锁,才能开始执行。
notifyAll唤醒所有的线程
notify和notifyAll
使用notifyAll和wait实现一个生产者和消费者模型
new Object()
isInterrupted是非静态的
interrupted是静态的,而且本质上是调用了当前线程中的isInterrupted 方法,不过传入了一个参数true
interrupted()是静态方法:内部实现是调用的当前线程的isInterrupted(),并且会重置当前线程的中断状态
isInterrupted()是实例方法,是调用该方法的对象所表示的那个线程的isInterrupted(),不会重置当前线程的中断状态
inInterrsupted和interrupted区别
中断
线程操作的常用方法
重量级锁
轻量级锁
偏向锁
无锁
锁升级过程
每个synchronized都和一个monitor关联,当获得锁的时候monitor中的成员变量recursions就+1
可以避免死锁,如果没有可重入特性,那么线程第二次获得该锁的时候就会死锁
可重入性
不可中断特性
异常会释放锁
特性
在静态方法上synchronized锁的是当前类的class对象
在普通方法上synchronized锁的是当前对象
传入对象的时候synchronized锁的是传入对象
锁的对象
synchronized
Compare And Swap(比较相同再交换)。是现代CPU广泛支持的一种对内存中的共享数据进行操作的一种特殊指令。
简介
CAS可以将比较和交换转换为原子操作,这个原子操作直接由CPU保证。CAS可以保证共享变量赋值时的原子操作。CAS操作依赖3个值:内存中的值V,旧的预估值X,要修改的新值B,如果旧的预估值X等于内存中的值V,就将新的值B保存到内存中。
作用
使用CAS和阻塞队列实现一个锁
CAS(乐观锁)
JUC是java.util.concurrent工具包的简称,他是并发大师Doug Lea的杰作
什么是JUC
是JUC的核心
围绕着一个同步队列和park还有自旋锁实现
公平锁线程在唤醒的时候不能插队,非公平锁可以插队
获取公平锁和非公平锁的区别
加锁成功,不在执行后续方法
返回true
调用AcquireQueued
调用addWaiter
返回false
tryAcquire
acquire方法
lock方法真正调用的是acquire(1方法)
lock加锁
真正调用的是sync.release方法
unlock解锁
详解(以公平锁为例)
AQS(AbstractQueuedSynchronizer)队列同步器
可重入可中断锁,他是Lock最重要的实现类之一
FairSync()这个方法是公平锁,调用NonfairSync()方法是非公平锁
ReentrantLock
创建该对象的时候可以传入一个数值,每次调用countDown就会减一,计数器为零的时候await才不会阻塞
用法
CountDownLatch(减法计数器)
创建该对象的时候需要设置一个值,并且可以写一个实现Runnable的类,每次调用await就会加1,当加到设定的值后就会触发实现Runnable类的线程开启
CyclicBarrier(加法计数器)
可以用来做多线程限流,创建该对象的时候可以传入一个资源数量,每次调用acquire就会减少一个资源,资源为零的时候在调用该方法会导致他的线程进入阻塞状态,除非调用release释放锁。
Semaphore(信号量)
读锁和写锁互斥
写锁与写锁互斥
读锁和读锁可以共存
演示
读写锁ReentrantReadWriteLock
详情参考容器中的BlockingQueue
容量为一的同步队列
SynchronousQueue
阻塞队列
阿里巴巴开发规范中不允许使用Executors去创建线程池,而是推荐使用ThreadPoolExecutor的方式创建
Executors.newSingleThreadExecutor();// 单个线程
Executors.newFixedThreadPool(5); // 创建一个固定的线程池的大小
Executors.newCachedThreadPool(); // 可伸缩
三大方法
RejectedExecutionHandler handle // 拒绝策略
七大参数
假设现在有一个银行,银行的窗口就是线程,进来办理业务的人就是task
new ThreadPoolExecutor.AbortPolicy() // 满了,还有人进来,不处理这个人的,抛出异常
new ThreadPoolExecutor.CallerRunsPolicy() // 哪来的去哪里!
new ThreadPoolExecutor.DiscardPolicy() //队列满了,丢掉任务,不会抛出异常!
new ThreadPoolExecutor.DiscardOldestPolicy() //队列满了,尝试去和最早的竞争,也不会抛出常!
4种拒绝策略
线程池
ForkJoin
Future
JUC
并发
frm文件:存储表的定义数据
MYD文件:存放表具体记录的数据
MYI文件:存储索引
文件
特点索引存放的是数据具体存放在磁盘上的地址
MyISAM
一张表最多有16个索引,每个索引的最大长度是255个字节
事务型数据库的首选引擎,支持事务安全表(ACID),支持行锁定和外键,上图也看到了,InnoDB是默认的MySQL引擎。
InnoDB
引擎
为什么MYSQL使用B+树作为索引数据结构?
即一个索引只包含单个列,一个表可以有多个单列索引(建议一张表索引不要超过5个优先考虑复合索引)
单值索引
即一个索引包含多个列
复合索引(联合索引)
将数据存储与索引放到了一块,找到索引也就找到了数据
聚簇索引具有唯一性
聚簇索引
索引列的值必须唯一,但允许有空值
唯一索引
因为MYSQL每次查询只能使用一个索引,如果我们sql语句查询条件包含两个字段,那么使用单值索引需要查询两次,但是复合索引只需要一次即可,有时候覆盖索引完全覆盖可以不回表查询
为什么推荐尽量使用复合索引而不是使用唯一索引呢?
索引的分类
提高数据检索效率,降低数据库IO成本
通过索引列对数据排序,降低数据排序成本,降低CPU的消耗
优点
索引只是提高效率的一个因素,如果你的MySQL有大数据量的表,就需要花时间研究建立优秀的索引,或优化查询语句
缺点
索引的优缺点
什么是回表?
索引
表的读取顺序
数据读取操作的操作类型
哪些索引可以使用
哪些索引被实际使用
表之间的引用
每张表有多少行被优化器查询
查询序号,id相同从上往下,id不同id越大优先级越高
ID
表示示查询中每个select子句的类型
select_type
显示这一步所访问数据库中表名称(显示这一行的数据是关于哪张表的),有时不是真实的表名字,可能是简称,例如上面的e,d,也可能是第几步执行的结果的简称
table(重要)
代表分区表中的命中情况,非分区表,该项为null
partitions
对表访问方式,表示MySQL在表中找到所需行的方式,又称“访问类型”。
ALL、index、range、 ref、eq_ref、const、system、NULL(从左到右,性能从差到好)
type(重要)
指出MySQL能使用哪个索引在表中找到记录,查询涉及到的字段上若存在索引,则该索引将被列出,但不一定被查询使用(该查询可以利用的索引,如果没有任何索引显示 null)
possible_keys
key列显示MySQL实际决定使用的键(索引),必然包含在possible_keys中
Key(重要)
表示索引中使用的字节数,可通过该列计算查询中使用的索引的长度(key_len显示的值为索引字段的最大可能长度,并非实际使用长度,即key_len是根据表定义计算而得,不是通过表内检索出的)
key_len
列与索引的比较,表示上述表的连接匹配条件,即哪些列或常量被用于查找索引列上的值
ref
表示MySQL估计未来找到所需要的行而要读取的行数
rows(重要)
这一列包含的是不适合在其他列显示的额为信息
Extra(重要)
字段
Explain(查询语句执行计划)
什么是复合索引的最左匹配原则?
如果查询条件用or,必须or条件中的每个列都加上索引,否则无效。(尽量使用union代替)
复合索引未用左列字段;
like以%开头;
需要类型转换;
where中索引列有运算;
where中索引列使用了函数;
如果mysql觉得全表扫描更快时(数据少)
索引失效的情况
保证被驱动表的join字段有索引
left join时,选择小表为驱动表,大表为被驱动表,因为驱动表一定要做全表扫描。
inner join时,mysql会自己帮你把小结果集的表选为驱动表
子查询尽量不要放在被驱动表。因为子查询会生成虚拟表导致有可能使用不到索引
能够直接关联查询,尽量不用子查询。
关联查询优化
索引优化
在mysql中myisam不支持事务
原子性
一致性
隔离性
持久性
事务四大特性(ACID)
读未提交:read uncommitted
Oracle默认隔离级别
读已提交:read committed
MySQL默认级别
可重复读:repeatable read
串行化:serializable
事务隔离级别
一个事务读取了另一个事务未提交的数据
脏读
一个事务两次读取同一个数据,两次读取的数据不一致
不可重复读
一个事务两次读取一个范围的 记录,两次读取的记录数不一致。
幻读
一个事务的更新覆盖了另一个事务的更新,解决办法使用乐观锁或者使用排它锁
更新丢失
事务带来的问题
事务
共享锁(读锁)
排他锁(写锁)
按照锁机制分类
表锁(偏读)
行锁(偏写)
页锁(DBD引擎采用)
按照锁的粒度分类
innoDB使用的是行锁myisam使用的是表锁
更新的时候没有索引或者索引失效时,InnoDB 的行锁变表锁
行锁退化到表锁
间隙锁(Gap Lock)是Innodb在可重复读提交下为了解决幻读问题时引入的锁机制,
间隙锁
锁
MYSQL
屏蔽各个硬件平台和操作系统的内存访问差异,以实现让Java程序在各种平台下都能达到一致的内存访问效果
意义
当前线程所执行的字节码的行号指示器:如果正在执行的是Java方法,计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行Natvie方法,计数器值为空(Undefined)
Java虚拟机字节码解释器通过改变这个计数器的值来选取下一条要执行的字节码指令
JVM规范中唯一没有规定任何OutOfMemoryError的情况
程序计数器
用途:为JVM执行Java方法服务
编译期间完成分配
对象句柄
方法参数
方法的局部变量
结构:栈帧:局部变量表、操作数栈、动态链接、方法出口
两种异常:StackOverFlowError、如果可以动态扩展的话会产生(OutOfMemoryError) -Xss设置栈大小
虚拟机栈
用途:为虚拟机使用到的native方法服务
两种异常:StackOverFlowError、OutOfMemoryError
本地方法栈
线程私有
用途:用于存储已被JVM加载的类信息、常量、静态变量、即时编译器编译后的代码等数据
需要垃圾回收
类和接口的全限定名
字段的名称和描述符
方法的名称和描述符
内容:存放编译产生的字面量(常量final)和符号引用
Java语言并不要求常量一定只有编译期才能产生,运行期间也能将新的常量放入池中
特点:运行时常量池相对于Class文件常量池的一个重要特征是具备动态性
运行时常量池
异常:(无法扩展)OutOfMemoryError
参考垃圾对象的判定
方法区的GC
元空间
目的:存放对象实例和数组
需要垃圾回收gc管理的主要区域
目的:更好的回收内存和更快的分配内存
Eden空间
From Survivor空间
To Survivor空间等
新生代
老年代
分代处理
空间结构:逻辑连续的空间,物理可以不连续
优先分配TLAB(Thread Local Allocation Buffer),减少加锁,提高效率
-Xms初始堆大小 -Xmx最大堆大小
异常:如果在堆中没有完成内存分配,并且堆也无法扩展时,会抛出OutOfMemoryError
并不是所有的对象都在堆中分配
GC堆
线程共有
运行时数据区组成
若没有做进行类加载
1、检查参数是否在常量池中定位到一个类的符号引用;该类是否被加载、解析、初始化过
用“指针碰撞”来分配内存
内存绝对规整
用“空闲列表”来分配内存
内存不规整
采用CAS+失败重试的方式保证更新操作的原子性
对分配内存空间的工作进行同步处理
-XX:+/-UseTLAB
TLAB
每个线程分配一块本地线程分配缓冲区
2、若有则分配内存
3、始化已分配内存为零值(保证类型不赋初值可以使用)
4、上面工作完成后,执行init方法按照程序员意愿初始化对象
存储运行时数据
存储类型指针
如果对象是数组对象,还有一个保存数组长度的空间,占4个字节
对象头
是对象真正存储的有效信息
实例数据
起占位符的作用
对齐填充
对象的内存布局
对象的创建
对象创建流程图
堆中有句柄池,存储到实例数据和类型数据的指针;栈中的引用指向对象的句柄地址
reference中地址相对稳定;对象被移动(GC时)时只会改变句柄中的实例数据指针
使用句柄
栈中的引用直接存储对象地址,到方法区中类型数据的指针包含在对象实例数据中
访问速度快,节省了一次指针定位的开销
直接指针
对象的访问定位
线程请求栈深度大于最大深度StackOverFlowError
不断创建新线程,并让创建的线程不断运行
新线程拓展栈时无法扩展出现OutOfMemoryError错误
-Xss
虚拟机栈和本地方法栈溢出
不断创建新的字符串常量,并添加到list中
java.lang.OutOfMemoryError后会跟PermGen space
-XX:PermSize和-XX:MaxPermSize
方法区和运行时常量池溢出
通过不断创建新对象,并放入list中,保证GCRoots到对象之间路径可达
内存泄漏
内存溢出
java.lang.OutOfMemoryError:Java heap space
-Xms -Xmx
堆溢出
在Heap Dump文件中没有明显异常
-XX
本机直接内存溢出
OOM
存在就不回收
强引用
实现缓存
将要发生内存溢出之前
软引用
回调函数中防止内存泄露
下一次垃圾回收
弱引用
能在这个对象被收集器回收时收到一个系统通知
对对象的生存时间没有影响
虚引用
对象的引用
难以解决对象之间相互循环引用的问题
引用计数法
从GC Roots向下搜索建立引用链;一个对象如果到GC Roots没有任何引用链相连时,证明对象不可用
虚拟机栈中引用的对象
本地方法栈中引用的对象
方法区中类静态属性引用的对象
方法区中常量引用的对象
GC Roots
注意:当对象被标记为可回收后,对象不会马上被回收,而是会去执行finalize方法,如果finalize方法被覆写了后引用了其他对象,那么这个对象有可能会复活。
可达性分析法
1、如果对象在进行可达性分析后发现没有与GC Roots相连接的引用链,那它将会被第一次标记
2、判断对象是否有必要执行finalize()方法,(没有覆盖,被调用过,都没有必要执行),放入F-Queue队列
3、放入F-Queue中,进行第二次标记
4、被拯救的移除队列,被两次标记的被回收
堆中垃圾回收过程
没有任何一个对象引用常量池中的“abc”常量
废弃常量
1、该类所有的实例都已经被回收
2、加载该类的加载器被回收
3、该类对应的javalang.Class对象没有在任何地方被引用,无法通过反射访问该类的方法
无用的类(满足条件可以被回收,非必然)
方法区中垃圾回收
垃圾对象的判定
首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象
效率问题,标记和清除两个过程的效率都不高空间问题,产生大量不连续的内存碎片,连续内存不足会再次触发GC
标记-清除算法
将内存等分,每次用一块,当这块内存用完了,就将活着的对象复制到另一块,然后把前者清空
对象存活率较高时就要进行较多的复制操作,效率将会降低 空间利用率低
标记-复制算法
所有存活的对象移向一端,然后直接清理掉端边界以外的内存
标记-整理算法
复制算法
标记-整理或标记-清除
分代收集算法
当执行系统停顿下来后,并不需要一个不漏的检查完所在执行上下文和全局的引用位置,在HotSpot的实现中,使用一组称为OopMap的数据结构来存放对象引用
枚举根结点
在这些特定的位置,线程的状态可以被确定
方法调用指令
循环跳转指令
异常跳转指令
位置
GC发生时,首先把所有线程全部中断,如果发现有线程中断的地方不在安全点上,就恢复线程,让它跑在安全点上
抢占式
设置一个标志,各个线程执行时主动轮询这个标志,发现中断标志为真时就自己中断挂起
主动式
中断方式
安全点
背景:线程Sleep状态或者Blocked状态的时候,无法响应JVM中断,走到安全的地方,JVM也不能等他们,这样就无法进行GC
安全区域是指在一段代码中,引用关系不会发生变化,这个区域中的任何地方开始GC都是安全的。
安全区域
HotSpot算法
垃圾回收算法
新生代收集器
采用复制算法
单线程收集
进行垃圾收集时,必须暂停所有工作线程,直到完成
是HotSpot在Client模式下默认的新生代收集器
简单高效(与其他收集器的单线程相比)
对于限定单个CPU的环境来说,Serial收集器没有线程交互(切换)开销,可以获得最高的单线程收集效率;
\"-XX:+UseSerialGC\":添加该参数来显式的使用串行垃圾收集器;
参数设置
Serial收集器
除了多线程外,其余的行为、特点和Serial收集器一样
Server模式下,ParNew收集器是一个非常重要的收集器
单个CPU环境中,不会比Serail收集器有更好的效果,因为存在线程交互开销
\"-XX:+UseConcMarkSweepGC\":指定使用CMS后,会默认使用ParNew作为新生代收集器;
\"-XX:+UseParNewGC\":强制指定使用ParNew;
\"-XX:ParallelGCThreads\":指定垃圾收集的线程数量,ParNew默认开启的收集线程与CPU的数量相同;
ParNew收集器
多线程收集
CMS等收集器的关注点是尽可能地缩短垃圾收集时用户线程的停顿时间; 而Parallel Scavenge收集器的目标则是达一个可控制的吞吐量(Throughput)
高吞吐量为目标,即减少垃圾收集时间,让用户代码获得更长的运行时间
当应用程序运行在具有多个CPU上,对暂停时间没有特别高的要求时,即程序主要在后台进行计算,而不需要与用户进行太多交互
\"-XX:MaxGCPauseMillis\"控制最大垃圾收集停顿时间
\"-XX:GCTimeRatio\" 设置垃圾收集时间占总时间的比率
\"-XX:+UseAdptiveSizePolicy\"
Parallel Scavenge收集器
针对老年代
采用\"标记-整理\"算法(还有压缩,Mark-Sweep-Compact)
主要用于Client模式
在JDK1.5及之前,与Parallel Scavenge收集器搭配使用(JDK1.6有Parallel Old收集器可搭配)
作为CMS收集器的后备预案,在并发收集发生Concurrent Mode Failure时使用
在Server模式中
Serial Olc收集器
采用\"标记-整理\"算法
JDK1.6及之后用来代替老年代的Serial Old收集器
特别是在Server模式,多CPU的情况下
在注重吞吐量以及CPU资源敏感的场景,就有了Parallel Scavenge加Parallel Old收集器的\"给力\"应用组合
\"-XX:+UseParallelOldGC\":指定使用Parallel Old收集器
Parallel Old收集器
基于\"标记-清除\"算法(不进行压缩操作,产生内存碎片)
以获取最短回收停顿时间为目标
并发收集、低停顿
需要更多的内存(看后面的缺点)
与用户交互较多的场景
希望系统停顿时间最短,注重服务的响应速度
以给用户带来较好的体验
如常见WEB、B/S系统的服务器上的应用
\"-XX:+UseConcMarkSweepGC\":指定使用CMS收集器
初始标记
并发标记
重新标记
并发清除
运行过程
对CPU资源非常敏感
产生大量内存碎片
CMS收集器
GC收集线程并行
用户线程与GC线程并发
并行与并发
分代收集,收集范围包括新生代和老年代
空间整合:结合多种垃圾收集算法,空间整合,不产生碎片
可预测的停顿:低停顿的同时实现高吞吐量
面向服务端应用,针对具有大内存、多处理器的机器
最主要的应用是为需要低GC延迟,并具有大堆的应用程序提供解决方案
需要停顿用户线程
标记GC Root能直接关联到的对象
并发执行
对堆中对象可达性分析
需要停顿线程
修正并发标记中因用户线程运行发生改变的标记记录
最终标记
可以并发
对Region的回收价值和成本排序,根据参数指定回收计划
筛选回收
运行过程(不计Remembered Set操作)
\"-XX:+UseG1GC\":指定使用G1收集器
\"-XX:InitiatingHeapOccupancyPercent\":当整个Java堆的占用率达到参数值时,开始并发标记阶段;默认为45
\"-XX:MaxGCPauseMillis\":为G1设置暂停时间目标,默认值为200毫秒
\"-XX:G1HeapRegionSize\":设置每个Region大小,范围1MB到32MB;目标是在最小Java堆时可以拥有约2048个Region
G1收集器
大多数情况下,对象在新生代Eden区中分配,当Eden区没有足够空间进行分配时,虚拟机将发起一次Minor GC
对象优先在Eden分配
所谓大对象,就是需要大量连续内存空间的对象,最典型的大对象就是那种很长的字符串以及数组
大对象直接进入老年代
长期存活的对象将进入老年代
如果在Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象就可以进入老年代,无须等到MaxTenuringThreshold中要求的年龄
动态对象年龄判定
Minor GC之前,JVM检查老年代最大连续空间是否大于新生代所有对象的空间,成立则确保Minor GC安全
不成立,参看参数HandlePromotionFailure是否允许担保失败,允许则检查老年代最大连续空间是否大于历次晋升的对象的平均大小,大于则尝试Minor GC
否则,进行Full GC
空间分配担保
内存分配和回收策略
垃圾收集器
加载、验证、准备、解析、初始化、使用和卸载7个阶段,其中验证、准备、解析3个部分统称为连接
生命周期
遇到new、getstatic、putstatic或invokestatic这四条字节码指令时,如果类没有进行过初始化,则需要先触发其初始化
使用java.lang.reflect包的方法对类进行反射调用的时候,如果类没有进行过初始化,则需要先触发其初始化
当初始化一个类的时候,如果发现其父类还没有进行过初始化,则需要先触发父类的初始化
当虚拟机启动时,用户需要指定一个要执行的主类,虚拟机会先初始化这个主类
以下情况对类进行初始化
类加载的时机
通过一个类的全限定名来获取定义此类的二进制字节流
将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口
完成3件事
从ZIP包中读取,这很常见,最终成为日后JAR、EAR、WAR格式的基础
从网络中获取,这种场景最典型的应用就是applet
运行时计算生成,这种场景使用得最多的就是动态代理基础
由其它文件生成,典型场景就是JSP应用,即由JSP文件生成赌赢的Class类
从数据库中读取,这种场景相对的少些,例如有些中间件服务器可以选择把程序安装到数据库中来完成程序代码在集群间的分发
类的来源
加载
验证是连接阶段的第一步,这一阶段的目的就是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全
文件格式验证
元数据验证
字节码验证
符号引用验证
验证项
验证
准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些变量所使用的内存都讲在方法区汇总进行分配
准备
解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程
类或接口的解析
字段解析
类方法解析
接口方法解析
解析动作分类
解析
类初始化是类加载过程中的最后一步,这时才真正开始执行类中定义的Java程序代码
初始化
类加载的过程(5)
虚拟机设计团队把类加载阶段中的“通过一个类的全限定名来获取此类的二进制字节流”这个动作放到Java虚拟机外部去实现,以便让应用程序自己决定如何去获取所需要的类,实现这个动作的代码模块称为类加载器
比较两个类是否“相等”,只有在这两个类是由同一个类加载器加载的前提下才有意义,否则,即使这两个类来源于同一个Class文件,被同一个虚拟机加载,只要它们的类加载器不同,那这两个类就必定不相等
类加载器
这个类加载器使用C++语言实现,是虚拟机自身的一部分
启动类加载器
扩展类加载器
应用类加载器
这些类加载器由Java语言实现,独立于虚拟机外部,并且全都继承自抽象类java.lang.ClassLoader
其他类加载器
保护Java底层类,防止被破坏
当应用类加载器需要加在某个类时,这个时候它不会自己直接加载而是先去缓存查询,如果没有再委托父类加载器加载,父类加载器也是相同逻辑,直到到了启动类加载器如果还是没有,那么启动类加载器就会委派子类加载器加载,直到应用类加载器自己去加载。
双亲委派模型
虚拟机类加载机制
java虚拟机
磁盘操作:File
字节操作:InputStream 和 OutputStream
字符操作:Reader 和 Writer
对象操作:Serializable
网络操作:Socket
新的输入/输出:NIO和AIO
BIO
NIO是指JDK1.4开始提供的新的api,他是一个非阻塞面向缓冲区的高性能IO
如果理解NIO是面向缓冲区的呢?
NIO是面向块的BIO是面向流的
NIO是非阻塞的,BIO是阻塞的
NIO和BIO的区别
非阻塞的有什么用?
非直接缓冲区需要经过一个copy的阶段从内核空间copy到用户空间
直接缓冲区不需要经过copy阶段
使用ByteBuffer的allocateDirect方法
使用FileChannel的map方法
使用直接缓冲区的两种方式
直接缓冲区与非直接缓冲区
数组
本质
capacity 最大容量
position 当前已经读写的字节数
还可以读写的字节数
状态变量
ByteBuffer
CharBuffer
ShortBuffer
IntBuffer
LongBuffer
FloatBuffer
DoubleBuffer
常用实现
buffer缓冲区
通道 Channel 是对原 I/O 包中的流的模拟,可以通过它读取和写入数据。可以理解为管道就是火车道,缓冲区就是火车。
管道的理解
FileChannel 从文件中读写数据。
DatagramChannel 能通过UDP读写网络中的数据。
SocketChannel 能通过TCP读写网络中的数据。
ServerSocketChannel可以监听新进来的TCP连接,像Web服务器那样。对每一个新进来的连接都会创建一个SocketChannel
常用管道
管道可以同时进行读写,而流只能读或者只能写
管道可以实现异步读写数据
管道可以从缓冲读数据,也可以写数据到缓冲
管道和流的区别
Channel管道
一个组件,可以检测多个NIO channel,看看读或者写事件是否就绪。多个Channel以事件的方式可以注册到同一个Selector,从而达到用一个线程处理多个请求成为可能。
Selector选择器
NIO的三个核心部分
NIO
阻塞IO
非阻塞IO
信号驱动IO
IO多路转接
异步IO
阻塞程度
阻塞IO>非阻塞IO>多路转接IO>信号驱动IO>异步IO
阻塞IO<非阻塞IO<多路转接IO<信号驱动IO<异步IO
效率
5种IO模型
IO
java
0 条评论
回复 删除
下一页