本次聊聊GC 书的搬运工
一、JVM
1.组成
a.JAVA栈
b.本地方法栈
c.JAVA堆
d.--方法区 即永久区1.7以后放于堆
2.释义
a.JAVA栈 :线程私有。由一系列帧组成,也称为帧栈。保存了局部变量表、操作数栈、常量池指针。
b.本地方法栈。Native外部调用。
c.JAVA堆:私有线程共享堆[全局共享]。应用系统的对象、数组对象都放于堆。(引用放栈,对象放堆)。分代GC来说,堆也分代:新生代,老年代。
d.--方法区 即永久区1.7以后放于堆
3.JVM 内存模型 -- JDK8
3.1 分类
线程私有:程序计数器 虚拟机栈 本地方法栈
线程共享:MeteaSpace java堆
3.2 线程私有
a.程序计数器:字节码指令。当前线程锁执行的字节码行号指示器;改变计数器的值来取下一条需要执行 的字节码指令
b.Java虚拟机栈:java方法。Java方法执行的内存模型,包含多个栈帧。
局部变量表:包含方法执行过程中的所有变量
操作数栈:入栈、出栈、复制、交换、产生消费变量
c.本地方法栈:native方法
3.3 所有线程共享
a.MeteaSpace:类加载信息。元空间使用本地内存。
b.堆:数组和类对象。对象实例的分配区域;GC管理的主要区域。
二、 堆 栈
1.JAVA内存模型中堆 和 栈的区别
a.联系:应用对象、数组时,栈里定义变量保存堆中目标的首地址
Person p=new Person(); p是引用,new Person是对象。或者说:引用放栈,对象放堆!
b.区别:
管理方式:栈自动释放,堆需要GC 【就这一个需要记住】
空间大小:栈比堆小
碎片相关:栈产生的碎片远小于堆
分配方式:栈支持静态和动态分配,而堆仅支持动态分配
效率:栈的效率比堆高
三、GC 算法
1.GC算法分类
a.引用计数法-java没有采用。问题:A引用B,B引用A。
b.标记清除法
c.标记压缩法。将所有存活对象压缩到内存的一端,之后清除边界所有空间。适用于存活对象较多的场合--老年代
d.复制算法。高效的回收方法。2块空间完全相同的内存,每次只用一块。问题:浪费空间
d.1 内存空间分2块,每次只用一块。
d.2 将正在使用内存A中存活对象复制到未被使用的内存块B,清除正在使用的内存块A中谁有对象
d.3 交换两个内存的角色,完成垃圾回收。
2.分代思想
a.复制算法--新生代明确使用--少量对象存活,浪费一半空间
b.标记清除,标记压缩 --老年代明确使用--大量对象存活
3.根节点
a.栈中引用的对象(局部变量正在被使用)
b.方法区静态成员或者常量引用的对象(全局对象,全局对象任何时候可悲任何人使用,不能随意被回收)
c.JNI方法栈中引用的对象(JAVA native 也是栈中的对象)
4. 分代收集算法 minor GC
4.1 minor GC
发生在年轻代中的垃圾收集动作,采用的是复制算法,年轻代几乎是所有对象出生的地方,即java申请的内存及存放都是在这个地方进行的,java的对象不需要长久存活,具有朝生夕灭的性质,当一个对象被判定为死亡的时候,gc就有责任回收这部分内存空间,新生代是垃圾回收的频繁区域
a.年轻代:尽可能迅速地收集掉那些生命周期短的对象
b.对象如何晋升到老年代:
经历一定minor 次数仍然存活的对象
survivor区中存不下的对象
新生成的大对象(-XX:+PretenuerSizeThreshold)
c.常用的调优参数
-XX:SurvivorRadio: Eden和Survivor的比值,默认是8:1
-XX:NewRadio 老年代和年轻代内存大小的比例
-XX:MaxTrnuringThreshold:对象从年轻代晋升到老年代经过GC次数的最大阈值
4.2 Full GC 、Major GC
老年代 存放生命周期较长的对象
标记-清理 标记-整理
触发Full GC的条件
-老年代空间不足
-minor gc 晋升到老年代的平均大小大于老年代剩余空间
-调用System.gc()
-永久代空间不足
-CMS GC时出现promotion failed,concurrent mode failure
-使用RMI来进行RPC或管理的JDK应用,没1H执行1次Full GC
5
5.1 CMS – 标记-清除算法 -老年代常用垃圾收集器
5.2 G1收集器 复制+标记-整理 算法
G1 收集器,既用于老年代,也用于新生代
使命:寄希望于替代CMS收集器
- 将整个Java堆内存分为多个大小相等的Region
- 年轻代和老年代不再物理隔离
在G1中,堆被划分成 许多个连续的区域(region)。采用G1算法进行回收,吸收了CMS收集器特点。
特点:支持很大的堆,高吞吐量
--支持多CPU和垃圾回收线程
--在主线程暂停的情况下,使用并行收集
--在主线程运行的情况下,使用并发收集
实时目标:可配置在N毫秒内最多只占用M毫秒的时间进行垃圾回收
Garbage First收集器
-将整个Java堆内存分成多个大小相等的Region
-年轻代和老年代不再物理隔离
6 最后
GC仍在飞速发展
JDK11 epsilon gc 和 Zgc 正在开发中,据说很牛逼
四 、类加载器的双亲委派机制
1.目的:避免多份同样字节码的加载
2.先自底向上检查类是否已经加载,之后自顶向下尝试加载类
Bootstrap ClassLoader -- C++编写,加载核心库java.* -- Load JRE\lib\rt.jar 或者 Xbootclasspath选项指定的jar包
Extension ClassLoader -- Java编写,加载扩展库javax.* -- Load JRE\lib\ext\*.jar 或者 -Djava.EXT.dirs指定目录下的jar包
App ClassLoader -- Java编写,加载程序所在目录 -- Load CLASSPATH 或者 Djava.class.path所指定的目录下的类和jar包
Custom ClassLoader -- Java编写,定制化加载 -- 通过java.lang.ClassLoader的子类自定义加载class
1.三大性能调优参数
-Xss:规定了每个线程虚拟机栈(堆栈)的大小。一般情况下256K足够了,此配置影响此进程中并发线程数的大小
-Xms:堆的初始值。初始的java堆的大小,即该线程刚创建出来,专属java堆大小,一旦对象容量超过了java堆的初始容量,java堆会自动扩容至 -Xmx大小。
-Xmx:堆能达到的最大值。
通常将 Xms 和 Xmx 设置成一样的,因为当heap不够用发生内存而扩容时,会发生内存抖动,影响程序运行时的稳定性。
4 引用类型
引用类型 被垃圾回收时间 用途 生存时间
强引用 从来不会 对象的一般状态 JVM停止运行时终止
软引用 在内存不足时 对象缓存 内存不足时终止
弱引用 在垃圾回收时 对象缓存 GC运行后终止
虚引用 unkown 标记、哨兵[gc] unkown
五 、线程 进程
1.run start
Run()方法还是在主线程里执行—只是Thread的一个普通方法的调用,还是在主线程里执行。
调用start会用非main的线程执行—创建一个新的子线程并启动。
start-start0-native方法-JVM_startThread-thread_entry-Thread.run
2.线程的状态 -- public enum State {}
New Runnable Blocked Waiting Timed_Waiting Terminated
a.新建new 创建后尚未启动的线程的状态。新创建了一个线程对象,但还未调用线程start方法。
b.运行Runnable:包含了操作系统中的Running和Ready
c.无限期等待Waiting:不会被分配CPU执行时间,需要显示被唤醒。thread.sleep();不给时间
d.限期等待 Timed Waiting:不会被分配CPU执行时间,在一定时间后会由系统自动唤醒。 thread.sleep(1000);
e.阻塞Blocked 等待获取排它锁 -- synchronize关键字修饰
阻塞状态 vs 等待状态区别:阻塞状态在等待获取一个排他锁,这个事件在另外一个线程在放弃排他锁的时间发生;而等待状态,是等待一段时间,或者有唤醒动作的时候发生,在程序等待进入同步区域的时候,线程将进入blocked状态,比如当某个线程进入synchronize关键字修饰的方法或者代码块的时候及获取锁执行的时候,其他想进入此方法或者代码块的线程,就只能等着,他们的状态就是blocked
f.结束状态 Terminated:已终止的线程状态,线程已经结束执行。
3.sleep await
Thread.sleep只会让出CPU,不会导致锁行为的改变。
如果当前线程是拥有锁的,Thread.sleep不会让线程释放锁,而只会主动让出CPU,CPU就可以执行其他任务了。
Object.wait不仅让出CPU,还会释放已经占有的同步资源锁。
4.Yield
当前线程愿意让出CPU使用权,但取决于线程调度器。
5.Synchronize
互斥性 可见性
实现:Java对象头 Monitor锁
四种状态: 无锁 偏向锁 轻量级锁 重量级锁
6.自旋锁
6.1
让一个没有获取的锁的线程在门外等待一段时间,但不放弃CPU的执行时间
让线程通过执行忙循环,类似while(true)一样,等待持有锁的线程释放锁,不像sleep一样,放弃CPU的执行时间。
如果锁占有的时间非常短,锁性能非常好;如果锁被占用的时间非常长,会带来更多性能开销。
如果自旋超过了限定的尝试次数,仍然没有成功获取到锁,就应该使用传统的方式去挂起线程。
6.2 自适应自旋锁
自选的次数不再固定
由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定
7.ReentrantLock
1.可以实现公平锁 synchronize是非公平锁
2.调用lock()之后,必须调用unlock()释放锁
六、JMM java memory
1.和JVM区别
JAVA内存模型-JMM 本身是一种抽象概念,并不真实存在,它描述的是一组规则或规范,
通过这组规范定义了程序中各个变量(实例字段 静态字段 和构成数组对象的元素)的访问方式
JMM 是一组规则,通过规则控制程序中各个变量在共享数据区域和私有数据区域的访问方式;围绕原子性,有序性,可见性展开
相似点:存在共享数据区域和私有数据区域
JMM中主内存属于共享数据区域,从某个程度讲,应该包括了堆 和 方法区;而工作内存私有数据区域,应该包括,程序计数器、虚拟机栈、本地方法栈。
2.线程 本地内存(共享变量的副本) 主内存(共享变量)
3.volatile
4.CAS
乐观锁 号称lock-free
3个操作数 内存位置 V 预期原值A 新值B
执行CAS操作时,将内存位置V与预期原值A进行比较,如果相匹配,那么处理器会自动将该位置的值更新为新值B;否则,处理器不作任何操作
七、 线程池
1.种类
a.newFixedThreadPool(int nThreads)
b.newCachedThreadPool()
处理大量短时间工作任务的线程池
--试图缓存线程并重用,当无缓存线程可用时,就会创建新的工作线程
--如果线程闲置的时间超过阈值,则会被终止并移除缓存
--心痛长时间闲置的时候,不会消耗什么资源
c.newSingleThreadExecutor()
创建唯一的工作线程来执行任务,如果线程异常结束,会有并一个线程取代它
d.newSingleThreadScheduledExecutor()
e.newWorkStealingPool 内部会构建ForkJoinPool,利用working-stealing算法,并行地处理任务,不保证处理顺序
working-stealing算法:more个线程从其他队列里窃取任务来执行
2.handler:线程池的饱和策略
a.AbortPolicy 直接抛出异常,这是默认策略
b.CallerRunsPolicy 用调用者所在的线程来执行任务
c.DiscardOldestPolicy 丢弃队列中靠最前的任务,并执行当前任务
d.DiscardPolicy 直接丢弃任务
e.实现RejectedExecutionHandler 接口的自定义handler
3.线程池的状态
a.running
b.shutdown
c.stop
d.tidying
e.terminated