深入理解jvm

上一篇已经介绍了 jvm运行时数据区 ,下面将学习垃圾收集器和内存分配与回收策略

为什么要了解gc和内存分配呢?

  • 当需要排查各种内存溢出、内存泄漏问题时
  • 当垃圾收集成为系统达到更高并发量的瓶颈时

灵魂拷问

  • 哪些内存需要回收?
  • 什么时候回收?
  • 怎么回收?
哪些内存需要回收

  在运行区域中,程序计数器,虚拟机栈,本地方法栈三个区域随线程而生,随线程而灭,栈中的栈帧随着方法的进入和退出进行入栈出栈操作,每一个栈帧 在类结构确定下来就已确定了大小 因此这几个区域的分配和回收是具有确定性的。不考虑回收的问题

一个栈帧就是一个方法,方法内创建的对象只是引用变量在栈中并且大小基本是固定的。当类结构确定时,需要创建多少变量以及方法调用都已经确定了

Java堆和方法区则不一样,一个接口中的多个实现类需要的内存可能不一样,一个方法中的多个分支需要的内存也可能不一样,我们只有在程序处于运行期间才能知道会创建
哪些对象,这部分内存的分配和回收都是动态的,垃圾回收器所以关注的也是这部分内存。

1
2
A a = new A();
B b = new B();
  • 如有接口A,有两个实现类B和C,B中有变量id,name,C中只有id,那么B,C对象所需的内存是不确定的。b,c在栈变量表大小是固定的,而new A(),new B(),引用类型在堆中不固定。
  • 单个对象的内存都不固定,更别说方法和方法分支了
什么时候回收

  对象回收肯定是回收“无用”的对象,还需要访问的对象总不能被回收了,那么如何判断一个对象是否无用了呢?

  • 引用计数法

      很多教科书判断对象是否存活的算法是这样的:给对象添加一个引用计数其器,每当有一个地方引用它,计数器值就加1;当引用失效,计数器值就减1;任何时刻计数器都为0的对象就是不可能再被使用的。

  • 可达性分析(根搜索算法)

  这个算法的基本思路就是通过名为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为“引用链”,当一个对象到GC Root没有任何
引用链相连(就是GC Roots到这个对象不可达)时,则这个对象是不可用的。

可作为GC Roots对象包括:

- 虚拟机栈中的引用的对象(栈帧中的局部变量表)即**局部变量**
- 法区中的类静态属性引用的对象即**静态变量**
- 方法区中的常量引用的对象即**常量**
- 本地方法栈中JNI的引用的对象(本地方法中引用对象)

什么是引用?

  在JDK1.2之前,JDK定义的引用是:

如果一个引用类型的数据存储的是另一块内存的地址就成这块内存代表着一个引用

在JDK1.2之后,将引用分为强引用、软引用、弱引用、虚引用

  • 强引用

  类似Object obj = new Object()这种,

  • 软引用

  软引用描述一些还有用,但不是必须的对象,当系统将要发生内存溢出时,将会把这些对象列为回收对象并进行回收,如果回收完这些对象内存还是不足,才会抛出异常。
SoftReference类来实现软引用

  • 弱引用

  弱引用也是用来描述非必需的对象,强度比软引用弱,被弱引用关联的对象只能生存到下一次垃圾回收之前,无论内存空间是否足够,都会被回收
WeakReference来实现

  • 虚引用

  是最弱的一种引用关系,无法通过虚引用来获取对象的实例,唯一的目的是在这个对象回收之前接到系统通知。用PhantomReference来实现

判断对象死亡的过程

  对象真正的死亡要经历两次的标记过程,如果发现没有GC Roots相连接的引用链,那么会经历第一次筛选,筛选的条件是是否要执行finalize方法
当没有重写finalize或方法已经执行过了,将视为没有必要,否则会将对象放置于一个队列之中,稍后会由一条虚拟机自动建立、低优先级的线程去执行。
(需要注意的是虚拟机只会去触发这个方法,并不会一定等待方法运行结束)。finalize方法还是对象逃脱死亡的最后一次机会,在finalize将对象重新与RC Roots建立关联。
然后在稍后GC对队列对象进行二次小规模标记时,将会被移除即将回收的对象列表。

对象回收的算法

  • 标记-清除

markdown

先标记要回收的垃圾,在标记完成后统一回收对象

缺点:

①效率问题

②会产生垃圾碎片,会导致后续提早触发垃圾回收动作。

  • 复制算法

将内存按一定比例的大小分成两块,每次只用其中一块,当用完后,就将存活的对象复制到另一块。这种算法避免了垃圾碎片的问题

缺点:

①需要浪费一定大小的内存

  • 标记-整理
  • 分代收集算法

垃圾回收器

评论