[JVM]内存分配策略

骑猪看日落 2022-05-09 06:50 355阅读 0赞

1、优先分配到eden

  1. package 深入理解java虚拟机;
  2. public class 对象优先分配到eden {
  3. /**
  4. * 1M的内存大小
  5. */
  6. private static final int _1MB = 1024 * 1024;
  7. /**
  8. * jvm参数设置:-verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8
  9. *
  10. * 1、打印垃圾回收日志信息:-verbose:gc -XX:+PrintGCDetails
  11. * 2、设置jvm初始堆内存为20M:-Xms20M
  12. * 3、设置jvm最大堆内存为20M:-Xmx20M
  13. * 4、设置新生代堆内存大小为10M:-Xmn10M
  14. * 5、设置新生代中Eden区与一个Survivor区的空间比例是8:1:-XX:SurvivorRatio=8
  15. *
  16. * @author jinqiwen 2018年10月2日
  17. *
  18. * @version v1.0
  19. */
  20. public static void testAllocation() {
  21. byte[] allocation1, allocation2, allocation3, allocation4;
  22. allocation1 = new byte[2 * _1MB];
  23. allocation2 = new byte[2 * _1MB];
  24. allocation3 = new byte[2 * _1MB];
  25. allocation4 = new byte[4 * _1MB]; // 出现一次Minor GC
  26. }
  27. /**
  28. * 运行结果如下:
  29. *
  30. * Heap
  31. * PSYoungGen total 9216K, used 7291K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
  32. * eden space 8192K, 89% used [0x00000000ff600000,0x00000000ffd1ef00,0x00000000ffe00000)
  33. * from space 1024K, 0% used [0x00000000fff00000,0x00000000fff00000,0x0000000100000000)
  34. * to space 1024K, 0% used [0x00000000ffe00000,0x00000000ffe00000,0x00000000fff00000)
  35. * ParOldGen total 10240K, used 4096K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
  36. * object space 10240K, 40% used [0x00000000fec00000,0x00000000ff000010,0x00000000ff600000)
  37. * Metaspace used 2669K, capacity 4486K, committed 4864K, reserved 1056768K
  38. * class space used 288K, capacity 386K, committed 512K, reserved 1048576K
  39. *
  40. * PSYoungGen表示当前所使用的垃圾收集器是Parallel,可使用参数-XX:+UseSerialGC 来指定jvm使用Serial
  41. *
  42. * 使用Serial收集器的运行结果如下:
  43. *
  44. * [GC (Allocation Failure) [DefNew: 7127K->522K(9216K), 0.0033855 secs] 7127K->6666K(19456K), 0.0034315 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
  45. * Heap
  46. * def new generation total 9216K, used 4701K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
  47. * eden space 8192K, 51% used [0x00000000fec00000, 0x00000000ff014930, 0x00000000ff400000)
  48. * from space 1024K, 51% used [0x00000000ff500000, 0x00000000ff582ad0, 0x00000000ff600000)
  49. * to space 1024K, 0% used [0x00000000ff400000, 0x00000000ff400000, 0x00000000ff500000)
  50. * tenured generation total 10240K, used 6144K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
  51. * the space 10240K, 60% used [0x00000000ff600000, 0x00000000ffc00030, 0x00000000ffc00200, 0x0000000100000000)
  52. * Metaspace used 2669K, capacity 4486K, committed 4864K, reserved 1056768K
  53. * class space used 288K, capacity 386K, committed 512K, reserved 1048576K
  54. *
  55. * 可见,Serial收集器使用的是def new generation来标识新生代的
  56. * 新生代的总内存是10M,eden区占8M,两个Survivor区各占1M
  57. * 在分配allocation1到allocation3的时候,eden区的空间是够用的,此时eden区占用了6M,剩余2M
  58. * 当分配allocation4的时候,由于allocation4需要4M的内存,此时eden区的空间已经不够用了,所以jvm就触发了一次新生代的垃圾回收
  59. * 新生代的垃圾回收使用了复制算法,是将新生代中的所有存活的对象移动到Survivor中。
  60. * 由于Survivor区只有1M的内存,很明显三个对象都放不下,此时就必须触发内存担保机制了,即将三个对象移动到老年代中。
  61. * 此时eden区就是出于空闲状态了,所以allocation4就成功的被分配到了eden区中。
  62. * 所以我们看到的结果就是最终新生代使用了4M内存,而老年代使用了6M内存,而从另一方面也可以看出对象创建时是优先分配到eden区中的。
  63. *
  64. * @author jinqiwen 2018年10月2日
  65. *
  66. * @version v1.0
  67. */
  68. public static void main(String[] args) {
  69. testAllocation();
  70. }
  71. }

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

  1. public class 大对象直接分配到老年代 {
  2. private static final int _1MB = 1024 * 1024;
  3. /**
  4. * jvm参数设置:-verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails
  5. * -XX:SurvivorRatio=8 -XX:PretenureSizeThreshold=3145728
  6. *
  7. * 1、指定内存占用大于3145728的对象直接分配到老年代:-XX:PretenureSizeThreshold=3145728
  8. * (3145728=3M=3*1024*1024,超过3M的对象会被直接放到老年代中)
  9. *
  10. * 2、PretenureSizeThreshold只针对Serial和ParNew有效
  11. *
  12. * @author jinqiwen 2018年10月2日
  13. *
  14. * @version v1.0
  15. */
  16. public static void testPretenureSizeThreshold() {
  17. byte[] allocation;
  18. allocation = new byte[4 * _1MB]; // 直接分配在老年代中
  19. }
  20. }

