Java虚拟机知识点整理

StackOverflowError栈深度过深
OutOfMemoryError内存溢出
Java虚拟机栈为 执行的Java方法(也就是字节码)服务 里面有个局部变量表:存储基本数据类型
本地方法栈为 虚拟机使用到的Native方法服务
几乎所有的对象实例以及数组都在堆上分配 (JIT编译器的发展与逃逸分析技术的成熟,栈上分配\标量替换优化技术导致的微妙变化)
分代收集算法:java队细分:新生代和老年代 在细分:Eden空间、FromSurvivor空间、ToSurvivor空间
方法区: (堆的一个逻辑部分)线程共享的区域,存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
sb.intern() 返回字符串对象的首次出现的引用

判断对象是否存活:引用计数算法、可达性分析算法(java虚拟机使用的)

可达性分析:GC ROOTs 包括:虚拟机栈中引用的对象 + 方法区中类静态属性引用的对象 + 方法区中常量引用的对象 + 本地方法栈中JNI(一般说Native方法)引用的对象
一个对象真正的死亡最少需要经过两次标记:如果对象在进行可达性分析后发现没有与GcRoots相连接的引用链,那它将会被第一次标记并且进行一次筛选,筛选的条件是此对象是否有必要执行finalize()方法。当对象没有覆盖finalize()方法,或finalize()方法已经被虚拟机调用过,虚拟机将这两种情况都视为“没有必要执行”.
如果有必要执行就会放入F-Queue的队列中,并在稍后由虚拟机自动建立的、低优先级的Finalizer线程去执行它。
垃圾收集算法:①标记-清除算法(最基础的,后续的回收算法都是基于这个)不足:标记加清除的效率不高,再有是清除完 会产生大量不连续的空间碎片,空间碎片过多会导致以后在程序运行中需要分配较大对象时,无法找到足够的连续内存而不得已提前触发另外一次垃圾收集动作。
②复制算法:将可用内存按容量划分大小相等的两块,每次只使用其中的一块。当一块的内存用完了,就将还存活着的对象复制到另一块上面,然后再把已经使用过的内存空间一次性的清理掉。这样使得每次都对整个半区进行内存回收,内存分配时也就不用考虑内存碎片等复杂情况,只要移动堆顶的指针,按照顺序分配内存即可。不足:将内存缩小为原来的一半,代价过高。

现在的商业虚拟机都是采用复制算法来回收新生代的,新生代的对象98%是“朝生夕死”的,所有不需要1:1的比例分配内存空间,而是将内存分为一块较大的Eden空间和两块较小的Survivor空间,每次使用Eden和其中一块Survivor.当回收时,将Eden和Survivor中还存活这的对象一次性复制到另一块Survivor上面,最后清理掉Eden跟刚才用过的Survivor空间。HotSpot虚拟机默认比例8:1(只有百分之十会被浪费)
当Survivor空间不足时,需要依赖其他内存(这里是老年代)进行分配担保。

③标记整理算法:复制算法在对象存活率较高时就要进行较多的复制操作,效率将会变低。老年代一般不能直接选用这种算法。根据老年代的特点,有人提出了“标记-整理”算法,标记过程与标记清楚算法一样,但是后续不是直接对可回收对象进行清理,而是让所有存活的对象都像一端移动,然后直接清理掉端边界以外的内存。

分代收集算法:当前商业虚拟机都是采用“分代收集”算法,新生代用复制算法,老年代由于没有担保的空间采用标记清除或者标记整理算法。

CMS是一款优秀的收集器,它的主要优点在名字上已经体现出来了:并发收集、低停顿。
G1 收集器是当今收集器技术发展的最前沿成果之一。G1是一款面像服务端应用的垃圾收集器。特点:并行与并发、分代收集、空间整合、可预测的停顿。

长久存活的对象直接进入老年代:虚拟机给每个对象定义一个对象年龄计数器。如果对象在Eden出生并且经过第一次的GC后仍然存活,并且能被Surviver接纳的话,将被移动到Surviver空间中,并且对象年龄设为1.对象在Surviver中每经过一次GC,年龄就增加1岁,当到了一定程度(默认15岁,可以通过参数设置)就会晋升到老年代。
当然并不是年龄到了才进入,当Surviver空间中相同年龄的所有对象大小的总和大于Surviver空间的一半时,年龄年龄大于或等于该年龄的对象直接进入老年代,无需等到设定的年龄阈值。

类的生命周期: 加载、验证、准备、解析、初始化、| 前面五个是类加载的过程 加上后面的是类的完整生命周期| 使用、卸载。 (验证、准备、解析合称为连接)

