Skip to content

Latest commit

 

History

History
159 lines (113 loc) · 8.8 KB

jvmsee.md

File metadata and controls

159 lines (113 loc) · 8.8 KB

进阶篇开 JVM 内存分配与回收

对象优先在Eden区分配

大多数的情况下,对象是在新生代中Eden区分配 当Eden区没有足够的空间进行分配 虚拟机将会发起一次MinorGC

Minor gc = young gc = 指发生在新生代的垃圾收集动作minor gc非常频繁 回收速度一般也比较快

major gc = full gc 一般会回收老年代 年轻代 方法区的垃圾 full gc

full gc 速度一般会比young gc 的速度慢10倍以上

详情请看 GCtest 记得添加‐XX:+PrintGCDetails 日志参数

大对象直接进入老年代

大对象就是需要大量连续内存空间的对象(比如字符串,数组) jvm参数 -XX:PretenureSizeThreshold可以直接设置

大对象的大小 如果对象超过设置大小则会直接进入老年代不然就会进入年轻代

注意:这个参数只在serial 和 ParNew 俩个收集器下有效

 比如设置JVM参数:-XX:PretenureSizeThreshold=1000000 -
 XX:+UseSerialGC ,再执行下上面的第一个程序会发现大对象直接进了老年代
 为什么要这样呢?
 为了避免为大对象分配内存时的复制操作而降低效率(会进行对象复制在s0和s1区域)

长期存活的对象会进入老年代

虚拟机既然采用了分代收集的思想来管理内存 那么内存回收时就必须要识别那些对象需要放在新生代那些对象 要放在老年代 为了做到这一点 虚拟机给每一个对象一个对象年龄计数器(Age)

如果对象在Eden 出生并且经过了第一个young gc 后仍然能够存活 并且能被Survivor 容纳的话 将会被移动到 Survivor 空间中 并将对象的年龄设定为1 对象在Survivor 中每熬过一个young gc 年龄就会增加一岁当他的年龄增加 到一定程度的时候(默认15次) 就会被晋升到老年代 对象晋升到老年代的阀值可以通过参数-XX:MaxTenuringThreshold来设置

对象动态年龄判断

当前放对象的Survivor区域里 一批对象的总大小大于这块Survivor 区域的大小的50% 那么此时大于等于这批年龄 最大值的对象 就可以直接进入老年代

举例 Survivor 区域里现在有一批对象 年龄1+2+......N 的多个年龄对象的综合超过了Survivor 区域的50% 此时就会把年龄N 已上的对象都会放入到老年代

注意:这个规则其实是希望那些长期存活的对象尽早进入到老年代 对象动态年龄判断机制一般是在young gc之后出发的

young gc 后存活的对象survivor 区域放不下

这种情况下会把存活的对象部分挪到老年代 部分还可能放在Survivor区域

老年代空间分配担保机制

图片

Eden与Survivor区默认8:1:1

大量的对象被分配在eden区,eden区满了后会触发minor gc,可能会有99%
以上的对象成为垃圾被回收掉,剩余存活的对象会被挪到为空的那块survivor
区,下一次eden区满了后又会触发minor gc,把eden区和survivor去垃圾对象
回收,把剩余存活的对象一次性挪动到另外一块为空的survivor区,因为新生代
的对象都是朝生夕死的,存活时间很短,所以JVM默认的8:1:1的比例是很合适
的,让eden区尽量的大,survivor区够用即可
JVM默认有这个参数-XX:+UseAdaptiveSizePolicy,会导致这个比例自动变
化,如果不想这个比例有变化可以设置参数-XX:-UseAdaptiveSizePolicy

如何判断对象是否可以被回收

引用计数器

  每有一个地方引用他就将计数器加1 这个方法实现简单高效 但是他很难解决对象之间的互相循环引用的问题
  所谓对象之间的相互引用问题,如下面代码所示:除了对象objA 和 objB 相互引用着
  对方之外,这两个对象之间再无任何引用。但是他们因为互相引用对方,导致它
  们的引用计数器都不为0,于是引用计数算法无法通知 GC 回收器回收他们

可达性算法分析

