《深入理解JVM》--内存管理机制

Java内存区域的分类

  • 程序计数器(线程私有):可以看做是当前线程所执行的字节码的行号指示器。每一条线程都需要有一个独立的程序计数器来确定正在执行的字节码的地址。

  • Java虚拟机栈(线程私有):主要描述的是Java方法执行的内存模型,每个方法在执行的同时都会创建一个栈帧,用于存储局部变量表,操作数栈,动态连接,方法出口等信息。

    • 若是线程请求的深度的栈的深度超出了虚拟机所允许的深度,就会抛出StackOverflowError,若是虚拟机栈可以动态拓展,那么就可以申请新的内存,若是申请新的内存失败,则就抛出OutOfMemoryError。
  • 本地方法栈(线程私有):本地方法栈和Java虚拟机栈类似,只是本地方法栈执行的是本地Native方法,而虚拟机栈执行的是Java方法。同样的本地方法栈也会抛出StackOverflowError和OutOfMemoryError这两个异常。

  • Java堆(线程共享):Java堆是Java虚拟机所管理的内存中最大的一块,Java堆是被所有线程共享的一块内存区域,基本上所有的对象实例都在堆上分配内存,堆也是垃圾回收器管理的主要区域。

    • 若是在堆中无法完成内存分配,而且堆也无法再拓展时,将会抛出OutOfMemoryError异常。
  • 方法区(线程共享):方法区用于存储已经被虚拟机加载的类的信息,常量,静态变量,及时编译器编译后的代码等数据。同样的,当其无法满足内存分配的需求时,就会抛出OutOfMemoryError异常。

  • 运行时常量池(线程共享):运行时常量池其实属于方法区的一部分,主要用于存储在编译期生成的各种字面量和符号引用,同样的也会受到OutOfMemoryError的影响。

Java中对象的创建:

对象内存的分配方式:

对象内存的分配方式主要有两大类,一种称之为指针碰撞,另一种称之为空闲列表。

  • 指针碰撞:这是对于规则的内存而言采用的一种内存分配方式。假设Java堆中的内存时绝对规整的,已经使用的内存和空闲内存由中间的一个指针做为分界点的指示器,那么内存分配就是将指针向空闲内存方向移动所需的内存大小即可。具体采用哪种方式主要是取决于垃圾回收机制是否带有压缩功能。

  • 空闲列表:这是对于不规则的内存而言采取的一种内存分配方式。虚拟机通过维护一个空闲列表来记录内存的使用情况,在分配是需要在空闲列表中找到一个所需的内存,并更新空闲列表。

在划分内存时的线程同步问题:

在创建实例对象时需要考虑线程同步问题,因为对于内存的划分是非线程安全的,解决思路主要有两种:一种是直接在内存划分时进行线程同步,另一种是对每一个线程都提前划分一块内存,称为本地线程缓冲,可以先在本地线程的内存上划分,只有当内存不够,需要进行拓展时才进行线程同步,进行内存的增加。

实例对象的初始化:

在已经分配好内存空间之后就需要进行实例对象的初始化,主要操作包含内存空间的初始化为零值,对对象进行必要的设置,执行对象的init方法。只有这样才算是完成了一个对象的创建。

内存回收:

内存回收主要需要解决的三个问题就是:
1. 那些内存需要回收
2. 什么时候需要回收
3. 怎么回收。

判断对象是否可用:

判断对象占用的内存是否需要回收其实就是判断当前的对象是否可用,若是处于可用状态,则不需要进行回收,若是处于不可用状态,则需要进行内存回收。

判断对象是否可用主要分为两种方法:引用计数法和可达性分析法。

  • 引用计数法:每个对象有一个引用计数器,每当有一个地方引用他时,引用计数器就加1;当引用失效时就减1;任意时刻计数器为0的对象就是不可能再被使用的对象。

  • 可达性分析法:以一系列的GC Roots为起点,从这些节点开始往下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链时(图论中的不可达),证明这个对象是不可用的。

    • 可以作为GC Roots的对象:
      • 虚拟机栈(栈帧中的本地变量表)中引用的对象
      • 方法区中类静态属性引用的变量
      • 方法区中常量引用的变量
      • 本地方法栈中JNI(Native方法)引用的变量

