Jarvis’s Blog

黄自豪的博客


  • 首页

  • 关于

  • 标签

  • 分类

  • 归档

6、垃圾收集器

发表于 2017-05-13 | 分类于 Java虚拟机

在HotSpot虚拟机中,有7种作用于不同分代的收集器,如下图:

垃圾收集器

阅读全文 »

5、HotSpot的算法实现

发表于 2017-05-11 | 分类于 Java虚拟机

前面了解关于对象的生死判定和垃圾收集算法,而在HotSpot中是怎么实现的呢?

枚举根结点

根据可达性分析算法,GC的时候从GC Roots节点开始找引用链的,而做为GC Roots的节点主要是在全局性引用与执行上下文中,如果逐个检查的话,必然是需要耗费很多时间的。

而执行GC的时候,所有线程是必然要停顿的。因为在执行时,如果对象的引用关系还在不断变化,就无法准确的进行GC了。

不过,主流的虚拟机现在都是采用精确式GC,即执行系统停顿后,并不需要检查全部执行上下文和全局引用位置,而是通过某些方法知道哪些地方存放着对象引用。

在HotSpot中是使用一组称为OopMap的数据结构来达到这个目的的。

安全点

在HotSpot中,特定的位置会使用OopMap来记录信息,这个位置被称为“安全点”。程序执行时到达安全点才能停下来。

安全点的选定是以程序“是否具有让程序长时间执行的特征”为标准选定的————因为每条指令执行时间很短,而长时间执行的最明显特征就是指令序列重复,如方法调用、循环跳转、异常跳转等,所以适合产生安全点。

那么在GC发生时,是如何让线程达到安全点再停顿下来呢。主要有两种方案:抢断式中断和主动式中断。

1、抢断式中断

在GC发生时,中断所有线程,如果有线程不在安全点的话就恢复线程,让它到安全点再中断。(这种方式几乎被弃用了)

2、主动式中断

当GC时,简单设置一个标志,各个线程主动去轮询这个标志,标志为真时就自己中断挂起。

安全区域

安全区域是指一段代码片段中,引用关系不发生变化。在这个区域中任何地方GC都是安全的。

如果程序在执行的话,安全点就足以解决了GC问题。但是如果程序没有在运行呢,没在运行也就是说没有分配CPU时间,比如线程处于Sleep或者Blocked状态,这时候就无法响应JVM的中断请求了,这时候就需要用安全区域来处理了。

在线程执行到这个区域时,会先标识自己是安全区域(Safe Region)。当JVM在这段时间内发生GC时,就不用管标识自己为安全区域的线程了。线程离开安全区域时,要检查系统是否完成GC的整个过程(根节点枚举),完成的话就继续执行线程,否则必须等待,直到收到可以安全离开安全区域的信号。

4、垃圾收集器之垃圾收集算法

发表于 2017-05-10 | 分类于 Java虚拟机

导读

垃圾收集器回收内存是根据定义的条件来收集的,有了条件就需要算法来实现这些条件了。

垃圾收集算法

垃圾收集主要的算法有标记-清楚算法、复制算法、标记-整理算法、分代收集算法这四种。

1、标记-清除算法

从名字就可以看出这个算法比较简单,首先标记出所有要回收的对象,在标记完成后统一回收被标记的对象。

它有两个不足的地方:

  • 效率不高,标记和清除两个过程的效率都不高;
  • 会产生大量不连续的内存碎片,这会导致后续如果需要比较大的内存来分配对象时,因为找不到足够的连续内存,而不得不触发垃圾收集。

不过,标记-清除算法是最基础的收集算法,后续的收集算法都是基于此进行改进得到的。

2、复制算法

复制算法把内存按容量分成了大小相等的两块,每次只使用其中一块。当这一块内存用完后,就把还存活的对象复制到另一块,然后把使用过的内存空间一次清理掉。

这样每次都是对半个区进行内存回收,且每次只需要移动堆顶指针,按顺序分配内存即可,简单高效。不过,虽然不用考虑内存碎片这种情况,但使用内存缩小一半,代价还是有点高。

这个收集算法主要用来回收新生代的,不过因为新生代(后面分代收集算法会提及到)的对象98%是“朝生夕死”的,所以没必要按1:1的比例来划分空间,而是分为一块较大的Eden空间和两块小的Survivor空间,每次使用一块Survivor和Eden空间。回收时,则将Eden和Survivor空间还存活的对象一次性复制到另一块Survivor空间,然后清理掉Eden和用过的Survivor空间。

一般Eden和Survivor的大小比例为8:1,也就是说每次新生代可用内存占整个新生代容量的90%,只有10%是空闲的。而当存活对象超过10%的空间时,空间就不够用了,这时候得依赖其他内存(指老年代,后面会再提到)来进行分担了。

