JVM相关整理(有错误的请指出)
JVM的内存结构,JVM内存里面主要有方法区(jdk1.8后变为了元数据区),堆内存,和栈区三大块
方法区存着类的元数据(类的信息),常量,静态变量等信息(jdk1.8以后,常量和静态变量在堆中,类的信息加载在了本地内存(元数据区)),堆里面存着类的实例,又分为,年轻代和年老代,年轻代有又为伊甸园和幸存者区,栈区又分为java栈区,本地栈区,程序计数区,
那么这些内存块又是怎么用的呢
我们从一个类的生命周期来理解整个内存区域的使用,
1.首先,应用启动的时候,类要加载进内存里面,简单来说jvm首先将.class类文件转换为二进制流,然后再将流里面的类的定义的数据结构转换为可运行的数据结构(1.8之前放到方法区里,1.8放到元数据区里),再生成一个当前class的实例对象扔到堆里,
2.然后JVM对字节流做验证(文件格式,元数据,字节码,符号),
1).文件格式验证:
(1)是否以魔数0xCAFEBABE开头。
(2)主、次版本号是否在当前虚拟机处理范围之内。
(3)常量池的常量中是否有不被支持的常量类型(检查常量tag标志)。
(4)指向常量的各种索引值中是否有指向不存在的常量或不符合类型的常量。
(5)CONSTANT_Utf8_info型的常量中是否有不符合UTF8编码的数据。
(6)Class文件中各个部分及文件本身是否有被删除的或附加的其他信息。
2).元数据验证:
(1)这个类是否有父类(除了java.lang.Object之外,所有类都应当有父类)。
(2)这个类是否继承了不允许被继承的类(被final修饰的类)。
(3)如果这个类不是抽象类,是否实现了其父类或接口之中所要求实现的所有方法。
(4)类中的字段、方法是否与父类产生矛盾(例如覆盖了父类的final字段,或者出现不符合规则的方法重载,例如方法参数都一致,但返回值类型却不同等等)。
3).字节码验证:
主要目的是通过数据流和控制流分析,确定程序语义是合法的、符合逻辑的。这个阶段将对类的方法体进行校验分析,保证被校验类的方法在运行时不会产生危害虚拟机安全的事件,例如:
(1)保证任意时刻操作数栈的数据类型与指令代码序列都能配合工作,例如不会出现类似这样的情况:在操作数栈放置了一个int类型的数据,使用时却按long类型来加载入本地变量表中。
(2)保证跳转指令不会跳转到方法体以外的字节码指令上。
(3)保证方法体中的类型转换是有效的,例如可以把一个子类对象赋值给父类数据类型,但是把父类对象赋值给子类数据类型,甚至把对象赋值给与它毫无继承关系、完全不相干的一个数据类型,则是危险不合法的。
(Halting Problem:通过程序去校验程序逻辑是无法做到绝对准确的——不能通过程序准确的检查出程序是否能在有限时间之内结束运行。)
4).符号引用验证:
符号引用验证可以看作是类对自身以外(常量池中的各种符号引用)的信息进行匹配性校验,通常需要校验以下内容:
(1)符号引用中通过字符串描述的全限定名是否能够找到对应的类。
(2)在指定类中是否存在符合方法的字段描述符以及简单名称所描述的方法和字段。
(3)符号引用中的类、字段、方法的访问性(private、protected、public、default)是否可被当前类访问。
3.然后进入准备阶段,准备阶段主要对类变量分配内存(静态变量和常量),这里只是分配内存
4.第四步进入解析阶段,这阶段主要做的事是将对象与对象之间的关系,方法与方法之间的关系解析出来,将引用存放到栈中,引用指向堆空间内的对象
5.初始化,这里才开始对变量进行赋值
到这里后,程序启动完成,接下来就到程序运行的阶段,以方法调用为例,由于jvm运行以线程为基准,一个请求进来后必然的会创建一个线程,这时候JVM会为这个线程创建一个独立的栈空间和程序计数器空间,存放存放栈空间里存放的栈针,每一次方法的调用,都会在栈针里面存放局部变量和方法的运行结果,局部变量存在局部变量区,运行结果存在操作数栈区内,同时程序计数器空间中存入下一个要执行的指令在方法中的偏移量(下一个执行指令的有效内存地址),涉及到方法内对象的引用,则直接由之前加载的引用指向堆内存的对象使用
而方法执行完成后,栈空间立即回收,堆空间内的对象被标记上等待GC回收
几乎所有的对象都在堆中管理,那么对象又是怎么被GC回收的呢
首先GC是什么,GC是java垃圾回收线程,是JVM的守护线程,JVM启动时自动启动,JVM销毁时自动销毁,那么GC线程是怎么判断哪些对象需要回收的呢
GC判断对象是否可以被回收涉及两种算法:引用计数法,根搜索算法
引用计数法:就是判断该对象的引用数是否为0,为0则认为可被回收
根搜索算法:找若干种对象作为根对象,当一个对象被所有根对象的引用都不可达时,认为他可以被回收:可以被作为根对象的对象类型有四种,栈中引用的对象,方法区中静态属性引用的对象,常量引用的对象,本地方法栈JNI引用的对象
那么判断出对象可以被回收后,JVM做了什么?
这涉及到GC线程的回收过程,标记-清除算法、复制算法、标记-整理算法,标记过程就是将对象标记为根对象可达的对象,清除过程就是遍历堆中的的对象,将没有标记的对象清除
复制算法就是将存活的对象全都复制到新的一个内存区间,将旧空间清除
标记整理算法,则是先标记可达的对象,然后整理存活对象内存地址变成连续的,将末端地址后面的所有内存回收
那么一个对象又是怎么从新生代到老年代再被GC回收的呢,首先新创建的对象都在伊甸园内,当伊甸园内的对象被标记成可达对象后,进入幸存者区,幸存者区有两个,在幸存者区经过多次回收依然存活的对象进入年老区,可以设置MaxTenuringThreshold参数来设置,坚持多少次GC进入年老代
年轻代的GC称之为YGC,年老代的GC称之为FGC,每个区域的GC都会有三种不同的策略
我们称之为串行收集,并行收集和并发收集
串行收集,只有一个线程进行收集并清除
并行收集,多个线程进行标记回收时依然会停止JVM时间比单线程少很多
并发收集,多个线程进行标记,只有标记的时候停止JVM,清除的时候可以合程序一起执行