类加载的过程:加载->连接(验证,准备,解析)->初始化->使用->卸载
加载:查找并加载类文件的二进制数据
连接:将已经读入内存的类的二进制数据合并到JVM运行时环境中去,包含以下几个步骤:1)验证:确保被加载的类的正确性 2)准备:为类的静态变量分配内存并初始化他们 3)解析:把常量池中的符号引用转换成直接引用
初始化:为类的静态变量赋初始值
类加载要完成的功能
1.通过类的全限定名来获取该类的二进制字节流
2.把二进制字节流转化为方法区的运行时数据结构
3.在堆上创建一个java.lang.Class对象(对象.getClass()),用来封装类在方法区内的数据结构,并向外提供了访问方法区内数据结构的接口
加载类的方式
1.最常见的方式:本地文件系统中加载,从jar等归档文件中加载
2.动态的方式:将java源文件动态编译成class
3.其他方式:网络下载,从专有数据库中加载等等
类加载器
java虚拟机自带的加载器包括以下几种:
1.启动类加载器BootstrapClassLoader
2.扩展类加载器ExtensionClassLoader (jdk9之后是平台类加载器PlatformClassLoader)
3.应用程序类加载器AppClassLoader (我们写的类就是它来加载的)
4.用户自定义的加载器,是java.lang.ClassLoader的子类,用户可以定制类的加载方式,自定义类加载器的加载顺序是在所有系统类加载器的后面
类加载器的关系
类加载器说明
1.启动类加载器:用于加载启动的基础模块类,比如:java.base ,java.management ,java.xml等等。启动类加载器不允许被改变和使用,String.getClass().getClassLoader()=null,返回的是null
2.平台类加载器:用于加载一些平台相关的模块,比如:java.scripting ,java.compiler* ,java.corba* 等等
3.应用程序类加载器:用于加载应用级别的模块,比如:jdk.compiler ,jdk.jartool ,jdk.jshell等等,还加载classpath路径中的所有类库
4.java程序不能直接引用启动类加载器,直接设置classLoader为null,默认就使用启动类加载器
5.类加载器并不需要等到某个类“首次主动使用”的时候才加载它,jvm规范允许类加载器在预料到某个类将要被使用的时候就预先加载它
6.如果在加载的时候.class文件缺失,会在该类首次主动使用时报LinkageError错误,如果一直没被使用就不会报错
双亲委派模型
JVM中的ClassLoader通常采用双亲委派模型,要求除了启动类加载器外,其余的类加载器都要有父级加载器。这里的父子关系是组合而不是继承,工作过程如下:
1.一个类加载器收到类加载请求后,首先搜索它的内建加载器定义的所有“具名模块”
2.如果找到了合适的模块定义,将会使用该加载器来加载
3.如果class没有在这些加载器定义的具名模块中找到,那么将会委托给父级加载器,直到启动类加载器
4.如果父级加载器也不能完成加载请求,比如在它的搜索路径下找不到这个类,那么子类加载器才来加载
5.在类路径下找到的类将成为这些加载器的无名模块
双亲委派模型说明
1.双亲委派模型对于保证java程序的稳定运行很重要
①避免同样的字节码重复加载
②出于安全性考虑,防止javaAPI中的类被随意替换
2.实现双亲委派的代码在java.lang.ClassLoader的loadClass()方法中,如果自定义类加载器的话,推荐覆盖实现findClass()方法
3.如果有一个类加载器能加载某个类,称为定义类加载器,所有能成功返回该类的Class的类加载器都被称为初始类加载器
4.如果没有指定父加载器,默认就是启动类加载器
5.每个类加载器都有自己的命名空间,命名空间由该加载器及其所有父加载器所加载的类构成,不同的命名空间,可以出现类的全限定名相同的情况
6.运行时包由同一个类加载器的类构成,决定两个类是否属于同一个运行时包,不仅要看全限定名是否一样,还要看定义类加载器是否相同。只有属于同一个运行时包的类才能实现相互包内可见
破坏双亲委派模型
1.双亲委派模型有个问题:父加载器无法向下识别子加载器加载的资源
2.为了解决这个问题,引入了线程上下文类加载器,可以通过Thread的setContentClassLoader()进行设置
java.sql.DriverManager源码
3.另外一种典型的情况就是实现热替换,比如OSGI的模块化热部署,它的类加载器就不是严格按照双亲委派模型,可能在平级的类加载器中执行了
类连接主要验证的内容
1.类文件结构检查:按照JVM规范规定的类文件结构进行
2.元数据验证:对字节码描述的信息进行语义分析,保证其符合java语言规范要求
3.字节码验证:通过对数据流和控制流进行分析,确保程序语义是合法和符合逻辑的。这里主要对方法体进行校验
4.符号引用验证:对类自身以外的信息,也就是常量池中的各种符号引用,进行匹配校验
类连接中的解析
所谓解析就是把常量池中的符号引用转换成直接引用发的过程
符号引用:以一组无歧义的符号来描述所引用的目标,与虚拟机的实现无关
直接引用:直接指向目标的指针、相对偏移量、或是能间接定位到目标的句柄,和虚拟机的实现相关
主要针对:类、接口、字段、类方法、接口方法、方法类型、方法句柄、调用点限定符
类的初始化
类的初始化就是为类的静态变量赋初始值,或者说是执行类构造器<init>方法的过程 (不是实例构造器方法)
1)如果类还没有加载和连接,就先加载和连接
2)如果类存在父类,且父类没有初始化,就先初始化父类
3)如果类中存在初始化语句,就依次执行这些初始化语句
4)如果是接口:
a.初始化一个类的时候,并不会先初始化它实现的接口
b.初始化一个接口时,并不会初始化它的父接口
c.只有当程序首次使用接口里面的变量或者调用接口的方法时,才会初始化接口
5)调用ClassLoader类的loadClass方法来加载类的时候,并不会初始化这个类,加载类不是对类的主动使用
类的初始化时机
java程序对类的使用分为主动使用和被动使用。JVM在每个类或接口“首次主动使用”时才初始化他们;被动使用类不会导致类的初始化。
主动使用的情况:
a.创建类实例
b.访问某个类或接口的静态变量
c.调用类的静态方法
d.反射某个类
e.初始化某个类的子类,而父类还没有初始化
f.JVM启动的时候运行的主类
g.定义了default方法的接口,当接口的实现类初始化时
被动使用的情况:
1.通过子类去调用父类的静态变量 。例如MyChidl.parentStr,不会初始化子类,会初始化父类
2.数组的方式。例如MyChild[] mcs = new MyChild[2],不会初始化类
3.访问常量也不会初始化类。例如public static final String childStr = "now in child!"
类的初始化机制和顺序
类的卸载
当代表一个类的Class对象不在被引用,那么Class对象的生命周期就结束了,对应的在方法区中的数据也会被卸载
JVM自带的类加载器加载的类。是不会卸载的。由用户自定义的类加载器加载的类是可以卸载的
JVM内存分配基础
JVM的简化架构
运行时数据区:包括方法区、虚拟机栈、本地方法栈、堆、程序计数器(PC寄存器)
PC寄存器:
1.每个线程都拥有一个PC寄存器,是线程私有的,用来存储指向下一条指令的地址,也可以看着是当前程序所执行的字节码的行号指示器,也可以看着是程序流的指示器
2.在创建线程的时候创建相应的PC寄存器
3.执行本地方法(执行JNI方法)时,PC寄存器的值为undefined
4.是一块比较小的内存空间,是唯一一个在JVM规范中没有规定OutOfMemoryError的内存区域,不会内存溢出
java栈
1.栈由一系列帧组成,是线程私有的
2.帧用来保存一个方法的局部变量表、操作数栈(java没有寄存器,所有参数传递使用操作数栈)、常量池指针、动态链接、方法返回值等
3.每一次方法调用创建一个帧,并压栈,退出方法的时候,修改栈顶指针就可以把栈桢中的内容销毁
4.局部变量表存放了编译期可知的各种基本数据类型和引用类型,每个slot(插槽)存放32位的数据,long、double占两个槽位
5.栈的优点:存取速度比堆块,仅次于寄存器
6.栈的缺点:存在栈中的数据大小、生命周期是在编译期决定的,缺乏灵活性
java堆
用来存放应用系统创建的对象和数组,所有线程共享java堆
GC主要管理堆空间
堆的优点:运行期动态分配内存大小,自动进行垃圾回收
堆的缺点:效率相对较慢
方法区
方法区是线程共享的,用来保存类的结构信息(运行时的常量池、静态变量、常量、类元信息)
通常和元空间关联在一起,具体跟JVM的实现和版本有关
JVM规范把方法区描述为堆的一个逻辑部分,但它有一个别名Non-heap(非堆),是为了与java堆区分开
运行时常量池
是Class文件中每个类或接口的常量池表,在运行期间的表示形式,通过包括:类的版本,字段、方法、接口等信息
在方法区中分配
通常在加载类或接口到JVM中后,就创建相应的运行时常量池
本地方法栈
在JVM中用来支持native方法执行的栈就是本地方法栈。是线程私有的
Java堆内存概述
java堆用来存放应用系统创建的对象和数组,所有线程共享java堆
java堆是在运行期动态分配内存大小,自动进行垃圾回收
java垃圾回收主要就是回收堆内存,对分代GC来说,堆也是分代的
java堆的结构
1. 新生代用来存放新分配的对象;新生代中经过垃圾回收,没有被回收掉的对象,被复制到老年代
2.老年代存储的对象比新生代存储的对象的年龄大的多
3.老年代存储一些大对象,大对象会直接分配到老年代
4.整个堆的大小=新生代+老年代
5.新生代=Eden去+存活区(Survivor)
6.从JDK8开始去掉了持久代(用来存放Class、Method等元信息的区域),取而代之的是元空间(MetaSpace),元空间并不在虚拟机里面,而是直接使用本地内存
对象的内存布局
对象在内存中存储的布局(以HotSpot虚拟机为例)分为:对象头,实例数据和对齐填充
1.对象头,包括两部分
(1)Mark Word:存储对象自身的运行数据,如:HashCode、GC分代年龄、锁状态标志等
(2)类型指针:指向它的类元信息(在方法区)的指针
2.实例数据:真正存放对象实例数据的地方
3.对齐填充:这部分不一定存在,也没有特别的含义,仅仅是占位符。因为HotSpot要求对象起始地址都是8字节的整数倍,如果不是,就对齐
对象的访问定位
.对象的访问定位:在JVM规范中只规范了reference类型是一个指向对象的引用,但没有规定这个引用具体如何去定位、访问堆中对象的具体位置
因此对象的访问方式取决于JVM的实现,目前主流的有:使用句柄或使用指针两张方式
1.使用句柄:Java堆中会划分出一块内存来作为句柄池,reference中存储句柄的地址,句柄中存储对象实例数据和类元数据的地址
2.使用指针:java堆中存放访问类元数据的地址,reference存储的是对象实例数据的地址
Java内存分配参数
Trace跟踪参数 (版本jdk9+)
打印GC的简要信息:-Xlog:gc
打印GC详细信息:-Xlog:gc*
指定GC log的位置,以文件输出:-Xlog:gc:garbage-collection.log
每一次GC后,都打印堆信息:-Xlog:gc+heap=debug
Java堆的参数
-Xms:堆的大小,默认为物理内存的1/64
-Xmx:堆的最大值,默认为物理内存的1/4
建议-Xms和-Xmx设置相等,避免每次GC后调整堆的大小,减少系统内存分配的开销
-Xmn:新生代的大小,默认整个堆的3/8 (JDK8是1/3)
设置垃圾回收器 -XX:+UseConcMarkSweepGC 使用CMS垃圾收集器 -XX:+UseG1GC 使用G1收集器
-XX:+UseConcMarkSweepGC -Xms10m -Xmx10m -Xmn3m -Xlog:gc
保存GC日志
-XX:+UseConcMarkSweepGC -Xms10m -Xmx10m -Xmn3m -Xlog:gc+heap=debug:logs/mygc.log -XX:+HeapDumpOnOutOfMemoryError
-XX:NewRatio:老年代与新生代的比值。如果设置Xms=Xmx,且设置了Xmn的情况下,该参数不用设置
-XX:SurvivorRatio:Eden区和Survivor区的大小比值
-XX:+HeapDumpOnOutOfMemoryError:OOM时导出堆文件
-XX:+HeapDumpPath:导出OOM的路径
-XX:OnOutOfMemoryError:在OOM时,执行一个脚本
Java栈的参数
-Xss:通常只有几百k,决定了函数调用的深度
元空间的参数
-XX:MetaspaceSize:元空间初始大小
-XX:MaxMetaspaceSize:最大空间,默认没有限制
-XX:MinMetaspaceFreeRatio:在GC之后,最小的Metaspace剩余空间容量的百分比
-XX:MaxMetaspaceFreeRatio:在GC之后,最大的Metaspace剩余空间容量的百分比
字节码执行引擎
概述
JVM的字节码执行引擎,功能就是输入字节码文件,然后对字节码进行解析并处理,最后输出执行结果
实现方式有通过解释器直接解释字节码文件,或者是通过即时编译器产生本地代码,也就是编译执行,当然也可能两者皆有
栈桢概述
栈桢是用于支持JVM方法调用和方法执行的数据结构
栈桢随着方法的调用而创建,随着方法结束而销毁
栈桢里面存储了方法的局部表里表、操作数栈、动态链接、方法返回地址等信息
局部变量表:用来存放方法参数和方法内部定义的局部变量的存储空间
1.以变量槽slot为单位,一个slot槽存放32位以内的数据类型
2.对于64位的数据占2个slot
3.对于实例方法,第0位slot存放的是this,然后从1到n,依次分配给参数列表。静态方法是从0开始
4.然后根据方法体内部定义的变量顺序和作用域来分配slot
5.slot是复用的,以节省栈桢的空间,这种设计可能会影响到系统的垃圾收集行为
操作数栈
操作数栈:用来存放方法运行期间,各个指令操作的数据
1.操作数栈中元素的数据类型必须和字节码指令的顺序严格匹配
2.虚拟机在实现栈桢的时候可能会做一些优化,让两个栈桢出现部分重叠区域,以存放公用数据
动态链接
动态链接:每个栈桢持有一个指向运行时常量池中该栈桢所属方法的引用,以支持方法调用过程的动态链接
1.静态解析:类加载的时候,符号引用转换成直接引用
2.动态链接:运行期间转化为直接引用
方法返回地址
方法返回地址:方法执行后返回的地址
方法调用
方法调用:就是确定具体调用哪一个方法,不涉及方法内部的执行过程
1.部分方法是直接在类加载的解析阶段,就确定了直接引用关系
2.但是对于实例方法,也称虚方法,因为重载和多态,需要运行期动态委派
分派
分派:分为静态太分派和动态分派
1.静态分派:所有依赖静态类型来定位方法执行版本的分派方式,比如:重载方法
2.动态分派:根据运行期的实际类型来定位方法执行版本的分派方式,比如:覆盖方法、接口的实现
单分派和多分派:就是按照分派思考的维度,多余一个的就算多分派,只有一个的称为单分派
如何执行方法中的字节码指令:JVM通过基于栈的字节码解释执行引擎来执行指令,JVM的指令集也是基于栈的
垃圾回收概述
什么是垃圾:简单说就是内存中已经不再使用的内存空间就是垃圾
引用计数算法:给对象添加一个引用计数器,有访问就加1,引用失效就减1
优点:失效简单,效率高;缺点:不能解决对象之间循环引用的问题
可达性分析算法(根搜索算法):从根节点向下搜索对象节点,搜索走过的路径称为引用链,当一个对象到根之间没有连通的话,则该对象不可用
可作为GC Roots的对象包括:虚拟机栈(栈桢局部变量)中引用的对象、方法区类静态属性引用的对象、方法区中常量引用的对象、本地方法栈中JNI引用的对象
HotSpot使用了一组叫做OopMap的数据结构达到准确式GC的目的
在OopMap的协助下,JVM可以很快的做完GC Roots枚举。但是JVM并没有为每条指令生成一个OopMap
记录OopMap的这些“特定位置”被称为安全点,即当前线程执行到安全点后才允许暂停进行GC
如果一段代码中,对象引用关系不会发生变化,这个区域中任何地方开始GC都是安全的,这个区域称为安全区域
引用分类
强引用:new出来的对象,Object a = new A(),不会被回收
软引用:还有用但不必须的对象,内存不够时才会回收掉。用SoftReference来表示
弱引用:非必须对象,发生GC一定会被回收掉。用WeakReference来表示
虚引用:也叫幽灵引用或幻影引用,是最弱的引用,GC时会回收掉。用PhantomReference来表示
跨代引用
跨代引用:一个代中的对象引用另一个代中的对象
跨代引用假说:跨代引用相对于同代引用来说只是极少数
隐含推论:存在相互引用关系的两个对象,是应该倾向于同时生存或同时消亡的
记忆集
记忆集:一种用于记录从非收集区域向收集区域的指针集合的抽象数据结构
写屏障
写屏障可以看着是JVM对“引用类型字段赋值”这个动作的AOP
判断是否垃圾的步骤
1.根搜索算法判断不可用
2.看是否有必要执行finalize()方法(在该方法中对象能自救,但是不建议覆盖finailze方法)
3.两个步骤执行完对象仍然没有被使用,就属于垃圾
GC类型
MinorGC/YoungGC:发生在新生代的收集动作
MajorGC/OldGC:发生在老年代的GC,目前只有CMS收集器会有单独收集老年代的行为
MixedGC:收集整个新生代及部分老年代,目前只有G1收集器会有这种行为
FullGC:收集整个Java堆和方法区的GC
Stop-The-World
STW是Java中一种全局暂停的现象,多半由于GC引起。所谓全局停顿,就是所有java代码停止运行,native代码可以执行,但是不能和JVM交互
其危害是长时间服务停止,没有响应;对于HA系统,可能引起主备切换,严重危害生产环境
垃圾收集类型
串行收集:GC单线程内存回收,会暂停所有的用户线程,比如:Serial
并行收集:多个GC线程并行工作,此时用户线程是暂停的,比如:Parallel
并发收集:用户线程和GC线程同时执行(不一定是并行,可能交替执行),不需要停顿用户线程,比如:CMS
判断类无用的条件
JVM中该类的所有实例都已经被回收
加载该类的ClassLoader已经被回收
没有任何地方引用该类的Class对象
无法在任何地方通过反射访问这个类
垃圾回收算法
标记清除算法
优点是简单
缺点是:效率不高,标记和清除的效率都不高;标记清除后会产生大量不连续的内存碎片,从而导致分配大对象时触发GC
复制算法
复制算法:把内存分成两块完全相同的区域,每次使用其中一块,当一块使用完了,就把这块上还存活的对象拷贝到另一块,然后把这块清除掉
优点:实现简单、运行高效,不用考虑内存碎片
缺点:内存浪费
JVM实际实现中,是将新生到内存分为一块较大的Eden区和两块较小的Survivor区,每次使用Eden和一块Survivor,回收时,把存货的对象复制到另一块Survivor
HotSpot默认的Eden和Survivor比是8:1,也就是每次能用90%的新生代空间
如果Survivor空间不够,就要依赖老年代进行空间分配担保,把放不下的对象直接进入老年代
空间分配担保
空间分配担保:当新生代进行垃圾回收后,新生代的存活区放置不下,那么需要把这些对象放置到老年代去的策略,也就是老年代为新生代的GC做空间分配担保,步骤如下:
1.在发生MinorGC前,JVM会检测老年代的最大可用的连续空间,是否大于新生代所有对象的总空间,如果大于,可以确保MinorGC是安全的
2.如果小于,那么JVM会检查是否设置了允许担保失败,如果允许,则继续检查老年代最大可用的连续空间,是否大于历次晋升到老年代对象的平均大小
3.如果大于,则尝试进行MinorGC
4.如果不大于,则进行FullGC
标记整理算法
标记整理算法:由于复制算法在存活对象比较多的时候,效率较低,且浪费空间,因此老年代一般不会选用复制算法,老年代多用标记整理算法
标记过程跟标记清除算法一样,但后续不是直接清除垃圾对象,而是让所有存活的对象向一端移动,然后直接清除边界以外的内存空间
垃圾收集器
串行收集器
Serial收集器/Serial Old收集器,是一个单线程的收集器,在垃圾收集时,会Stop-The-World
优点是简单,对于单CPU,由于没有多线程交互的开销,可能更高效,是默认的Client模式下的新生代收集器
使用-XX:UseSerialGC来开启,会使用:Serial+Seria Old的收集器组合
新生代使用复制算法,老年代使用标记整理算法
并行收集器
ParNew收集器:使用多线程进行垃圾回收,在垃圾收集时,会Stop-The-World
在并发能力好的CPU环境里,它停顿的时间要比串行收集器短;但对于单CPU或并发能力较弱的CPU,由于多线程的交互开销,效率可能比串行收集器更差
是Server模式下首选的新生代收集器,且能和CMS收集器配合使用
-XX:ParallelGCThreads:指定线程数,最好和CPU核数一致
新生代Parallel Scavenge收集器
新生代Parallel Scavenge收集器是一个用于新生代、使用复制算法,并行收集器,配合Parallel Old使用
跟ParNew相似,但更关注吞吐量,能最高效的利用CPU,适合运行后台项目
使用-XX:+UseParallelGC来开启
CMS收集器
CMS(Concurrent Mark and Sweep 并发标记清除)收集器分为4个步骤:
1.初始标记:只标记GC Roots能直接关联到的对象,暂停用户线程
2.并发标记:进行GC Roots Tracing的过程
3.重新标记:修正并发标记期间,因程序运行导致标记发生变化的对象,暂停用户线程
4.并发清除:并发回收垃圾对象
在初始标记和重新标记阶段会暂停用户线程,会Stop-The-World
使用在老年代,使用标记清除算法,多线程并发收集
优点:低停顿、并发执行
缺点:并发执行,对CPU资源压力大;无法处理CMS收集器GC过程中产生的垃圾,可能导致FullGC;采用的标记清除算法会产生大量不连续的内存碎片,从而在分配大对象时可能触发FullGC
开启:-XX:+UseConcMarkSweepGC,使用ParNew + CMS + Serial Old的收集器组合,Serial Old为CMS出错的后备收集器
G1收集器
G1(Garbage-First)收集器:是一款面向服务端应用的收集器,与其他收集器相比有如下特点:
1.G1把内存划分成多个独立的区域(Region)
2.G1仍采用了分代思想,保留了新生代和老年代,但它们不在是物理隔离的, 而是一部分Region的集合,且不需要Region是连续的
3.G1能充分利用多CPU、多核环境听见优势,尽量缩短STW
4.G1整体上采用标记-整理算法,局部是通过复制算法,不会产生内存碎片
5.G1的停顿可预测,能明确指定在一个时间段内,消耗在垃圾收集上的时间不能超过多长时间
6.G1跟踪各个Region里面垃圾堆的价值大小,在后台维护一个优先列表,每次根据允许的时间来回收价值最大的区域,从而保证在有限时间内的高效收集
跟CMS类似,有分为四个阶段:
1.初始标记:只标记GC Roots能直接关联到的对象
2.并发标记:进行GC Roots Tracing的过程
3.最终标记:修正并发标记期间,因程序运行导致标记发生变化的那一部分对象
4.筛选回收:根据时间来进行最大化价值的回收
开启:-XX:+UseG1GC
-XX:MaxGCPauseMillis=n:最大GC停顿时间,这是个软目标,JVM将尽可能(但不保证)停顿小于这个时间
-XX:InitiatingHeapOccupancyPercent=n:堆占用了多少的时间就触发GC,默认为45
-XX:NewRatio=n:默认为2
-XX:SurvivorRatio=n:默认为8
-XX:MaxTenuringThreshold=n:新生代到老年代的岁数,默认是15
-XX:G1HeapRegionSize=n:设置G1区域的大小。值是2的幂,范围是1MB到32MB。目标是根据最小的Java堆划分出约2048个区域
ZGC收集器
ZGC收集器:JDK11加入的具有实验性质的低延迟收集器
ZGC的设计目标是:支持TB级内存容量,暂停时间低(<10ms),对整个程序吞吐量的影响小于15%
ZGC的新技术:着色指针 和 读屏障
GC性能指标
吞吐量=应用代码执行的时/运行的总时间
GC负荷=GC时间/运行的总时间
暂停时间=发生STW的总时间
GC频率:一个时间段发生GC的次数
反应速度:对象成为垃圾到被回收的时间
交互式应用通常希望暂停时间越少越好
JVM内存配置原则
新生代尽可能设置大点,如果太小会导致:
1.YGC次数更加频繁
2.可能导致YGC后的对象进入老年代,如果此时老年代满了,会触发Full GC
3.对老年代,针对响应时间优先的应用:由于老年代通常采用并发收集器,因此其大小要综合考虑并发量和并发持续时间等参数
锁优化之偏向锁
偏向锁是在无竞争情况下,直接把整个同步消除了,连乐观锁都不用,从而提高性能;所谓的偏向就是偏心,即锁会偏向于当前已经占用锁的线程
只要没有竞争,获得偏向锁的线程,在将来进入同步块,也不需要做同步
当有其他的线程请求相同的锁时,偏向模式结束
如果程序中大多数锁总是被多个线程访问的时候,也就是竞争比较激烈,偏向锁会降低性能
使用-XX:+UseBiasedLocking来禁用偏向锁,默认开启
JVM中获取锁的步骤
1.会先尝试偏向锁,然后尝试轻量级锁
2.再然后尝试自旋锁
3.最好尝试普通锁,使用OS互斥量在操作系统层挂起
同步代码的基本规则
1.尽量减少锁持有的时间
2.进来减少锁的粒度
JVM性能监控
命令行工具
jps
jps(JVM Process Status Tool):主要用来输出JVM中运行的状态信息,语法格式:jps [options] [hostid]
jinfo
打印给定线程或核心文件或远程调试服务器的配置信息。语法格式:info [option] pid #pid进程号
jstack
jstack主要用来查看某个java进程内的线程堆栈信息。语法格式:jstacj [option] pid
jmap
jmap用来查看堆内存使用状况,语法格式:jmap [option] pid
jstat
JVM统计检测工具,查看各个区内存和GC情况
jstated
虚拟机的jstat的守护进程,主要用来监控JVM的创建与终止,并提供一个接口,以允许远程监视工具附加到在本地系统上允许的JVM
jcmd
JVM诊断命令工具,将诊断命令请求发送到正在允许的java虚拟机,比如可以用来导出堆,查看java进程,导出线程信息,执行GC等
JVM图形监控工具
1.jconsole
jdk自带的一个用于监视java虚拟机的符号JMX的图形工具。它可以监视本地和远程的JVM,还可以监视和管理应用程序
2.jmc
jmc(JDK Mission Control)Java任务监控(JMC)客户端包括用于监视和管理java应用程序的工具,而不会引入通常与这些类型的工具相关联的性能开销
下载地址:https://www.oracle.com/java/technologies/javase-downloads.html
3.VisualVM
一个图形工具,它提供有关在java虚拟机中运行的基于java技术的应用程序的详细信息
java VisualVM提供内存和CPU分析,堆转储分析,内存泄漏检测,访问MBean和垃圾回收