JVM为什么能跨平台,原理?
本质就是不同操作系统上运行的JVM是不一样的,这才是JVM跨平台本质。
jvm,字节码
jvm整体架构
正在上传…重新上传取消
类加载子系统将字节码从磁盘读取到内存中。
热点字节码指令只翻译一次(JIT编译器)
JIT编译器对于热点的字节码指令翻译成机械指令缓存起来提高效率。
JVM执行流程(简单)
首先是源文件编译成字节码文件,之后通过类加载子系统将磁盘中的字节码文件读取到内存中去,首先是读取到方法区中,我们的字节码文件需要用到解释器解释为机械指令,其中的热点字节码文件会被JIT编译器翻译成机械指令,之后其中的对象放入到堆中,jvm本地方法放到本地方法栈,java有关的创建的一些变量存放在java方法栈中,程序计数器存放将要执行下一条指令的地址。
类加载子系统
正在上传…重新上传取消
链接阶段
验证待加载class文件是否正确,比如验证文件格式,准备阶段是为static变量分配内存并赋零值,解析阶段是将符号引用解析为直接引用,符号引用就是方法名,直接引用是方法的地址。
类加载器分类
分为引导类加载器(BootStrapClassLoader)(JVM内部默认提供)与自定义类加载器(继承实现ClassLoader类)
自定义加载器指的就是我们自己写的类继承实现了ClassLoader类。而我们默认使用的是引导类加载器和EXTclassLoader(扩展类加载器),AppClassLoader(应用类加载器)之后包括我们使用Tomcat时tomcat会帮我提供一个WebAppClassLoader类加载器。
三个默认类加载器对应的加载目录分析(在Launcher下)
EXT:
APP:
引导类加载器加载(/jre/Lib)
双亲委派模型
正在上传…重新上传取消
主要是负责避免类的重复加载
防止核心API被篡改(获取到String.class.getClassLoader()方法返回的是null,这说明我们返回的是BootStrapClassLoader()加载的,而外面黑客所加载的是加载不到我们内部这个)
用的ClassLoader中的loaderClass 。
Tomcat中为什么要自定义类加载器?
正在上传…重新上传取消
虽然是我们自己写的类去继承加载自定义类加载器,但是其实使用的是它的一个实例对象。
JVM判断一个类是不是已经被加载的逻辑是:类名+对应的类加载器实例。
不同应用写了多个一样的,只用AppClassLoader只能加载一个。其他的就是不可以被加载了,名字相同。所以就是针对应用A,B各自独立的设置类加载器也就是WebAppClassLoader。这个两个一个用中的就是都被各自的类加载器所加载,不会冲突。这就是Tomcat自定义类加载器的核心原因,是为了实现类加载的隔离。
JVM整体结构(运行时数据区域由哪些部分组成他们的作用)
正在上传…重新上传取消
正在上传…重新上传取消
方法区中包含了常量池,方法信息和类信息。是线程公用的。java方法栈中是很多个栈帧(每个线程都有一个)还有堆,程序计数器是下一次指令地址,本地方法栈中存放jvm的方法。
程序计数器作用
正在上传…重新上传取消
OutOfMemoryError内存溢出错误
虚拟机栈(java栈,java方法栈)
正在上传…重新上传取消
StackOverFlowError:栈溢出错误。
每个线程创建时候都会创建一个虚拟机栈,栈会保存一个个的栈帧,每个栈帧都会对应一个方法。
1,虚拟机栈是线程私有的
2,一个方法开始执行栈帧入栈,方法执行完对应的栈帧就会出栈,所以虚拟机栈不需要进行垃圾回收。
3.虚拟机栈会存在OutOfMemoryError,和StackOverFlowError
4,县城太多,会可能出现内存溢出,线程创建时没有足够的内存区创建虚拟机栈
5,方法调用层次太多,就可能会出现栈溢出
6,可以通过-Xss来设置虚拟机栈的大小。
栈帧(局部变量表和操作数栈有什么作用,如何工作的?)
正在上传…重新上传取消
包含局部变量表,操作数栈,方法返回地址,动态链接以及附加消息。
局部变量表中就是我们创建的局部变量,操作数栈也是操作栈,是用来执行字节码指令过程中用来计算的。
局部变量表的作用就是对数据进行一个实时的记录。
操作数栈就是字节码执行过程的计算。
本地方法栈(native method)
正在上传…重新上传取消
堆区以及其中区域作用
正在上传…重新上传取消
堆是JVM中最重要的一块区域,JVM规范中规定所有的对象和数组都应该存放在堆中,在执行字节码指令时,会把创建的对象存入堆中,在执行字节码指令时,会把创建的对象存入堆中,对象的引用地址存入虚拟机栈中的栈帧中,不过当方法执行完之后,刚刚创建的对象不会被马上回收,而是要等到JVM后台执行GC后(垃圾回收)对象才会被回收。
堆初始化内存与最大内存:
正在上传…重新上传取消
老年代与新生代:
正在上传…重新上传取消
新生代:
正在上传…重新上传取消
新生代分为Eden区与s0,s1区
Eden:伊甸园区,新对象都会先放到Eden区(除非对象大小都超过了Eden区,那么就只能直接进入老年代)
s0,s1:Survivor0,1区,也叫from区域,to区,用来存放MinorGC(YGC)后存在的对象。
默认比例为8:1:1,也就是Eden区占新生代大小的十分之八,可以通过-XX:SurvivorRatio来调整。
java对象在各个区流畅过程
垃圾回收流程:
首先是对象放在Eden区:之后存满了之后进行垃圾回收(YGC)之后放入到s0区,之后满了在进行回收放入到s1区域,往复,之后超过十五次放入到老年代中,如果比较大对象那么先放入到Eden中之后s0,1放不下那么放入到老年代,如果超大对象(文件)直接放入到老年代。
垃圾回收:
Young GC/Minor GC:负责对新生代进行垃圾回收。
Old GC/Major GC:负责对老年代继续宁垃圾回收,目前只有CMS垃圾收集器会单独对老年代进行垃圾收集,其他垃圾收集器基本都是整对回收的时候对老年代进行垃圾收集。
Full GC:针对回收,也会堆方法区进行垃圾收集
垃圾回收之引用计数法思路:
为什么要进行垃圾回收?
垃圾是指再JVM中没有任何引用指向它的对象,如果不清理这些垃圾对象,那么它们就会一直占用着内存,而不能给吧其他对象使用,最终垃圾对象越来越多,就会出现OOM。
垃圾标记阶段:
也就是找到JVM(主要是堆中)有哪些垃圾对象,两种方式:
引用计数法:正在上传…重新上传取消
每个对象都保存一个引用计数器属性,用户记录对象被引用次数。
实现简单,计数器为0是垃圾
需要额外空间来储存引用计数,
需要额外时间来维护引用计数,
无法处理循环引用问题。
可达性分析法:
正在上传…重新上传取消
以GC Root作为起始点,一层一层找到所引用对象,找到就是存活,没找到不可达就是垃圾对象。
正在上传…重新上传取消
GC Roots是一组引用,包含:
虚拟机栈中,本地方法栈正在执行方法中的方法参数,局部变量所对应的对象引用。
方法去中保存的类信息中静态属性,常量属性所对应的对象引用。
标记-清除算法
正在上传…重新上传取消
基础和常用的垃圾回收算法,针对某块内存空间,比如新生代,老年代,如果内存不足就会进行STW,暂停用户线程执行,然后执行算法进行垃圾回收:
1,标记阶段:GC Roots开始遍历,找到可达对象,并且再对象头中记录
2,清除阶段:对堆内存空间进行线性遍历,如果发现对象头中没有记录是可达对象,进行回收。
缺点是:效率不高,内存碎片
优点:实现简单
复制算法(空间换时间)
正在上传…重新上传取消
将内存空间分为两块,每次只使用一块,在进行垃圾回收时,将可达对象复制到另外没有被使用的内存块中,然后再清除当前模块中所有对象,后续按照流程,交换进行。
正在上传…重新上传取消
优点:没有标记,清除阶段,通过GC Roots找到可达对象,直接复制,不需要修改对象头,效率高。不会出现内存碎片。
缺点:
需要更多的内存,始终有一半的内存空闲。
对象赋值后,对象存放的内存地址发生了便能话,需要额外的时间修改栈帧中的记录的引用地址。
用在(新生代)如果可达对象比较多,垃圾对象比较少,那么复制算法的效率就会比较低,所以,垃圾对象比较多情况下,复制算法比较合适。
标记整理算法
正在上传…重新上传取消
分为三个阶段
第一个阶段和标记清除算法一样,从GC Roots找到并且标记可达对象。
第二个阶段将所有存活对象移动到内存的一端
最后清理边界外所有空间。
缺点是效率比较低,还要修改栈帧的地址
对比总结
正在上传…重新上传取消
分代收集算法
正在上传…重新上传取消
老年代中对象存活时间比较长,不适合复制算法,适合标记清除和标记整理算法。
CMS垃圾收集器采用的就是标记清除算法
Serial Old垃圾收集器采用的就是标记整理算法
常见垃圾收集器
正在上传…重新上传取消
Serial GC串行,工作暂停,一个线程进行垃圾回收,复制算法。
Old老年代,标记清除算法
Parallel Gc并行,工作暂停,多个线程进行垃圾回收,新生代使用复制算法
Old老年代,标记整理算法
CMS垃圾回收器(低暂停)
正在上传…重新上传取消
正在上传…重新上传取消
正在上传…重新上传取消