java内存布局与GC

java数据区域

java虚拟机会在执行java的过程中,将其所管理的区域划分为几个部分用于管理,每个区域都有自己独立的生命周期。这些区域包括

  • Program Counter Register (程序计数器区)
  • Java Virtual Machine Stacks (Java虚拟机栈)
  • Native Method Stack (本地方法栈)
  • Java Heap (堆区)
  • Method Area(方法区)
  • Runtime Constant Pool(运行时常量池)
  • Direct Memory(直接内存)

接下来分别介绍以上区域。

Program Counter Register (程序计数器区)

程序计数器是执行字节码的指示器,指示当前需要执行的字节码。由于计数器指明了当前要执行的代码,在多线程的环境中为了线程能在切换的过程中记录上次执行的位置,所以每一个线程都会有一个独立的程序计数器。

Java Virtual Machine Stacks (Java虚拟机栈)

在Java方法执行的时候,会先创建一个栈帧(Stack Frame)用于存储局部变量,操作数栈,动态链接,方法出口等信息。和Program Counter Register一样,Java Virtual Machine Stacks也是线程私有的。

Native Method Stack (本地方法栈)

Native Method Stack在功能上和Java Virtual Machine Stacks是一致的,唯一的不同在于Native Method Stack用于给Native方法调用的时候申请。

Java Heap (堆区)

Java Heap是虚拟机中占用内存最大的区域。与之前的不一样,Java Heap的内存是所有线程共享的,此区域用于存放对象的实例,几乎所有的对象都在此处分配。GC主要就是发生在这个区域。该区域粗分可以分为:新生代和老年代。细分可以有Eden空间,From Survivor和To Survivor空间等。根据java虚拟机的规范,Java Heap的内存空间可以是不连续的,只要逻辑上连续就可以了。

Method Area

和Java Heap一样Method Area是各个线程共享的,它存储了虚拟机加载的类信息,常量,静态变量等数据。Method Area的别名叫Non-Heap。

Runtime Constant Pool(运行时常量池)

Runtime Constant Pool是Method Area的一部分,专门用来存储常量。

Direct Memory(直接内存)

这块内存很特别,只在NIO中用到,用于避免数据在java堆和native堆中来回切换,用于提高NIO性能。

对象

对象的创建

对象的创建要经过定位类信息和分配内存,以及对象信息填充三个步骤,才能产生一个可用的对象。

类符号的定位

当使用new创建对象的时候,会先在常量池中尝试是否能定位到类的符号引用,然后检查该类是否已经被加载,解析和初始化过。如果没有,则初始化类加载。最后才是对象的创建。

分配内存

分配内存的方式有两种,一种是Bump the Point,另一种是Free List。

Bump the Point 指针碰撞

当Java Heap中的内存是规整的,也即使用的内存和未使用的内存规整的置于Heap的两端,中间一个指示器Postion指明分割点。分配内存的过程,就是指示器向未使用的内存移位的过程。这样的分配方式称为Bump
the Point

Free List

实际使用的场景中,Java Heap并不总是规整的,当使用的内存和未使用的内存交错的时候,就很难通过Bump the Point来简单分配内存了。这个时候就需要虚拟机维护一个列表,记录哪些地方内存是可用的,当需要内存的时候,虚拟机分配一块内存,然后更新这个列表。这样的分配方式称为Free List。

分配内存的并发问题

由于Java Heap是所有线程共享的,如果不对分配的过程加以控制,几乎一定会有并发问题的。虚拟机解决并发问题用的手段比较常规。主要有两种

  • CAS 操作配合重试
  • 使用Thread Local Allocation Buffer(TLAB),也就是每个线程预先分配一块内存作为缓冲区,只有当TLAB使用完的时候才使用同步锁定重新分配。

对象初始化内容填充

在给对象分配完内存之后,需要给对象填充基本的数据信息,在HotSpot虚拟机中,对象的布局分为:Header,Instance Data,Padding,三个区域。

Header 对象头

对象头部内部还分为两个部分。第一个用于存储对象运行时的信息,包括哈希码,GC分代年龄,锁状态标志,线程持有的锁,偏向的线程ID,偏向时间戳等信息。另一部分存储的是类型指针,用于指明对象是哪个类的实例。

Instance Data 实例数据

存储程序代码中声明的各个类型字段的内容,也包括从超类继承的数据。

Padding 对齐填充区

由于HotSpot的虚拟机要求对象的起始位置必须是8字节的整数倍,当一个对象的大小不是8字节的时候,为了使得下一个对象的起始位置在8字节的整数倍,会给对象填充字节,使得对象能占用最近的8字节整数倍的位置上,这个填充的字节就是Padding。

