JVM详解:运行时数据区-堆
目录
参考:
- 链接1:[https://blog.csdn.net/qq_48435252/article/details/123697193
# JVM详解:运行时数据区-堆
一个jvm实例只存在一个堆内存,堆也是java内存管理的核心区域
Java堆区在JVM启动的时候即被创建,其空间大小也就确定了。是JVM管理的最大一块内存空间(堆内存的大小是可以调节的)
《Java虚拟机规范》规定,堆可以处于物理上不连续的内存空间中,但在逻辑上它应该被视为连续的
所有的线程共享java堆,在这里还可以划分线程私有的缓冲区(TLAB:Thread Local Allocation Buffer).(面试问题:堆空间一定是所有线程共享的么?不是,TLAB线程在堆中独有的)
《Java虚拟机规范》中对java堆的描述是:所有的对象实例以及数组都应当在运行时分配在堆上。
从实际使用的角度看,“几乎”所有的对象的实例都在这里分配内存 (‘几乎’是因为可能存储在栈上)
数组或对象永远不会存储在栈上,因为栈帧中保存引用,这个引用指向对象或者数组在堆中的位置
在方法结束后,堆中的对象不会马上被移除,仅仅在垃圾收集的时候才会被移除
堆,是GC(Garbage Collection,垃圾收集器)执行垃圾回收的重点区域
// -Xms100m : 设置初始内存100MB
// -Xmx100m : 设置JVM最大内存100MB
// -XX:+PrintGCDetails : 打印GC回收信息
System.out.println("最大可用内存:"+Runtime.getRuntime().maxMemory()/1024/1024+"MB");
System.out.println("当前JVM空闲内存:"+Runtime.getRuntime().freeMemory()/1024/1024+"MB");
System.out.println("当前JVM占用的内存总数:"+Runtime.getRuntime().totalMemory()/1024/1024+"MB");
2
3
4
5
6
7
8
9
堆的细分内存结构
JDK7及以前
- 逻辑:新生区(伊甸园区+幸存者1区+幸存者2区)+养老区+永久区(方法区在1.7的实现)
JDK8及以后
- 逻辑:新生区(伊甸园区+幸存者1区+幸存者2区)+养老区+元空间(直接内存)(方法区在1.8的实现)
新生代和老年代
存储在JVM中的java对象可以被划分为两类:
- 一类是生命周期较短的瞬时对象,这类对象的创建和消亡都非常迅速(存入新生代)
- 另外一类对象时生命周期非常长,在某些情况下还能与JVM的生命周期保持一致 (存入老年代)
Java堆区进一步细分可以分为新生代(YoungGen)和老年代(OldGen)
- 其中新生代可以分为伊甸园区(Eden)、新生区1(from)和新生区2(to)
图解对象分配过程
(1)new的对象先放伊甸园区。此区有大小限制。
(2)当伊甸园的空间填满时,程序又需要创建对象,JVM的垃圾回收器将对伊甸园区进行垃圾回收(Minor GC),将伊甸园区中的不再被其他对象所引用的对象进行销毁。将伊甸园中的剩余对象移动到幸存者0区。
(3)然后加载新的对象放到伊甸园区
(4)如果再次触发垃圾回收,此时上次幸存下来的放到幸存者0区的,如果没有回收,就会放到幸存者1区。
(5)如果再次经历垃圾回收,此时会重新放回幸存者0区,接着再去幸存者1区。
(6)啥时候能去养老区呢?可以设置次数。默认是15次。·可以设置参数:-XX:MaxTenuringThreshold=进行设置。
(7)在老年区,相对悠闲。当老年区内存不足时,再次触发GC:Major GC,进行养老区的内存清理。
(8)若老年区执行了Major GC之后发现依然无法进行对象的保存,就会产生OOM异常
总结:
针对幸存者s0,s1区:复制之后有交换,谁空谁是to 关于垃圾回收:频繁在新生区收集,很少在养老区收集,几乎不再永久区/元空间收
为对象分配内存:TLAB
8.1为什么要有TLAB?
堆区是线程共享区域,任何线程都可以访问到堆区中的共享数据 由于对象实例的创建在JVM中非常频繁,因此在并发环境下从堆区中划分内存空间是线程不安全的 为避免多个线程操作同一地址,需要使用加锁等机制,进而影响分配速度
8.2什么是TLAB
从内存模型而不是垃圾收集的角度,对Eden区域继续进行划分,JVM为每个线程分配了一个私有缓存区域,它包含在Eden空间内
- 多线程同时分配内存时,使用TLAB可以避免一系列的非线程安全问题,同时还能够提升内存分配的吞吐量,因此我们可以将这种内存分配方式称之为快速分配策略
- 所有OpenJDK衍生出来的JVM都提供了TLAB的设计
TLAB对象分配过程