美文网首页
Java内存结构和垃圾回收

Java内存结构和垃圾回收

作者: 亮晶晶满天星 | 来源:发表于2017-10-19 18:06 被阅读0次

    内存结构

    java内存结构主要有三大区块:栈内存,堆内存,堆外内存(直接内存)。其中:

    1.栈内存主要是存放线程的栈信息,每个线程有独立的线程栈。

    2.堆内存有元信息区和对象区,对象区则采用分代的策略,便于垃圾回收。

    3.元信息区主要存放类信息,静态变量信息。

    4.此外还有堆外内存,这部分内存不受垃圾回收器的管理,其内存管理自成另一套体系。

    所有的内存空间之和不能超过物理内存,以及操作系统规定的内存限制。

    image.png

    栈内存

    每个线程拥有独立的线程栈。占中存放线程的调用信息,对象引用,以及基本类型的对象(大小确定的,如int ,short, long, byte, float, double,boolean,char)。

    可以通过-Xss 指定每个线程栈的大小,默认会使用jdk自带的大小。

    当递归层数过多,或者线程的对象应用过多,可能造成栈内存不足的报错:StackOverFlowError.

    堆内存

    存放通过new 创建的对象实例。所有线程共享。运行时动态分配内存。jvm会监控对象实例的引用状态等,并采用垃圾回收机制,尽可能优化堆内存的使用,释放无用的对象实例。

    垃圾回收

    jvm通过垃圾回收来回收堆和方法区中的内存,基本原理就是找到城中不再被使用的对象,然后回收掉这些对象占用的内存。自动回收机制,以使得程序员摆脱内存管理的繁杂事务。垃圾回收有可达性检测,整理无用对象实例,以及分代/分区思想等内容。

    可达性检测

    即检测对象是否不再被使用。主要有引用计数和跟踪计数两种。

    1. 引用计数器记录对象是否被引用,当计数器为0时,说明对象已经不再被使用,可以回收。但是java中的引用关系非常复杂,存在循环引用的情况,因此引用计数器不适合。

    2. 跟踪计数:执行的时候,从根集合开始扫描对象的引用关系,从节点向下搜索,如果一个对象到根集合没有引用链,那么该对象是不可用的。根集合主要值:虚拟机栈中引用的对象、方法区中类静态属性引用的休想、方法区中敞亮引用的对象、本地方法栈中jni引用的对象。

    整理策略

    1. 复制:从根集合中扫描出存活的对象,然后将存活的对象复制到一块新的未使用空间。当存活对象较少时,比较高效。
    2. 标记清除:从根集合开始扫描,标记存活的对象。再扫描整个空间未标价的对象,然后回收,对象无需移动,高效但是有碎片。
    3. 标记压缩:标记形式与“标记清除”一样,但是回收不存在的对象后,会做一次内存整理。成本高但是无碎片。

    分代、分区思想

    分代和分区思想,将堆内存区分管理,对不同的区块进行不同策略的回收机制,使得生命周期短的对象能够快速被收回,生命周期长的对象能够在适当的时候做统一回收。

    年轻代,年老代的设置即时分代和分区思想的一种实现。

    垃圾回收器

    1. 串行收集器(Serial):指的是使用一个单线程进行垃圾收集,工作的时候会暂停其他工作线程。

    2. 并行收集器(Parallel):使用多线程完成垃圾收集,工作的时候会暂停其他工作线程。

    3. CMS收集器:并发标记清除,少量时间会暂停其他工作线程,其余时间与工作线程共同执行。

    4. G1收集器:垃圾优先收集器,设计初衷是尽量缩短超大堆(大于4G)时产生的停顿,相对于cms来说内存碎片大大降低,但是也会存在可能会造成停顿时间较长。jdk 1.7u4以上可以使用。不过目前线上还没有使用这种策略。

    堆外内存

    将对象分配到java虚拟机以外的内存,这部分内存直接受操作系统管理,可以减少受到垃圾回收对应用程序造成的影响。使用java.nio.DirectByteBuffer对象进行堆外内存的管理和使用。

    为什么要有堆外内存

    减少垃圾回收;加快复制到远程的速度:堆内存在flush到远程时,会先复制到直接内存,然后再发送,而堆外内存少了复制这一步。

    堆外内存的问题

    内存难以控制

    gc触发时机的问题

    何时触发minor gc

    1. 当eden区分配内存,发现空间不足,jvm就会触发minor gc
    2. 程序中调用Sysetm.gc()

    何时触发cms gc

    1. 老年代或者持久代已经使用的空间达到设定的百分比时(CMSInitiatingOccupancyFraction这个设置old区,perm区也可以设置);
    2. JVM自动触发(JVM的动态策略,也就是悲观策略)(基于之前GC的频率以及旧生代的增长趋势来评估决定什么时候开始执行),如果不希望JVM自行决定,可以通过-XX:UseCMSInitiatingOccupancyOnly=true来禁用

    何时触发full gc

    1. 老年代空间不足:java.lang.OutOfMemoryError: java heap space
    2. Perm空间不足:java.lang.OutOfMemoryError: PermGen space
    3. cms gc 时出现 promotion failed 和 concurrent mode failure(cms正在进行,但是老年代内存不足,需要尽快回收老年代的对象)
    4. 统计得到的minor gc 晋升到老年代的平均大小大于老年代的剩余空间
    5. 主动触发 full gc : 执行 jmap -histo:live [pid]

    关于内存管理的jvm 参数设置

    大小

    • -Xms5g 最小堆内存
    • -Xmx5g 最大堆内存,建议与最小堆内存一致,避免jvm动态调整
    • -Xmn2g 新生代内存
    • -XX:MetaspaceSize=512m jdk8 中元信息的空间大小
    • -XX:MaxMetaspaceSize=512m jdk8 中元信息的最大空间大小
    • -XX:MaxDirectMemorySize=1g 直接内存的最大空间,超过空间,会执行一次内存回收。如果不设置,会和-Xmx基本差不多,导致直接内存和堆内存相加超过物理内存,报内存空间不足的错误。
    • -XX:SurvivorRatio=8 eden和suvivor的比例。例如设置为8,则eden:s0:s1的比例为8:1:1

    垃圾回收器策略

    • -XX:+UseConcMarkSweepGC 使用cms(并发标记消除)进行老年代的回收,默认新生代采用ParNew回收
    • -XX:+UseParallelGC 使用并行
    • -XX:+UseSerialGC 串行gc

    其他

    cms相关的参数

    • -XX:CMSInitiatingOccupancyFraction=75 内存使用率达到75%后,开始cms收集
    • -XX:+UseCMSInitiatingOccupancyOnly 禁用jvm cms的悲观策略,仅根据内存使用率来触发cms
    • -XX:+UseCMSCompactAtFullCollection cms是消除策略,有内存碎片。指定该参数后,在fgc后会整理碎片。
    • -XX:CMSMaxAbortablePrecleanTime =5000
    • -XX:+CMSClassUnloadingEnabled 方法区使用cms,允许对类的元数据进行回收
    • -XX:CMSPermGenSweepingEnabled 方法区使用cms

    gc日志相关参数

    • -Xloggc=/gc.log gc日志输出的地址
    • -XX:+PrintGCDetails 开启详细gc模式
    • -XX:+PrintGCDateStamps 每一行前面加上绝对时间戳
    • -XX:+HeapDumpOnOutOfMemoryError 内存溢出时输出堆日志
    • -XX:HeapDumpPath=/heap.hprof 堆日志输出地址

    参考资料:
    [1]【原】java内存区域理解-初步了解 http://iamzhongyong.iteye.com/blog/1333100
    [2] Java HotSpot VM Options http://www.oracle.com/technetwork/java/javase/tech/vmoptions-jsp-140102.html#Options

    相关文章

      网友评论

          本文标题:Java内存结构和垃圾回收

          本文链接:https://www.haomeiwen.com/subject/fwkpuxtx.html