Java对象内存布局
对象头 :
对象自身运行时数据(MarkWorld ) :哈希码 、GC分代年龄 、锁状态标志 、偏向线程ID 、偏向时间戳
class对象指针:
对象数组:
**实例数据:**对象实际数据
对齐填充
JVM内存结构
程序计数器 :用于指示当前线程正在执行的字节码指令的地址。(线程私有)
Java虚拟机栈 :用于存储局部变量、方法参数、部分计算结果和方法调用等信息。(线程私有)
本地方法栈 :用于执行本地方法(Native Metho d)native:在java中有用native修饰的,表示这个方法不是java原生的(线程私有)
堆 :用于存储Java对象实例和数组。堆是Java虚拟机管理的最大的一块内存区域。(线程共享)
元数据区 :class结构信息,方法的定义,静态变量等.而常量池放到元数据区**(线程共享)**
直接内存:不是Java虚拟机运行时数据区的一部分,但也被频繁使用。它是通过Native函数库直接分配的内存,用于提高访问性能。
注意:
堆和元数据区是线程共享的,在Java虚拟机中只有一个堆、一个元数据区,并在JVM启动的时候就创建,JVM停止才销毁。
Java虚拟机栈、本地方法栈、程序计数器是每个线程私有的,随着线程的创建而创建,随着线程的结束而死亡。
在JVM中什么是直接内存
直接内存(Direct Memory)是指由操作系统直接管理的内存区域 ,而不是由Java堆来管理。它是通过使用Java NIO (New Input/Output)库中的ByteBuffer类来分配和操作的。
直接内存的分配和释放不受Java堆大小的限制,因此可以更灵活地使用大量的内存。它的分配是通过调用 ByteBuffer.allocateDirect() 方法来完成的,而不是使用传统的Java堆分配方式。
直接内存的优势在于它可以直接与操作系统交互,实现零拷贝(Zero-copy)操作 ,提高了IO操作的效率。然而,由于直接内存的分配和释放需要与操作系统进行交互,所以其性能开销相对较高。
需要注意的是,直接内存的使用需要谨慎,过度使用直接内存可能导致操作系统的内存资源耗尽,从而影响系统的稳定性。因此,在使用直接内存时应该合理控制其分配的大小,并及时释放不再使用的内存。
JDK1.8堆内存结构
-
新生代(Young Generation):新生代是Java堆中的一块区域,用于存放新创建的对象。它又被分为Eden区和两个Survivor区(S0和S1) 。当Eden区满时,会触发Minor GC(Young GC),将存活的对象复制到Survivor区,同时清理掉Eden区和From区中的无用对象。
-
老年代(Old Generation):老年代是Java堆中的另一块区域,用于存放长时间存活的对象。当Survivor区无法容纳对象时,会将对象放入老年代。当老年代满时,会触发Full GC,进行垃圾回收。MajorGC(Full GC)
元数据区:在JDK1.8中,永久代被移除,取而代之的是元数据区(Metaspace),用于存放类的元数据信息。元数据区的大小可以通过设置JVM参数来控制。
在JDK1.8中,Java堆内存结构的大小和分配方式可以通过JVM参数来进行调整,以满足不同应用程序的需求。
新生代与老年代的区别与关系
区别:
-
存放对象类型:新生代主要用于存放新创建的对象,而老年代主要用于存放长时间存活的对象。
-
垃圾回收频率:由于大部分对象的生命周期很短,新生代的垃圾回收频率通常比老年代高。新生代使用的垃圾回收算法通常是基于复制(Copying)的,这种算法适用于短生命周期的对象。
-
垃圾回收时间:新生代的垃圾回收时间通常较短,因为只需要清理较小的一部分堆内存。而老年代的垃圾回收时间较长,因为需要遍历整个堆内存。
关系:
-
年龄判定:新生代中的对象的年龄会逐渐增加,当达到一定年龄阈值时,会被晋升到老年代。
-
内存分配:新生代中的内存空间被划分为Eden区和两个Survivor区(From和To)。新创建的对象首先被分配到Eden区,当Eden区满时,会触发Minor GC,将存活的对象复制到Survivor区,同时清理掉Eden区和From区中的无用对象。
-
大对象直接进入老年代:如果对象的大小超过了新生代的一半大小,或者在Eden区无法分配连续内存空间时,该对象将直接被分配到老年代。
GC垃圾回收如何发现垃圾
1、引用计数算法:核心思想是,堆中的对象每被引用一次,则计数器加1,每减少一个引用就减1,当对象的引用计数器为0时可以被当作垃圾收集。(无法解决循环引用问题)
2、根搜索算法(可达性算法):垃圾回收器通过从根对象出发,沿着对象之间的引用链进行遍历,标记所有可达的对象。可达的对象意味着它们被程序中的其他对象引用着,因此是活动的,不是垃圾;剩余的节点则被认为是没有被引用到的节点,即可以当作垃圾。
Java中可作为GCRoot的对象有:
1.虚拟机栈中引用的对象
2.本地方法栈引用的对象
3.元空间中静态属性引用的对象
4.元空间中常量引用的对象
GC垃圾回收算法
1、标记-清除算法(markandsweep)
分为"标记"和"清除"两个阶段:首先标记出所有需要回收的对象,在标记完成之后统一回收掉所有被标记的对象**。** 缺点:首先,效率问题,标记和清除效率都不高。其次,标记清除之后会产生大量的不连续的内存碎片
2、标记-整理算法
对标记-清除算法的改进。标记阶段是相同的,但标记完成之后不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,在移动过程中清理掉可回收的。缺点:耗时耗力
优点:不会产生大量不连续的内存碎片。
3、复制算法(copying)
该算法将堆内存分为两个相等大小的区域,每次只使用其中一个。当需要进行垃圾回收时,将存活的对象复制到另一个区域,并将原区域中的对象全部清除。复制算法简单高效,但会浪费一半的内存空间。
4、分代收集算法(generation)
当前主流JVM都采用分代收集(GenerationalCollection)算法,这种算法会根据对象存活周期的不同将内存划分为年轻代 、老年代,不同生命周期的对象可以采取不同的回收算法,以便提高回收效率。
新生代的比例默认为:8:1:1 Eden:S0:S1=8:1:1
**注意:**当一个大对象不足于存放到eden区时,就将存活对象直接存放到老年代。若是老年代也满了就会触发一次FullGC,也就是新生代、老年代都进行回收。
JVM调优
jvm调优可以对jvm的一些参数进行调整
1、增加堆内存 -Xms -Xma
2、调整垃圾回收机制,选择合适的垃圾回收算法
3、设置合适的年轻代和老年代的比例
-Xms8g :设置JVM最小内存为8g 。此值可以设置与-Xmx相同,以避免每次垃圾回收完成后JVM重新分配内存。(最小内存等于最大内存,减少GC回收次数)
-Xmx8g:设置JVM最大可用内存为8g。
-Xmn4g:设置年轻代大小为4G。
-XX:NewRatio=2 设置年轻代(包括Eden和两个Survivor区)与年老代的比值。设置为2,则年轻代与年老代所占比值为1:2,年轻代占整个堆栈的1/3。
-XX:MaxMetaSpaceSize=256m: 设置元空间大小为256m(避免发生OOM,内存溢出)
-XX:MaxTenuringThreshold=15:设置垃圾最大年龄。如果设置为0的话,则年轻代对象不经过Survivor区,直接进入老年代。 对于老年代比较多的应用,可以提高效率。最大不操过15。
注意:
因为每个jdk的版本不一致,但是-X是标配,不会改变,-XX可能会因为版本不一致而发生改变
Java引用类型分类
java的引用类型一般分为四种:强引用 、软引用 、弱引用 、虚引用
**强引用:**普通的变量引用
**软引用:**将对象用SoftReference软引用类型的对象包裹,正常情况不会被回收,但是GC做完后发现释放不出空间存放新的对象,则会把这些软引用的对象回收掉。(星巴克)
**弱引用:**将对象用WeakReference弱引用类型的对象包裹,弱引用跟没引用差不多,GC会直接回收掉,只要GC执行了,他就会被回收掉(沙县小吃)
虚引用:虚引用也**称为幽灵引用或者幻影引用,它是最弱的一种引用关系,几乎不用。
观察者模式
观察者模式(Observer Pattern)是一种行为型设计模式,也叫发布-订阅模式,它定义了对象之间一对多的依赖关系,使得当一个对象状态改变时,所有依赖它的对象都能收到通知并自动更新。
观察者模式中,有两种类型的对象:观察者 和主题(或称为被观察者)。观察者将自己注册到主题,以便在主题的状态改变时接收通知。主题会维护一组观察者,并在自身状态改变时通知它们。主题通过调用观察者的统一接口来通知它们状态的变化。
springBoot事件监听就是观察者模式