建设银行网站特色,信息流推广渠道,Wordpress写网页,阿里云做网站视频无法播放文章目录对象的创建与内存分配机制对象的创建类加载检查分配内存初始化零值设置对象头指向init方法其他#xff1a;指针压缩对象内存分配对象在栈上分配对象在Eden区中分配大对象直接分配到老年代长期存活的对象进入老年代对象动态年龄判断老年代空间分配担保机制对象的内存回…
文章目录对象的创建与内存分配机制对象的创建类加载检查分配内存初始化零值设置对象头指向init方法其他指针压缩对象内存分配对象在栈上分配对象在Eden区中分配大对象直接分配到老年代长期存活的对象进入老年代对象动态年龄判断老年代空间分配担保机制对象的内存回收垃圾标记算法常见的引用类型finalize()方法如何判断一个类是无用的类对象的创建与内存分配机制
对象的创建
java中对象的创建过程如下图所示分为这几步类加载检查、分配内存、初始化、设置对象头、执行init方法 类加载检查
检查当前要创建对象所对应的类是否被加载过类元数据信息是否已经在方法区中保存过如果没有则会去进行类加载的过程如果加载了则进行下一步 分配内存
这一部分需要考虑两点
应该将哪一块内存区域分配给对象如何解决多个线程并发分配同一块内存问题
如何分配内存
java中有两种为对象分配内存的方式 指针碰撞(Bump the Pointer) 默认使用 对于绝对工整的内存区域一边是已经分配的内存一边是未分配的内存中间有一个指针分割。 经过内加载后就已经知道了此对象会占用多大的内存从指针位置开始往后分配一块内存给该对象 空闲列表(Free List) 对于内存比较碎片化部署绝对工整的情况就需要使用空闲列表机制来分配内存了JVM底层会维护一个空闲列表然后选出一块合适该对象的内存区域进行分配
解决并发分配问题
在多线程的情况下就可以会出现多个线程都再指针移动前读取到了当前指针的位置然后把后面的一块内存区域进行了重复分配。解决这个问题有两种方法 CAS算法 再使用内存前先进行一次验证比如判断指针是否移动了如果验证不通过则进行重试 TLABJDK8默认使用 本地线程分配缓冲Thread Local Allocation Buffer,TLAB 为每个线程在Eden区中事先分配一块内存区域各个线程就在自己的这块区域中进行分配这样就不会有并发问题当这块内存区域不够使用时在使用CAS算法在Eden去中分配 通过-XX:/-UseTLAB参数来设定虚拟机是否使用TLAB(JVM会默认开启-XX:UseTLAB)-XX:TLABSize 指定TLAB大小。 初始化零值
为对象的属性赋零值这里需要注意在类加载的时候是操作的静态变量而这里才是操作的普通成员变量
初始化零值的作用是避免了对象属性没有初始化就被使用 设置对象头
对象头的作用是保存Hash值、分代年龄、锁信息、指向方法区中的类元信息等等
java的对象一般分为三部分对象头、实例属性、对其填充
对象头接下来详细讲实例属性保存此对象各个属性的值对其填充保证整个对象占用内存是8字节的整数倍
对象头如下图所示又分为三个部分 mark word标记阶段 记录对象hash值、分代年龄、锁信息。32位机器占4字节64为机器占8字节 Klass point 类型指针 指向方法区中类元数据。64位机器开启指针压缩后占4字节否则占8字节 虚拟机通过这个指针来确定这个对象是哪个类的实例。 数组长度只有数组对象才有占4字节
32位机器中的对象头信息 64位机器中的对象头信息 指向init方法
对成员变量赋程序员自定义的值并调用对象的构造方法 其他指针压缩
JDK1.6开始对于64为的机器默认是开启指针压缩的
启用指针压缩:-XX:UseCompressedOops(默认开启)禁止指针压缩:-XX:-UseCompressedOops
之所以要使用指针压缩最主要的原因就是节约堆的内存使用 对象内存分配
一个对象的内存分配过程大致如下图所示 对象在栈上分配
我们创建一个对象其实最开始的时候会判断该对象是否能够在栈上分配目的是减轻GC的压力当方法执行完 出栈后这一块栈帧内存区域也就被回收了
经过逃逸分析我们就能判断此对象是否能在栈上分配其实就是看此对象有没有在该方法之外的地方被引用是否有作为方法的返回值给其他地方引用。
同时还有标量替换机制对象是需要一块连续的内存空间而栈帧的内存可能没有这么大的一块连续的内存空间分配给对象而标量替换就是将对象拆开分散存储对象的成员属性。
标量与聚合量java的基本类型就是标量java的对象就是聚合量
逃逸分析和标量替换JDK1.7之后都是默认开启的逃逸分析开关-XX:DoEscapeAnalysis 标量替换开关-XX:EliminateAllocations 对象在Eden区中分配
年轻代默认占用堆的1/3内存其中Eden区大致占用8/10S0占用1/10 S1占用1/10
JVM参数-XX:UseAdaptiveSizePolicy(默认开启)会导致这个8:1:1比例自动变化如果不想这个比例有变化可以设置参数-XX:-UseAdaptiveSizePolicy
一般情况下对象会在Eden区中创建当存满后进行MinorGC将存活的对象移至S0区下一次MinorGC就回收Eden区和S0区将存活对象移至S1这其中分代年龄一直递增默认达到15后就移至老年代
如果一个对象在经过MinorGC后Survicor的剩余空间不足已存放该对象这个对象就会提前 直接进入到老年代中
当老年代内存使用满后触发FullGC回收堆和方法区的内存 大对象直接分配到老年代
JVM的参数 -XX:PretenureSizeThreshold能配置超过这个存储空间的对象就是大对象直接分配到老年代中目的是减少大对象来回拷贝占用Survicor的空间
这个参数只在 Serial 和ParNew两个收集器下有效。
所以我们一般都是两个参数一起设置JVM参数-XX:PretenureSizeThreshold1000000 (单位是字节) -XX:UseSerialGC 长期存活的对象进入老年代
对象每经过一次GC如果还存活该对象年龄则1默认达到15次后就进入老年代CMS收集器默认6岁不同的垃圾收集器会略微有点不同
对象晋升到老年代的年龄阈值可以通过参数 -XX:MaxTenuringThreshold 来设 对象动态年龄判断
经过一次MinorGC后存活的对象会存放在Survicor区这一批对象总大小如果大于了当前Servicor区内存的一半此时对象年龄大于等于这批对象最大年龄的对象就直接进入到老年代比如此时Servicor区现有一些对象年龄1年龄2年龄n的多个年龄对象总和超过了Survivor区域的50%此时就会把年龄n(含)以上的对象都放入老年代
-XX:TargetSurvivorRatio 目标存活率默认为50%
对象动态年龄判断机制一般是在minor gc之后触发的。 老年代空间分配担保机制
每经过一个MinorGC前都会计算当前老年代剩余可用空间判断老年代剩余可用空间是否小于年轻代所有对象总大小
如果小于则检查当前是否有-XX:-HandlePromotionFailure参数JDK8默认有
如果有这个参数则判断当前老年代剩余可用空间是否小于以往MinorGC移入老年代对象内存空间的平均值
如果小于或者上一步参数没有配置则直接进行FullGC之后其实还是会进行一次MinorGC 对象的内存回收
垃圾标记算法
引用计数法对象循环引用问题无法解决可达性分析算法一般默认使用的这种算法从GCRoot对象开始向下寻找引用。GFRoot对象是栈帧的成员变量、静态变量 常见的引用类型 强引用 普通的变量引用GC不会被清理 软引用 将对象用SoftReference对象包裹正常情况下不会被回收如果经过一次GC后还是释放不出空间存放新的对象则会将软引用的对象回收 public static SoftReferenceUser user new SoftReferenceUser(new User());弱引用 将对象用WeakReference软引用类型的对象包裹弱引用跟没引用差不多GC会直接回收掉很少用 public static WeakReferenceUser user new WeakReferenceUser(new User());虚引用 虚引用也称为幽灵引用或者幻影引用它是最弱的一种引用关系几乎不用 finalize()方法
finalize()方法最终判定对象是否存活当一个对象被标记为垃圾对象后如果该对象重写了finalize()方法则不会立刻回收该对象此时该对象有一次自救的机会只要重新与引用链上的任何的一个对象建立关联即可。那在第二次标记时它将移除出“即将回收”的集合。如果对象这时候还没逃脱那基本上它就真的被回收了。
注意一个对象的finalize()方法只会被执行一次也就是说通过调用finalize方法自我救命的机会就一次。
finalize()方法的运行代价高昂 不确定性大 无法保证各个对象的调用顺序 如今已被官方明确声明为不推荐使用的语法。 如何判断一个类是无用的类
在进行FullGC回收方法区中的内存时就需要判断类是不是无用的类
需要满足下面三个条件才是无用的类
类的实例对象都已经被回收了加载该类的类加载器被回收了堆中该类的java.lang.Class 对象没有在任何地方被引用