zoukankan      html  css  js  c++  java
  • java虚拟机:堆内存

    一、简介

    Java 堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。这个区域是用来存放对象实例的,几乎所有对象实例都会在这里分配内存。堆是Java垃圾收集器管理的主要区域(GC堆),垃圾收集器实现了对象的自动销毁。Java堆可以分为:新生代和老年代。Java堆可以处于物理上不连续的内存空间中,只要逻辑上是连续的即可,就像我们的磁盘空间一样。可以通过-Xmx和-Xms控制

    二、heap分代

    由于GC需要消耗一些资源和时间的,Java在对对象的生命周期特征进行分析后,采用了分代的方式来进行对象的收集,即按照新生代、旧生代的方式来对对象进行收集,以尽可能的缩短GC对应用造成的暂停


    heap 的组成有三区域/世代:(可以理解随着时间,对象实例不断变换heap中的等级,有点像年级)

    1. 新生代 Young Generation

      • Eden Space 任何新进入运行时数据区域的实例都会存放在此

      • S0 Suvivor Space 存在时间较长,经过垃圾回收没有被清除的实例,就从Eden 搬到了S0

      • S1 Survivor Space 同理,存在时间更长的实例,就从S0 搬到了S1

    2. 旧生代 Old Generation/tenured

      同理,存在时间更长的实例,对象多次回收没被清除,就从S1 搬到了tenured

    3. Perm 存放运行时数据区的方法区

    三、对象新建

    new的对象绝大部分会放在eden区,当出现大对象的时候,jvm用牺牲部分宝贵的old区的方式来保证了整个jvm的正常运转,可以通过参数-XX:PretenureSizeThreshold设置新建对象的最大值,当新对象申请的内存空间大于这个参数值的时候,直接扔到old区。

    堆是所有线程共享的,因此在堆上分配内存需要加锁,Sun JDK为提升效率,会为每个新建的线程在Eden上分配一块独立的空间由该线程独享,这块空间称为TLAB(Thread Local Allocation Buffer)。其大小由JVM根据运行情况计算得到,也可通过参数-XX:TLABWasteTargetPercent来设置TLAB可占用的Eden空间的百分比,默认值为1%。在TLAB上分配内存不需要加锁,因此JVM在给线程中的对象分配内存时会尽量在TLAB上分配。如果对象过大或TLAB用完,则仍然在堆上进行分配。

    四、对象移动与内存回收

    不同的世代使用不同的 GC 算法

    1. Minor collection:新生代 Young Generation 使用将 Eden 还有 Survivor 内的数据利用 semi-space 做复制收集(Copying collection), 并将原本 Survivor 内经过多次垃圾收集仍然存活的对象移动到 Tenured。

    2. Major collection 则会进行 Minor collection,Tenured 世代则进行标记压缩收集。

    这个搬运工作都是GC 完成的,这也是garbage collector 的名字来源,而不是叫garbage cleaner. GC负责在heap中搬运实例,以及收回存储空间。 

    1)eden到survivor

    在java设计者看来,新生成的对象绝大部分对象都是需要被销毁掉的,就像在做java WEB应用上一样,一个列表请求过来,可能请求的内容有2K的内容,请求完成后,这个内容一般说来自然就不需要了,也就是在他原始的考虑下它没有考虑你自己在应用级别去做page cache的操作。

    那么当内存不够的时候,就是会对Young空间进行回收,由于新生成的对象,java认为这块空间不会很大,而且绝大部分应该是被回收的内容,所以很多时候java会采用单线程的复制算法(当然你也可以设置为多线程),将其拷贝到其中一个survivor区域中,当下一次做操作时,就会将Eden中活着的以及前一个surivor活着的一起拷贝到另一个survivor中,这就是为什么要设置两个survivor区域,而拷贝后,Eden区域为空、另一个survivor也为空,可以完全直接整体清除掉,所以非常快速,而拷贝的目标也会被连续化,新生成的对象又从Eden的初始位置开始分配空间。

    2)移动到Old区

    在说明下,以下三种情况对象会被晋升到old区域:

    1、在eden和survivor中可以来回被minor gc多次,这个次数超过了-XX:MaxTenuringThreshold。

    2、在发生minor gc时,发现to survivor无法放下这些对象,就会进入old

    3、在新申请对象,大于eden区域的一半大小时直接进入old,也可以专门设置参数-XX:PretenureSizeThreshold这个参数指定当超过这个值就直接进入old。

    当上面的对象被移动到了Tenured区域,这个区域一般非常大,占用了HeapSize的绝大部分空间,此时若它发生一次内存回收,就不能像刚才那样来回拷贝了,那样代价太大,而且这个区域可以说是经得起考验的对象才会被移动过来,在概率上是不容易被销毁掉的对象才会被移动过来。

    五、堆内存参数设置

    JVM初始分配的内存由-Xms指定,默认是物理内存的1/64但小于1G,JVM最大分配的内存由-Xmx指定,默认是物理内存的 1/4但小于1G。

    默认空余堆内存小于40%时,JVM就会增大堆直到-Xmx的最大限制,可以由-XX:MinHeapFreeRatio=指 定。
    默认空余堆内存大于70%时,JVM会减少堆直到-Xms的最小限制,可以由-XX:MaxHeapFreeRatio=指定。

    这样收缩和扩展必然导致一些问题,但是java的初衷是想让你再没有使用这块地址表的时候,回收内存的大小会小一些,因为young区域的一般是使用单线程的回收方式,这个时间段是会被暂停的,所以它认为内存使用较少的时候回收就内存的速度应该加快;另个一问题是若Xms设置的足够大,内存长时间不回收,将会产生大量碎片。

    但是,和实际相反的是,服务器一般占用内存都较大,需要的是内存使用较大的时候,加快回收的速度,内存使用小的时候,回收都是无所谓的;所以我们在很多时候建议将-Xms和-Xmx设置成一样的大小,不用这么来回倒腾。

  • 相关阅读:
    指针、数组和结构体的一些思考
    Leetcode589.N-ary Tree Preorder TraversalN叉树的前序遍历
    Leetcode563.Binary Tree Tilt二叉树的坡度
    Leetcode559.Maximum Depth of N-ary TreeN叉树的最大深度
    Leetcode561.Array Partition I数组拆分1
    Leetcode551.Student Attendance Record I学生出勤记录1
    Leetcode543.Diameter of Binary Tree二叉树的直径
    Leetcode520Detect Capital检测大写字母
    Leetcode532.K-diff Pairs in an Array数组中的K-diff数对
    Leetcode496.Next Greater Element I下一个更大的元素1
  • 原文地址:https://www.cnblogs.com/xiaotian15/p/7008537.html
Copyright © 2011-2022 走看看