对象的访问

对象创建好了之后,接下来要做的就是对对象的访问。在我们使用对象的时候,都是通过reference的方式访问对象,但是落实到细节,则有两种对象的访问模式。

句柄访问

reference持有的是一个句柄,虚拟机维护一个句柄池,池中存储对象的位置信息。当数据改变的时候,只需要改变句柄,而不需要改变reference本身。

直接使用指针

如名称一样,直接使用指针,直接使用指针的好处,在于减少减少了一层间接访问,提高了性能。

java GC

无用的对象在使用之后,要通过Garbage Collection释放,否则会导致无内存资源的境况。GC要做的事情的事情,包括确定需要GC的对象,对对象进行进行GC,然后整理内存。

什么对象应该被GC

在开始GC前,虚拟机需要确定哪些对象是应该被GC的,确定的方法主要有两种。

  • Reference Counting 引用计数法。每当对象被引用的时候,计数器就+1,当引用失效的时候就-1。当计数器为0的时候,意味着对象应该被GC了。不过Reference Counting有个缺陷,就是无法处理循环引用的问题。
  • Reachability Analysis 可达性分析算法。从当前所有的GC Roots开始搜索被引用的对象,所有可以被触达的对象组合成一条Reference Chain,而当一个对象不在任一条Reference Chain的时候,则该对象是需要被回收的。

    当一个对象满足以下至少一个条件的时候,我们称其为GC Roots。
    1. 虚拟机栈中引用额对象。
    2. 方法区中类静态属性引用的对象。
    3. 方法区中常量引用的对象。
    4. 本地方法栈中JNI(Native)引用的对象。
    

引用的分类

在JDK1.2之后,java对应用添加分类。分为了四种级别。

  • Strong Reference 强引用。类似a.b = new BClass();即为强引用。
  • Soft Reference 软引用,用于描述有用但是非必需的对象,当系统内存将要溢出的时候,才会尝试将这些对象回收。
  • Weak Reference 弱引用,用于描述非必需的对象,在发生GC的时候,弱引用的对象会直接被清理掉。
  • Phantom Reference 虚引用,虚引用不会对一个对象的生命周期造成任何影响,唯一的作用在于当虚引用关联的对象对回收的时候,可以收到一个系统通知。

回收的过程

一般来说,对象的回收会经历如下过程。
这里写图片描述
这里判断是否需要执行finalize的条件为,当满足下面两个条件的时候才认定为需要执行finalize方法。
* 对象覆盖了finalize方法
* 对象的finalize方法还未被调用过
对象在finalize中,可以做到加入到GC Roots中,暂时逃过被清除的命运,但是当对象第二次被标记回收的时候,由于finalize已经被掉用过一次了,所以会被直接删除。

    一般来说,为了减少对主进程的影响,Finalizer线程通常的优先级是很低的,并且虚拟机并不
    会等待Finalizer的执行,主要是为了防止finalize中出现了执行缓慢的方法或者死循环,导
    致F-Queue中的对象一直在等待。

垃圾回收算法

垃圾回收算法有很多种,这里只讨论最常见的几种方案的思想。

Mark-Sweep 标记 - 清除 法

将标记的内存,置为空闲的状态,用于下次内存分配的时候使用。这个算法简单,但是造成的问题是,容易使得内存被碎片化,在频繁的内存分配后,很难找到大块的可分配的区域。

Copying 复制算法

Mark - Sweep算法的问题除了使得内存不连续外,另一个问题在于标记和清除的过程耗费的时间一般是较多的。而Copying算法的方案是,先将可用的内存分为两块,每次只使用其中一块,当需要回收内存的时候,将之前那块中存活的对象依次复制到另一块内存上,然后将之前那块内存完全清理,然后使用新的内存块。

  • 优点:速度快,内存不会被碎片化
  • 缺点:浪费了一般的内存空间

Mark - Compact 标记 - 整理算法

Mark - Compact 算法在Mark - Sweep算法的基础上做了改进,不同于Mark - Sweep算法将可回收的对象做清理,Mark - Compact算法,将所有可用的对象向同一端移动,所有对象移动完成后,清理掉边界外的对象。

  • 优点:空间利用率高
  • 缺点:性能较低

Generational Collection 分代收集算法

所谓Generational Collection,其实就是根据内存区块不同年代的特点,选择不同的回收算法。一般将内存区块分为新生代和老年代,一般来说新生代回收率高,对于这样的区块使用Copying算法,而对于老年代由于回收率低,所以使用Mark - Compact或者Mark - Sweep算法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值