一、导论

  java技术体系中所提到的内存自动化管理归根结底就是内存的分配与回收两个问题,之前已经和大家谈过java回收的相关知识,今天来和大家聊聊java对象的在内存中的分配。通俗的讲,对象的内存分配就是在堆上的分配,对象主要分配在新生代的Eden上(关于对象在内存上的分代在垃圾回收中会补上,想了解的也可以参考《深入理解java虚拟机》),如果启动了本地线程分配缓冲,讲按线程优先在TLAB上分配。少数情况下也是直接在老年代中分配。

二、经典的分配策略

1、对象优先在Eden上分配

  一般情况下对象都是优先分配在Eden上,当Eden没有足够的空间进行分配时,jvm会发起一次Minor GC。如果还是没有足够的空间分配,后面还有另外的措施,下面会提到。

  设置虚拟机的偶记日志参数-XX:+PrintGCDetails,在垃圾回收的时候会打印内存的回收日志,并且在进程退出的时候会输出当前内存各区域的分配情况。下面来看下具体的例子,首先需要设置jvm的参数-Xms20m -Xmx20m -Xmn10m,这三个参数说明java堆大小为20M,且不可扩展,其中10M分配给新生代,剩下的10M分配给老年代。-XX:SurvivorRatio=8是jvm默认的新生代中Eden和Survivor比例,默认为8:1。原因是新生代中的对象98%都会在下一次GC的时候回收掉,所以很适合采用复制算法进行垃圾回收,所以新生代10M的内存中,8M是Eden,1M是Survivor,另外的1M是未使用配合复制算法的内存块,也是Survivor。

 public class ReflectTest {

     private static final int _1MB = 1024*1024;

     public static void testAllocation(){
byte[] allocation1 , allocation2 , allocation3 , allocation4;
allocation1 = new byte[2 * _1MB];
allocation2 = new byte[2 * _1MB];
allocation3 = new byte[2 * _1MB];
allocation4 = new byte[6 * _1MB];
} public static void main(String[] args) {
ReflectTest.testAllocation();
} }

输出如下

Heap
PSYoungGen total 9216K, used 6651K [0x000000000b520000, 0x000000000bf20000, 0x000000000bf20000)
eden space 8192K, 81% used [0x000000000b520000,0x000000000bb9ef28,0x000000000bd20000)
from space 1024K, 0% used [0x000000000be20000,0x000000000be20000,0x000000000bf20000)
to space 1024K, 0% used [0x000000000bd20000,0x000000000bd20000,0x000000000be20000)
PSOldGen total 10240K, used 6144K [0x000000000ab20000, 0x000000000b520000, 0x000000000b520000)
object space 10240K, 60% used [0x000000000ab20000,0x000000000b120018,0x000000000b520000)
PSPermGen total 21248K, used 2973K [0x0000000005720000, 0x0000000006be0000, 0x000000000ab20000)
object space 21248K, 13% used [0x0000000005720000,0x0000000005a07498,0x0000000006be0000)

可以看到eden占用了81%,说明allocation1 , allocation2 , allocation3 都是分配在新生代Eden上。

2、大对象直接分配在老年代上

  大对象是指需要大量连续内存空间去存放的对象,类似于那种很长的字符串和数组。大对象对于虚拟机的内存分布来讲并不是好事,当遇到很多存活仅一轮的大对象jvm更加难处理,写代码的时候应该避免这样的问题。虚拟机中提供了-XX:PretenureSizeThreshold参数,另大于这个值的对象直接分配到老年代,这样做的目的是为了避免在Eden区和Survivor区之间发生大量的内存copy,在之前讲过的垃圾回收算法复制算法有提到过,就不多说了。

public class ReflectTestBig {

    private static final int _1MB = 1024*1024;

    public static void testAllocation(){
byte[] allocation2 , allocation3 , allocation4;
allocation2 = new byte[2 * _1MB];
allocation3 = new byte[2 * _1MB];
allocation4 = new byte[6 * _1MB];
} public static void main(String[] args) {
ReflectTestBig.testAllocation();
} }

输出如下

