热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

JVM之(7)内存分配

堆内存划分为新生代(Eden空间、Survivor空间)和老年代(TenuredOld空间)。1.对象优先在Eden分配大

堆内存划分为 新生代(Eden空间、Survivor空间)和 老年代(Tenured/Old 空间)。



1.对象优先在Eden分配

大多是情况下,对象在新生代Eden区中分配。当Eden区中没有足够空间进行分配时,虚拟机将发起一次Minor GC



-verbose:gc  -XX:+PrintGCDetails  -XX:+PrintGCTimeStamps 打印gc日志
 -Xms20M -Xmx20M 指定堆内存
-Xmn10M 指定新生代内存
-XX:SurvivorRatio=8 指定Eden区与一个Survivor区的空间比例是8:1。

private static final int _1MB = 1024 * 1024;

public static void main(String[] args) {byte[] allocation1 = new byte[2 * _1MB];
byte[] allocation2 = new byte[2 * _1MB];
byte[] allocation3 = new byte[2 * _1MB];
byte[] allocation4 = new byte[4 * _1MB];
}

[GC (Allocation Failure) [PSYoungGen: 6420K->1010K(9216K)] 6420K->3594K(19456K), 0.0157046 secs] [Times: user=0.00 sys=0.00, real=0.02 secs]
HeapPSYoungGen total 9216K, used 5427K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)eden space 8192K, 53% used [0x00000000ff600000,0x00000000ffa50630,0x00000000ffe00000)from space 1024K, 98% used [0x00000000ffe00000,0x00000000ffefc888,0x00000000fff00000)to space 1024K, 0% used [0x00000000fff00000,0x00000000fff00000,0x0000000100000000)ParOldGen total 10240K, used 6680K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)object space 10240K, 65% used [0x00000000fec00000,0x00000000ff286298,0x00000000ff600000)Metaspace used 3305K, capacity 4500K, committed 4864K, reserved 1056768Kclass space used 357K, capacity 388K, committed 512K, reserved 1048576K

    PSYoungGen     使用了Parallel收集器,关于JVM垃圾收集器漫谈,可以参考《JVM 之(5)垃圾收集器》。

分配allocation4对象的语句会发生一次MinorGC,给allocation4分配内存时,发现Eden已经被占用了6MB,剩余空间已不足分配all4所需的4MB内存,因此发生一次MinorGC。

    GC期间,虚拟机又发现已有的3个2MB大小的对象全部无法放入Survivor空间(Survivor空间只有1MB大小),所以只好通过分配担保机制提前分配到老年代去。

    GC结束后,4MBallocation4对象顺利分配在Eden中,Survivor空闲,老年代被占用6MB。


2.大对象直接进入老年代

    对于体积较大的对象,直接进入老年代区域而不是分配到新生代。
    JVM参数-XX:PretenureSizeThreshold的意思就是将体积大于这个设置值的对象直接在老年代分配。 这样做是为了避免在Eden区及两个Survivor区之间发生大量的内存复制。
    PretenureSizeThreshold参数只对 Serial 和 ParNew两款收集器有效



private static final int _1MB = 1024 * 1024;

public static void main(String[] args) {byte[] allocation = new byte[7 * _1MB];
}

PSYoungGen total 9216K, used 4537K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)eden space 8192K, 55% used [0x00000000ff600000,0x00000000ffa6e478,0x00000000ffe00000)from space 1024K, 0% used [0x00000000fff00000,0x00000000fff00000,0x0000000100000000)to space 1024K, 0% used [0x00000000ffe00000,0x00000000ffe00000,0x00000000fff00000)ParOldGen total 10240K, used 7168K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)object space 10240K, 70% used [0x00000000fec00000,0x00000000ff300010,0x00000000ff600000)Metaspace used 3326K, capacity 4500K, committed 4864K, reserved 1056768Kclass space used 361K, capacity 388K, committed 512K, reserved 1048576K



3.长期存活的对象将进入老年代

    既然虚拟机采用了分代收集的思想来管理内存,那么内存回收时就必须能识别哪些对象应放在新生代,哪些对象应放在老年代中。 
为了做到这点,虚拟机给每个对象定义了一个对象年龄(Age)计数器。 如果对象在Eden出生并经过第一次Minor GC后仍然存活,并且能被Survivor容纳的话,将被移动到Survivor空间中,并且对象年龄设为1。 
    对象在Survivor区中每“熬过”一次Minor GC,年龄就增加1岁,当它的年龄增加到一定程度(默认为15岁),就将会被晋升到老年代中。
