Java堆
描述:
一个JVM实例只有一个堆内存,堆也是JVM管理的核心区域
Java堆在JVM启动时就被创建,其空间大小也就确定
--堆内存的大小是可以通过参数来调整的
堆可以处于物理上不连续的内存空间,但在逻辑上被视为连续的
所有的线程共享Java堆,但是在堆中还可以划分线程私有的缓冲区(Thread Local Allocation Buffer,TLAB)TLAB放后面做个详细的解释
方法结束之后,堆中对象不会马上被移除,只有执行垃圾回收时才会被移除
堆是GC执行垃圾回收的重点区域
内存细分:
现代垃圾收集器大部分都基于分代收集理论设计,在Java8内存逻辑做了部分调整,如下
| Java版本 | 堆 | 方法区 | |
| Java7及之前 | 新生代 | 老年代 | 永久代 |
| Java8之后 | 新生代 | 老年代 | 元空间 |
即:
在Java7及之前,堆空间分为新生代、老年代、永久代
在Java8及之后,堆空间分为新生代、老年代、元空间
区别:Java8中使用元空间代替了永久代
逻辑上对空间分为3个区域,实际上堆空间是指新生代和老年代;而永久代和元空间不过是方法区在不同JDK版本的不同实现
先上一个含堆空间细分的运行时数据区

上图中包含的知识点有:
新生代包括了Eden区(伊甸园区)、s0(幸存者0区)、s1(幸存者1区),其内存大小比例为 Eden区:s0:s1 = 8:1:1
在实际运行时,s0和s1之间有一个是空的,这个涉及到复制算法,后面再做解释
新生代和老年代的内存比例为 新生代:老年代 = 1 :2
新生代和老年代的比例参数设置
默认-XX:NewRation=2,表示新生代占1,老年代占2,新生代占整个堆的1/3
修改-XX:NewRation=2,表示新生代占1,老年代占4,新生代占整个对的1/5
堆空间大小的设置
Java堆用于存储Java对象实例,堆的大小在JVM启动时就已经设置好了,可以通过参数选项“-Xmx”和“-Xms”来设置
>> "-Xms"用于表示堆区的起始内存
>> "-Xmx"用于表示堆区的最大内存
当堆区的内存大小超过"-Xmx"设定的最大内存时,将会抛出OutOfMemoryError异常
通常会将 -Xms 和 -Xmx 两个参数设置为相同的值,其目的是为了能够在java垃圾回收机制清理完堆区后不用再重新分配计算堆区的大小,从而提高性能
堆空间大小=年轻代+老年代
几乎所有的Java对象都是在Eden区被new出来的(例外:对象太大,Eden放不下,放到老年代)
绝大部分的对象的销毁都是在新生代进行的
对象分配过程
1、对象在Eden区创建,当Eden区内存占满之后,将会对Eden区进行垃圾回收(Minor GC),将Eden区中不再被其它对象引用的对象进行销毁
2、将Eden中剩余的对象放到幸存者0区,并为对象分配年龄计数器

3、当再次进行垃圾回收时,将会把S0区未被回收对象移入S1区,并将其年龄计数器+1;Eden区中未被回收的对象移入S1区,为其分配年龄计数器,置为1
4、每次经历垃圾回收,都会将S0区和S1区进行交替移入对象,并为对象的年龄计数器+1

5、当年龄计数器达到一定临界值(默认15)时,将对应的对象移入老年代

修改临界值的参数:-XX:MaxTenuringThreshold=<N>
!!!!!!!!!!!!!!!!
Eden区内存空间占满会触发YoungGC,幸存者区内存占满不会触发YoungGC
但是幸存者区是有垃圾回收的,即:当Eden区触发YoungGC时,会将Eden区和幸存者区一并执行垃圾回收
S0和S1之间的关系:复制之后有交换,谁空谁是to(from区和to区的概念)
关于垃圾回收:频繁收集新生代,较少收集老年代,几乎不动永久代/元空间
!!!!!!!!!!!!!!!!
对象分配的特殊情况
什么时候会在老年代里分配对象呢,看图

TLAB(Thread Local Allocation Buffer)
为什么会有TLAB
堆区是线程共享区域,任何线程都可以访问到堆区中的共享数据
对象创建比较频繁,并发环境下在堆区划分内存空间是线程不安全的
为避免多个线程操作同一地址,需使用加锁机制,会影响分配速度
什么是TLAB
JVM为每个线程分配了一个私有的缓存区域,它包含在Eden空间内
优点
多线程分配内存时,使用TLAB可以避免一系列的非线程安全问题,同时提高内存分配的吞吐量,所以这种内存分配方式也叫做快速分配策略
TLAB的本质其实是三个指针管理的区域:start,top 和 end,每个线程都会从Eden分配一块空间,例如说100KB,作为自己的TLAB,其中 start 和 end 是占位用的,标识出 eden 里被这个 TLAB 所管理的区域,卡住eden里的一块空间不让其它线程来这里分配

方法区
方法区和Java堆一样,是各个线程共享的内存区域,它是用来存储已被虚拟机加载的类信息、常量、静态变量、即时编译其编译后的代码等数据
方法区只是《Java虚拟机规范》中的一个概念
在JDK1.7及之前,是使用永久代来实现方法区的概念,此时永久代使用的是虚拟机内存;
在JDK1.8及之后,采用元空间代替永久代来实现方法区,此时元空间使用的是本地内存,所以默认情况下元空间的大小仅受本地内存限制
方法区中存放的内容如下

方法区中有一部分叫“运行时常量池”,需要与常量池区分开来
运行时常量池是方法区的一部分
常量池是字节码文件的一部分,用来存放编译生成的各种字面量与符号引用,这部分内容会在类加载之后存放到方法区的运行时常量池中
每一个类或接口加载之后,就会创建对应的运行时常量池
当在创建类或接口的运行时常量池时,如果构造运行时常量池所需要的内存空间超过了方法区所能提供的最大值,则JVM将会抛出OOM异常
方法区的垃圾回收主要回收两部分内容:常量池中废弃的常量和不再使用的类型
常见面试题:
1、JVM内存模型分为哪些区?分别是干什么的2、Java8的内存分代改进?(主要是方法区的实现方式变化)3、栈的结构?堆的结构?栈和堆的区别4、为什么会有两个survivor区?(敬请关注后续文章讲述复制算法)5、Eden和Survior的比例分配6、堆区为什么分为新生代和老年代?7、什么时候对象会进入老年代?(1、超年龄2、大对象)
至此,运行时数据区已完毕,只要能将上面的7道题有条理的描述或解答出来,就能说明对这部分内容已经相当理解了
详情请参考《深入理解Java虚拟机-JVM高级特性与最佳实践》
最近都没有涨粉
,欢迎各位看官关注,后续将推出个人SpringBoot分布式项目的搭建过程,敬请关注!!!




