. JVM跨平台原理
JVM是java虚拟机,在不同的系统上安装不同版本的java虚拟机,java虚拟机运行经过编译后的字节码文件,以达到跨平台的原理
JVM组成
大致可以分为:
①类加载器子系统
②运行时数据区
③ 执行引擎
④本地方法库
类加载器子系统
类加载的过程
类加载大致可以分为 加载,验证,准备,解析,初始化这五个步骤。
加载:把字节码文件加载到内存中
验证:验证字节码文件是否为真的字节码文件
准备:给static修饰的变量分配内存空间,如果有final的修饰的变量会直接在这一阶段赋值
解析: 将全限定名替换成内存中的地址
初始化:对准备阶段的变量进行赋值
双亲委派机制(先爷爷,后爸爸,最后是自己)
双亲委派机制主要的目的是不能覆盖系统本来的功能并能支持我们自己的扩展功能,在类加载的时候,会优先使用启动类加载器 加载,没有的话使用扩展类加载器,在没有的话使用应用程序类加载器
启动类加载器:加载jre/lib包下面的jar包
扩展类加载器:加载lib/ext包下面的jar包
应用程序类加载器:加载classpath下的字节码文件
运行时的数据区有哪几部分组成?
程序计数器 :用来指定指令的执行顺序
本地方法栈 :储存native的方法
虚拟机栈:线程是共享的,每个线程都会分配一个虚拟机栈,调用方法就会有入栈和出栈。
方法区:在jdk1.8的时候,方法区里面的静态变量和常量池已经移到了堆内存空间中,剩下的保留但是被元空间取代了,元空间直接使用的是本地内存。
堆:存放的是我们的对象,也是GC的主要区域,可以被细分为新生代和老年代
新生代和老年代?
新生代和老年代是我们对堆内存的一个分类,新生代可以分为eden和两个survivor,eden中存放的是刚创建出来的对象,大多数声明周期都很短,正常情况下,经历一次GC以后会转入到我们的一个survivor里面,再经历15次GC以后会转入到老年代中。但如果经历GC的时候,survivor已经满了或者该对象很大就会直接放入到老年代中。
JVM内存溢出有哪些?
堆内存溢出,栈内存溢出,方法区溢出,本机直接内存溢出,我们主要关注的是堆和栈的内存溢出。
垃圾回收
垃圾回收的大致流程:判断对象是否已死--》使用什么算法进行垃圾回收--》什么时间进行垃圾回收--》用什么垃圾回收器。
如何判断对象是否已死?
主要是判断对象是否已经失去引用,
引用计数算法,给每个对象添加一个计时器,每有一个对象引用计数器就加1,计数器为0就表示对象已死,但是无法解决循环引用的问题。
一般使用的是可达性算法:需要一个GC Roots(垃圾收集的起点),当没有任何引用链相连(GC Roots到这个对象不可达)时,就说明此对象是不可用的,是死对象
哪些可以作为垃圾收集的起点:静态属性的对象,常量池的对象,本地方法(native)方法中的对象,虚拟机栈中本地变量表中的对象。
常用垃圾回收算法:
标记清除算法 、复制算法、标记整理算法
新生代的垃圾回收一般用的是minor GC ,新生代的回收一般很快,使用复制算法;老年代的回收一般使用的是full GC 一般使用的是我们的标记清除算法或者标记整理算法。
垃圾回收的时间:
安全点和安全区的时候
常见的垃圾回收器有哪些?
新生代收集器:
serial 单线程收集器 采用复制算法进行垃圾收集 在垃圾收集时所有用户线程必须暂停
parnew 就是一个searial 的多线程版本,其他并无区别
paralle scavenge 多线程收集器 尽可能缩短垃圾收集时用户线程的停顿时间 复制算法
老年代收集器:
serial old 单线程 采用标记整理算法。
cms 多线程 基于标记清除算法,所以垃圾回收后会产生空间碎片
parrallel old 多线程 采用标可以记整理算法 可以和parrallel scavenge收集器搭配,充分发挥多线程的性能
G1收集器 jdk1.9默认的收集器 要收费
JVM调优:
需要考虑几个重要的指标:内存暂用, 延迟, 吞吐量
工作中一般可以使用可视化工具jvisualvm 或者阿里的阿尔萨斯 来分析jvm的运行情况
代码实现方面:
① 避免创建过大的对象及数组:过大的对象或数组在新生代没有足够空间容纳时会直 接进入老年代,如果是短命的大对象,会提前出发Full GC。
②避免同时加载大量数据,如一次从数据库中取出大量数据,或者一次从Excel中读取大量记录,可以分批读取,用完尽快清空引用。
③当集合中有对象的引用,这些对象使用完之后要尽快把集合中的引用清空,这些无用对象尽快回收避免进入老年代。
④避免产生死循环,产生死循环后,循环体内可能重复产生大量实例,导致内存空间被迅速占满。