5. 堆 - 【整理版】

本文详细解析了Java堆内存结构的变化,从旧版本的永久代到Java8的元空间,以及堆内存分配逻辑。重点介绍了新生代和老年代的垃圾回收,堆大小设置原则,以及堆内存分代思想。还讨论了TLAB的作用和堆相关参数的设置。
摘要由CSDN通过智能技术生成

来源:JVM系列-第5章-堆 | 风祈的时光录

纪要:

1. 堆与进程

  1. 一个进程只有一个JVM实例,一个JVM实例中就有一个运行时数据区,一个运行时数据区只有一个堆和一个方法区。
  2. 但是进程包含多个线程,他们是共享同一堆空间的
  3. 在方法结束后,触发了GC的时候,堆才会进行回收

2. java7及其之前的堆和方法区的物理存储

jvm内存结构

Java7及以前版本的Hotspot中方法区位于永久代中

永久代和堆是相互隔离的,但它们使用的物理内存是连续的

永久代的垃圾收集是和老年代捆绑在一起的,因此无论谁满了,都会触发永久代和老年代的垃圾收集

3. java8中 堆和方法区的物理存储

1. 在Java8中,时代变了,Hotspot取消了永久代。永久代的参数-XX:PermSize和-XX:MaxPermSize也随之失效。

2. 对于Java8,HotSpots取消了永久代,那么是不是就没有方法区了呢?当然不是,方法区只是一个规范,只不过它的实现变了。

3. 在Java8中,元空间(Metaspace)登上舞台,方法区存在于元空间(Metaspace)。同时,元空间不再与堆连续,而且是存在于本地内存(Native memory)。

4. 本地内存(Native memory),是供JVM自身进程使用的。当Java Heap空间不足时会触发GC,但Native memory空间不够却不会触发GC。 

5. 元空间存在于本地内存,意味着只要本地内存足够,它不会出现像永久代中“java.lang.OutOfMemoryError: PermGen space”这种错误

6. 更深层的原因还是要合并HotSpot和JRockit的代码,JRockit从来没有所谓的永久代,也不需要开发运维人员设置永久代的大小,但是运行良好。同时也不用担心运行性能问题了,在覆盖到的测试中, 程序启动和运行速度降低不超过1%,但是这点性能损失换来了更大的安全保障。

4. 设置堆内存大小和OOM

  1. Java堆区用于存储Java对象实例,那么堆的大小在JVM启动时就已经设定好了,大家可以通过选项”-Xms”和”-Xmx”来进行设置
  2. 一旦堆区中的内存大小超过“-Xmx”所指定的最大内存时,将会抛出OutofMemoryError异常。
  3. 通常会将-Xms和-Xmx两个参数配置相同的值
  • 原因:假设两个不一样,初始内存小,最大内存大。在运行期间如果堆内存不够用了,会一直扩容直到最大内存。如果内存够用且多了,也会不断的缩容释放。频繁的扩容和释放造成不必要的压力,避免在GC之后调整堆内存给服务器带来压力
  • 如果两个设置一样的就少了频繁扩容和缩容的步骤。内存不够了就直接报OOM

5. 堆的比例大小

6. 内存分配逻辑

 

 

7. 逻辑分配特殊说明

8. GC分类

部分收集:不是完整收集整个Java堆的垃圾收集。其中又分为:

  • 新生代收集(Minor GC/Young GC):只是新生代(Eden,s0,s1)的垃圾收集
  • 老年代收集(Major GC/Old GC):只是老年代的圾收集。
  • 注意,很多时候Major GC会和Full GC混淆使用,需要具体分辨是老年代回收还是整堆回收。
  •  

9. GC日志分析

public class TestMain1 {