加载阶段: ①通过一个类的全限定名来获取定义此类的二进制字节流。
②将这个字节流所代表的静态存储结构转换为方法区的运行时数据结构。
③在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。
验证: 验证是连接阶段的第一步,这一阶段的目的是为了保证Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。
①文件格式验证:是否以魔数))0xCAEBABE开头,主、次版本号是否在当前虚拟机的处理范围之内,常量池中的常量是否有不被支持的常量类型(检查常量tag标志),CONSTANT_UTF8_info型的常量是否有不符合UTF8编码的数据。等等
②元数据验证:对字节码描述的信息进行语义分析,以保证其描述的信息符合Java语言规范的要求,这个阶段可能包括的验证点有:这个类是否有父类,这个类是否继承了不能被继承的类,如果不是抽象类是否实现了父类的或者接口之中要求的实现的所有方法等。
③字节码验证:这个阶段将对类的方法体进行校验分析,保证被校验的方法在运行时不会做出危害虚拟机安全的事件。保证跳转指令不会跳到方法体意外的字节码指令上去。保证方法体中的类型转换是有效的。(子类赋值给父类可以,父类赋值给子类或者不相干的类,则是危险和不合法的)
④符号引用验证:最后一个阶段的校验发生在虚拟机将符号引用转化为直接引用的时候,这个转换动作将在连接的第三个阶段-解析阶段发生。
准备阶段:准备阶段是正式为父类变量分配内存并设置类变量初始值的阶段,这些变量使用的内存都将在方法区中进行分配。注意:进行内存分配的仅包括类变量(被static修饰的变量),而不包括实例变量,实例变量将会在对象实例化是随着对象一起分配在Java堆中。其次这里所说的初始值“通常情况”下是数据类型的零值,而不是代码里写的赋值的值,只有是final修饰的才会真的赋值。
解析阶段:虚拟机将常量池内的符号引用替换成直接引用的过程。
初始化阶段:初始化阶段,jvm主要完成对静态变量的初始化,静态块执行等工作。

栈帧:用于支持虚拟机进行方法调用和方法执行的数据结构,它是虚拟机运行时数据区中的虚拟机栈的栈元素。栈帧存储了方法的局部变量表、操作数栈、动态连接和方法返回地址等信息。每一个方法从调用开始至执行完成的过程,都对应着一个栈帧在虚拟机栈里面从入栈到出栈的过程。

在活动过程中,只有在栈顶的栈帧才是有效的成为当前栈帧。与这个栈帧相关联的方法称之为当前方法。
每一个栈帧都包括:局部变量表、操作数栈、动态连接、方法返回地址和一些额外的附加信息。

局部变量表:是一组变量值存储空间,用于存放方法参数和方法内部定义的局部变量。

操作数栈:也称作“操作栈”,它是后入先出栈。同局部变量表一样,操作数栈的最大深度也是在编译的时候写入到Code属性的max_stacks数据项中。操作数栈的每一个元素可以是任意的Java数据类型,包括long和double.当一个方法刚开始执行的时候,这个方法的操作栈是空的,在方法执行过程中,会有各种字节码指令往操作栈中写入和提取内容,也就是入栈、出栈。

动态连接:每一个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用,持有这个引用是为了支持方法调用过程中的动态连接。Class文件的常量池中有大量的符号引用,字节码中的方法调用指令就以常量池中的指向方法的符号引用作为参数。这些符号引用一部分会在类加载阶段或者第一次使用的时候就转化为直接引用,这种转化称之为静态解析。另外一部分会在每一次运行期间转化为直接引用,这部分称为动态连接。

方法返回地址:执行引擎遇到任意一个方法返回的字节码指令,这个时候可能会有返回值传递给上层调用者,是否有返回值和返回值类型由遇到的方法的返回指令决定的,这种退出称之为正常完成出口。另外一种是,方法中出现异常,没有catch住 也会退出方法,称之为异常完成出口。

静态分派的典型是方法重载。静态分派发生在编译阶段,因此确定静态分派的动作实际上不是由虚拟机来执行的。
动态分派的典型是方法重写。

内存间的交互:Java内存模型中定义了8种操作来完成交互,lock(锁定)、unlock(解锁)、read(读取)、load(载入)、user(使用)、assign(赋值)、store(存储)、write(写入)。主要,Java内存模型只要求上述两个操作必须按照顺序执行,而没有保证是连续执行。

除此之外,Java内存模型还规定了在执行上述8种基本操作时必须满足如下规则:
①不允许read和load、store和write操作之一单独出现,即不允许一个变量从主内存读取了但是工作内存不接受。
②不允许一个线程丢弃它的最近的assign操作,即变量在工作内存中改变了之后必须吧该变化同步回主内存。
③不允许一个线程无原因的(没有发生过任何assign操作)把数据从线程的工作内存同步回主内存。
④一个新的变量只能在主内存中诞生,不允许在工作内存中直接使用一个未被初始化的变量。
⑤一个变量在同一时刻只允许一条线程对其lock操作,但是lock操作可以被同一条线程重复执行多次,多次执行lock操作后,必须执行相同次数的解锁操作。
⑥如果对一个变量执行lock操作,将会清空工作内存中此变量的值,在执行引擎舒勇这个变量前,需要重新执行load或assign操作初始化变量的值。
⑦如果一个变量事先没有被lock锁定,那就不允许对它解锁。
⑧对一个变量执行解锁前,必须先把此变量同步回主内存中(执行store、write操作)

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值