新建的网站只能用临时域名打开,网站开发案例图片,网站建设那里好,太原推广型网站制作申明#xff1a;文章内容是本人学习极客时间课程所写#xff0c;文字和图片基本来源于课程资料#xff0c;在某些地方会插入一点自己的理解#xff0c;未用于商业用途#xff0c;侵删。 原资料地址#xff1a;课程资料
对象的创建流程 常量池检查:检查new指令是否能在常…申明文章内容是本人学习极客时间课程所写文字和图片基本来源于课程资料在某些地方会插入一点自己的理解未用于商业用途侵删。 原资料地址课程资料
对象的创建流程 常量池检查:检查new指令是否能在常量池中定位到这个类的符号引用检查类之前是否被加载过
分配内存空间 有两种方式
指针碰撞: GC不带压缩功能Serial和ParNew
空闲列表: GC带压缩功能CMS 注内存的分配大多数new出来的对象都是新生代但是也有部分会去到老年代。 指针碰撞往往是一片连续的区域空闲列表是非连续的。 *内存分配存在的问题 1 线程安全问题因为堆是共享的如果多个线程同时执行可能存在线程安全问题。 解决方案 1 通过CAS乐观锁:IVM虚拟机采用CAS失败重试的方式保证更新操作的原子性 2TLAB (Thread LocalAllocation Buffer) 本地线程分配缓存预分配
分配主流程:首先从TLAB里面分配如果分配不到再使用CAS从堆里面划分
必要信息设置 对象类的元数据对象哈希码GC分带年龄存储在对象头中
进入老年代 进入老年代的条件
1 存活年龄太大默认超过15次【-XX:MaxTenuringThreshold】
2 动态年龄判断MinorGC之后发现Survivor区中的一批对象的总大小大于了这块Survivor区 的50%那么就会将此时大于等于这批对象年龄最大值的所有对象直接进入老年代。 举个栗子Survivor区中有一批对象年龄分别为年龄1年龄2年龄n的多个对象对象总和大小超过了Survivor区域的50%此时就会把年龄n及以上的对象都放入老年代。
为什么会这样希望那些可能是长期存活的对象尽早进入老年代。 -XX:TargetSurvivorRatio可以指定
2 大对象直接进入老年代前提是Serial和ParNew收集器 举个栗子字符串或数组 -XX:PretenureSizeThreshold 一般设置为1M 为什么会这样为了避免大对象分配内存时的复制操作降低效率。避免了Eden和Survivor区的复制
3 MinorGC后存活对象太多无法放入Survivor
空间担保机制当新生代无法分配内存的时候我们想把新生代的老对象转移到老年代然后把新对象 放入腾空的新生代。此种机制我们称之为内存担保。
MinorGC前判断老年代可用内存是否小于新时代对象全部对象大小如果小于则继续判断判断老年代可用内存大小是否小于之前每次MinorGC后进入老年代的对象平均大小
如果是则会进行一次FullGC判断是否放得下放不下OOM 如果否则会进行一些MinorGC MinorGC后剩余存活对象小于Survivor区大小直接进入Survivor区 MinorGC后剩余存活对象大于Survivor区大小但是小于老年代可用内存直接进入老年代 MinorGC后剩余存活对象大于Survivor区大小也大于老年代可用内存进行FullGC FullGC之后任然没有足够内存存放MinorGC的剩余对象就会OOM
案例1,大对象直接进入老年区
// -Xmx60m -Xms60m -XX:NewRatio2 -XX:SurvivorRatio8 -XX:PrintGCDetails
// Xmx 程序运行时最大内存大小
// Xms 程序启动时最大内存大小
// NewRatio 年轻代和老年代的比例为12
// SurvivorRatio survivor区域和eden 区域的内存比例 1 8
// PrintGCDetails 打印详细日志
public static void main(String[] args) {byte[] buffer new byte[1024*1024*20];}由这个例子我们可以得出结论因为年轻代的内存总共也就18M左右导致年轻代无法存放我们创建的20M大小的数组所以直接放入到了老年代。 案例2
// -Xmx600m -Xms600m -XX:PrintGCDetails
public class HeapInstance {public static void main(String[] args) {ListPicture list new ArrayList();while (true) {try {Thread.sleep(20);} catch (InterruptedException e) {e.printStackTrace();}list.add(new Picture(new Random().nextInt(1024 * 1024)));}}
}public class Picture {private byte[] pixels;public Picture(int length){this.pixels new byte[length];}
}与前面的描述呼应进行三次FG后任然无法分配内存则OOM。整个过程大致是这样我们不断地创建对象然后Eden区逐渐被占满从而一部分对象复制到幸存区然后幸存区这个过程中会有动态年龄的判断来回复制放不下了进入老年区直到老年区也无法放下就内存溢出。
空间担保机制当新生代无法分配内存的时候我们想把新生代的老对象转移到老年代然后把新对象放入腾空的新生代。此种机制我们称之为内存担保。 案例3,动态内存担保机制的演示
// -Xms20M -Xmx20M -Xmn10M -XX:PrintGCDetails -XX:SurvivorRatio8 -XX:UseSerialGC
public class Demo {private static final int _1MB 1024 * 1024;public static void main(String[] args) {memoryAllocation();}public static void memoryAllocation() {byte[] allocation1, allocation2, allocation3, allocation4;allocation1 new byte[1 * _1MB];//1Mallocation2 new byte[1 * _1MB];//1Mallocation3 new byte[1 * _1MB];//1Mallocation4 new byte[5 * _1MB];//5MSystem.out.println(完毕);}
}那么它执行的流程是这样的前面3M的对象在eden区域分配后后面来了一个5M的对象发现内存不足于是将之前3M 的数据移入了老年代之后将5M的数据放入新生代这就是内存担保机制。
对象内存布局
对象里的三个区 对象头HeaderJava对象头占8byte。如果是数组则占12byte。因为JVM里数组size需要使用4byte存储。 标记字段MarkWord 用于存储对象自身的运行时数据它是synchronized实现轻量级锁和偏向锁的关键。 默认存储对象HashCode、GC分代年龄、锁状态等等信息。 为了节省空间也会随着锁标志位的变化存储数据发生变化。下面画图解释 类型指针KlassPoint 是对象指向它的类元数据的指针虚拟机通过这个指针来确定这个对象是哪个类的实例 开启指针压缩存储空间4byte不开启8byte。 JDK1.6默认开启 **数组长度**如果对象是数组则记录数组长度占4个byte如果对象不是数组则不存在。 **对齐填充**保证数组的大小永远是8byte的整数倍。 实例数据Instance Data生成对象的时候对象的非静态成员变量也会存入堆空间 对齐填充PaddingJVM内对象都采用8byte对齐不够8byte的会自动补齐。 标记字段的结构 案例1
dependencygroupIdorg.openjdk.jol/groupIdartifactIdjol-core/artifactIdversion0.9/version
/dependencyObject o new Object();System.out.println(new Object: ClassLayout.parseInstance(o).toPrintable());注首先对象头是包含MarkWord和类型指针这两部分信息的 开启指针压缩的情况下存放Class指针的空间大小是4字节MarkWord是8字节对象头为12字节 新建Object对象会在内存占用16个字节其中Header占12个MarkWord占8个KlassPoint占4个没有实例数据补充对齐4个。 结论对象大小 对象头12 实例数据0 对齐填充4 16 bytes 案例2
public class TT {public static void main(String[] args) {Hero a new Hero();System.out.println(new A:ClassLayout.parseInstance(a).toPrintable());a.setFlag(true);a.setI(1);a.setStr(ABC);System.out.println(赋值 A:ClassLayout.parseInstance(a).toPrintable());}
}