好好捋一捋类加载和垃圾回收

背景

之前自己对类加载和垃圾回收的理解,总是迷迷糊糊。今天特地结合了一些文章还有《深入理解Java虚拟机》好好梳理了一下。

废话少说

1、类的加载

Java 的类加载过程分为三个主要步骤:加载、链接、初始化。

首先是加载阶段:此阶段是Java将字节码数据从不同的数据源读取到JVM中,并映射为JVM认可的数据结构(Class 对象,我们可可看到java.lang.Class) ,我们的jar文件、class

  • 加载阶段,我们可以自定义类加载器,去实现自己的类加载过程。 第二阶段是链接 ,这是核心的步骤,简单说是把原始的类定义信息平滑地转化入JVM运行的过程中。这里可进一步细分为三个步骤:
  • 验证 :这是虚拟机安全的重要保障,JVM 需要核验字节信息是符合Java虚拟机规范的,验证阶段有可能触发更多class的加载。
  • 准备:创建类或接口中的静态变量,并初始化静态变量的初始值。这里的初始化重点在于分配所需要的内存空间,不会进行赋值。赋值操作在初始化阶段。
  • 解析:这部分自己暂时还没有特别的理解,等捋清了再来不上。

最后是初始化阶段 , 这一步开始执行静态字段赋值的动作,静态初始化块内的逻辑,编译器在编译阶段就已经把该执行的代码逻辑整理好了,这里需要注意的是:父类的初始化逻辑优先于子类的逻辑。

一直走到这,是类加载完毕,也是生成了我们对应的Class。但是请留意,这里还没有涉及到类的实例化,也就是说此时还没有开始new操作。

当执行new的时候,而过此类如果已经经历过加载,那么就会执行对应的实例化,比如分配内存,执行代码块,构造方法之类的(如果有父类要先对应执行这些内容)。最后将内存地址指向引用。


2、垃圾回收

其实这是一个老生常谈的话题。为啥还要再提,就是我和MDove聊天的过程中发现我们的理解都不够成体系。所以我们参考了很多文章重新把这相关的内容,捋一捋。

什么样的对象可以被回收?

我相信大家应该都明确“可达性分析”这个概念吧,就是一种用于标记对象是否能被回收的做法。我们先简单看一下几种能被回收的情况。 我们先假设有一个被认定为GC Root的对象a=new A(),它内部引用了b =new B(),b内部又引用了c=new C()。

例子1

(1)c=new C();
(2)c=new C();//重新new了一个新的C对象。

复制代码

那么我们(1)过程中被new的对象就可以被回收(这里需要注意,当这个对象被标记俩次可回收时,才会回收)

例子2

(1)c=new C();
(2)c=null;
复制代码

此时我们(1)过程中new的对象也可以被回收

例子3

此外所有方法内有局部变量引用的,新new出来对象,在方法结束后都会被回收。


3、常见垃圾回收算法

这里特别以英文命名,因为翻译大家并没有一个统一的叫法。所以就一英文为主吧。

Mark-Sweep(标记-清除算法): 思想简单粗暴:标记阶段标记出所有需要回收的对象,清除阶段回收被标记的对象所占用的空间。但是问题也很直接,会造成内存碎片,到来的后果就是明明空间够,但是申请不出来内存(OOM),就是因为碎片的原因,没办法申请对应大小的连续内存了。

Copying(复制算法): 为了应对Mark-Sweep的缺点,应运而生了Copying,它的思想按内存容量大小,将内存划分为两块相同的内存。每次只使用其中一块,当这一块内存满了之后,将还活着的对象复制到另一块内存上,并且把可回收的内存回收掉,以此来保证当前使用的内存是连续的。但是缺点也很直接,那就是把可以使用的内存砍了一半。而且如果存活对象较多,复制起来速度会比较的慢。

Mark-Compact(标记-整理算法): 此算法是结合了上述俩种算法的综合。标记阶段和Mark-Sweep类似,标记后并非直接清除,还是将存活对象一项内存的一端,清掉其他内容,那么这样就可以就解决Mark-Sweep中的碎片问题,而且没有Copying中的内存分块。但是频繁的移动,对速度要求会比较的高。

Generational Collection(分代):

以下只是大概说一个笼统的思路,因为对应不同的分代情况,不同的垃圾回收器会有自己的算法实现。

综合以上的想法,出现了比较被广泛接受的算法思路。也就是我们常提的按对象的生老病死的比例分块(Copying的思想),认可度比较高的做法:将新生代划分为一块较大的Eden空间和两个较小的Survivor空间(From Survivor, To Survivor),分别对应8:1:1,老年代另算。正常使用时会使用新生代的Eden和From Survivor空间,如果new对象时,空间不足,触发一次GC。GC后,Eden和From Survivor区的存活对象会被挪到To Survivor,然后将Eden和From Survivor进行清理。如果To Survivor无法足够存储某个对象,则将这个对象存储到老生代。之后就可以正常再使用Eden和To Survivor了。当对象在Survivor区经历一次GC后,其年龄就会+1。年龄达到一定程度(可以配置)后会被移到老生代中。


4、具体的垃圾回收器

因为目前被接受的垃圾回收的思路为分代的思想,因此为了能各高效的利用资源,正真被实现出来的垃圾回收器都会根据不同的代,做不同的处理。比如:

1. Serial/Serial Old

最古老的收集器,是一个单线程收集器,用它进行垃圾回收时,必须暂停所有用户线程。Serial是针对新生代的收集器,采用Copying算法;而Serial Old是针对老生代的收集器,采用Mark-Compact算法。优点是简单高效,缺点是需要暂停用户线程。

2. ParNew

Seral/Serial Old的多线程版本,使用多个线程进行垃圾收集。

3. Parallel Scavenge

新生代的并行收集器,回收期间不需要暂停其他线程,采用Copying算法。该收集器与前两个收集器不同,主要为了达到一个可控的吞吐量。

4. Parallel Old

Parallel Scavenge的老生代版本,采用Mark-Compact算法和多线程。

5. CMS

Current Mark Sweep收集器是一种以最小回收时间停顿为目标的并发回收器,因而采用Mark-Sweep算法。

6. G1

G1(Garbage First)收集器技术的前沿成果,是面向服务端的收集器,能充分利用CPU和多核环境。是一款并行与并发收集器,它能够建立可预测的停顿时间模型。

总结一下:现在的回收思想是以分代为基础,对于不同的代,对应实现最佳的算法(不同业务场景会对应不同的最佳方案)。

以上内容算是我个人的一个学习总结,如果有不当之处,欢迎评论区之处。共同学习,共同进步。


尾声

这是一个主推面试踩坑的公众号!

不感兴趣的就直接无视掉吧

我们是一个应届生学习小组,这些分享我们会坚持下去,一定会。这条路大家都走的太辛苦,一起互相鼓励,一起并肩同行!

因为身边的同学从事互联网相关职业的比较多,并且大家闲时聊天时总会吐槽找工作有很多坑,所以打算把身边同学找工作的经验,统统收集起来。提供给想从事这方面同学,希望圈内好友可以共同进步,共同少踩坑。

个人公众号

(打个广告)我们基友团其他朋友的文章:

Web基友 Android基友

猜你喜欢

转载自juejin.im/post/5b52be8151882519f647696a
今日推荐