3、标记-整理算法

如果存活对象比较多,用复制收集算法就比较低,如果不想浪费50%的空间,还得额外空间来做担保,所以,老年代一般不直接用这种算法,而是采用“标记-整理”算法。

它的标记过程和“标记-清除”算法一样,只是后续步骤不是直接回收,而是把存活对象往一端移动,然后清理掉另一端的内存。

4、分代收集算法

这种方法是把内存划分为几块,一般是分为新生代和老年代,然后根据各个年代特点采用适当的收集算法。

  • 新生代:会有大批对象死去,少量存活,一般就采用复制算法;
  • 老年代:对象存活率高,没有额外空间担保,就采用“标记-清除”或“标记-整理”算法来回收

3、垃圾收集器针对的对象

发表于 2017-05-08 | 分类于 Java虚拟机

导读

关于垃圾收集需要做的是3件事:

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

我们已经了解到,程序计数器、虚拟机栈、本地方法栈(忘记的话,请重新看前两篇内容)这3个区域是随着线程生灭的;这几个区域的内存分配和回收是可以确定的,在方法结束或者线程结束时,内存就跟着被回收了。

而JAVA堆和方法区不一样,它们只有在程序运行时才会动态的创建对象,动态的对内存进行分配和回收,而垃圾收集器关注的就是这部分内存。我们常说JAVA是面向对象的,而这部分内存里也是充满了一个又一个的对象。那么,垃圾收集器要根据什么条件来判断哪些对象是该回收的呢?

什么样的对象会被回收?

很明显,当一个对象不再被任何地方使用的时候,就说明它该被回收了。所以,我们要了解的其实是怎么去判断一个对象已死。判断的方法有两种:引用计数算法和可达性分析算法。而在JAVA中使用的是可达性分析算法。原因下面会说名。

引用计数算法

给对象添加一个引用计数器,每当有地方引用它,计数器就+1,引用失效时就-1;当计数器为0时就是对象不再使用的时候。

这个方法简单高效,不过在JAVA中却没有被使用。因为它很难处理对象之间互相循环引用的问题。

可达性分析算法

同一个一系列称为“GC Roots”的对象为起始点,从这些节点往下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链的时候,则证明这个对象不可用。

可达性分析

可作为GC Roots的对象包括下面几种:

  • 虚拟机栈中的引用对象;
  • 方法区中类静态属性引用的对象;
  • 方法区中常量引用的对象;
  • 本地方法栈中JNI引用的对象。

关于引用

无论是引用计数算法还是可达性分析算法,它们判断对象是还或者都与引用有关。在Java中,引用被分为四种:强引用、软引用、弱引用、虚引用。四种引用依次逐渐减弱。

  • 强引用:类似”Object obj=new Object()”这样的引用,只要这类引用存在,对象就不会被回收。
  • 软引用:还有用但并非必须的对象。在系统将要发生内存溢出异常前,将会把这类对象列在回收范围内,在第二次内存回收时回收。
  • 弱引用:也是非必须对象,比软饮用的强度还弱一些。只能生存到下一次垃圾收集之前。当垃圾收集器工作时,无论内存是否足够,都会回收掉这部分对象。
  • 虚引用:也成为幽灵引用或幻影引用。设置虚引用的唯一目的就是能在这个对象被回收时收到一个系统通知。

然而,即使是可达性分析算法不可达的对象,也不是非死不可的。这时候,它们会执行finalize()方法,暂时处于死缓阶段;而当对象没有覆盖这个方法或者这个方法已经被执行过了,那么它就真要死了。

回收方法区

方法区的垃圾收集主要分为两部分:废弃常量和无用的类。废弃常量回收与Java堆中的对象相似;要回收无用的类,条件就比较苛刻一些了:

  • 该类所有实例已经被回收,也就是java堆中不存在该类任何实例。
  • 加载该类的ClassLoader已经被回收。
  • 该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。

满足以上条件后,也仅仅只是可以被回收而不是必然被回收。

2、Java堆中的对象

发表于 2017-05-06 | 分类于 Java虚拟机

对象

阅读全文 »

1、Java内存区域

发表于 2017-04-13 | 分类于 Java虚拟机

为什么要了解JAVA内存管理?

我们知道,用JAVA进行编程,最大的优势在于内存管理。我们可以完全忽略内存管理的细节,专注于逻辑业务上。但是,并不代表它就不会出现内存溢出或泄露的问题。而且,因为JAVA有自动管理内存的机制而对这一块不了解,显然是有点low的。所以,了解一下JAVA的内存管理,既有助于提高逼格,也能让我们在进行开发的时候,可以针对内存溢出或泄露的问题进行一些优化。

JAVA运行时内存区域