对象晋升老年代的年龄阈值,可以通过参数-XX:MaxTenuringThreshold设置。

4.对象年龄的动态判定

    为了能更好地适应不同程序的内存状况,虚拟机并不是永远地要求对象的年龄必须达到了MaxTenuringThreshold才能晋升老年代。 
    如果在Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代,无须等到MaxTenuringThreshold中要求的年龄。



5. 空间分配担保
    在发生Minor GC之前,虚拟机会先检查老年代最大可用的连续空间是否大于新生代所有对象总空间,如果这个条件成立,那么Minor GC可以确保是安全的。 

    如果不成立,则虚拟机会查看
-
XX:HandlePromotionFailure设置值是否允许担保失败。 

    如果允许,那么会继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小,如果大于,将尝试着进行一次Minor GC,尽管这次Minor GC是有风险的; 

    如果小于,或者-XX:HandlePromotionFailure设置不允许冒险,那这时也要改为进行一次Full GC。

    触发Full GC执行的情况:
        1. System.gc();
        2 . 老年代空间不足
        3. Permanet Generation空间满
        4. CMS GC时出现promotion failed和concurrent mode failure(《JVM 之(5)垃圾收集器》中提到)
        5.  统计得到的Minor GC晋升到老年代的平均大小大于旧生代的剩余空间




6. 栈上分配与逃逸分析(Escape Analysis)


1.  什么是栈上分配?

   栈上分配主要是指在Java程序的执行过程中,在方法体中声明的变量以及创建的对象,将直接从该线程所使用的栈中分配空间。 一般而言,创建对象都是从堆中来分配的,这里是指在栈上来分配空间给新创建的对象。





2.  什么是逃逸?

   逃逸是指在某个方法之内创建的对象,除了在方法体之内被引用之外,还在方法体之外被其它变量引用到;这样带来的后果是在该方法执行完毕之后,该方法中创建的对象将无法被GC回收,由于其被其它变量引用。正常的方法调用中,方法体中创建的对象将在执行完毕之后,将回收其中创建的对象;故由于无法回收,即成为逃逸。





public class StackAllocation {public StackAllocation obj;

/**
* 方法返回StackAllocation对象,发生逃逸
* @return
*/
public StackAllocation getInstance(){return obj == null ? new StackAllocation() : obj;
}/**
* 为成员变量赋值,发生逃逸
*/
public void setObj(){this.obj = new StackAllocation();
}/**
* 对象作用域在方法体内,没有发生逃逸
*/
public void useStackAllocation(){StackAllocation stackAllocation = new StackAllocation();
}/**
* 引用成员变量,发生逃逸
*/
public void useStackAllocationObj(){StackAllocation stackAllocation = getInstance();
}
}


3.  逃逸分析   
    在JDK 6之后支持对象的栈上分析和逃逸分析,在JDK 7中完全支持栈上分配对象。 其是否打开逃逸分析依赖于以下JVM的设置:-XX:+DoEscapeAnalysis  

4. 栈上分配与逃逸分析的关系  
    进行逃逸分析之后,产生的后果是所有的对象都将由栈上分配,而非从JVM内存模型中的堆来分配 。


5. 逃逸分析/栈上分配的优劣分析

     优势表现在以下两个方面:

  •    消除同步。线程同步的代价是相当高的,同步的后果是降低并发性和性能。逃逸分析可以判断出某个对象是否始终只被一个线程访问,如果只被一个线程访问,那么对该对象的同步操作就可以转化成没有同步保护的操作,这样就能大大提高并发程度和性能。
  •  矢量替代。逃逸分析方法如果发现对象的内存存储结构不需要连续进行的话,就可以将对象的部分甚至全部都保存在CPU寄存器内,这样能大大提高访问速度。

      劣势:  栈上分配受限于栈的空间大小,一般自我迭代类的需求以及大的对象空间需求操作,将导致栈的内存溢出;故只适用于一定范围之内的内存范围请求。