四种引用类型:

Java中的引用主要分为四种:强引用,软引用,弱引用,虚引用

  • 强引用:在程序中显示的指定引用的,例如“Object obj = new Object()”,只要强引用还在,垃圾收集器永远不会回收被引用的对象。

  • 软引用:用来描述一些很有用但是并非必须的对象。在系统将要发生内存溢出之前会将软引用的的对象进行标记回收,若是回收之后还没有足够的内存,才会报内存溢出的异常。在Java中的实现类是SoftReference类

  • 弱引用:也是用来描述非必须对象的,被弱引用关联的对象只能生存到下一次垃圾收集发生之前。当垃圾收集器工作时,无论当前内存是否够用都会将弱引用所指向的对象回收。在Java中的实现类是WeakReference。

  • 虚引用:比弱引用的强度更低,一个对象是否有弱引用对其生命周期完全不产生影响,也无法通过弱引用来获取到一个对象的实例,唯一的作用就是当弱引用指向的对象被回收之后会收到一个系统的通知。在Java中的实现类是PhantomReference。

对象被回收的流程:

要真正宣告一个对象完全不可达需要经历两次标记过程:如果对象在进行可达性分析之后发现没有与GC Roots相连接的引用链,那么他将会被第一次标记并且进行一次筛选,筛选的条件是此对象是否有必要去执行finalize()方法(finalize方法只会被虚拟机调用一次)。

筛选的结果:

  • 当对象没有重写finalize()方法,或者是此方法已经被虚拟机调用过,虚拟机将这两种情况都视为”没有必要执行”。

  • 若是这个对象被认为是需要执行finalize()方法,那么这个对象会被放到一个F-Queue的队列中,并在稍后由一个虚拟机自动建立的,优先级比较低的Finalizer线程去执行finalize()方法,但是仅仅是保证会执行该方法,并不保证会等待finalize()方法执行完成。

垃圾收集算法

标记–清除算法:

标记–清除算法主要有“标记”和“清除”两个阶段:首先标记出所有需要回收的对象,然后在标记完成之后统一回收所有被标记的对象。

优缺点:

  • 优点是算法比较简单,易于实现,
  • 缺点是标记和清除两个过程效率都不高,同时内存回收之后会产生大量不连续的内存碎片,浪费内存空间。

复制算法:

复制算法是将内存划分为为大小相等的两块,每次只使用其中的一块。当这一块用完了,就将仍然存活的对象复制到另一块上边,然后把已经使用完的那块内存一次全部清理掉。

优缺点:

  • 优点是解决了标记–清除算法中产生内存碎片的问题,每次都是对半个内存空间进行清除,简单高效。
  • 缺点是将原有的可使用内存缩小了一半,用于进行存活对象的保存。

标记–整理算法:

标记–整理算法和标记–清除算法一样,都是先对可回收对象进行标记,但是不同的是,在标记完成之后是让所有的存活对象都向一端移动,然后直接清理掉端边界以外的内存。

优缺点:

  • 优点是解决了复制算法浪费内存空间的问题,可以将所有的内存空间都利用起来。
  • 缺点是需要对所有存活对象进行移动,需要消耗一定的时间。

分代收集算法:

分代收集算法其实是各种算法的一个组合。根据对象存活周期的不同将内存划分为不同的几个区域,一般是将Java堆划分成新生代和老年代,这样根据各个年龄代的特点采取最适当的算法。例如:在新生代中,每次垃圾回收都有大量对象死去,那么久采用复制算法;而老年代中因为对象存活率高,没有额外空间对他进行分配担保,所以必须使用标记–清理算法或者是标记–整理算法。

优缺点:

  • 优点是可以根据内存中对象的不同状态来进行针对性的回收,有较高的利用率
  • 缺点是使得垃圾回收进行了分类,增加了实现的难度。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值