3、长期存活的对象分配到老年代

  1. public class 长期存活的对象分配到老年代 {
  2. private static final int _1MB = 1024 * 1024;
  3. /**
  4. * jvm参数设置:-verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails
  5. * -XX:SurvivorRatio=8 -XX:+PrintTenuringDistribution
  6. * -XX:MaxTenuringThreshold=1
  7. *
  8. * 1、对象在新生代中每经历过一次Minor GC,若对象进入存活区的话,则将对象的年龄都会加1,当对象的年龄到达某一个值时,对象就会进入老年代
  9. *
  10. * 2、设置进入老年代年龄的阈值为1:-XX:MaxTenuringThreshold=1
  11. *
  12. *@author jinqiwen 2018年10月2日
  13. *
  14. * @version v1.0
  15. */
  16. public static void testTenuringThreshold() {
  17. byte[] allocation1, allocation2, allocation3;
  18. allocation1 = new byte[_1MB / 4]; // 什么时候进入老年代决定于XX:MaxTenuringThreshold设置
  19. allocation2 = new byte[4 * _1MB];
  20. allocation3 = new byte[4 * _1MB];
  21. allocation3 = null;
  22. allocation3 = new byte[4 * _1MB];
  23. }
  24. }

4、动态对象年龄判断

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

