文章目录
前言
很多小伙伴可能没用过甚至没见过这两个方法,但作为一名 java 开发仔,你肯定见过 System.gc();
带着这个想法,我们接着往下看
一、Runtime.getRuntime().gc()
1.与System.gc() 对比
我们点开 System.gc() 的源码,发现里面其实就是调用 Runtime.getRuntime().gc() ,所以他们两者是等效的。只是我们平时习惯使用 jdk 给我们提供的 System 类
2.官方说明
Runtime.getRuntime().gc() 方法,官方给出的注释是这样的:
翻译过来就是:
在 Java 虚拟机中运行垃圾收集器。
调用该gc方法表明 Java 虚拟机花费精力回收未使用的对象,以使它们当前占用的内存可供 Java 虚拟机重用。当控制从方法调用返回时,Java 虚拟机已尽最大努力从所有未使用的对象中回收空间。无法保证此工作将回收任何特定数量的未使用对象,回收任何特定数量的空间,或在任何特定时间(如果有的话)在方法返回之前或永远完成。也不能保证这种努力将确定任何特定数量的对象的可达性变化,或者任何特定数量的Reference 对象将被清除和排队。
调用System.gc()实际上等同于调用:Runtime.getRuntime().gc()
二、Runtime.getRuntime().runFinalization()
1.与System.runFinalization() 对比
同样,我们点开 System.runFinalization() 的源码,发现里面其实就是调用Runtime.getRuntime().runFinalization() ,所以他们两者是等效的。
2.官方说明
Runtime.getRuntime().runFinalization() 方法,官方给出的注释是这样的:
翻译过来就是:
运行任何等待终结的对象的终结方法。调用此方法表明 Java 虚拟机将努力运行finalize已发现已丢弃但其finalize 方法尚未运行的对象的方法。当控制从方法调用返回时,Java 虚拟机已尽最大努力完成所有未完成的终结。
调用System.runFinalization()实际上等同于调用: Runtime.getRuntime().runFinalization()
Runtime.getRuntime().runFinalization() 是和 object 的 finalize() 搭配使用的
但是,从 Java9 开始,object 的 finalize() 方法已被标注为 @Deprecated 过期了
所以,已经不推荐再使用 Runtime.getRuntime().runFinalization() 进行gc 了
注意:在最新的 java18 中,finalize() 和 runFinalization() 已经被标记为弃用了,,不再是简单的过期了,未来可能会被删除
3.为什么不推荐使用finalize()
- 调用时机不确定
虽然finalize()方法早晚会被调用到,但这种调用时机的不可控性可能会导致资源迟迟不被释放而出现系统级异常。因为计算机的资源有是限的,当明确要释放某些资源时(比如上面例子中reader所掌控的一系列资源),应该使用其它的办法让这些资源立即释放 - 影响代码的可移植性
因为每种JVM内置的垃圾回收算法都是不同的,所以可能在你的JVM里,你辛辛苦苦编写的使用finalize方法的案例运行的很好,但移植到不同的JVM中时,很有可能会崩溃的一塌糊涂! - 成本较高
如果某个类重载了finalize方法且在方法内部实现了一些逻辑,那么JVM在构造或销毁这个类的对象之前,会做很多额外的工作。很明显,如果一个类没有重载finalize方法,那么销毁时只要将堆中的内存处理一下就可以了,而如果重载了finalize方法的话,就要执行finalize方法,万一执行过程中再出现点异常或错误,那消耗的成本就更高了。 - 异常丢失
万一fianlize方法中抛出了异常,那么finalize会终止运行,而抛出的这个异常也会被舍弃,最终会让对象实例处于一种半销毁半存活的僵尸状态,导致意想不到的后果!
该片段引用自:
https://www.bilibili.com/read/cv4055723
三、测试GC
可能我们都是八股文的职业选手,精通各种技术框架的底层原理,熟练背诵各种 sql 优化和 gc 原理,当然,这是在面试的情况下,实际开发中,我们大部分人都是不曾接触这些的,也就面试拿来装一下。
但是,为了让我们更加直观的感受到 GC 带来的效果,也算是我们一次简单的 GC 入门案例,我用实际代码带大家接触下神秘的 GC。
不知道大家有没有了解过mbean,java 代码在运行时会将各种信息作为一个个的 mbean 放进 mbeanServer 管理 ,例如 OS 的信息,jvm 的内存使用情况,并且它可以动态被修改。
1.查看当前操作系统(OS)占用内存
在 mbeanServer 中,OS 的信息唯一对应ObjectName:java.lang:type=OperatingSystem
MBeanServer mBeanServer = ManagementFactory.getPlatformMBeanServer();
ObjectName objectName = new ObjectName("java.lang:type=OperatingSystem");
// OS的最大内存
Long totalPhysicalMemorySize = (Long)mBeanServer.getAttribute(objectName, "TotalPhysicalMemorySize");
// OS的空闲内存
Long freePhysicalMemorySize = (Long)mBeanServer.getAttribute(objectName, "FreePhysicalMemorySize");
Long totalRAM = totalPhysicalMemorySize / 1024 / 1024;
Long freeRAM = freePhysicalMemorySize / 1024 / 1024;
System.out.println("操作系统总内存:" + totalRAM + " MB");
System.out.println("操作系统的内存使用了:" + (totalRAM - freeRAM) + " MB");
2.查看jvm内存堆中年轻代和老年代
在 mbeanServer 中约定内存池的 ObjectName 以 java.lang:type=MemoryPool 开头
可以用 * 来匹配所有的 jvm内存池,包括我们熟悉的 老年代、年轻代(Eden区,survivor区)、元空间、代码缓存等。
获取的 CompositeDataSupport 是 MXBean 的类型,里面包含了属性 max、used、init、commited
MBeanServer mBeanServer = ManagementFactory.getPlatformMBeanServer();
ObjectName objectName = new ObjectName("java.lang:type=MemoryPool,*");
Set<ObjectInstance> memoryPools = mBeanServer.queryMBeans(objectName, null);
for (ObjectInstance memoryPool : memoryPools) {
ObjectName objectName = memoryPool.getObjectName();
String name = (String)mBeanServer.getAttribute(objectName, "Name");
CompositeDataSupport cd = (CompositeDataSupport)mBeanServer.getAttribute(objectName, "Usage");
}
3.完整例子
代码里面说明很详细了,直接看吧:
直接运行 main 方法,
代码逻辑:
- 先查看 main 方法运行中基础使用的 jvm 内存
- 声明一个List,放入数据,也就是使用了一些 jvm 的内存(先放到年轻代,内存不足时转移到老年代)
- 再查看此时占用了的jvm 内存
- 把 List 的引用去掉,再 gc
- 再查看此时占用了的jvm 内存(这时候已经把 LIst 数据占用的内存垃圾回收了)
public class TestGc {
static List<Integer> list;
static MBeanServer mBeanServer;
static ObjectName os_objectName;
static ObjectName mp_objectName;
static{
// 获取 mbeanServer
mBeanServer = ManagementFactory.getPlatformMBeanServer();
try {
// jvm运行时会将操作系统内存等信息放进mBeanServer,ObjectName唯一对应:java.lang:type=OperatingSystem
os_objectName = new ObjectName("java.lang:type=OperatingSystem");
// 在java8的mBeanServer中约定内存池的MBean的ObjectName以 java.lang:type=MemoryPool 开头
mp_objectName = new ObjectName("java.lang:type=MemoryPool,*");
} catch (MalformedObjectNameException e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws Exception {
osRAM();
System.out.println("main方法启动后,jvm内存堆中年轻代和老年代的使用情况:");
jvmRAM();
list = new ArrayList<>();
for (int i = 0; i < 10000000; i++) {
list.add(i);
}
System.out.println("new出List并放进数据后,jvm内存堆中年轻代和老年代的使用情况:");
jvmRAM();
list = null; // 这里如果不取消引用,即使调用 gc 也不会回收 list占用的内存
System.gc();
System.out.println("取消List的引用,执行gc后,jvm内存堆中年轻代和老年代的使用情况:");
jvmRAM();
}
public static void osRAM() throws Exception {
// OS的最大内存
Long totalPhysicalMemorySize = (Long)mBeanServer.getAttribute(os_objectName, "TotalPhysicalMemorySize");
// OS的空闲内存
Long freePhysicalMemorySize = (Long)mBeanServer.getAttribute(os_objectName, "FreePhysicalMemorySize");
Long totalRAM = totalPhysicalMemorySize / 1024 / 1024;
Long freeRAM = freePhysicalMemorySize / 1024 / 1024;
System.out.println("操作系统总内存:" + totalRAM + " MB");
System.out.println("操作系统的内存使用了:" + (totalRAM - freeRAM) + " MB");
System.out.println("----------------------------------------------------");
}
public static void jvmRAM() throws Exception {
// jvm中堆的年轻代和老年代
long eden = 0;
long survivor = 0;
long old = 0;
Set<ObjectInstance> memoryPools = mBeanServer.queryMBeans(mp_objectName, null);
for (ObjectInstance memoryPool : memoryPools) {
ObjectName objectName = memoryPool.getObjectName();
String name = (String)mBeanServer.getAttribute(objectName, "Name");
CompositeDataSupport cd = (CompositeDataSupport)mBeanServer.getAttribute(objectName, "Usage");
if(cd != null){
if("PS Eden Space".equals(name)){
eden = (Long) cd.get("used");
}else if("PS Survivor Space".equals(name)){
survivor = (Long) cd.get("used");
}else if("PS Old Gen".equals(name)){
old = (Long) cd.get("used");
}
}
}
System.out.println("年轻代:" + (eden + survivor) / 1024 / 1024 + " MB [eden " + eden / 1024 / 1024 + " MB + survivor " + survivor / 1024 / 1024 + " MB]");
System.out.println("老年代:" + old / 1024 / 1024 + " MB");
System.out.println("----------------------------------------------------");
}
}
总结
欢迎指出我的错误!