Heap
PSYoungGen total 8960K, used 4597K [0x000000000b510000, 0x000000000bf10000, 0x000000000bf10000)
eden space 7680K, 59% used [0x000000000b510000,0x000000000b98d458,0x000000000bc90000)
from space 1280K, 0% used [0x000000000bdd0000,0x000000000bdd0000,0x000000000bf10000)
to space 1280K, 0% used [0x000000000bc90000,0x000000000bc90000,0x000000000bdd0000)
PSOldGen total 10240K, used 6144K [0x000000000ab10000, 0x000000000b510000, 0x000000000b510000)
object space 10240K, 60% used [0x000000000ab10000,0x000000000b110018,0x000000000b510000)
PSPermGen total 21248K, used 2973K [0x0000000005710000, 0x0000000006bd0000, 0x000000000ab10000)
object space 21248K, 13% used [0x0000000005710000,0x00000000059f7460,0x0000000006bd0000)

可以看到allocation4已经超过了设置的-XX:PretenureSizeThreshold=3145728,随意allocation4直接被分配到了老年代,老年代占用率为60%。注意这里设置-XX:PretenureSizeThreshold=3145728不能写成-XX:PretenureSizeThreshold=3m,否则jvm将无法识别。

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

  虚拟机既然采用了分带收集的思想来管理内存,那内存回收就必须识别哪些对象应该放在新生代,哪些对象应该放在老年代。为了打到目的,jvm给每个对象定义了一个年龄计数器(Age)。如果对象在Eden出生并且能过第一次Minor GC后仍然存活,并且可以在Survivor存放的话,将被移动到Survivor中,并将对象的年龄设为1。对象每躲过一次Minor GC,年龄就会加1,当他的年龄超过一年的阈值的时候,该对象就会晋升到老年代。这个阈值jvm默认是15,可以通过-XX:MaxTenuringThreshold来设置。

public class JavaTest {  

    static int m = 1024 * 1024;  

    public static void main(String[] args) {
byte[] a1 = new byte[1 * m / 4];      byte[] a2 = new byte[7 * m];      byte[] a3 = new byte[3 * m]; //GC
}
}

输出如下

[GC [DefNew: 7767K->403K(9216K), 0.0062209 secs] 7767K->7571K(19456K), 0.0062482 secs]
[Times: user=0.00 sys=0.00, real=0.01 secs]
a3 ok
Heap
def new generation total 9216K, used 3639K [0x331d0000, 0x33bd0000, 0x33bd0000)
eden space 8192K, 39% used [0x331d0000, 0x334f9040, 0x339d0000)
from space 1024K, 39% used [0x33ad0000, 0x33b34de8, 0x33bd0000)
to space 1024K, 0% used [0x339d0000, 0x339d0000, 0x33ad0000)
tenured generation total 10240K, used 7168K [0x33bd0000, 0x345d0000, 0x345d0000)
the space 10240K, 70% used [0x33bd0000, 0x342d0010, 0x342d0200, 0x345d0000)
compacting perm gen total 12288K, used 381K [0x345d0000, 0x351d0000, 0x385d0000)
the space 12288K, 3% used [0x345d0000, 0x3462f548, 0x3462f600, 0x351d0000)
ro space 10240K, 55% used [0x385d0000, 0x38b51140, 0x38b51200, 0x38fd0000)
rw space 12288K, 55% used [0x38fd0000, 0x396744c8, 0x39674600, 0x39bd0000)

可以看到a2已经存活了一次,年龄为1,满足所设置的-XX:MaxTenuringThreshold=1,所以a2进入了老年代,而a3则进入了新生代。

4、动态对象年龄判定

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

5、空间分配担保

  在发生Minor GC的时候,虚拟机会检测每次晋升到老年代的平均大小是否大于老年代的剩余空间,如果大于,则直接进行一次FUll GC。如果小于,则查看HandlerPromotionFailyre设置是否允许担保失败,如果允许那就只进行Minor GC,如果不允许则也要改进一次FUll GC。也就是说新生代Eden存不下改对象的时候就会将该对象存放在老年代。

三、常用的jvm参数设置

1、-Xms: 初始堆大小, 默认(MinHeapFreeRatio参数可以调整)空余堆内存小于40%时,JVM就会增大堆直到-Xmx的最大限制。

2、Xmx: 最大堆大小,默认(MaxHeapFreeRatio参数可以调整)空余堆内存大于70%时,JVM会减少堆直到 -Xms的最小限制。