推荐阅读
  • 先看官方文档TheJavaTutorialshavebeenwrittenforJDK8.Examplesandpracticesdescribedinthispagedontta ... [详细]
  • Java中包装类的设计原因以及操作方法
    本文主要介绍了Java中设计包装类的原因以及操作方法。在Java中,除了对象类型,还有八大基本类型,为了将基本类型转换成对象,Java引入了包装类。文章通过介绍包装类的定义和实现,解答了为什么需要包装类的问题,并提供了简单易用的操作方法。通过本文的学习,读者可以更好地理解和应用Java中的包装类。 ... [详细]
  • Iamtryingtomakeaclassthatwillreadatextfileofnamesintoanarray,thenreturnthatarra ... [详细]
  • Java容器中的compareto方法排序原理解析
    本文从源码解析Java容器中的compareto方法的排序原理,讲解了在使用数组存储数据时的限制以及存储效率的问题。同时提到了Redis的五大数据结构和list、set等知识点,回忆了作者大学时代的Java学习经历。文章以作者做的思维导图作为目录,展示了整个讲解过程。 ... [详细]
  • 阿,里,云,物,联网,net,core,客户端,czgl,aliiotclient, ... [详细]
  • 本文讨论了一个关于cuowu类的问题,作者在使用cuowu类时遇到了错误提示和使用AdjustmentListener的问题。文章提供了16个解决方案,并给出了两个可能导致错误的原因。 ... [详细]
  • 纠正网上的错误:自定义一个类叫java.lang.System/String的方法
    本文纠正了网上关于自定义一个类叫java.lang.System/String的错误答案,并详细解释了为什么这种方法是错误的。作者指出,虽然双亲委托机制确实可以阻止自定义的System类被加载,但通过自定义一个特殊的类加载器,可以绕过双亲委托机制,达到自定义System类的目的。作者呼吁读者对网上的内容持怀疑态度,并带着问题来阅读文章。 ... [详细]
  • 本文讨论了在VMWARE5.1的虚拟服务器Windows Server 2008R2上安装oracle 10g客户端时出现的问题,并提供了解决方法。错误日志显示了异常访问违例,通过分析日志中的问题帧,找到了解决问题的线索。文章详细介绍了解决方法,帮助读者顺利安装oracle 10g客户端。 ... [详细]
  • 开发笔记:加密&json&StringIO模块&BytesIO模块
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了加密&json&StringIO模块&BytesIO模块相关的知识,希望对你有一定的参考价值。一、加密加密 ... [详细]
  • 本文介绍了OC学习笔记中的@property和@synthesize,包括属性的定义和合成的使用方法。通过示例代码详细讲解了@property和@synthesize的作用和用法。 ... [详细]
  • JavaSE笔试题-接口、抽象类、多态等问题解答
    本文解答了JavaSE笔试题中关于接口、抽象类、多态等问题。包括Math类的取整数方法、接口是否可继承、抽象类是否可实现接口、抽象类是否可继承具体类、抽象类中是否可以有静态main方法等问题。同时介绍了面向对象的特征,以及Java中实现多态的机制。 ... [详细]
  • 本文介绍了Python爬虫技术基础篇面向对象高级编程(中)中的多重继承概念。通过继承,子类可以扩展父类的功能。文章以动物类层次的设计为例,讨论了按照不同分类方式设计类层次的复杂性和多重继承的优势。最后给出了哺乳动物和鸟类的设计示例,以及能跑、能飞、宠物类和非宠物类的增加对类数量的影响。 ... [详细]
  • 本文介绍了深入浅出Linux设备驱动编程的重要性,以及两种加载和删除Linux内核模块的方法。通过一个内核模块的例子,展示了模块的编译和加载过程,并讨论了模块对内核大小的控制。深入理解Linux设备驱动编程对于开发者来说非常重要。 ... [详细]
  • 本文介绍了在处理不规则数据时如何使用Python自动提取文本中的时间日期,包括使用dateutil.parser模块统一日期字符串格式和使用datefinder模块提取日期。同时,还介绍了一段使用正则表达式的代码,可以支持中文日期和一些特殊的时间识别,例如'2012年12月12日'、'3小时前'、'在2012/12/13哈哈'等。 ... [详细]
  • Java SE从入门到放弃(三)的逻辑运算符详解
    本文详细介绍了Java SE中的逻辑运算符,包括逻辑运算符的操作和运算结果,以及与运算符的不同之处。通过代码演示,展示了逻辑运算符的使用方法和注意事项。文章以Java SE从入门到放弃(三)为背景,对逻辑运算符进行了深入的解析。 ... [详细]
author-avatar
隐阁6090j
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有