本文共 4973 字,大约阅读时间需要 16 分钟。
本章将是jvm相关面试的核心内容之一,而之所以将jvm作为java面试的重点,正是在于相对于C++而言的。因为jvm的存在,导致java存在了很多的优点,如跨平台、开发者不需要过多的关注内存变化等,因为这些正是jvm帮助我们做的,而对于C++而言,比如回收内存都是需要开发者亲自来操作的,并且其中很多的错误都是由此导致的。
还记得本科大二的时候(2014年),当时正在为找工作准备方向,当时是的方向很多,C++,前端,安卓等,宿舍的大神也正在学C++,其中在许多方面我都是受着他的引导。但是重点是c语言我掌握并不是很好,记得当时期末考试我只考了68分(大学之前我是没怎么接触过电脑的,直到高考模拟填报志愿时我都不怎么会复制粘贴。但是这不是理由,主要原因在于我压根没怎么上课,而这个高三形成的坏习惯将在之后的很长时间内影响着我,影响着我的际遇,但是我却不想也不能改变)。最初,我将C++作为工作方向,当时也并不知道C++是做什么的,但知道java是做安卓的。但是C++和C一样的繁杂,特别是指针,这个老师和书本都喜欢重点讲述的知识,却是最容易被误导的。大学时代中,其实很简单的知识,总是被老师以各种题目的方式考察,这样反倒是让我们不知所以。最终,在一个星期后,当我看到了一本java书籍后,我就抛弃了我当初一心选择的C++。因为java在许多方面要比C++等开发清晰得多,相对我而言,那种格式让一个初学者一看就会,只需要一个100页的“垃圾书”就可以入门了。
3.1 概述
本章将主要围绕下面几个问题来讲述:
那些内存是垃圾,需要回收?
什么时候回收?
一个问题:
既然垃圾回收器自动回收,为什么还需要了解分配机制? 因为自动回收器就像一个机器人,每一个机器人都有一个界限,比如一分钟处理10个对象,突然有有一天来了100个对象,此时就打破了这个界限,就需要人为改变。3.2 什么时候回收
对于一个对象,我们什么时候需要回收它?对于这个问题,主要有两种方式:可达性分析法
java和C#以及lisp都是使用的这种方式。 可达性的方式相当于一个树形结构,通过定义一个根节点GC ROOTS对象,然后所有的其它对象都是被他引用,一直往下走,就相当于一个树形结构。这里面最重要的就是根节点GC ROOTS的设定,一般而言,GC Roots主要分为以下四类(也包括其他的):3.3 引用的分类-
最开始一个对象就只有有引用和没有引用之分,这样其实很简单。但是这样处理有很多问题,比如有些对象只是在一定时期类没有引用,但是之后会用到,如果直接就回收的话,会导致一些错误出现。因而,为了改善这些“食之无味,弃之可惜”的对象,我们将对象更具引用的强弱又分为以下四种引用:强引用、软引用、弱引用、虚引用
对这些引用分类的依据为判断当前内存是否足够及应对实际场景解决问题,如果足够就丢弃,即什么都要。这就相当与《荒野行动》这个游戏一样,玩家的背包容量有限,但是最初的时候我们什么也没有,包是空的,所有我们简单什么都会装在包里吗;但是随着东西的增多,包满了,这时候我们就会有选择行的扔掉一些东西,比如多装一些步枪子弹,而丢掉一些没用的散弹枪子弹。
public class Main { public static void main(String[] args) { new Main().fun1(); } public void fun1() { Object object = new Object(); Object[] objArr = new Object[1000]; }}
2软引用(缓存)
软引用是用来描述一些有用但并不是必需的对象,在Java中用java.lang.ref.SoftReference类来表示。对于软引用关联着的对象,只有在内存不足的时候JVM才会回收该对象。因此,这一点可以很好地用来解决OOM的问题,并且这个特性很适合用来实现缓存:比如网页缓存、图片缓存等。软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被JVM回收,这个软引用就会被加入到与之关联的引用队列中。下面是一个使用示例:
import java.lang.ref.SoftReference;public class Main { public static void main(String[] args) { SoftReferencesr = new SoftReference (new String("hello")); System.out.println(sr.get()); }}
Product productA = new Product(...);
我们将这个商品添加到hashmap中。当有一天这个商品被买走了,这个商品就不存在了,productA = null
也没有存在的意义,但是productA 并不能被回收,因为hashmap还引用着它,所以这时候就可以加入一个中间弱引用对象对Product进行封装WeakReference weakProductA = new WeakReference<>(productA);然后添加weakProductA 到hashmap中,此时弱引用对象就会被立即回收,同时productA也会被回收。import java.lang.ref.WeakReference;public class Main { public static void main(String[] args) { WeakReferencesr = new WeakReference (new String("hello")); System.out.println(sr.get()); System.gc(); //通知JVM的gc进行垃圾回收 System.out.println(sr.get()); // 输出结果为: // hello // null }}
import java.lang.ref.PhantomReference;import java.lang.ref.ReferenceQueue;public class Main { public static void main(String[] args) { ReferenceQueuequeue = new ReferenceQueue (); PhantomReference pr = new PhantomReference (new String("hello"), queue); System.out.println(pr.get()); }}
3.2.4 对象的两次生命
java中使用新生代和老生代来进行对象回收,即一个对象有两次生命,在第一次并不会直接死亡;当在第二次回收前还是没有新的引用,就会被二次回收(),这才算是最终死亡。 多提一点:对象在回收前会执行一次finalize方法,在这个方法中对象可以自救(重新让自己被引用),但是一个对象的finalize方法只能被系统调用一次,所以只能自救一次。(这个可能并没有实际应用)3.2.5 方法区的回收
方法区跟堆一样,被所有的线程共享。用于存储虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。就是类的一些信息。
一般而言垃圾回收是对堆来说的,因为大部分垃圾都是由对产生的,即对于的对象。所以称方法区为永久代,同样常量池也是永久代。常量池的回收就是看是或否有引用。而对于方法区需要满足以下三个条件:(无用的类)
3.3 垃圾收集算法
书中的垃圾收集算法只是基本的,当前的垃圾收集算法可能已经更加完善3.3.1 标记-清除算法
即对无用对象进行标记,然后统一回收。两个缺点:3.3.2 复制算法(改进1)
即将内存分为两个部分,每次只是用一个部分,将另外一部分作为备用。这样在标记清除后,将还存在的对象复制到另一部分,同时清理当前的内存部分。这样就解决了内存碎片化的问题,同时由于分配内存时是连续分配的,所以堆指针连续移动,运行更加高效。其实现在更加高效的垃圾回收算法就是采用这种方式,只是不是简单地分为均等的两个部分。因为经过研究发现一般新生代的对象中98%的对象都是“朝生夕死”的,
3.3.3 标记-整理算法
标记整理算法是在标记清除算法的基础上进行改进的。使用整理来代替清除,从而解决了碎片空间的情况。具体方法是,在对对象进行标记后,不是直接进行清除,而是先整理,将所有存活的对象向一边移动,之后将另一边的对象全部清理3.3.4 分代收集算法
这是当前商业虚拟机主要使用的方法,主要是集成了复制算法和标记整理算法的集合。首先将对象分为新生代和老年代,新生代中只有大部分对象存活,所以使用复制算法;而在老年代中,大部分对象都会存活下来,所以使用标记整理算法3.4 HotSpot的算法实现
>转载地址:http://fqepi.baihongyu.com/