java
2023-05-09 15:03:45 33 举报
AI智能生成
java脑图
作者其他创作
大纲/内容
问题排查
进程挂了
查看系统资源情况
Linux进行OOM-killer机制
是否触发OOM
/var/log/message
JVM
dump
当jvm出现致命错误时,会生成一个错误文件 hs_err_pid.log。
版本升级特性
8
Lambda
依托
匿名内部类(VM Anonymous Class)
invokedynamic
MethodHandle
字节码
常量池
JVM_CONSTANT_InvokeDynamic
JVM_CONSTANT_MethodHandle
JVM_CONSTANT_MethodType
类属性
BootstrapMethods
多一个lambda方法
函数式编程
Optional
Streams
MetaSpace代替了永久代
接口可以添加默认方法和静态方法
方法引用
9
String存储结构
不用 char[] 来存储啦,改成了 byte[] 加上编码标记
G1为JVM默认垃圾收集器
接口方法可以使用private来修饰
支持http2.0的API
10
并行Full GC,来优化G1的延迟
局部变量类型推断,类似JS可以通过var来修饰局部变量,编译之后会推断出值的真实类型
11
ZGC
Flight Recorder(飞行记录器)
基于OS、JVM和JDK的事件产生的数据收集框架
12
Shenandoah GC
Switch 表达式表达式
17
JDK tools
jconsole
jstack -l
jstack(查看线程)、
接 top -hp 查看线程的16进制
jmap(查看内存)
得到运行java程序的内存分配的详细情况。
jmap -dump:format=b,file=filename pid来导出 dump 文件
通过 mat(Eclipse Memory Analysis Tools)导入 dump 文件进行分析,内存泄漏问题一般我们直接选 Leak Suspects 即可
jmap -histo pid(查看实例)
jstat(性能分析)
可以观察到classloader,compiler,gc相关信息
监控线程状态
运行
休眠
等待
驻留
监视
基础
语法
泛型
泛型作用只在编译期
泛型的擦除保证了有泛型和没有泛型产生的代码(class文件)是一致的。
使用
泛型类
泛型接口
泛型方法
关键字
native
final
修饰一个引用
如果引用为基本数据类型,则该引用为常量,该值无法修改
如果引用为引用数据类型,比如对象、数组,则该对象、数组本身可以修改,但指向该对象或数组的地址的引用不能修改
如果引用时类的成员变量,则必须当场赋值,否则编译会报错
修饰一个方法
final修饰方法时,这个方法将成为最终方法,无法被子类重写。但是,该方法仍然可以被继承。
修饰类
该类成为最终类,无法被继承
equals和==
== 比较的是变量(栈)内存中存放的对象的(堆)内存地址,用来判断两个对象的地址是否相同,即是否是指相同一个对象。比较的是真正意义上的指针操作。
==是判断两个变量或实例是不是指向同一个内存空间,equals是判断两个变量或实例所指向的内存空间的值是不是相同
==指引用是否相同, equals()指的是值是否相同
==是指对内存地址进行比较 , equals()是对字符串的内容进行比较
类没有覆盖 equals() 方法。则通过 equals() 比较该类的两个对象时,等价于通过“==”比较这两个对象。
^
次方
位移操作
>> and >>>
>>:带符号右移。正数右移高位补0,负数右移高位补1
4 >> 1,结果是2;-4 >> 1,结果是-2。-2 >> 1,结果是-1。
>>>:无符号右移。无论是正数还是负数,高位通通补0。
位运算
~(按位非)
| (按位或)
&(按位与)
^ (按 位异或)
引用
强引用
软引用(SoftReference )
SoftReference 的原理是:在保持对对象的引用时保证在 JVM 报告内存不足情况之前将清除所有的软引用
弱引用(WeakReference )
弱引用(WeakReference )
虚引用(PhantomReference )
PhantomReference 类只能用于跟踪对被引用对象即将进行的收集。
抽象类
不能实例化的类
应为抽象类中有抽象方法,抽象方法没有方法体,抽象类不是完整的类,因此不能实例化。
接口
接口是对行为的抽象,它是抽象方法的集合,利用接口可以达到 API 定义和实现分离的目的。
不能包含任何非常量成员
区别
单继承和多继承
实例成员、类成员、抽象方法
接口中只能有抽象方法和常量,在JDK8.0之后可以有Static和default方法
extends和implements
抽象类中既可以定义常量也可以定义变量
接口中只能定义常量(使用public static final修饰)
抽象类中可以有构造方法
函数编程
Consumer
accept(T t)
Supplier
get()
Function
Function.apply
Predicate
test(T t)
循环
foreach
无法删除和无法修改数据
数据类型
String
String str="i"
常量池
String str=new String(“i”)
堆内存
方法
length()
getByte()
toCharArray()
split(String)
equals()
equalsIsIgnoreCase(String)
contains(String)
startsWith(String)
endsWith(String)
intern()
取常量池
对象
对象头 (object header)
标记字段
哈希码
GC 信息
持有的锁信息
64 位
数组对象包含数组长度
类型指针
指向该对象的类 Class
64 位
反射
运行时获取实例
classloader
Class.forName
class是已经初始化完成的
通过 UnSafe 类
通过属性相对对象起始地址的偏移量,来读取和写入属性的值
锁
synchronized
作用
synchronized(lockObject){}
public synchornized void test()
public synchornized void test()
对象的结构
对象头
32/64bit Mark Work hashCode,GC分代年龄,锁信息
实例数据
32/64bit Class Metadata Address 指向对象类型数据的指针
填充数据
填充数据
锁的升级
无锁
锁标志位 01
锁膨胀(无锁-偏向锁)
当有一个线程访问同步块
偏向锁
当线程执行到临界区(critical section)时
锁标志位01
该锁没有被其他线程所获取,没有其他线程来竞争该锁,那么持有偏向锁的线程将永远不需要进行同步操作。
锁膨胀(偏向锁-轻量级锁)
有锁竞争
线程在自己的栈桢中创建锁记录 LockRecord。
将锁对象的对象头中的MarkWord复制到线程的刚刚创建的锁记录中。
锁记录中的Owner指针指向锁对象。
将锁对象的对象头的MarkWord替换为指向锁记录的指针。
轻量级锁(自旋锁)
指向LockRecord的指针 锁标志位 00
自旋锁
自适应自旋锁
轻量级锁也被称为非阻塞同步、乐观锁,因为这个过程并没有把线程阻塞挂起,而是让线程空循环等待,串行执行。
锁膨胀(轻量级锁-重量级锁)
自旋转十次失败
重量级锁
monitor锁来实现的,而monitor又依赖操作系统的MutexLock(互斥锁)来实现的,所以重量级锁也被成为互斥锁。
指向Mutex的指针 10
当系统检查到锁是重量级锁之后,会把等待想要获得锁的线程进行阻塞,被阻塞的线程不会消耗cup。但是阻塞或者唤醒一个线程时,都需要操作系统来帮忙,这就需要从用户态转换到内核态,而转换状态是需要消耗很多时间的,有可能比用户执行代码的时间还要长。
锁标志位11
底层支持
Entry Set
锁池
对于Entry Set:如果线程A已经持有了对象锁,此时如果有其他线程也想获得该对象锁的话,它只能进入Entry Set,并且处于线程的BLOCKED状态。
对于Entry Set中的线程,当对象锁被释放的时候,JVM会唤醒处于Entry Set中的某一个线程,这个线程的状态就从BLOCKED转变为RUNNABLE。
Wait Set
等待池
对于Wait Set:如果线程A调用了wait()方法,那么线程A会释放该对象的锁,进入到Wait Set,并且处于线程的WAITING状态。
对于Wait Set中的线程,当对象的notify()方法被调用时,JVM会唤醒处于Wait Set中的某一个线程,这个线程的状态就从WAITING转变为RUNNABLE;或者当notifyAll()方法被调用时,Wait Set中的全部线程会转变为RUNNABLE状态。所有Wait Set中被唤醒的线程会被转移到Entry Set中。
SPI
JDBC
JCE
JNDI
JAXP
JBI
SPI ( Service Provider Interface),是一种服务发现机制。
SPI 的本质是将接口的实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载对应接口的实现类。这样就可以在运行时,获取接口的实现类。
Java SPI 是“基于接口的编程+策略模式+配置文件”组合实现的动态加载机制。
源码
集合类
hashmap
数组 链表 红黑树
扩容 容量 因子
调用对象的hashcode()方法获取hashcode 寻找 bucket位置 来存储Entry对象
使用key.equals判断同一个bucket中的key值是否相同,进行遍历链表
线程不安全
put
A线程覆盖B线程PUT 同一个桶 覆盖
新增一个节点 在链表末尾 覆盖
resize扩容
table 已经指向newtable
正好执行到 table = newTab
无法删除元素
1.7JDK
resize copy链表 指针错误 引起死锁
多个线程都发现hashmap需要扩容的时候 ,同时调整hashmap的大小。调整时会出现尾部遍历,导致死锁。
DEFAULT_INITIAL_CAPACITY 初始容量
1 << 4
16
MAXIMUM_CAPACITY最大容量
1<<30
DEFAULT_LOAD_FACTOR 加载因子
0.75f
TREEIFY_THRESHOLD
使用树形阈值
6
hash算法
高16位跟低16位进行异或运算,这样目的是使结果更加随机性,尽可能使数据均匀分布
在第一次put的时候进行初始化 table
懒加载
resize()
根据hash跟(新数组的容量-1)异或计算节点在新数组的位置
arrayList
add
1.判断elementData数组容量是否满足需求
2.在elementData对应位置上设置值
线程不安全
数组越界
elementData[size++] = e不是原子操作
覆盖 一个为空
扩容
elementData = Arrays.copyOf(elementData, newCapacity);
SortedMap
NavigableMap(接口)
treemap
红黑树
queue
BlockQueue
ArrayBlockQueue
用数组实现的有界阻塞队列,FIFO先进先出,支持公平所和非公平锁
LinkedBlockingQueue
链表结构阻塞队列,FIFO,默认长度为Intger.MAX_VALUE,默认有容量风险
PriorityBlockQueue
支持线程优先级排序的队列,可自定义ComparaTo()方法来指定元素排序,不保证同优先级顺序
DalayQueue
实现PriorityBlockQueue的无界队列,创建元素时,可以指定多久从才能从队列中获取当前元素。
SynchronousQueue
不存储元素的队列,一个put元素必须等待一个take操作
LinkedTransferQueue
LinkedBlockDeque
双向阻塞队列,多线程并发时,降低锁的竞争
java.net
URL
URLConnection
HttpURLConnection
java.lang
System
getSecurityManager
SecurityManager
Object
getClass
获取运行时类
toString()
equals()
如果两个对象equals相等,那么这两个对象的HashCode一定也相同
如果两个对象的HashCode相同,不代表两个对象就相同,只能说明这两个对象在散列存储结构中,存放于同一个位置
equals方法重写的话,建议也一起重写hashcode方法
hashCode
String 类型 Aa 和 BB hascode相同 2112
算法
0
Park-Miller RNG的随机数生成策略
1
内存地址
2
1
3
自增变量
4
内存地址
5(默认)
状态值进行异或(XOR)运算得到的一个 hash 值
clone
对象复制
notify
notifyAll
之所以我们应该尽量使用notifyAll()的原因就是,notify()非常容易导致死锁
wait
释放占有的锁
超时和非超时
需要另一个线程使用Object.notify()唤醒
被唤醒不一定立即执行原代码
有中断异常
需要在synchronized中执行
finalize
当垃圾回收器将要回收对象所占内存之前被调用
此方法有很大的不确定性(不保证方法中的任务执行完)而且运行代价较高。
@Deprecated
表示此方法已废弃、暂时可用
Comparable
compareTo方法对比
Thread
sleep
不会释放占有的锁
超时版本
自己醒来
一定会继续执行后续代码
Throwable
Error
VirtualMachineError
StackOverflowError
函数调用栈太深了
OutOfMemoryError
Exception
annotation
RetentionPolicy
RetentionPolicy.SOURCE:注解只保留在源文件,当Java文件编译成class文件的时候,注解被遗弃;
RetentionPolicy.CLASS:注解被保留到class文件,但jvm加载class文件时候被遗弃,这是默认的生命周期;
RetentionPolicy.RUNTIME:注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在;
Target
@Target(ElementType.TYPE) //接口、类、枚举、注解
@Target(ElementType.FIELD) //字段、枚举的常量
@Target(ElementType.METHOD) //方法
@Target(ElementType.PARAMETER) //方法参数
@Target(ElementType.CONSTRUCTOR) //构造函数
@Target(ElementType.LOCAL_VARIABLE)//局部变量
@Target(ElementType.ANNOTATION_TYPE)//注解
@Target(ElementType.PACKAGE) ///包
ExceptionInInitializerError
静态初始化块中出现异常的时候
Exception
日志输出方法
e.printStackTrace();// 只能输出在控制台当中,日志文件看不到
log.error(e.getMessage());// 只能输出简短的错误信息,不便于排错
log.error(e.getStackTrace().toString());// 不能输出错误信息
log.error("test fail:",e);// 可以在日志中输出完整的错误信息,""里要写内容
log.error(e.toString());// 只能输出简短的错误信息,不便于排错
java.util
Date
concurrent
AQS(AbstractQueuedSynchronizer)
AQS是一个同步器,设计模式是模板模式。
双向链表 + state(锁状态)
CAS
atomic
Locks
AbstractQueuedSynchronizer
同步队列
条件队列
LockSupport.park()
ReentrantLock
自旋锁
避免了使线程进入内核态的阻塞状态
ReentrantReadWriteLock
StampedLock
LockSupport
park
超时和非超时
任意处执行
一定继续执行源代码
Condition
await
释放锁资源
不一定继续执行原代码
在lock.lock()和lock.unlock之间
signal()
TimeUnit
CountDownLatch
这个类使一个线程等待其他线程各自执行完毕后再执行
CyclicBarrier
循环栅栏
它的作用就是会让所有线程都等待完成后才会继续下一步行动。
Semaphore
ConcurrentHashMap
nextTable
转移时使用数组
MOVED
正在转移
tabAt
返回节点数组的指定位置的节点的原子操作
casTabAt
cas原子操作,在指定位置设定值
setTabAt
原子操作,在指定位置设定值
·在ConcurrentHashMap中,同步处理主要是通过Synchronized和unsafe两种方式来完成的。
·在取得sizeCtl、某个位置的Node的时候,使用的都是unsafe的方法,来达到并发安全的目的
·当需要在某个位置设置节点的时候,则会通过Synchronized的同步机制来锁定该位置的节点。
·在数组扩容的时候,则通过处理的步长和fwd节点来达到并发安全的目的,通过设置hash值为MOVED
·当把某个位置的节点复制到扩张后的table的时候,也通过Synchronized的同步机制来保证现程安全
阻塞队列
ArrayBlockingQueue
有界队列
LinkedBlockingDeque
双向链表的队列
DelayQueue
支持延时获取元素的无界阻塞队列
PriorityQueue
PriorityBlockingQueue
支持优先级的无界阻塞队列
LinkedTransferQueue
LinkedTransferQueue
SynchronousQueue
线程池
FutureTask
outcome
线程执行结果
callable
implements RunnableFuture
extends Runnable, Future<V>
ScheduledThreadPoolExecutor
DelayedFutureTask
DelayedWorkQueue
ThreadPoolExecutor
池化思想管理线程
避免了处理任务时创建销毁线程开销的代价
避免了线程数量膨胀导致的过分调度问题,保证了对内核的充分利用。
abstractExecutorService
ExecutorService
Executor
将任务提交和任务执行进行解耦
扩充执行任务的能力,补充可以为一
个或一批异步任务生成 Future 的方法
提供了管控线程池的方法,比如停止线
程池的运行
线程池在内部实际上构建了一个生产者消费者模型,将线程和任务两者解耦,并不
直接关联,从而良好的缓冲任务,复用线程。
生命周期管理
运行状态 (runState) 和线程数量 (workerCount)
用一个变量去存储两个值,可避免在做相关
决策时,出现不一致的情况,不必为了维护两者的一致,而占用锁资源。
ctl共包括32位。其中,高3位表示"线程池状态",低29位表示"线程池中的任务数量"。
状态
RUNNING
(01) 状态说明:线程池处在RUNNING状态时,能够接收新任务,以及对已添加的任务进行处理。
(02) 状态切换:线程池的初始化状态是RUNNING。换句话说,线程池被一旦被创建,就处于RUNNING状态!
高3位值是111
SHUTDOWN
(01) 状态说明:线程池处在SHUTDOWN状态时,不接收新任务,但能处理已添加的任务。
(02) 状态切换:调用线程池的shutdown()接口时,线程池由RUNNING -> SHUTDOWN
对应的高3位值是000
STOP
(01) 状态说明:线程池处在STOP状态时,不接收新任务,不处理已添加的任务,并且会中断正在处理的任务。
(02) 状态切换:调用线程池的shutdownNow()接口时,线程池由(RUNNING or SHUTDOWN ) -> STOP。
高3位值是001
TIDYING
(01) 状态说明:当所有的任务已终止,ctl记录的"任务数量"为0,线程池会变为TIDYING状态。当线程池变为TIDYING状态时,会执行钩子函数terminated()。terminated()在ThreadPoolExecutor类中是空的,若用户想在线程池变为TIDYING时,进行相应的处理;可以通过重载terminated()函数来实现。
(02) 状态切换:当线程池在SHUTDOWN状态下,阻塞队列为空并且线程池中执行的任务也为空时,就会由 SHUTDOWN -> TIDYING。
高3位值是010
TERMINATED
高3位值是011
(01) 状态说明:线程池彻底终止,就变成TERMINATED状态。
(02) 状态切换:线程池处在TIDYING状态时,执行完terminated()之后,就会由 TIDYING -> TERMINATED。
拒绝策略
AbortPolicy
当任务添加到线程池中被拒绝时,它将抛出 RejectedExecutionException 异常
RejectedExecutionException
shutdown()
CallerRunsPolicy
当任务添加到线程池中被拒绝时,会在线程池当前正在运行的Thread线程池中处理被拒绝的任务。
DiscardOldestPolicy
当任务添加到线程池中被拒绝时,线程池会放弃等待队列中最旧的未处理任务,然后将被拒绝的任务添加到等待队列中。
DiscardPolicy
当任务添加到线程池中被拒绝时,线程池将丢弃被拒绝的任务
(1)并行执行子任务,提高响应速度。这种情况下,应该使用同步队
列,没有什么任务应该被缓存下来,而是应该立即执行
(2)并行执行大批次任务,提升吞吐量。这种情况下,应该使用有界队列,使用队列去缓冲大批量的任务,队列容量必须声明,防止任务无限制堆积。
Worker
extends AbstractQueuedSynchronizer
implements Runnable
DiscardPolicy
丢弃策略
DiscardOldestPolicy
关闭线程池
shutdown
不是立刻就被关闭,因为这时线程池中可能还有任务正在执行,或是任务队列中有正在等待的任务,它会等待正在执行的任务和队列中等待的任务执行完毕后才彻底关闭。
注意:调用 shutdown() 方法后如果还有新的任务被提交,线程池则会根据拒绝策略直接拒绝提交的任务。
shutdownNow
shutdownNow() 表示立刻关闭的意思。在执行 shutdownNow 方法之后,首先会给所有线程池中的线程发送 interrupt 中断信号尝试中断这些任务的执行,然后将任务队列中的任务转移到一个 List 中并返回
注意:即便我们调用了 shutdownNow 方法,如果被中断的线程对于中断信号不理不睬,那么依然有可能导致任务不会停止。所以我们自己编写的线程应当具有响应中断信号的能力。
isTerminated()
isTerminated()方法可以检测线程池是否真正“终结”了,这不仅代表线程池已关闭,同时代表线程池中的所有任务都已经都执行完毕了。
Iterator
Stream
中间操作
无状态
指元素的处理不受之前元素的影响
filter
过滤流中的某些元素
map
接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素
flatMap
接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流。
peek
如同于 map,能得到流中的每一个元素。但 map 接收的是一个 Function 表达式,有返回值;而 peek 接收的是 Consumer 表达式,没有返回值。
有状态
指该操作只有拿到所有元素之后才能继续下去
distinct
通过流中元素的 hashCode() 和 equals() 去除重复元素
limit
获取 n 个元素
skip
跳过 n 元素,配合 limit(n) 可实现分页
sorted
自然排序,流中元素需实现 Comparable 接口
sorted(Comparator com)
定制排序,自定义 Comparator 排序器
结果操作
非短路操作
指必须处理所有元素才能得到最终结果
collect
接收一个 Collector 实例,将流中元素收集成另外一个数据结构。
reduce
规约操作
max
返回流中元素最大值
min
返回流中元素最小值
count
返回流中元素的总个数
短路操作
指遇到某些符合条件的元素就可以得到最终结果
anyMatch
接收一个 Predicate 函数,只要流中有一个元素满足该断言则返回 true,否则返回 false
noneMatch
接收一个 Predicate 函数,当流中每个元素都不符合该断言时才返回 true,否则返回 false
allMatch
接收一个 Predicate 函数,当流中每个元素都符合该断言时才返回 true,否则返回 false
findFirst
返回流中第一个元素
findAny
返回流中的任意元素
常用创建方法
使用 Arrays 中的 stream() 方法,将数组转成流:
Executor
线程集合和阻塞队列
sort源码
双精度快速排序+插入排序
BitSet
jdk.internal
vm
annotation
Contended
javax.annotation
PostConstruct
被@PostConstruct修饰的方法会在服务器加载Servlet的时候运行,并且只会被服务器调用一次,类似于Servlet的inti()方法。被@PostConstruct修饰的方法会在构造函数之后,init()方法之前运行。
PreConstruct
@PreConstruct修饰的方法会在服务器卸载Servlet的时候运行,并且只会被服务器调用一次,类似于Servlet的destroy()方法。被@PreConstruct修饰的方法会在destroy()方法之后运行,在Servlet被彻底卸载之前
java.sql
PreparedStatement
Statement
sun.misc
Unsafe
unpark
park
挂起
IO
NIO(new IO)
Channel通道类似流
FileChannel, 从文件中读写数据。
DatagramChannel,通过UDP读写网络中的数据。
SocketChannel,通过TCP读写网络中的数据。
ServerSocketChannel,可以监听新进来的TCP连接,对每一个新进来的连接都会创建一个SocketChannel
Buffer用于和NIO通道进行交互。数据从通道读入到缓冲区,从缓冲区写入到通道中
数据读取到一个它稍后处理的缓冲区,需要时可在缓冲区中前后移动。这就增加了处理过程中的灵活性
Selector
Selector(选择区)用于监听多个通道的事件
reactor
Old IO
字节流
inputStream
ByteArrayInputStream
outputStream
ByteArrayOutputStream
对byte类型数据进行写入的类 相当于一个中间缓冲层,将类写入到文件等其他outputStream。它是对字节进行操作,属于内存操作流
字符流
Reader
InputStreamReader
InputStreamReader类是从字节流到字符流的桥接器:它使用指定的字符集读取字节并将它们解码为字符
每次调用一个InputStreamReader的read()方法都可能导致从底层字节输入流中读取一个或多个字节。 为了实现字节到字符的有效转换,可以从基础流中提取比满足当前读取操作所需的更多字节。为了获得最高效率,请考虑在BufferedReader中包装InputStreamReader
Writer
区别
NIO处理数据是以数据块为单位,而传统IO流是以字节为单位
NIO 非阻塞
NIO 选择器
JVM
java内存模型
主内存
Java内存模型规定了所有的变量都存储在主内存(Main Memory)中(此处的主内存与介绍物理硬件时的主内存名字一样,两者也可以互相类比,但此处仅是虚拟机内存的一部分)。
工作内存
每条线程还有自己的工作内存(Working Memory,可与前面讲的处理器高速缓存类比),线程的工作内存中保存了被该线程使用到的变量的主内存副本拷贝,线程对变量的所有操作(读取、赋值等)都必须在工作内存中进行,而不能直接读写主内存中的变量。不同的线程之间也无法直接访问对方工作内存中的变量,线程间变量值的传递均需要通过主内存来完成
内存间的交互
•lock(锁定):作用于主内存的变量,它把一个变量标识为一条线程独占的状态。
•unlock(解锁):作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定。
•read(读取):作用于主内存的变量,它把一个变量的值从主内存传输到线程的工作内存中,以便随后的load动作使用。
•load(载入):作用于工作内存的变量,它把read操作从主内存中得到的变量值放入工作内存的变量副本中。
•use(使用):作用于工作内存的变量,它把工作内存中一个变量的值传递给执行引擎,每当虚拟机遇到一个需要使用到变量的值的字节码指令时将会执行这个操作。
•assign(赋值):作用于工作内存的变量,它把一个从执行引擎接收到的值赋给工作内存的变量,每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作。
•store(存储):作用于工作内存的变量,它把工作内存中一个变量的值传送到主内存中,以便随后的write操作使用。
•write(写入):作用于主内存的变量,它把store操作从工作内存中得到的变量的值放入主内存的变量中。
内存模型JMM
•程序计数器(PC)
程序计数器是一块很小的内存空间,用于记录下一条要运行的指令。每个线程都需要一个程序计数器,各个线程之中的计数器相互独立,是线程中私有的内存空间
•java虚拟机栈
java虚拟机栈也是线程私有的内存空间,它和java线程同一时间创建,保存了局部变量、部分结果,并参与方法的调用和返回
•本地方法栈
本地方法栈和java虚拟机栈的功能相似,java虚拟机栈用于管理Java函数的调用,而本地方法栈用于管理本地方法的调用,但不是由Java实现的,而是由C实现的
•java堆
为所有创建的对象和数组分配内存空间,被JVM中所有的线程共享
•方法区
也被称为永久区,与堆空间相似,被JVM中所有的线程共享。方法区主要保存的信息是类的元数据,方法区中最为重要的是类的类型信息、常量池、域信息、方法信息,其中运行时常量池就在方法区,对永久区的GC回收,一是GC对永久区常量池的回收;二是永久区对元数据的回收
内存结构
stack
program counter stack
VM stack
native stack
heap
Young
eden
s1
s2
old
tenured
Humongous
runtime constant pool
symbolc Relerence
Literal
non-heap
meta
compile code
comprssed class space
feild&Method data
native
JNI Memory
Direct Memory
stack Memory
code cache
JIT Compile
JIT code
GC
基本功能
当应用程序请求分配内存时,GC 负责提供内存。提供内存的过程应尽可能快
GC 检测应用程序不再使用的内存。这个操作也应当十分高效,不应消耗太多时间。这种不再使用的内存称为“垃圾”;
GC 将同一块内存再次提供给应用程序,最好是“实时”,也就是要快。
垃圾回收器
Parallel GC
版本
JDK 8 以及更早版本的默认回收期
专注
吞吐量
概念
多线程的stop-the-world压缩和分代回收
概念
多线程的stop-the-world压缩和分代回收
解释
Parallel GC 是 JDK 8 以及更早版本的默认回收期。它专注于吞吐量,尽快完成工作,而很少考虑延迟(暂停)。
Parallel GC 会在 STW(全局暂停)期间,以更紧凑的方式,将正在使用中的内存移动(复制)到堆中的其他位置,从而制造出大片的空闲内存区域。当内存分配请求无法满足时就会发生 STW 暂停,然后JVM完全停止应用程序运行,投入尽可能多的处理器线程,让垃圾回收算法执行内存压缩工作,然后分配请求的内存,最后恢复应用程序执行。
ParNew
ParNew收集器是Serial收集器的多线程版本
CMS
Concurrent Mark Sweep 并发、使用标记-清除算法的gc。
CMS以获取最小停顿时间为目的。
在一些对响应时间有很高要求的应用或网站中,用户程序不能有长时间的停顿,CMS 可以用于此场景。
步骤
1 初始标记(STW)
进行可达性分析,标记GC ROOT能直接关联到的对象。
注意是直接关联间接关联的对象在下一阶段标记。
2 并发标记
该阶段进行GC ROOT TRACING,在第一个阶段被暂停的线程重新开始运行。
由前阶段标记过的对象出发,所有可到达的对象都在本阶段中标记。
3 并发预清理
CMS是以获取最短停顿时间为目的的GC。
重标记需要STW(Stop The World),因此重标记的工作尽可能多的在并发阶段完成来减少STW的时间。此阶段标记从新生代晋升的对象、新分配到老年代的对象以及在并发阶段被修改了的对象。
4 重标记(STW)
暂停所有用户线程,重新扫描堆中的对象,进行可达性分析,标记活着的对象。
有了前面的基础,这个阶段的工作量被大大减轻,停顿时间因此也会减少。注意这个阶段是多线程的。
5 并发清理
并发清理。用户线程被重新激活,同时清理那些无效的对象。
6 重置
CMS清除内部状态,为下次回收做准备。
1.CMS只能回收老年代
CMS + ParNew
2.CMS在old gc的时候会回收整个Old区
G1
版本
JDK6~ 9
专注
平衡
概念
多线程的stop-the-world压缩、并发活跃分代回收
解释
G1 的长时间操作会与应用程序并行进行,即通过多线程方式,在应用程序运行时执行。这样可以大幅度减少暂停,代价是整体的吞吐量会降低一点。
G1收集器的总体效果是好于CMS的,有更好的自我调节能力而G1从JDK9开始才是默认垃圾回收器。所以JDK8的情况下,最好主动设置G1垃圾回收器:-XX:+UseG1GC
1.G1同时回收老年代和年轻代
2.G1的分代更多是逻辑上的概念,G1将内存分成多个等大小的region,Eden/ Survivor/Old分别是一部分region的逻辑集合,物理上内存地址并不连续。
-XX:MaxGCPauseMillis
设置最大GC停顿时间(GC pause time)指标(target). 这是一个软性指标(soft goal), JVM 会尽量去达成这个目标.
-XX:InitiatingHeapOccupancyPercent
启动并发GC周期时的堆内存占用百分比. G1之类的垃圾收集器用它来触发并发GC周期,基于整个堆的使用率,而不只是某一代内存的使用比. 值为 0 则表示"一直执行GC循环". 默认值为 45
-XX:ParallelGCThreads
设置垃圾收集器在并行阶段使用的线程数,默认值随JVM运行的平台不同而不同
XX:G1ReservePercent
ZGC
版本
JDK 15
专注
延迟
ZGC:JDK11 中推出的一款低延迟垃圾回收器,适用于大内存低延迟服务的
内存管理和回收,SPECjbb 2015 基准测试,在 128G 的大堆下,最大停顿
时间才 1.68 ms,停顿时间远胜于 G1 和 CMS。
概念
多重映射
染色指针
子主题 1
读屏障
Shenandoah GC
版本
JDK 12
专注
延迟
跨代引用
年轻代引用老年代的这种跨代不需要单独处理。但是老年代引用年轻代的会影响young gc
这种跨代需要处理。
为了避免在回收年轻代的时候扫描整个老年代,需要记录老年代对年轻代的引用,young gc的时候只要扫描这个记录。CMS和G1都用到了Card Table
并发过程的对象变化
Full GC
CMS Full GC
Promotion Failure和Concurrent Mode Failure年轻代晋升的时候老年代没有足够的连续空间容纳,很有可能是内存碎片导致的;后者是在并发过程中jvm觉得在并发过程结束前堆就会满了,需要提前触发Full GC
G1 Full GC
1. Evacuation的时候没有足够的to-space来存放晋升的对象;2. 并发处理过程完成之前空间耗尽。这两个原因跟CMS类似。
TLAB
Thread Local Allocation Buffer 的简写,基于 CAS 的独享线程
(Mutator Threads)可以优先将对象分配在 Eden 中的一块内存,因为是
Java 线程独享的内存区没有锁竞争,所以分配速度更快,每个 TLAB 都是一
个线程独享的
性能调优
目的
1.将转移到老年代的对象数量降低到最小;
2.减少full GC的执行时间;
措施
减少使用全局变量和大对象;
调整新生代的大小到最合适;
设置老年代的大小为最合适;
选择合适的GC收集器
指标
Minor GC执行时间不到50ms;Minor GC执行不频繁(约10s一次);Full GC执行时间不到1s;Full GC执行不算频繁(不低于10m一次)
ClassLoader
Bootstrap ClassLoader
Extention ClassLoader
Appclass Loader
自定义ClassLoader
编写一个类继承自ClassLoader抽象类。
复写它的findClass()方法。
在findClass()方法中调用defineClass()。
子主题 4
类加载步骤
装载:(loading)找到class对应的字节码文件。
连接:(linking)将对应的字节码文件读入到JVM中。
初始化:(initializing)对class做相应的初始化动作
分配对象
Java 中对象地址操作主要使用 Unsafe 调用了 C 的 allocate 和 free 两个方法
空闲链表(free list):通过额外的存储记录空闲的地址,将随机 IO 变为顺序
IO,但带来了额外的空间消耗。
碰撞指针(bump pointer):通过一个指针作为分界点,需要分配内存时,仅
需把指针往空闲的一端移动与对象大小相等的距离,分配效率较高,但使用场
景有限
编译
基于栈的指令集架构(Instruction Set Architecture,ISA)
零地址指令,它们依赖操作数栈进行工作
字节码
Java 字节码的操作指令(OpCode)被固定为一个字节
Java 代码必须通过 Java 编译器将其转换成虚拟机所能识别的指令序列,也称为 Java 字节码
JVM 通过类加载器加载 class 文件里的字节码后,会通过解释器解释成汇编指令,最终再转译成 CPU 可以识别的机器指令
解释器是软件来实现的,主要是为了实现同一份 Java 字节码可以在不同的硬件平台上运行
将汇编指令转换成机器指令由硬件直接实现,这一步速度是很快的,当然 JVM 为了提高运行效率也可以将某些热点代码(一个方法内的代码)一次全部编译成机器指令后然后在执行,也就是和解释执行对应的即时编译(JIT), JVM 启动的时候可以通过 -Xint 和 -Xcomp 来控制执行模式。
收集对象
识别垃圾
引用计数法
引用计数法(Reference Counting):对每个对象的引用进行计数,每当有
一个地方引用它时计数器 +1、引用失效则 -1,引用的计数放到对象头中,大
于 0 的对象被认为是存活对象。虽然循环引用的问题可通过 Recycler 算法解
决,但是在多线程环境下,引用计数变更也要进行昂贵的同步操作,性能较
低,早期的编程语言会采用此算法
可达性分析
可达性分析,又称引用链法(Tracing GC):从 GC Root 开始进行对象搜索,
可以被搜索到的对象即为可达对象,此时还不足以判断对象是否存活 / 死亡,
需要经过多次标记才能更加准确地确定,整个连通图之外的对象便可以作为垃
圾被回收掉。目前 Java 中主流的虚拟机均采用此算法。
收集算法
Mark-Sweep(标记 - 清除)
Mark-Sweep(标记 - 清除):回收过程主要分为两个阶段,第一阶段为追踪
(Tracing)阶段,即从 GC Root 开始遍历对象图,并标记(Mark)所遇到的
每个对象,第二阶段为清除(Sweep)阶段,即回收器检查堆中每一个对象,
并将所有未被标记的对象进行回收,整个过程不会发生对象移动。整个算法在
不同的实现中会使用三色抽象(Tricolour Abstraction)、位图标记(BitMap)
等技术来提高算法的效率,存活对象较多时较高效
Mark-Compact(标记 - 整理)
Mark-Compact(标记 - 整理):这个算法的主要目的就是解决在非移动式回
收器中都会存在的碎片化问题,也分为两个阶段,第一阶段与 Mark-Sweep
类似,第二阶段则会对存活对象按照整理顺序(Compaction Order)进行整
理。主要实现有双指针(Two-Finger)回收算法、滑动回收(Lisp2)算法和
引线整理(Threaded Compaction)算法等
Copying(复制)
Copying(复制):将空间分为两个大小相同的 From 和 To 两个半区,同一时
间只会使用其中一个,每次进行回收时将一个半区的存活对象通过复制的方式
转移到另一个半区。有递归(Robert R. Fenichel 和 Jerome C. Yochelson
提出)和迭代(Cheney 提出)算法,以及解决了前两者递归栈、缓存行等问
题的近似优先搜索算法。复制算法可以通过碰撞指针的方式进行快速地分配内
存,但是也存在着空间利用率不高的缺点,另外就是存活对象比较大时复制的
成本比较高
分代收集器
ParNew:一款多线程的收集器,采用复制算法,主要工作在 Young 区,可以通过 -XX:ParallelGCThreads 参数来控制收集的线程数,整个过程都是STW 的,常与 CMS 组合使用
CMS:以获取最短回收停顿时间为目标,采用“标记 - 清除”算法,分 4 大步进行垃圾收集,其中初始标记和重新标记会 STW ,多数应用于互联网站或者 B/S 系统的服务器端上,JDK9 被标记弃用,JDK14 被删除,详情可见
分区收集器
G1:一种服务器端的垃圾收集器,应用在多处理器和大容量内存环境中,在实现高吞吐量的同时,尽可能地满足垃圾收集暂停时间的要求
物理上无分代概念,将堆内存分成多个相等的独立区域 region
逻辑上还有分代概念,是region的集合
年轻代转到老年代处理区别:对大对象的处理,会放到专门的大对象区——humongous;这样可节约老年代空间,避免GC开销;
算法
复制算法
Card Table
中文翻译为卡表,主要是用来标记卡页的状态,每个卡表项对应一个卡页。当卡页中一个对象引用有写操作时,写屏障将会标记对象所在的卡表状态改为 dirty,卡表的本质是用来解决跨代引用的问题。
Shenandoah(G1升级版):由 Red Hat 的 一 个 团 队 负 责 开 发, 与 G1 类 似, 基 于Region 设计的垃圾收集器,但不需要 Remember Set 或者 Card Table 来记录跨 Region 引用,停顿时间和堆的大小没有任何关系。停顿时间与 ZGC接近
ZGC
小型Region(Small Region) : 容量固定为2MB, 用于放置小于256KB的小对象。
中型Region(Medium Region) : 容量固定为32MB, 用于放置大于等于256KB但小于4MB的对象。
大型 Region(Large Region) : 容量不固定, 可以动态变化, 但必须为2MB的整数倍, 用于放置4MB或以上的大对象。 每个大型Region中只会存放一个大对象
侧重点
支持TB量级堆内存;
最大GC停顿时间不超过10ms;
没有分代机制-会导致整堆回收
指令
Invokevirtual:根据虚方法表调用虚方法。
invokespecial,:调用实例构造方法(方法),私有方法,父类继承方法。
invokeinteface:调用接口方法
invokestatic:调用静态方法
invokedynamic 动态语言支持 lamda
0 条评论
下一页