GC root 作为起点 从这些节点向下搜索 找到对象的都标记为废垃圾对象 其余为标记的都是垃圾对象 GC Roots根节点:线程栈的本地变量 类似局部变量表里面的 等等、静态变量、本地方法栈的变量等等

常见的引用类型

java的引用类型一般分为四种:强引用、软引用、弱引用、虚引用

强引用:普通的变量引用
1 public static User user = new User();
软引用:将对象用SoftReference软引用类型的对象包裹,正常情况不会被回
收,但是GC做完后发现释放不出空间存放新的对象,则会把这些软引用的对象
回收掉。软引用可用来实现内存敏感的高速缓存。
1 public static SoftReference<User> user = new SoftReference<User>(new
User());
软引用在实际中有重要的应用,例如浏览器的后退按钮。按后退时,这个后退时
显示的网页内容是重新进行请求还是从缓存中取出呢?这就要看具体的实现策略
了。
(1)如果一个网页在浏览结束时就进行内容的回收,则按后退查看前面浏览过
的页面时,需要重新构建
(2)如果将浏览过的网页存储到内存中会造成内存的大量浪费,甚至会造成内
存溢出
弱引用:将对象用WeakReference软引用类型的对象包裹,弱引用跟没引用差
不多,GC会直接回收掉,很少用
1 public static WeakReference<User> user = new WeakReference<User>(new
User());
虚引用:虚引用也称为幽灵引用或者幻影引用,它是最弱的一种引用关系,几乎
不用
2.4 finalize()方法最终判定对象是否存活
即使在可达性分析算法中不可达的对象,也并非是“非死不可”的,这时候它们
暂时处于“缓刑”阶段,要真正宣告一个对象死亡,至少要经历再次标记过程。
标记的前提是对象在进行可达性分析后发现没有与GC Roots相连接的引用链。
1. 第一次标记并进行一次筛选。
筛选的条件是此对象是否有必要执行finalize()方法。
当对象没有覆盖finalize方法,对象将直接被回收。
2. 第二次标记
如果这个对象覆盖了finalize方法,finalize方法是对象脱逃死亡命运的最后一次
机会,如果对象要在finalize()中成功拯救自己,只要重新与引用链上的任何的一
个对象建立关联即可,譬如把自己赋值给某个类变量或对象的成员变量,那在第
二次标记时它将移除出“即将回收”的集合。如果对象这时候还没逃脱,那基本
上它就真的被回收了

可以自救

如何判断一个类是无用的类

1.该类所有的实例都已经被回收,也就是 Java 堆中不存在该类的任何
实例
2.加载该类的 ClassLoader 已经被回收
3.该类对应的 java.lang.Class 对象没有在任何地方被引用,无法在任何
地方通过反射访问该类的方法

垃圾收集算法

标记清除算法

算法分为标记和清除俩个阶段 首先标记出所有的需要回收的对象 在标记完成后统一回收所有被标记的对象

问题:1 效率问题 2 空间问题 (会产生大量不连续的碎片)

复制算法

为了解决效率问题复制收集算法出现 他可以将内存分为大小相同的俩块 每次使用其中的一块 当着一块内存使用完成 后就将还存活的对象复制到另一块去 然后再把使用的空间一次性清理掉这样就使每次的内存回收都是对内存区间的一半 来进行回收

标记整理算法

根据老年代的特点特出的一种标记算法,标记过程仍然与“标记-清除”算法一 样,但后续步骤不是直接对可回收对象回收,而是让所有存活的对象向一段移 动,然后直接清理掉端边界以外的内存

分代收集算法

当前虚拟机的垃圾收集都采用分代收集算法,这种算法没有什么新的思想,只是
根据对象存活周期的不同将内存分为几块。一般将java堆分为新生代和老年代,
这样我们就可以根据各个年代的特点选择合适的垃圾收集算法。
比如在新生代中,每次收集都会有大量对象(近99%)死去,所以可以选择复制算
法,只需要付出少量对象的复制成本就可以完成每次垃圾收集。而老年代的对象
存活几率是比较高的,而且没有额外的空间对它进行分配担保,所以我们必须选
择“标记-清除”或“标记-整理”算法进行垃圾收集。注意,“标记-清
除”或“标记-整理”算法会比复制算法慢10倍以上