虚拟机的内存布局
1.分为五部分:线程私有的:程序计数器、本地方法栈、虚拟机栈。线程共享的:堆、方法区。
2.除了程序计数器,其他的内存区域都会出现OutOfMemoryError。
3.采用分代理论来实现垃圾收集器:堆被分为新生代和老年代,方法区被称为永久代,新生代又分为一个Eden和两个survior空间。
4.所有的对象和数组都应该在堆上分配。
5.方法区存储的是加载的类型数据、方法信息、字段信息,运行时常量池(class文件中的常量池)。
6.程序计数器存储的是所执行的字节码的行号。
方法区的演变
参考链接:https://www.cnblogs.com/ding-dang/p/13085355.html
1.只有HotSpot才有永久代。 BEA JRockit、IBM J9等来说,是不存在永久代的概念的。原则上如何实现方法区属于虛拟机实现细节,不受《Java虚拟机规范》管束,并不要求统一。
2.Hotspot中 方法区的变化:
(1)jdk1.7:有永久代,但已经逐步“去永久代”,字符串常量池、静态变量移除,保存在堆中。
(2)jdk1.8及之后:无永久代,类型信息、方法、常量保存在本地内存的元空间,但字符串常量池、静态变量仍在堆。
3.永久代为什么要被元空间替换:
(1) 为永久代设置空间大小是很难确定的。 在某些场景下,如果动态加载类过多,容易产生Perm区的OOM。而元空间和永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。因此,默认情况下,元空间的大小仅受本地内存限制。
(2)对永久代进行调优是很困难的。
4.StringTable 为什么要调整:
因为永久代的回收效率很低,在Full GC的时候才会触发,这就导致了StringTable回收效率不高,而我们开发中会有大量的字符串被创建,放到堆里,能及时回收内存。
对象的访问定位
1.一个对象由三部分组成:对象头、实例数据、对齐填充(每个对象的大小是8字节的的整数倍)。
2.对象头的组成是:对象自身运行时的数据(32bit或者是64bit,会进行复用,不同的状态下保存不同的信息)、指向类型元数据的指针(不一定所有的虚拟机实现都会保存这个值,有的是通过句柄访问的。hotspot会保存)、如果是数组类型还会记录数组的长度。
3.对象的访问方式有两种:通过句柄访问(在堆内存会分配一个句柄池,每一个句柄都会有一个指向对象实例数据的指针和指向对象类型数据的指针,此时对象头不需要保存指向类型元数据的指针)、通过直接指针访问(直接指向堆内存中的指针,hotspot使用此方式进行内存定位)。
对象的创建步骤
1.判断类是否被加载。
2..分配内存空间(采用指针碰撞或者空间列表,具体使用哪一种取决于垃圾收集器,指针碰撞采用CAS来分配空间。
3.把对象的属性初始化为零值。
4.设置对象头信息(hashCode要到调用hashCode方法时才计算)。
5.调用初始化方法。
类文件结构
1.类或者接口被编译为一个class文件(字节码文件),字节码的标准以及含义看jvm虚拟机。
2.介绍几个名词:
全限定名:代表一个全部的名称,比如类的全名。
简单名称:只代表这个名称不加任何的限定,比如一个方法或者是字段名。
字段的描述符:表示字段的的类型。
方法的描述符:表示方法的参数列表(包括数量、类型和顺序)和返回值。
3.class文件类的结构:魔数、版本号、常量池(存储的字面量和符号引用(类和接口的的全限定名、方法的名称和描述符、字段的名称和描述符))、类信息(访问标志、类索引(常量池符号的下标)、父类索引(常量池符号的下标)、接口索引集合(指常量池符号的下标))、字段信息(访问标志、名称(常量池符号的下标)、描述符(常量池符号的下标)、属性数量、属性信息)、方法信息(访问权限、方法名(常量池符号的下标)、方法描述符(常量池符号的下标)、属性数量、属性信息)。
4.除了上面已经介绍到的信息,没有介绍到的信息都存放在属性信息中了,比如方法的内部代码、方法抛出的异常等。
类加载
1.类的生命周期分为七个阶段:加载、验证、准备、解析、初始化、使用、卸载。
2.验证、准备、解析又称为连接。
3.有且只有六种情况要对类进行初始化(其余的阶段都要在此阶段之前开始):
(1)遇到这四条字节码指令时:new、getstatic、putstatic、invokestatic。
(2)对类型进行反射调用时。
(3)类初始化时,父类还没初始化,则先初始化父类。
(4)虚拟机开启时,用户要指定一个主类(虚拟机会先初始化这个主类)。
(5)一个接口如果定义了默认方法,这个接口的实现类发生了初始化,接口要在前进行初始化。
(6)MethodHandler实例最后的解析结果为REF_getStatic、REF_putStatic、REF_invokeStatic、REF_newInvokeSpecial四种类型的句柄,方法句柄对应的类没有初始化,需要触发初始化。
每个步骤实现的功能
1.加载需要完成三件事情:
(1)通过一个类的全限定名获取类的二进制字节流。
(2)把字节流锁代表的静态存储结构转化为方法区的运行时数据结构。
(3)在堆内存中生成这个类的class对象,作为方法区这个类的数据访问入口。
2. 验证:对文件格式、元数据、字节码、符号引用进行验证。
3. 准备:为类中定义的静态变量分配内存并初始化为零值。
4. 解析:把方法区运行时常量池的符号引用转换为直接引用。
5. 初始化:会执行类的构造器<cinit>(),类构造器是由编译器自动收类中的所有类变量的赋值动作和静态语句块中的语句合并产生的,编译器的收集顺序是由语句在源文件中出现的顺序决定的。
类加载器
1.通过一个类的全限定名获取类的二进制字节流,这个动作可以让用户自己去实现,实现这个动作的代码成为类加载器。
2.类加载器和类本身确定了这个类下虚拟机中的唯一性。只要类加载器不同,这两个类(来源于同一个class文件,被一个jvm加载)就不相等(类的equals方法 instanceof的关系判定)。
3.类加载器采用双亲委派机制,这样可以保证基础的类不会被修改。
4.类加载器的双亲委派模型(从上到下):启动类加载器()、扩展类加载器、系统类加载器、用户自定义类加载器。
分派
1.分派分为动态分派和静态分派。
2.