    public static void main(String[] args) {
        int i = 0;
        try {
            List<String> list = new ArrayList<>();
            String a = "atguigu.com";
            while (true) {
                list.add(a);
                a = a + a;
                i++;
            }

        } catch (Throwable t) {
            t.printStackTrace();
            System.out.println("遍历次数为:" + i);
        }
    }
}
[GC (Allocation Failure) [PSYoungGen: 2048K->512K(2560K)] 2048K->797K(9728K), 0.0008019 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [PSYoungGen: 2254K->496K(2560K)] 2540K->1388K(9728K), 0.0015801 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [PSYoungGen: 2316K->448K(2560K)] 3209K->2396K(9728K), 0.0012466 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
[Full GC (Ergonomics) [PSYoungGen: 1943K->0K(2560K)] [ParOldGen: 6172K->4917K(7168K)] 8115K->4917K(9728K), [Metaspace: 3086K->3086K(1056768K)], 0.0042929 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [PSYoungGen: 0K->0K(2560K)] 4917K->4917K(9728K), 0.0011728 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (Allocation Failure) [PSYoungGen: 0K->0K(2560K)] [ParOldGen: 4917K->4898K(7168K)] 4917K->4898K(9728K), [Metaspace: 3086K->3086K(1056768K)], 0.0044432 secs] [Times: user=0.03 sys=0.00, real=0.01 secs] 
遍历次数为:16
Heap
 PSYoungGen      total 2560K, used 147K [0x00000007bfd00000, 0x00000007c0000000, 0x00000007c0000000)
  eden space 2048K, 7% used [0x00000007bfd00000,0x00000007bfd24d08,0x00000007bff00000)
  from space 512K, 0% used [0x00000007bff80000,0x00000007bff80000,0x00000007c0000000)
  to   space 512K, 0% used [0x00000007bff00000,0x00000007bff00000,0x00000007bff80000)
 ParOldGen       total 7168K, used 4898K [0x00000007bf600000, 0x00000007bfd00000, 0x00000007bfd00000)
  object space 7168K, 68% used [0x00000007bf600000,0x00000007bfac8bd0,0x00000007bfd00000)
 Metaspace       used 3147K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 347K, capacity 388K, committed 512K, reserved 1048576K
java.lang.OutOfMemoryError: Java heap space
	at java.util.Arrays.copyOf(Arrays.java:3332)
	at java.lang.AbstractStringBuilder.ensureCapacityInternal(AbstractStringBuilder.java:124)
	at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:448)
	at java.lang.StringBuilder.append(StringBuilder.java:136)
	at com.luoyu.aiyu.luo1.TestMain1.main(TestMain1.java:16)

[GC (Allocation Failure) [PSYoungGen: 2048K->512K(2560K)] 2048K->797K(9728K), 0.0008019 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] 

[PSYoungGen: 2048K->512K(2560K)]:年轻代总空间为 2560K ,当前占用 2048K ,经过垃圾回收后剩余512K

  • 2048K->797K(9728K):堆内存总空间为 9728K ,当前占用2048K ,经过垃圾回收后剩余797K

9. Java堆空间分代思想

为什么要把Java堆分代?不分代就不能正常工作了吗?经研究,不同对象的生命周期不同。70%-99%的对象是临时对象。

10. TLAB

1. 为什么有TLAB?

堆是共享的。并发环境下从堆区中划分内存空间是线程不安全的;

JVM为每个线程分配了一个私有缓存区域,它包含在Eden空间内

多线程同时分配内存时,使用TLAB可以避免一系列的非线程安全问题,称之为快速分配策略

11. 堆相关参数

/**
 * -Xms:初始堆空间内存 (默认为物理内存的1/64)
 * -Xmx:最大堆空间内存(默认为物理内存的1/4)
 * -XX:+PrintGCDetails:输出详细的GC处理日志
 * 打印gc简要信息:① -XX:+PrintGC   ② -verbose:gc
 */

12. 堆上分配是唯一吗?

在《深入理解Java虚拟机》中关于Java堆内存有这样一段描述:

  1. 随着JIT编译期的发展与逃逸分析技术逐渐成熟,栈上分配、标量替换优化技术将会导致一些微妙的变化,所有的对象都分配到堆上也渐渐变得不那么“绝对”了。
  2. 在Java虚拟机中,对象是在Java堆中分配内存的,这是一个普遍的常识。但是,有一种特殊情况,那就是如果经过逃逸分析(Escape Analysis)后发现,一个对象并没有逃逸出方法的话,那么就可能被优化成栈上分配。这样就无需在堆上分配内存,也无须进行垃圾回收了。这也是最常见的堆外存储技术。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值