一、堆的核心概述
JVM启动时,被创建,每一个JVM实例,都对应这一个堆空间,所有的线程共享 java堆
每个线程占一小块(TLAB),线程私有的,并发性更好一些
栈里存放的是 s1实例在 堆里的 地址
1.1、堆空间细分、内存细节
现代垃圾收集器大部分都基于分代收集理论设计的
1.1.1、jdk7以级之前 堆内存 逻辑上分为 三部分:新生区、养老区、永久区
1.1.2、jdk8之后 堆内存 逻辑上 分为三部分:新生区、养老区、元空间
1.1.3、同名都可叫
二、设置堆内存的大小与OOM
java堆区用于存储java对象的实例,那么堆的大小在JVM启动的时候就已经确定好了,大家可以通过选项设置“ -Xms”和“ - Xmx”来进行设置
------->“ -Xms” 表示 堆的 起始内存:“ -Xms:起始内存”
------->“ -Xmx” 表示 堆的 最大内存:“ -Xmx:最大内存”
一旦堆区的大小超过了 最大内存 ,那么就会报 OOM错误 ,OutOfMemoryError异常
**默认的情况:
初识内存大小:物理内存大小的/64
最大内存大小:物理内存大小的/4 **
如何取查看设置的参数:
方式一:cmd -》jps-》jstat -gc +进程id
方式二:-XX:+PrintGCDetails
三、年轻代与老年代
几乎所有的java对象都是在 年轻代的 eden 区被 new 出来的(eden放的下的话)
绝大多数java对象的销毁 都是在 新生代的 进行了(例如局部变量)
java堆 也可以分为 年轻代和老年代
年轻代 还可以分为 伊甸园区(Eden)和 幸存者0区(survivor 0)或者幸存者1区(survivor 1)
幸存者零区和 幸存者一区 只能有一个 被使用
设置 年轻代与老年代 的 堆内存比例
设置 Eden 和 Survivor 的一个 比例 :
-XX:SurvivorRatio=8 , 8:1:1
四、图解、描述对象分配过程
图:
对象分配过程:
1.new 的对象先放 到 eden 区,此区有大小限制
2.当eden区填满的时候,程序又需要创建对象,JVM的垃圾回收器将对eden区的垃圾进行回收(Minor GC),将 eden区 不在被引用的对象 进行销毁,再加载新的对象到 eden区
3.然后 将 eden 区 剩下的对象 移动到 幸存者零区(Survivor 0)
4.如果伊甸园区在满,再次触发垃圾回收,一并回收三个区的垃圾,此时,上次幸存下来的在 幸存者零区(Survivor 0)的,如果没有被回收,就会被 移动到 幸存者一区(Survivor 1),Eden区不是垃圾的对象也会被放到 幸存者一区(Survivor 1)
5.如果再次经历垃圾回收,此时,会重新放回 幸存者 零区(Survivor 0),接着再去幸存者一区(Survivor 1)......循环 0 1
6.啥时候去养老区呢??可以设置次数。默认是 15次(被移动了15次的对象,有个计数器)
-XX:MaxTenuringThreshold=<N>进行设置
7.在养老区比较悠闲,当养老区不足时,触发:Major GC ,进行养老区的内存清理
8.若清理完 还不够,就会产生OOM(OutOfMemoryError)异常
4.1、总结:
1 针对幸存者S0,S1区的总结,复制之后有交换,谁空谁是to
2 关于垃圾回收,频繁的在新生区 收集,很少在养老区收集,几乎不在 永久区/元空间 收集
3 程序流程图
五、Minor GC(YGC)、Major GC(OGC)、Full GC
5.1、Major GC 和 Full GC 速度 要比 Minor GC 慢十倍以上,STW的时间更长,如果Major GC 后,还是不足,报OOM
JVM在进行GC时候,并非每次都对三个内存(新生代、老年代、元空间)进行一起回收,大部分的回收都是指的是新生代
针对HotSpot VM的实现,它里面的GC按照回收区域可以分为两种:一种是部分收集(Partial GC)、一种是整堆收集(Full GC)
··········部分收集(Partial GC):不是完整的收集整个java 堆的垃圾。其中分为:
······················->新生代收集(YGC):只对新生代进行收集(eden/s0,s1)
······················->老年代收集(OGC):只对老年代进行收集
··································目前,只有 CMS GC会有单独收集老年代的行为
··································很多时候 OGC 会和FULL GC 混淆使用 ,需要具体分析是老年代收集还是整体收集
·······················->混淆收集(Mixed GC):将新生代和部分老年代的垃圾收集
································目前,只有 G1 GC会有这种行为
··········整堆收集(Full GC):收集整个java堆内存的垃圾
-------补充:Minor GC(YGC) 会引发STW,暂停其它用户线程,等待垃圾回收结束后,用户线程才继续执行
5.2、Full GC
六、堆空间分代思想
为什么要把java堆分代?不分代就不能正产工作吗?
答:经研究,不同对象的生命周期不同,70-99%的对象都是临时对象
新生代:有eden区两个大小相同的幸存者区构成
老年代:存放新生代经理多次GC还存活的
分代的唯一理由 就是优化GC的性能
七、内存分配策略
针对不同年龄的对象分配原则如下:
1>优先分配到Eden区
2>大对象直接分配到老年代(尽量避免程序中出现超大的对象)
3>长期存活的对象分配到老年代
4>对象年龄的判断(如果幸存者区中相同年龄的所有对象大小的总和大于幸存者区空间的一半,年龄大于或者等于该年龄的对象可以直接进入老年代,无需等到 年龄到达 Max的值)
八、为对象分配内存:TLAB
8.1、为什么要有TLAB(Thread Local Allocation Buffer)
1.堆区线程共享的,任何线程都可以访问到堆区的共享区
2.由于对象实例的创建在JVM中非常频繁,因此多个线程操作共享数据是线程不安全的
3.为了避免多个线程同时操作同一地址,需要使用加锁的机制,影响分配速度
8.2、什么是TLAB?
1.从内存模型而不是垃圾收集的角度,对Eden区进行划分,JVM为每一个线程单独分配一个私有的区域,它包含在Eden空间内
2.多个线程同时分配内存,使用TLAB可以避免一系列的非线程安全得问题,同时还能增加内存分配的吞吐量,因此我们将这种方式叫做 快速分配策略
8.3、TLAB空间仅为Eden区的1%,空间很小
TLAB机制是首选,空间用尽了之后,JVM尝试加锁机制来保证操作的原子性
8.4、对象分配过程TLAB程序流程图
优先分配到TLAB区,TLAB在属于Eden区的私有区域,
如果内存不够的话,直接分配到Eden的其他区域
九、小结堆空间的参数设置
测试堆空间常用的JVM参数
十、堆是分配对象存储的唯一选择吗?
堆中分配是正常的,但是有特殊情况:就会被在栈上分配,从而提升性能(逃逸分析)
逃逸:脱离了原有的范围,如果逃逸出了方法,就会在堆上分配,否则,就在栈上分配,
这也是常见的堆外存储技术
没有发生逃逸,则可以分配到栈上
逃逸分析概述
图解
绿色区发生了逃逸,因为将sb返回了,这个对象可能被别的方法修改堆上分配
黄色区没有发生逃逸,sb只在内部用,返回的是sb的字符串,栈上分配
实际就是说 在方法里new 对象 出没出方法,出了方法就是逃逸了,没出就是没逃逸
不逃逸 之 代码优化
1、栈上分配
方法内new void,自产自销
2、同步省略
3、分离对象或标量替换
**标量替换:将一个结构体(对象)拆解后 分配在栈上