5、空间分配担保(eden区内存不够,到老年代那去借)

  1. public class 空间分配担保 {
  2. private static final int _1MB = 1024 * 1024;
  3. /**
  4. * jvm参数设置:-Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8
  5. * -XX:-HandlePromotionFailure
  6. *
  7. * 1、此设置中,eden区有8M,两个Survivor区各占1M,老年代占10M
  8. * 2、每次进行Minor GC之前,虚拟机会先检查老年代的最大可用的连续空间是否大于新生代所有对象的空间
  9. * 3、如果是的话,则Minor GC是安全的
  10. * 4、如果不是的话,则虚拟机先看HandlePromotionFailure的值,如果值设置成-的话,则虚拟机需要启动一次Full GC让老年代腾出更多的空间
  11. * 5、如果HandlePromotionFailure的值设置成+的话,则会检查老年代中的最大可用连续空间是否大于历次晋升到老年代对象的平均大小
  12. * 6、如果不是的话,则会进行一次Full GC,如果是的话,虚拟机则会尝试一次Minor GC,尽管它是有风险的
  13. * 7、如果Minor GC失败了,则会重新发起一次Full GC
  14. * 8、虽然担保失败时绕的圈子是最大的,但大多数情况下还是会将HandlePromotionFailure打开,避免Full GC过于频繁
  15. *
  16. * 注:HandlePromotionFailure参数只在JDK6之前有效,JDK6之后该值不可变且默认为打开。
  17. *
  18. * @author jinqiwen 2018年10月2日
  19. *
  20. * @version v1.0
  21. */
  22. public static void testHandlePromotion() {
  23. byte[] allocation1, allocation2, allocation3, allocation4, allocation5, allocation6, allocation7;
  24. allocation1 = new byte[2 * _1MB];
  25. allocation2 = new byte[2 * _1MB];
  26. allocation3 = new byte[2 * _1MB];
  27. allocation1 = null;
  28. allocation4 = new byte[2 * _1MB];
  29. allocation5 = new byte[2 * _1MB];
  30. allocation6 = new byte[2 * _1MB];
  31. allocation4 = null;
  32. allocation5 = null;
  33. allocation6 = null;
  34. allocation7 = new byte[2 * _1MB];
  35. }
  36. }

6、逃逸分析与栈上分配

  1. public class 逃逸分析与栈上分配 {
  2. public String str;
  3. /**
  4. * 方法返回str,对象发生了逃逸
  5. *
  6. *@author jinqiwen 2018年10月2日
  7. *
  8. * @version v1.0
  9. */
  10. public String getStr() {
  11. return str == null ? new String() : str;
  12. }
  13. /**
  14. * 为成员属性赋值,发生逃逸
  15. *
  16. * @author jinqiwen 2018年10月2日
  17. *
  18. * @version v1.0
  19. */
  20. public void setStr() {
  21. this.str = new String();
  22. }
  23. /**
  24. * 对象的作用域仅在当前方法中有效,没有发生逃逸(
  25. *
  26. * (只要对象不发生逃逸,那么就会把对象分配到栈内存中去。对象随栈帧的消失而消失)
  27. *
  28. * @author huangxj 2018年1月21日
  29. *
  30. * @version v1.0
  31. */
  32. public void useStr() {
  33. String str = new String();
  34. }
  35. /**
  36. * 引用成员变量的值,发生逃逸
  37. *
  38. * @author jinqiwen 2018年10月2日
  39. *
  40. * @version v1.0
  41. */
  42. public void useStr2() {
  43. String str = getStr();
  44. }
  45. }

发表评论

表情:
评论列表 (有 0 条评论,355人围观)

还没有评论,来说两句吧...

相关阅读

    相关 JVM-内存分配与回收策略

        JAVA技术体系中的自动内存管理实际上就是自动化的解决了给对象分配内存以及回收给对象分配的内存这两个问题。回收部分通过之前的[《GC设计思路分析》][GC]和[《垃圾收

    相关 JVM中的内存分配策略

    对象的内存分配,从大方向上将,就是在堆上分配(但也可能经过JIT编译后被拆散为标量类型并间接地在栈上分配),对象主要分配在新生代的Eden区上,如果启动了本地线程分配缓冲,将按

    相关 jvm 垃圾回收 内存分配策略

    1.那些内存需要回收 其中程序计数器,虚拟机栈,本地方法栈3个区域随线程而生,随线程而灭,不需要过多考虑回收。因此内存回收主要是在java堆和方法区。 2.对象何时回收?

    相关 JVM内存分配策略

    1、对象优先在Eden分配 大多情况,对象在新生代Eden区分配。当Eden区没有足够空间进行分配时,虚拟机将进行一次Minor GC。虚拟机提供了参数 -XX:+Pri