3、-Xmn: 年轻代大小(1.4or lator), 此处的大小是(eden+ 2 survivor space).与jmap -heap中显示的New gen是不同的。
  整个堆大小=年轻代大小 + 年老代大小 + 持久代大小。
  增大年轻代后,将会减小年老代大小.此值对系统性能影响较大,Sun官方推荐配置为整个堆的3/8。

4、-XX:NewSize: 设置年轻代大小(for 1.3/1.4)。

5、-XX:MaxNewSize: 年轻代最大值(for 1.3/1.4)。

6、-XX:PermSize: 设置持久代(perm gen)初始值。

7、-XX:MaxPermSize: 设置持久代最大值。

8、-Xss: 每个线程的堆栈大小,JDK5.0以后每个线程堆栈大小为1M,以前每个线程堆栈大小为256K.更具应用的线程所需内存大小进行 调整.在相同物理内存下,减小这个值能生成更多的线程.但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在3000~5000左右。

9、-XX:NewRatio: 年轻代(包括Eden和两个Survivor区)与年老代的比值(除去持久代),-XX:NewRatio=4表示年轻代与年老代所占比值为1:4,年轻代占整个堆栈的1/5。Xms=Xmx并且设置了Xmn的情况下,该参数不需要进行设置。

10、-XX:SurvivorRatio: Eden区与Survivor区的大小比值,设置为8,则两个Survivor区与一个Eden区的比值为2:8,一个Survivor区占整个年轻代的1/10。

11、-XX:LargePageSizeInBytes: 内存页的大小不可设置过大, 会影响Perm的大小。

12、-XX:+DisableExplicitGC: 关闭System.gc()

13、-XX:MaxTenuringThreshold: 垃圾最大年龄,如果设置为0的话,则年轻代对象不经过Survivor区,直接进入年老代. 对于年老代比较多的应用,可以提高效率.如果将此值设置为一个较大值,则年轻代对象会在Survivor区进行多次复制,这样可以增加对象再年轻代的存活 时间,增加在年轻代即被回收的概率该参数只有在串行GC时才有效。

14、-XX:PretenureSizeThreshold: 对象超过多大是直接在旧生代分配,单位字节 新生代采用Parallel Scavenge GC时无效另一种直接在旧生代分配的情况是大的数组对象,且数组中无外部引用对象。

15、-XX:TLABWasteTargetPercent: TLAB占eden区的百分比。

四、补充

Minor GC和FUll GC的区别:

  新生代GC(Minor GC):指发生在新生代的垃圾收集动作,因为java对象大对数都是逃不过第一轮的GC,所以Minor GC使用很频繁,一般回收速度也比较快。

  老年代GC(FULL GC/Major GC) :指发生在老年代的GC,出现了Major GC,经常会伴随至少一次的Minor GC(但非绝对,在ParallelScavenge收集器的收集策略中就有直接进行Major GC的选择过程 )。Major GC的速度一般会比Minor GC慢10倍以上。