JAVA虚拟机在执行JAVA程序的过程中会把它所管理的内存分为若干个不同的数据区域。这些区域都有各自的用途、创建和销毁的时间,有的是随着虚拟机进程启动而存在的,有的则依赖用户线程的启动和结束而建立和销毁的。

通过下面这张图,我们可以比较直观的看出,虚拟机划分的几个数据区域。
JAVA运行时区域

根据JAVA虚拟机规范,内存被划分为:方法区、虚拟机栈、本地方法栈、堆、程序计数器、运行时常量池(程序运行时,由方法区分配的区域,所以在图中没画出来)

  • 程序计数器(线程私有)

记载了线程中正在运行的JAVA方法的地址,如果执行的是本地方法,这个计数器则为空地址。

每个线程都有独立的程序计数器,该计数器主要是用来支持多线程的阻塞、挂起、恢复等操作。所以,这个计数器也是每个线程私有的内存区域。而且也是唯一一个JAVA虚拟机规范中没有规定任何OutOfMemoryError情况的区域。

  • 虚拟机栈(线程私有)

虚拟机栈也是线程私有的。每个线程创建的同时跟着一起创建的,用于存储栈桢。

每个方法在执行时都会创建一个栈桢,用于存储局部变量表,操作数栈,动态链接,方法出口等信息。每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。

在JAVA虚拟机规范中,这个区域规定了两种异常情况:

  1. 线程请求的栈深度大于虚拟机运行深度,抛出StackOverFlowError异常;
  2. 虚拟机栈可动态拓展时,没有申请到足够内存,抛出OutOfMemoryError异常。
  • 本地方法栈(线程私有)

与虚拟机栈类似,也是线程私有的。它是用来支持Native方法的执行的。在虚拟机规范中,对本地方法栈中的方法使用的语言、使用方法、数据结构都没有强制规定,可以自由实现它。和虚拟机栈一样,也会抛出StackOverFlowError异常和OutOfMemoryError异常。

  • 堆(全局共享的)

用于存储对象实例及数组的。这个区域也是垃圾收集器(GC)的主要管理区域。为了方便内存回收,该区域还会进行很多划分,不变的是,存放的内容都是对象实例。该区域的大小可以通过-Xmx和-Xms来控制。如果没有内存可给实例分配,且无法拓展时,会抛出OutOfMemoryError异常。

  • 方法区(全局共享的)

用于存储已被虚拟机加载的类信息,常量,静态变量,即时编译器编译后的代码等数据。

在JAVA虚拟机规范中,没有强制要求实现这部分的内存管理。垃圾收集行为在这部分也比较少。当方法区无法满足内存分配需求时,会抛出OutOfMemoryError异常。

内存的管理其实就是内存分配和内存回收。而内存分配的几个区域可以分为两类,程序计数器、本地方法栈、虚拟机栈这几个区域是属于线程私有的,随着线程创建而创建,销毁而销毁,销毁时内存也会释放掉,所以这几个区域是不需要进行GC的。方法区因为它的定义GC行为相对会比较少,所以,堆则成为了垃圾回收器的重点维护对象。

搭建SpringMVC+MyBatis+MySql框架

发表于 2016-11-29 | 分类于 java

bg
第一次搭建springmvc+mybatis框架还是踩了不少坑。通过不断的搜索和比对别人的搭建过程,还是让我搭建出来了。于是,打算总结一下我的搭建过程,并附上所需的jar包。希望能帮到下一个要搭建SpringMVC框架的人。

阅读全文 »

(3)、类与接口

发表于 2016-11-24 | 分类于 Effective Java笔记

为什么要使类和成员可访问能力最小化?什么时候用继承比较合适?用继承有哪些缺点?接口有哪些优点?稍微总结了一下,相信看完后对于用JAVA来编程的同学会有些有帮助的。如果想看更详细的内容,就去看《Effective JAVA》这本书吧

阅读全文 »

搭建S2SH框架

发表于 2016-10-15 | 分类于 java

10.23
从工作到现在一年多,从没搭建过S2SH(Struts2+Spring+Hibernate)框架,虽然也感觉应该是没什么难度,但是没有真正动手搭建过也不好意思说自己就真的会。于是,上天就给了我这么个机会,降大任于我,要我从搭建框架开始,苦我心智,劳我筋骨……然后,就有了这篇记录我如何快速搭建一个S2SH的教程

阅读全文 »

web项目添加maven支持

发表于 2016-09-24 | 分类于 java

项目开发了一段时间决定要加入maven的支持,然后就找到了下面的方法。

阅读全文 »
123…8

黄自豪

万物皆有裂缝处,那是光照进来的地方!

71 日志
17 分类
71 标签
GitHub CSDN
© 2016 — 2018 黄自豪
由 Hexo 强力驱动
|
主题 — NexT.Pisces v5.1.4