《深入java虚拟机》读书笔记之垃圾收集器与内存分配策略 (2)

废弃常量是指在常量池中存在一个值,假设为一个字面量,但是在当前系统中没有任何的一个对象引用了该字面量。那么久认为该字面量是废弃的,在下一次垃圾回收的时候将其进行回收。同理常量池中的其他类、接口、方法、字段等的符号引用的回收也是类似。

无用的类

要判定一个类是否可以被回收需要满足以下几个条件:

该类所有的实例都已经被回收

加载该类的ClassLoader已经被回收

该类对应的字节码对象Class没有在任何地方被引用,无法再任何地方通过反射访问到该类的方法

当一个类满足以上条件后就允许被回收,但不是一定会被回收。是否对类进行回收再HotSpot虚拟机中提供了-Xnoclassgc参数进行控制。也可以使用-verbose:class,-XX:+TraceClassLoading,-XX:+TraceClassUnloading参数查看类加载和卸载信息。

在使用反射、动态代理、动态生成jsp和OSGI等频繁自定义ClassLoader的场景都需要虚拟机具备卸载类的功能,保证永久代不会溢出。

需要注意的是在JDK1.7时HotSpot就已经将运行时常量池迁移到堆中,在JDK1.8中更是直接移除了方法区,所以上面的介绍需要对应到具体的版本,并不是指着一定是在方法区完成。虽然区域发生变化但是回收的原则基本还是这样。

元空间回收

JDK1.8开始把类的元数据放到本地堆内存(native heap)中,如果Metaspace的空间占用达到了设定的最大值,那么就会触发GC来收集死亡对象和进行类卸载,这一块的回收要求较高,上文中有简单说过。

有关元空间的JVM参数:

-XX:MetaspaceSize :是分配给类元数据空间(字节)的初始大小。该值设置的过大会延长垃圾回收时间。垃圾回收过后,引起下一次垃圾回收的类元数据空间的大小可能会变大。

-XX:MaxMetaspaceSize :是分配给类元数据空间的最大值,超过此值就会触发Full GC,此值默认没有限制,但应取决于系统内存的大小。JVM会动态地改变此值。

-XX:MinMetaspaceFreeRatio/-XX:MaxMetaspaceFreeRatio :表示一次GC以后,为了避免增加元数据空间的大小,空闲的类元数据的容量的最小/最大比例,不够就会触发垃圾回收。

垃圾收集算法 标记-清除算法

标记-清除算法的基本内容就同其名字一样,存在着标记和清除两个阶段:首先查找与GC Roots无任何关联的对象,标记处所需回收的对象(如何标记在内存回收中已经介绍了,通过判断是否有必要或已经执行了finalize()方法),在标记完成之后再统一清除。

标记过程:虚拟机从作为GC Roots的根节点出发进行搜索,对可被访问到的对象做一个标记,其他未被标记的对象就是需要被回收的。效率低是因为目前来说项目中的对象极多,单单是进行遍历就需要耗费较长的时间。

好处:实现简单,标记-清除算法流程十分简单,实现也没有很复杂的地方。

缺点:
1.效率较低:因为标记和清除的过程效率都不高
2.浪费内存空间:在清除标记的对象后造成了内存中大量不连续的空间,一旦有大的对象进入可能会因为没有合适的存放的地方而造成再一次的GC。

复制算法(多用于新生代)

复制算法的基本内容是要求虚拟机并不将所有的内存空间用来存放对象。复制算法将内存分为两块,每一次都只是使用其中的一块,当触发GC时,将存放对象的那一块内存上还存活的对象复制到另一块上去,然后将之前的内存块全部清除。

优点:实现简单,而且因为在将存活对象转移时顺序内存存放不用考虑内存碎片的问题,效率较高。

缺点:
1.始终有一部分内存没有得到使用,造成空间浪费。要保证存活的对象能够完全复制,那么就要求两块内存大小一致(50%),因为可能存在没有任何对象死亡的极端情况,但是这样将会极其浪费,而如果不这样分配,就必须引入其他机制保证对象能够被完整的复制。

标记-整理算法

标记整理算法的标记阶段同标记-清除算法一致,不过标记后并不立即清除,而是将存活(不会被GC)的对象移向内存的一端,将存活的对象全部移动后将边界外的清除掉。

优点:解决了内存碎片的问题

缺点:标记阶段效率本身较低,还多加了一个整理阶段,还是在于总体效率较低

分代收集算法

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zygpfp.html