浅谈java内存分配和回收策略的更多相关文章

  1. JAVA内存分配与回收策略

    对象的内存分配,大方向上讲,就是在堆上分配,对象主要分配在新生代的Eden区上,如果启动了本地线程分配缓冲,将按线程优先在TLAB上分配.少数情况下也可能会直接分配在老年代中,分配规则并不是百分百固定 ...

  2. Java虚拟机垃圾回收:内存分配与回收策略 方法区垃圾回收 以及 JVM垃圾回收的调优方法

    在<Java对象在Java虚拟机中的创建过程>了解到对象创建的内存分配,在<Java内存区域 JVM运行时数据区>中了解到各数据区有些什么特点.以及相关参数的调整,在<J ...

  3. Java虚拟机内存分配与回收策略

    内存分配与回收策略 Minor GC 和 Full GC Minor GC:发生在新生代上,因为新生代对象存活时间很短,因此 Minor GC 会频繁执行, 执行的速度一般也会比较快. Full GC ...

  4. 浅谈jvm中的垃圾回收策略

    下面小编就为大家带来一篇浅谈jvm中的垃圾回收策略.小编觉得挺不错的,现在就分享给大家,也给大家做个参考.一起跟随小编过来看看吧   java和C#中的内存的分配和释放都是由虚拟机自动管理的,此前我已 ...

  5. JVM垃圾回收器、内存分配与回收策略

    新生代垃圾收集器 1. Serial收集器 serial收集器即串行收集器,是一个单线程收集器. 串行收集器在进行垃圾回收时只使用一个CPU或一条收集线程去完成垃圾回收工作,并且会暂停其他的工作线程( ...

  6. JVM学习十 -(复习)内存分配与回收策略

    内存分配与回收策略 对象的内存分配,就是在堆上分配(也可能经过 JIT 编译后被拆散为标量类型并间接在栈上分配),对象主要分配在新生代的 Eden 区上,少数情况下可能直接分配在老年代,分配规则不固定 ...

  7. Java深入 - Java 内存分配和回收机制

    Java的GC机制是自动进行的,和c语言有些区别需要程序员自己保证内存的使用和回收. Java的内存分配和回收也主要在Java的堆上进行的,Java的堆中存储了大量的对象实例,所以Java的堆也叫GC ...

  8. jvm内存分配和回收策略

    在上一篇中,已经介绍了内存结构是什么样的. 这篇来介绍一下 内存是怎么分配的,和怎么回收的.(基本取自<深入理解Java虚拟机>一书) java技术体系中所提倡的自动内存管理最终可以归结为 ...

  9. Java深入 - Java 内存分配和回收机制-转

    Java的GC机制是自动进行的,和c语言有些区别需要程序员自己保证内存的使用和回收. Java的内存分配和回收也主要在Java的堆上进行的,Java的堆中存储了大量的对象实例,所以Java的堆也叫GC ...

随机推荐

  1. elasticsearch基础概念

    接近实时(NRT)        Elasticsearch是一个接近实时的搜索平台.这意味着,从索引一个文档直到这个文档能够被搜索到有一个轻微的延迟(通常是1秒).           集群(clu ...

  2. 跟Microsoft.AspNet.Identity学习哈希加盐法

    什么是哈希加盐法? 废话少说:对于MD5这种加密算法,同样的密码每次加密后得到的密文是一样的,所以黑客可以利用已知的密码库(彩虹库)对目标数据库密文进行对比进行攻击. 怎样解决:哈希加盐法,以下是网上 ...

  3. hadoop+hive+spark搭建(三)

    一.spark安装   因为之前安装过hadoop,所以,在“Choose a package type”后面需要选择“Pre-build with user-provided Hadoop [can ...

  4. 详细解剖大型H5单页面应用的核心技术点

    项目 Xut.js 阐述下开发中一个比较核心的优化技术点,这是一套平台代码,并非某一个插件功能或者框架可以直接拿来使用,核心代码大概是6万行左右(不包含任何插件) .这也并非一个开源项目,不能商业使用 ...

  5. linux vi 报错 E37: No write since last change (add ! to override)

    用 vi 命令编辑文本文件,没有文件写入权限的时候会报这个错.:q   :wq   怎么都不能退出. 这时只需 ctrl+z 即可,或者在退出命令后加 ! 忽略提示     :q!

  6. C++ 常见的 Undefined symbols for architecture *

    出现 Undefined symbols for architecture x86_64: 的原因 1.函数申明了,却未被定义. 2.申明的虚函数未被实现. NOTE: a missing vtabl ...

  7. DDD理论学习系列(2)-- 领域

    DDD理论学习系列目录 1. 引言 领域一词,主要有以下两个意思: 一国主权所达之地. 学术思想或社会活动的范围. 不管是指国家的主权范围也好还是学术活动范围,都是在讲一个范围,一个界限. 比如我们常 ...

  8. Memcache服务搭建

    Memcache Memcache的作用网上资料都讲的很好,说简单点就是减轻读取数据库的压力,原理也很简单: 被请求的数据会先到memcache里去取,如果没有就去数据库里取,顺便给memcache带 ...

  9. SpringMVC中的java.lang.ClassNotFoundException: org.aspectj.weaver.BCException 调试过程记录

    报错原因 上文本描述 java.lang.NoClassDefFoundError: org/aspectj/weaver/BCException at java.lang.Class.getDecl ...

  10. 分辨率、像素和PPI

    屏幕尺寸是指屏幕对角线的长度,一般以英寸为单位,1英寸(inch)=2.54厘米(cm).传统意义上的照片尺寸也是这个概念.所以同样尺寸(指对角线)的屏幕,也可能长宽比率不同.像素(Pixel):是位 ...