既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上大数据知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新
3.1,堆空间对象组成
在JVM中,java对象可以分为两类
一类是生命周期较短的瞬时对象,这类对象的创建和消亡都比较快
另一类周期长,某些极端情况下可以和JVM进程的生命周期保持一致
java堆细分可以分为新生代(YoungGen)和老年代(OldGen),年轻代又可以细分为Eden区和Survivor0空间和Survivor1空间,有时也被称为(from区和to区)
在这些对象中,也会对新生代和老年代的空间大小设置对应的比例,新生代和老年代的比例默认为1:2
当然也可以通过命令进行修改这个默认的比例,一般不会修改这个默认值,除非是知道这个老年代的对象偏多
-XX:NewRatio=2 : 表示新生代占1,老年代占2,新生代占1/3,默认比例
-XX:NewRatio=4 : 表示新生代占1,老年代占4,新生代占1/5
也可以直接通过命令查看,通过jps查出进程号之后,通过Jinfo去查看
jps
jinfo -flag NewRatio process(进程号)
除了老年代和新生代的对空间有比例之外,这个新生代中的Eden区和Survicor区也有对应的比例。如果是在Linux系统下,其比例为8:1 ;如果是在Windows系统下,其比例为6:1。
-XX:SurvivorRatio=8 : 表示eden区占8,Survicor区占1
-XX:SurvivorRatio=6 : 表示eden区占6,Survicor区占1
-XX:SurvivorRatio=4 : 表示eden区占4,Survicor区占1
这个比例也可以直接通过命令查看,通过jps查出进程号之后,通过Jinfo去查看
jps
jinfo -flag SurvivorRatio process(进程号)
因此可以得到一下结论
🎱 几乎所有的Java对象都是在Eden区被new出来
🎱 绝大多数对象的销毁在新生代就进行了
3.2,堆对象分配的一般过程
为新对象分配内存时一件非常严谨和复杂的任务,JVM的设计者们不仅需要考虑内存如何分配,在哪里分配等问题,并且由于内存分配算法与内存回收算法密切相关,所以还需要考虑GC执行完内存回收后是否会在内存空间中产生内存碎片等问题。
🎱 在new一个对象时,会先将对象放在eden区,并且该去有大小限制
🎱 当eden区的数据量满了,程序还需要创建对象的时候,JVM的Minor Gc就会对Eden区的垃圾进行回收,当Eden区不再被其他对象引用的对象就进行销毁,被引用的对象就加载到Survivor0区中,然后此时eden区为空,再将新数据加载到eden区中
🎱 只有eden去满才会触发这个Minor GC,但是在触发这个垃圾回收时,不仅会回收Eden区的对象,也会回收这个Survivor区的对象
🎱 如果再次触发垃圾回收,就会将eden区中的未被引用的对象销毁,并且此时会检查survicor0区中的的数据,如果不存在有对该对象的引用,那么该对象也会被销毁,然后将eden区未被销毁的和survicor0未被销毁的一起加入到survicor1区,后面这两个区的对象往返移动
🎱 每将对象移动一次survicor区,其age年龄就会加1,初始值为1,阈值为默认为15,可以修改,当年龄达到阈值时,其再gc一次到达16,就会将对象加入到老年代的空间区域中
🎱 当老年代中的空间满了时,就会触发这个Full GC
总结
- 针对幸存者s0和s1的总结:复制之后有交换,谁不空谁是from,谁空谁是to
- 针对垃圾回收总结:频繁回收在新生区,很少在老年区回收,几乎不在永久区/元空间回收
3.3,堆对象分配的特殊过程
🌲 前两个步骤还是一样,在new一个对象时,会先将对象放在eden区,并且该去有大小限制
🌲 当eden区的数据量满了,程序还需要创建对象的时候,JVM的Minor Gc就会对Eden区的垃圾进行回收,当Eden区不再被其他对象引用的对象就进行销毁,被引用的对象就加载到Survivor0区中,然后此时eden区为空,再将新数据加载到eden区中
🌲 这里开始就不一样了,如果遇到的是一个大对象,可以在eden区存的下,但是在survivor区存活不下,那么直接将这个对象晋升为老年代
🌲 如果遇到的是一个超大对象,在eden区放不下,那么也会直接晋升为老年代,将这个超大对象存入到老年区中,如果老年代内存不够,那么就会触发Full GC,如果还是不够或者内存大小直接小于这个超大对象的大小,那么就会直接触发这个OOM,内存溢出错误。
3.4,代码示例
给对应的jvm堆配置的大小如下:-Xms600m -Xmx600m
,然后其对应的代码如下,就是写一个死循环,一直去创建Student对象,list作为一个对象的引用,只要list存在,里面的对象就不会被回收掉。
public class Student {
//创建bu数组
byte bu[] = new byte[new Random().nextInt(1024\*200)];
public static void main(String[] args) {
List<Student> list = new ArrayList<>();
while (true){
list.add(new Student());
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
接下来继续通过这个jvisualvm这个jdk自带的工具来查看堆内信息,一段时间之后,就发现出现了这个OOM。
如下图所示,Old区占400内存,年轻代占200内存,所以可知这个老年代区域:新生代区域为2:1;eden区大小为150,survicor为25,我这个是在windows系统下,因此也符合6:1,如果是linux系统,那就是8:1了。
而这个eden区到达峰值时,就表示eden区的数据满了,那么就会触发一次Minor GC,那么就会有数据存储在survivor中,再到达一次峰值,又会触发一次Minor GC,因此可以看到数据在Survivor的两个区中移动,而由于对象一直被引用着,那么不管是Full GC还是Minor GC都是不能将数据给移除的,因此这个young区的数据不能被清除,数据就会都存在old区中,那么old区的数据就是成直线上升的,当old区满了之后,那么就会直接触发这个OOM了。
4,初识GC
4.1,初识Minor GC,Major GC,Full GC
JVM在进行GC的时候,并非每次都对上面三个内存(新生代,老年代和方法区)区域一起回收,大部分的时候指的还是新生代。
而在HotSpot Vm中,GC又分为两种类型:部分收集和整堆收集
- 部分收集,不是完整的收集整个Java堆的垃圾收集,期中又分为:
新生代收集(Minor GC / Young GC):只是新生代的垃圾收集
老年代收集(Major GC / Old GC):老年代的收集器,很多时候Major GC和Full GC会混合使用,区要区分是整堆回收还是老年代回收
混合收集(Mixed GC):收集整个新生代以及部分老年代的垃圾收集
- 整堆收集(Full GC),收集整个java堆和方法区的垃圾收集
4.1.1,新生代GC的触发条件
🐣 当新生代空间不足时,就会触发Minor GC,这里的新生代指的是Eden区,Survivor区是不会触发GC的
🐣 Java对象大多具有朝生夕灭的特性,所以Minor GC也非常频繁,回收速度也比较快
🐣 Minor GC会触发STW,就是暂停其他的用户线程,等垃圾回收结束,才会恢复运行
4.1.2,老年代GC的触发条件
🐣 指发生在老年代的GC,对象从老年代消失,即 Major GC
或者Full GC
发生了
🐣 出现了Major GC之后,经常会伴随着至少一次的Minor GC
🐣 Major GC的速度一般会比Minor GC慢10倍,其STW的时间甚至要更长
🐣 如果Major GC 之后,内存还是不足,就会直接报OOM了
4.1.3,Full GC的触发机制
🐣 调用System.gc()时,系统建议执行Full GC时,但是不必然执行
🐣 老年代或者方法区空间不足
🐣 Minor GC进入老年代的平均大小大于老年代的可用内存
🐣 直接加入到老年代的对象的大小大于老年代可用内存的大小
5,堆内存分配
5.1,基本的内存分配策略
🐋 将对象优先分配到Eden区
🐋 大对象直接分配到老年代,尽量避免程序中出现过多的大对象,如大树组,字符串等
🐋 长期存活的对象分配到老年代
🐋 动态对象年龄判断
🐋 空间分配担保
5.2,TLAB
Thread Local Allocation Buffer,即线程本地分配的一个缓冲区。
5.2.1,为什么要有TLAB
- 堆区是线程的共享区域,任何线程都可以访问到堆区的共享数据,好处是进程间的通信比较方便
- 由于对象的实例创建在JVM中非常频繁,因此在并发环境下,堆区划分的内存空间是线程不安全的
- 为避免多个线程操作同一个地址,需要使用加锁等机制,但是也会一定的效率
5.2.2,什么是TLAB
- 从内存模型角度来看,对Eden区域继续进行划分,JVM为每个线程分配了一个私有的缓冲区,存在Eden空间
- 多线程在分配内存时,使用TLAB可以避免一系列的线程安全问题,并提升一定的吞吐量
- JVM将这个TLAB作为内存分配的首选,可以通过
-XX:UseTLAB
设置开启TLAB空间 - 默认情况下的这个TLAB内存只占1%,也可以通过
-XX:TLABWasteTargetPercent
设置其空间的百分比 - 一旦对象在创建这个TLAB空间分配失败,JVM就会通过加锁机制确保数据操作的原子性
因此在堆空间中,也不一定全部都是数据共享的,每个线程直接还存在着TLAB
6,堆空间参数设置
在堆空间的参数设置主要有如下
内容 | 链接地址 |
---|---|
-Xms | 初始堆内存 |
-Xmx | 最大堆内存 |
-Xmn | 新生代比例 |
-XX:PrintFlagsInitial | 查看所有参数的默认值 |
-XX:PrintFlagsFinal | 查看所有参数的最终值 |
-XX:NewRatio | 设置新生代和老年代堆占比 |
-XX:SurvivorRatio | 设置s区在Eden区占比 |
-XX:MacTenuringThreshould | 设置新生代垃圾的最大年龄 |
-XX:PrintGCDetails | 输出详细GC日志 |
-XX:HandlePromotionFailure | 是否设置空间分配担保 |
如设置堆空间的初始大小和最大大小:-Xms1024M -Xmx1024M
7,逃逸分析(重点)
在java虚拟机中,对象是在Java堆内存中分配的,但是有一种特殊的情况,那就是经过了逃逸分析后发现,如果一个对象并没有套溢出方法的话,那么就有可能被栈上分配,这样就可能被优化成栈上分配。这样就无需进行堆上分配,也就无需进行垃圾回收了。
7.1,栈上分配
如何将堆上的对象分配到栈上,需要使用到逃逸分析的手段,这是一种可以有效的减少Java程序中同步负载和内存压力的跨函数全局数据流分析算法。
通过逃逸分析,虚拟机的编译器可以分析一个新的对象的引用使用范围从而决定是否要将这个对象分配到堆上,逃逸分析的基本行为就是分析对象的作用域:
🎈 当一个对象在方法中被定义之后,对象只在方法内部被使用,则没有发生逃逸
🎈 当一个对象在方法中被定义之后,他被外部所引用,则认为发生逃逸
//没有发生逃逸分析
public Student test1(){
//随着入栈出栈,该对象被销毁,对象分配在栈中
Student stu = new Student();
return null;
}
//发生了逃逸分析
public Student test2(){
//该对象被外部所引用,对象分配在堆中
Student stu = new Student();
return stu;
}
在实际开发中,能使用局部变量的,就不要使用在方法外定义,也不要考虑静态等问题,这也是堆优化的策略之一
开启逃逸分析的命令:-XX:+DoEscapeAnalysis
,关闭则是-XX:-DoEscapeAnalysis
7.2,同步省略
除了这个栈上分配对象之外,JIT即时编译器也可以借助逃逸分析来判断同步块所使用的锁对象是否只能被一个线程访问而没有发布到其他线程,如果没有,那么这个JIT编译器就会取消堆这部分代码的同步,这样就能大大的提高并发性能,这个取消同步的过程就叫同步省略,又名锁消除
public void test(){
Student stu = new Student();
//发现每次调用该方法锁的根本不是同一个对象,因此会将这个锁消除
synchronized(stu){
System.out.println("helloi stu");
}
}
7.3,标量替换
逃逸分析也能实现这个标量替换,标量指的就是无法再分解的更小的数据,如Java原始的数据类型就是标量,而还可以继续分解的对象化就被称为聚合量,java的对象就被称为聚合量,他为他还可以分解成标量或者其他聚合量
//聚合量是由标量和聚合量组成
public class User{ //聚合量
int age; //标量
String name; //标量
Account account; //聚合量
}
public Account{ //聚合量
Double money; //标量
}
而方法的局部变量是存在栈中的,在JIT编译期间,如果发现一个对象不被外界访问,那么结果这个JIT的优化,就会将这个对象拆分成一个个标量,即对应的局部变量,存储在栈帧里面。如这个Account对象,只有一个money这一个局部变量存储在栈帧中,而这个对象不存在,这个行为就被称为标量替换
标量替换的开启如下:-XX:EliminateAllocations
,默认是打开的,允许将对象打散分配在栈上。
8,堆空间是分配对象的唯一选择吗(重点)
答案是:是的!在HotSpot虚拟机确实是这样子
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上大数据知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新
默认是打开的,允许将对象打散分配在栈上。
8,堆空间是分配对象的唯一选择吗(重点)
答案是:是的!在HotSpot虚拟机确实是这样子
[外链图片转存中…(img-54KIG5HH-1715701072229)]
[外链图片转存中…(img-4CAZS5HS-1715701072229)]
[外链图片转存中…(img-Zfega2UB-1715701072229)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上大数据知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新