转载

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

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算法。

原文  https://juejin.im/post/5b52be8151882519f647696a
正文到此结束
Loading...