参考链接:
https://www.bilibili.com/video/BV1AE411E7uj?p=1
https://www.bilibili.com/video/BV1yE411Z7AP?spm_id_from=333.999.0.0
https://www.cnblogs.com/chenpt/p/9803298.html
https://www.iteye.com/problems/71569
https://codingdict.com/questions/113892
https://www.cnblogs.com/dmzna/archive/2020/05/18/12913458.html
https://www.cnblogs.com/shouyaya/p/13524476.html
https://blog.csdn.net/mccand1234/article/details/52078645
https://blog.csdn.net/qq_41701956/article/details/81664921
https://blog.csdn.net/yubujian_l/article/details/80804708
补:运行时数据区
补:程序计数器
为了保证程序(在操作系统中理解为进程)能够连续地执行下去,处理器必须具有某些手段来确定下一条指令的地址。而程序计数器正是起到这种作用,所以通常又称为指令计数器。在程序开始执行前,必须将它的起始地址,即程序的第一条指令所在的内存单元地址送入程序计数器,因此程序计数器的内容即是从内存提取的一条指令的地址。当执行指令时,处理器将自动修改PC的内容,即每执行一条指令PC增加一个量,这个量等于指令所含的字节数,以便使其保持的总是将要执行的下一条指令的地址。由于大多数指令都是按顺序来执行的,所以修改的过程通常只是简单的对PC加1。
但是,当遇到转移指令如JMP(跳转、外语全称:JUMP)指令时,后继指令的地址(即PC的内容)必须从指令寄存器中的地址字段取得。在这种情况下,下一条从内存取出的指令将由转移指令来规定,而不像通常一样按顺序来取得。因此程序计数器的结构应当是具有寄存信息和计数两种功能的结构。
这一部分不会发生内存泄漏,程序计数器指令可以标记if for 多线程等“运行到哪里” 通常又称之为‘指令计数器’
补:JVM堆空间(gc堆)
- 存放: 对象实例 and 数组
- 每次new操作都是在堆中开辟空间,不一定是连续的空间,
- 堆空间的分配常用分代策略(新生代 老年代 永久代(现:元空间))
补:垃圾回收的流程
以分代机制+回收算法+G1垃圾收集器为例:
- G1垃圾收集器决定了:垃圾回收的cpu资源调度方式是并发兼顾延迟和吞吐量,分堆;能结合多种算法策略,是一个监视者的角色。
- 回收算法决定了:xx对象在本轮回收中是否被回收,如强引用则不回收,其他引用回收策略不同
- 分代机制决定了:什么时候调用垃圾回收,如新生代区的Eden满后调用Minor GC
- 过程:不停的new,因为引用方式不同and对象大小不同,会导致不同的对象分在了元空间(相当于JDK8之前的永久代)、新生代(Eden满后Minor GC)、老年代(System.gc()或老年代区满后Full GC)
————因为垃圾回收器的区别,如G1会动态监控并标记GC Roots,调用回收算法
————进行可达性分析,决定对象的去留
————调用Full GC来进行垃圾回收,释放内存
JVM如何判定一个对象是否应该被回收?
1.引用计数器算法×
方法内容:
对象被引用一次,计数器+1;失效一次,计数器-1,计数器归零后失效
不能解决互相引用情况下的垃圾回收问题,例如:LinkedList源码中clear方法和unLinked方法需要多步操作来消除循环引用
2.可达性分析算法√
在Java C#中的主流算法
算法思想:
通过一些列成为“GC Roots”的根对象作为起始点,如果一条引用链的起始点不是GC Roots,那么就会被垃圾回收(不是强引用就会被垃圾回收,只是时间问题)
GC Roots包括:
- 虚拟机栈中引用的对象
- 方法区静态属性引用的对象
- 方法区常量引用的对象
- 本地方法引用的对象(底层是c或操作系统语言)
GC Roots
这其实是一个相对的概念,在调用Minor GC的时候,老年代就是根对象
像上面的那4种是“即使Full GC”也不会回收的,就是绝对的根对象
补充:引用的四种方式:
参考链接
强引用
Object obj = new Object();强引用在,垃圾收集器就一直不会被回收该对象,JVM宁愿抛出OutOfMemory错误也不会回收这种对象。如果想中断强引用和某个对象之间的关联,可以显示地将引用赋值为null,这样一来的话,JVM在合适的时间就会回收该对象。
软引用SoftReference
SoftReference aSoftRef=new SoftReference(obj);如果一个对象具有软引用,内存空间足够,垃圾回收器就不会回收它;软引用可用来实现内存敏感的高速缓存,一旦垃圾线程回收该Java对象之 后,get()方法将返回null
弱引用WeakReference
创建方法和WeakReference 相同,但比SoftReference更弱,仅仅能生存到下次垃圾回收之前,无论内存是否足够 都会回收
People people=new People("zjh",21);
WeakReference<People>reference=new WeakReference<People>(people);
System.out.println(reference.get()); //zjh 21
System.gc();
System.out.println(reference.get()); //null
虚引用PhantomReference
最弱的引用关系,虚引用主要用来跟踪对象被垃圾回收的活动,在回收之前通知系统,但不影响对象的生命周期。
JVM垃圾回收算法有哪些?
- 以下三种方法,在实际的虚拟机中是组合使用,去其糟粕,取其精华
- 垃圾回收算法 和 回收机制不同
标记清除算法
标记不连续,可用内存被分割,在申请连续大空间时可能没有可用的内存,资源浪费大
标记整理算法
标记的同时把可用对象整理在一起
复制算法
局限性很大,内存利用率很差,先复制可用存活对象复制到一块区域
右边这块的内存都是不能使用的
参考链接
JVM垃圾回收机制
- 回收机制≠回收算法
- 常用的是分代回收策略
- 一般是只要空间不足就触发垃圾回收机制
代的划分
参考链接1
参考链接2
商用Java内存分配和回收的机制概括的说,就是:分代分配,分代回收。
- 对象将根据存活的时间被分为:
年轻代(Young Generation):回收频率快
年老代(Old Generation):回收频率慢
永久代(Permanent Generation,也就是方法区,JDK1.8之后删除了永久代) - 不同代有不同的算法,不同的处理机制
- 内存担保机制:新生代到老年代
新生代的三个分区8:1:1
Eden伊甸园:From幸存区:To幸存区=8:1:1
From是上一次幸存的对象
To是本次幸存的对象
- new一个对象,默认采用伊甸园的空间,大对象则直接到老年代
- 一直new直到伊甸园空间放不够了就触发新生代垃圾回收Minor GC
- 直到连老年代都快满了,触发老年代垃圾回收Full GC
Minor GC
- 触发了Minor GC之后就调用垃圾回收算法(标记清除、标记整理、复制算法)
- 例如调用了标记算法,就把标记存活的对象复制到幸存区To中,寿命计数器+1;而被标记回收的伊甸园对象则被回收
- 伊甸园再次空闲,直到下次满的时候触发第二次Minor GC
- 寿命计数器>15(4bit)时(可能因为新生代空间严重不足而提前晋升),对象从新生代传入老年代,此后不会被Minor GC回收
- 会触发STW,但耗时短
Full GC
- 新生代老年代同时进行垃圾回收
- 会触发STW,且耗时长
- 调用System.gc()
STW
- stop thr world 当触发STW时,其他的所有线程都停止,直到垃圾回收完成
- 因此回收效率高,但会有停顿时间,适合在Client端使用,不适合在Sever端使用
- Minor GC 和 Full GC都会触发STW
垃圾收集器
JVM中的垃圾收集器主要包括7种,即Serial,Serial Old,ParNew,Parallel Scavenge,Parallel Old以及CMS,G1收集器。
G1——Garbage one
- 取代CMS垃圾回收器
- 出自JDK7,JDK9之后成为默认的垃圾回收器
- 并发
- 同时注重吞吐量和响应时间
- 适合于超大内存场景,会将堆均匀分区以便并发,每个区都有独立的年轻代、老年代堆区域
- CMS和G1都是Full GC(老年代垃圾回收机制)
串行
- 单线程
- 堆内存小,适合个人电脑
吞吐量优先
- 多线程
- 堆内存大,需要多核CPU支持(真并发),适合服务器
- 一次大量回收,单位时间STW时间尽可能短,总时间更短
响应时间有限
- 多线程
- 堆内存大,需要多核CPU支持(真并发),适合服务器
- 单次少量回收,单次STW时间尽可能短,每次影响更小