00:00
好,那接下来的话呢,我们看一看这个Java当中内存泄露的这八种情况啊,那么这八种情况呢,呃,也有助于呢,大家在实际面试当中,这个跟面试官在聊到的时候呢,说你能不能写一些关于内存泄露的这个代码啊,那大家呢,可以从这些角度呢去讲啊,这个呢,里边很多我也都写代码了,呃另外呢,如果谈这个案例的话呢,这也可以呢,有两个案例分析啊,包括我们如何呢去解决这个内存泄露的问题啊,就是我们的目的呢,不是为了写出内存泄露,而是呢,通过出内存泄露呢,面试官想知道呢,你确实呢是有经验的,是清楚什么是内存泄露的,对吧?然后的话呢,针对于内存泄露问题,我们最终目的呢,是要解决内存泄露的问题是吧,你有没有相关的一些方法?啊,这是我们说,呃,一个理解知识的一个深度,先要呢知道什么是内存泄露,然后呢,你也得能够写代码,然后还能够去解决这个代码的内存泄露问题,是吧?啊当然了,我们这里边举的例子呢,呃,其实一方面呢,是可以用上面的这个。工具呢,去分析了,然后去改代码,另外一个,另外一个呢,就直接我们看代码的话呢,其实也能够呃,有一些经验啊,直接呢就可以去调一调这个相关的这个泄露问题啊好,那这呢,我们按照这个顺序呢,一个一个去说,首先第一个啊,关于叫静态集合类。
01:14
啊,这个静态集合类,嗯,什么意思呢?就是我们在呃,就是这里边其实主要提到有些点呢,是可以合并的哈,只不过我们这里举的都是一些非常时代的例子啊,这的叫程,叫程这个静态集合类了啊这个我们现在呢,定义的这个呃集合的对象,它是一个静态的啊,这就成为呢叫静态集合类,比如说呢,我这里边儿定义的是一个list啊,当然你也可以用这个map呀,Set呀等等都可以啊。那定义完以后呢,我们在相关的方法当中呢,去向当前的这个静态的集合类当中去添加相关的对象,那这个对象本身呢,大家会发现我们是在方法内写的,这叫局部变量,对吧。哎,局部变量,那这个局部变量呢,正常来讲,它的生命周期其实都比较短啊,我们本身呢,也不希望它太长,要长的话你肯定就拿出来了,对吧?那由于呢是在里边创建的,但是呢,我们这个方法执行完以后呢,这个对象呢,其实并不能被回收啊,因为呢,我们把它呢添加到这个呃,List这个静态的结构当中了,而静态这个结构呢,它的生命周期呢,与GM的程序的这个生命周期是一致的,所以呢,始终导致我们这个obj的对象呢,不能够被释放啊,那这就构成了一个啊内存的泄露。
02:23
啊,这呢,应该说是一个最简单的一个例子了哈,然后第二个呢,这个我们叫单利模式啊,咱们讲上篇的时候呢,也举过这个单利模式的例子啊,它跟咱们这个静态集合类的这个,呃,这个理论上其实是类似的,因为单利模式呢,我们创建当前这个类它中的这个实例的话呢,其实也是一个静态的,对吧,那既然它是静态的,如果我们保有对其他的这个外部对象的一个引用的话。啊,导致这个外部对象呢,本身我们不想再被使用了,但是由于我们这个单力的这个实力的话呢,始终都存在。啊,始终都存在是吧,前提呢是你掉这个单例了啊,不管你是懒汉式也好,饿汉式也好,你这个单利你掉了啊,哎,相当于我们内存中肯定有这个单例了,那这个时候呢,由于你对外部对象的一个引用呢,这个就没办法被回收了,哎,它呢就相当于是构成了一个泄露的问题。
03:09
啊,这呢是单利模式的情况,然后下一个。所以呢,叫内部类啊,持有外部类。说如果一个外部类的实例对象呢,它的方法返回了一个内部类的一个实例对象。啊,外部类的实例对象返回一个内部类的实例对象,然后这个内部的这个实力对象呢,如果被长期引用了,或者换句话说呢,比如这正是我们的一个外部类,这正是一个内部类,然后我们在一个方法当中呢,获取一个内部类的对象,这个对象呢,结果被另外的一个结构呢,长期持有。啊,那现在我们这个外部类的对象呢,现在想回收了怎么办呢?啊,你一回收结果发现呢,你里边这个还有结构呢,这个被占用着呢,所以导致这个we部类的对象呢,就没有办法被回收啊这呢也是一个内存的泄露问题。啊,内存泄漏问题啊,这个呢,我就没有给大家去举例子了啊,然后咱们看下边啊,下边这个呃,例子要多一些,因为上边这个很好理解啊,我就没有去硬编这个这个这个代码了,就啊。嗯,当然肯定都能写出来对吧,比较简单啊,然后下边这个呢,涉及到相关的这个连接啊,这呢也是非常容易说的一个点啊,嗯,像我们说的这个数据库连接也好,Statement result set是吧,这都是属于这个相关的,呃,连接的这个资源跟数据库相关的啊,还有呢,我们包括跟本地交互相关的一些流啊等等,那么这些资源的话呢,我们必须呢,要手动的进行一个close的操作。
04:24
啊,否则的话呢,这个呃,JC它是不能够去回收你没有被关闭的这些资源的啊,这个大家一定要小心啊,所以说的话呢,呃,如果我们没有去实现这个关闭操作啊,一定要被执行的话呢,它也是一个内存的泄露问题。啊,因为这些回收不掉,而事实上呢,我们又不用了是吧?啊,你不用了,但是回收不了,这不就泄露吗。啊,这个例子呢,应该大家很熟是吧?啊,这个是比较好举的哈。好,看下一个这个呢,是叫变量不合理的一个作用域啊,一个不合理的作用域,刚才呢,我们提到一个静态的一个结构问题啊,那现在呢,有一个非静态的啊,这个我们叫个message是吧,然后呢,在我们这个方法当中啊,给大家举了个例子啊,在这个方法当中呢,我们又调了另外两个方法啊,也就这个message呢,是从网络当中获取一个message的数据,然后把这个数据呢,我们保存在这个数据库当中啊,就结束了啊,就这样的一个操作是吧,那在这个操作当中,我们想看一下这个message是吧?它。
05:20
嗯,那这呢是一个属性,或者我们叫做成序变量啊,更精准一些啊,你叫字段的话也没毛病啊,那么这个字段的话呢,它是一个非静态的,呃,我们创建当前这个类的对象,跟这个对象的生命周期是一样的的,对吧?但实际上呢,我们会发现呢,当我们调完这个方法以后,其实这个message就不用了,对吧。啊,因为它只在这里边儿,这不是被使用了啊,就不用了,那如果是这样的情况下呢,我们完全是不是可以把这个message呢,是不是就定义在咱们这个里边呀。对吧,哎,直接定义在里边啊,你直接在这个M。哎,就这样的进行一个赋值就行,然后当我们这个内部的方法调用完成之后呢,这个mesen呢,它就直接被回收就可以了,是吧?哎,所以说呢,这里边就涉及到我们不合理的一个这个作用域啊,作用域呢,你定义的有点大了,导致我们这个对象啊,导致我们这个变量呢,呃,这个对应的这个对象呢,如果说它始终不能被回收,那这个对象呢,那这个呃字段呢,就始终不能被回收。
06:21
啊,再再再说一下,有点绕是吧,就是我们这个变量,它所在的那个对象,比如说我们造了个对象叫欧巴,这个对象呢,始终回收不了,那它就回收不了啊,本来呢,你要是定义到里边呢,随着方法的执行完,它就应该被回收了,是吧?但现在呢,老回收不了,它不就是泄露吗?啊,就这个问题是吧,当然了,你说我就需要这样去写,那你还可以怎么办呢?但你整个这个执行完之后呢,在最后,哎你把咱们当前的这个哎变量是不是给它复制一个nu也行,让他呢指向呢,你那个实体呢,是不没有指针指向了,哎他就哎可以,当然别人也不止了是吧,哎我们也可以呢,被这个垃圾回收器呢进行回收啊这呢也是我们可以考虑的这个手段啊。
07:00
这个呢,就相当于我们去解决这个内存泄漏问题了,对吧。好,下一个啊。呃,这块呢,提到了一个关于哈希值的一个问题。啊,这呢主要是,呃,我们在哪些结构中会常常用到哈希值呢?在这里提到这个哈希set啊哈希set我们存储的数据呢,是不能重复的,那这里边我们需要借助一下这个哈希值来进行计算啊呃,那下边呢,这里边写了一些具体的一些文字啊,那嗯,如果说我们在往哈希赛当中去放数据的时候,那放了以后呢,你还修改这个对象中的那些字段了,而且这个字段呢,还参与了哈希值的一个计算。那就坏事了。啊就坏事了,这时候呢,就导致我们这个对象呢,就不能够呃被回收了啊,因为呢,他老是占用着呢,就在我们集合中哈气在中放着呢,但是呢,我们现在又没办法去拿到它。啊,就是说白了就是我们现在不用它了,但是呢,它这块呢,又由于指针指向呢,还是回收不了。这不就是泄漏问题吗?哎,那为了把这个问题说清楚呢,我这儿给大家举了两个例子。
08:01
嗯,在我们这个memory这里边是吧,把这两个关一下啊啊就这两个啊。这两个先看谁呢,都一样啊,比如我们先看这个吧。嗯,这个大家看一下,我这有一个point是吧?诶point里边有一个属性叫X get方法哈希code,这个哈code呢,就是诶使用的了我们这个X是吧?哎,用它呢来计算下我们这个哈希值,然后下边呢是E口的方法,OK,这个呢,都是咱们正常的自动生成的啊,然后呢,我们看上边我这呢,诶。扭了一个哈西set,然后扭了一个point,嗯,把这个point的它这个X呢制成是个十,然后放进去了,然后我这特意呢写了一下哈希扣的这个值,哎,那我们算一下啊,这个是十,哎,一呃,31乘以一个一,然后加上个十,就41,所以这时候的哈希值呢是41,当我们把它放到这个哈西S当中的时候,啊,这个大家呢,脑海中应该浮现出来啊,这呢就是哈希set,我们底层是哈希map,哈希map呢,底层又是一个数组是吧?啊比如说我们这个就在这儿啊,实际上指向了我们对应的这个。
09:02
哎,New的这个point这个叫CC了是吧?来简单标识一下啊,那接下来的话呢,就是它这个位置的存放呢,是跟我们这个哈希质量要参与的啊,接下来的话呢,你看我们做了一个比较差的一个操作啊,非常恶劣的就是我呢,诶又通过这个CC呢,去修改了我们这个对象中的这个X属性改成20了。那20加上31,不就变成51了吗?对吧,就变成51了,好,那这时候呢,如果我们试图呢,去remove掉这个CC啊remove掉这个CC,注意这个时候的咱们这个CC的它的属性X呢,已经是20了啊,那我们去remove的话呢,我们去呃,因为哈希赛的我们删除数据也需要呢,通过哈希值呢去找它的位置,结果找的时候呢,是不是计算的那个哈希值是51,那51跟我们刚才存的时候呢,可是按照41是存到这儿了。那你要找50亿的话呢,很有可能是不是就找这个位置了,那这个位置呢就没有,所以导致我们就删除失败了。删除失败了对吧,那这时候呢,你看我们怎么找到这个元素啊,现在是不是找不到了呀。
10:01
啊,就是它确实在我们这个这个数组当中,但是我们呢,哈希set呢,都是通过哈希值,然后找到index,然后才找到我们要的这个数据,但现在呢,这个数据呢,确实被引用着呢,但是我们已经没有办法你想删除和操作它了,啊那就是说白了,我们现在不需要它了,但是它现在呢,还不能被回收。对吧,哎,这是不是就泄漏啊。哎,所以呢,我们正常去remove的时候,大家你会发现失败了。失败了是吧,失败了那么再添加一个的话呢,再添加一个,注意这时候添加的其实是放到,诶刚才你这个哥们还在是吧?CC,然后下边呢,我们再添加的时候呢,呃,他呢,因为是按50亿来计算的,他可能就还放到这个位置的。对吧,那那实际上它指向的是不是就同一个东西啊。但是呢,是不是就放了俩是吧,啊这个我们跑一下啊。好大家看上面呢,删除失败了,那这呢就放了。哎,你要打印一下这个哈希赛的话呢。是我们看到的话,相当于它里边,哎,相当于是不是,哎,我们有两个相同的这个对象了啊,有同学可能会想,哎,我这里边呢,呃,咱们把这个重写一下,这个图存方法啊,说到我这里边呢,这个对象呢,也重写了这个,哎,整错了。
11:13
嗯,这个这个什么ES和和IECO了,说凭啥呢,我们这里边就能放进两个相同的对象呢,就按照ES去说的话呢,确实相同对吧?哎,但是的话呢,你注意这两个对象还真都放进去了,就是因为我们中途呢修改了他的哈希值啊,所以这个行为是比较恶劣的啊,这个行为呢,导致。哎,导致了我们,哎内存的一个泄露啊。好,这是我们说的这个事儿,然后呢,上边这个例子呢,其实跟下边这个呢,是同样的道理啊,同样的道理,那我们再看一个例子,Processon里边呢,你看有两个属性扩大器ES哈,Code,这我都是自动生成的啊,哎,这个都没毛病,然后的话呢,我们用了俩对象。啊,用了两个person是吧,然后呢,添加到这个set里边了,添加完以后呢,我把其中的一个对象的这个属性值给改了,跟我们刚才那个行为一样啊改完之后的话呢,我们再去remove,注意哎,你的这个操作呢。
12:08
就我们这个操作是不是它就导致了。哎,导致的内存的。呃,内存的一个泄露问题是吧,所以呢,我们再去做这个操作的时候呢,我们就会导致说删除失败啊。啊,这个呢,它就是一个失败的情况,那因为你失败了嘛,所以我们再去这个打印的话呢,显然里边是不是还是有两个对象呀。你看这两个对象它就都还在都还在啊,那你要是哎把这个呃注释掉呢。那除掉的话呢,当然是不是我们就可以删了。这肯定可以是吧,这没毛病哈,哎,所以这里边儿呢,我们放到赛当中的时候呢,你不要中途呢去改它的这个属性值啊。嗯,行,这呢就已经能说明问题了,下边呢,只不过我又呃又额外取得这个情况,然后大家呢,你能够进一步的对他进行一个了解啊理解,比如说我们这里边儿呢,我再去添加一个也叫CC的能进去吗?
13:06
哎,发现可以进去是吧?啊,那这个能进去了,我再加个这个1001A的能进去吗?诶发现也可以进去,那我们这里边最终是有几个呢。这有仨是吧,就是我们加了一个啊这块呢,是不是有四个了,那这个内存的结构大家能够分析出来吗。能够分析出来吗?说一下吧。你看啊。往上面露一点啊,这就可以了啊。好,这呢是咱们这个哈义赛的顶层的一个数组啊,比如说第一个1001A,我们根据这个含义值呢,假设呢,这个元素放在这儿啊,这个是1001啊,我讲起了啊,这是叫A行,然后呢,接下来呢,这个它一直然后呢,呃,底层做个算法,然后最后呢,它比如说放这。哎,这呢是1002BB。在接下来的话呢,我们把这个A,这个P的这个A呢,改成了CC。
14:00
好,这个改成CC了,那就放这了,嗯,这个没room掉,然后我们接着再去添加1001CC,根据这个含义值呢,去计算它的位置呢,可能是在这,哎,所以这呢,我们是1001CC,它根本就没有机会跟上面这个元素是不是进行ES比较,所以这个呢也一定能进来啊,那这不就进来了吗?啊,这个101CC,那这里边这不就有俩嘛,是吧,然后再接着的话呢,我们又去添加1001A,这个呢,你去计算含义值呢,跟我们最初的这个含义值呢,是一样的了,所以呢,它也放这哎含义值都一样,但是呢,我们取ES的时候发现你是101CC,我是101A,诶咱俩还不一样是不是,咱俩呢也都进去了,诶就用一个指针来放呗。哎,所以呢,100这个幺这个AA呢,你看也进来了,所以这块不就四个吗。哎,到底是谁指谁呢?这个跟JDK版本有关系啊,所以四个呢就全都有,哎这个呢,就是关于咱们这个哎哈set当中这个情况啊,那么给我们的这个启发是什么呢?这个我在上面应该写了啊。比如说我往里边放这个string的时候呢,这个string呢,已经设置成了叫不可变的类型了,哎,所以它就禁止你去做修改是吧?哎,这个问题啊。
15:06
好,其实的话呢,就是我们你要是不是final的啥的,你就不要中途去改这个对象的这个属性啊。好,下一个叫做缓存的一个泄露。啊,缓存的一个泄露,嗯,另外的一个场景呢,就是缓存问题啊,说一旦呢,你把这个对象呢,放到缓存当中,就很容易被忘掉啊,比如说呢,之前项目呢,在上一次上线的时候呢,这个启动这个应用启动呢慢啊这个直到这个夯死。诶,我看啊,哎,比如说之前项目在一次上线的时候呢,呃,这个启动特别慢啊,一直呢就挂掉了,哎,就是因为呢,我们这个代码当中加载了一个表中的这个数据到缓存当中,在测试环境当中呢,由于我们这个数据比较小,没问题,但是生产环境当中,我们可能这个缓存的数据量很大,哎导致呢,就整个我们就挂掉了啊,这个服务器就挂掉了啊,那我们如何呢去解决这个缓存当中的这个数据问题呢?啊这里边儿有个建议,就是我们改成这个叫弱引用。
16:02
啊,弱引用啊,去替换我们的强引用啊,就是能够及时的被我们这个JC呢,是不是回收掉了,哎,这样子的啊好,那这个我们有一个例子啊,就是这个map的一个test。嗯,就在这儿啊,好,大家来看这个例子啊。嗯,这呢,我们创建了两个map,一个是哈希map,一个是week哈希map啊相当于是一个弱引用,就我们发现的话呢,就能够被回收啊嗯,首先呢,是一个in尼的操作,我们造了四个字符串,然后呢,诶分别呢,把它们这个放到了咱们两个map当中啊,一个map里边放俩这样子好,然后呢,我们这个呃,相关的这四个变量啊呃,REFERENCE1234,它们因为是局部变量嘛,它们几个呢就相当于。诶,当然他们几个这这个变量本身没了,但是它对应的这个对象相当于还在这被了呢是吧?哎,相当于这几个没销毁啊,然后呢,我们再去调这两个方法,哎,这个和下边这个这两个呢,唯一区别呢,就是我们这个呃,Map本身不一样啊,具体的操作都一样,在这个JC之前,我们先遍利一下,看看里边都有什么,然后呢,我们进行一个JC,诶五秒钟之后呢,我们再进行一个,呃,就是确保呢,它这个垃圾回收执行了是吧,然后呢,我们再看里边还有没有数据啊,然后针对哈西map呢,也做同样的这个操作。
17:17
好,那我们这里呢,做一个执行。哎,中间因为有个五秒钟的时间啊,那我们这里边把这个数据呢,都放到这个各自的这个map中的时候,在这个week哈希map之前,那它里边呢,这不是有两个对象嘛,它放的是一跟二是吧,有这两对象啊,然后呢,在这个week哈map之后呢,大家会发现呢,我们就没有再进行这个for循环的一个执行了。哎,这个之后,那再进行放循环执行呢,没出来说明呢,就是我们这个放环就没有进去,哎言外之意呢,就里边就没有数据了啊那接下来又调用这个方法的时候呢,哈奇map执行之前里边有俩数据,执行之后呢,还有俩数据,那相当于它就没有回收,那对于我们缓存啊,内存中的这个呃,Map来讲的话呢,诶我们希望呢,就是及时的去清理这些缓存,那我们就倾向于是不是使用掉啊weak哈ma就行是吧?哎那大家举例子的话呢,你可以举例哈ma的情况,然后呢,这个诶改进的措施呢,哎就改成叫哎为新map。
18:16
啊,这就行啊,这就可以。哎,这呢是把这个图呢又画了画,哎这个呢相当于我们就能够进行JC成功,而这个呢就不能功回收成功啊,缓存呢没有及时的清理是吧。下一个呢,这块提到叫监听器和回调。呃,它的另外一种场景呢,就是监听器和回调啊,说如果呢,客户端在你实现这个API当中呢,注册了一个回调啊,但是没有显示的取消就会积攒啊,积攒过多的时候呢,就会导致这个内存的一个泄露问题啊说建议呢,就是诶保存它的一个弱引用,然后把它改写成用weak哈map啊刚才咱们其实也提到了这个情况是吧?哎,这个问题啊。嗯,第三个场景,这也不是第三个了,这个就是另一个这样写是吧。
19:04
好,那关于这个监听器和回调这个事儿呢,咱们在下边这个案例当中呢,我给大家也有一个说明啊,咱们直接放到下边案例的一个分析当中啊就可以了,行这呢,就是我们关于这个内存泄露的这八种情况啊,需要大家呢,能够诶熟悉的基础之上呢,能够记一记啊,那如果问到的时候呢,你能够表达出来,或者写代码的话呢,你能够写出来啊就可以了。
我来说两句