本文主要整理自炼术成金JVM教学资料和《深刻理解Java虚拟机》,部分资料整理自网络,已不明来源java
example程序员
-6
原码: 10000110
反码: 11111001
补码: 11111010
复制代码
不使用补码,将0看为算法
正数:0000 0000
负数:1000 0000
复制代码
则不一致
使用补码:数组
负数:1000 0000
反码:1111 111
补码:0000 0000 = 正数
复制代码
正数和负数使用补码作运算至关于用加法作运算
计算时都是使用补码进行计算
缓存
当指数位tomcat
计算方式 S*M*2^(e-127)
eg: -5的单精度表示
1 10000001 01000000000000000000000
其符号位 S为1,表示负数 -1
指数位E:10000001 ,e =129
尾数附加位:指数位不全为0,则为1
尾数M: 1+2^-2
;(-2,尾数位由右往左数第二位)
结果:-1 * ( 1+2^-2) * 2^( 129 - 127) = -5
安全
方法区物理上存在于堆里,并且是在堆的持久代里面;但在逻辑上,方法区和堆是独立的 方法区method area
只是JVM规范中定义的一个概念,用于存储类信息、常量池、静态变量、JIT编译后的代码等数据,具体放在哪里,不一样的实现能够放在不一样的地方。而永久代是Hotspot虚拟机特有的概念,是方法区的一种实现,别的JVM都没有这个东西bash
java 8和java 7的某版本后,perm gen 被去除了,取而代之的是metaspace。服务器
不一样点在于:perm gen 含class metadata、class static variables和interned string
metaspace只含class metadata了,class static variables和interned string被移到java heap上去了(因此java heap使用确定要大一点)网络
JVM主要管理两种类型的内存:堆和非堆. 简单来讲堆就是Java代码可及的内存,是留给开发人员使用的;非堆就是JVM留给本身用的 因此方法区,JVM内部处理或优化所需的内存(如JIT编译后的代码缓存),每一个类结构(如运行时常数池,字段和方法数据)以及方法和构造方法的代码都在非堆内存中.
线程私有
栈由一系列帧组成(故也叫帧栈)
帧保存每一个方法的局部变量表,操做数栈,常量池指针,程序计数器
每一次方法调用建立一个帧,并压栈
帧中有局部变量表
操做数栈
Java没有寄存器,全部参数传递使用操做数栈
小对象(几十bytes),在没有逃逸的状况下,能够直接分配在栈上
直接分配在栈上,能够自动回收,减轻GC压力
大对象或逃逸对象没法在栈上分配
逃逸对象:栈内对象被外部对象引用,其做用范围脱离了当前方法栈
public class AppMain {
//运行时, jvm 把appmain的信息都放入方法区
public static void main(String[] args) {
//main 方法自己放入方法区。
Sample test1 = new Sample( " 测试1 " );
//test1是引用,因此放到栈区里, Sample是自定义对象应该放到堆里面
Sample test2 = new Sample( " 测试2 " );
test1.printName();
test2.printName();
}
}
public class Sample {
//运行时, jvm 把appmain的信息都放入方法区
private name;
//new Sample实例后, name 引用放入栈区里, name 对象放入堆里
public Sample(String name) {
this .name = name;
}
//print方法自己放入 方法区里
public void printName() {
System.out.println(name);
}
}
复制代码
每个线程有一个工做内存和主存独立 工做内存存放主存中变量的值和拷贝
class OrderExample {
int a = 0;
boolean flag = false;
public void writer() {
a = 1;
flag = true;
}
public void reader() {
if (flag) {
int i = a +1;
……
}
}
}
复制代码
线程A首先执行writer()
方法 线程B线程接着执行reader()
方法 线程B在int i=a+1
是不必定能看到a已经被赋值为1
Thread.join()
,最后才终结interrupt()
先于被中断线程的代码,中断当即中止finalize()
方法-XX:+TraceClassLoading
:监控类的加载-XX:+PrintClassHistogram
: 按下Ctrl+Break后,打印类的信息XX:+HeapDumpOnOutOfMemoryError
:OOM时导出堆到文件-XX:OnOutOfMemoryError
: 在OOM时,执行一个脚本public class CanReliveObj {
public static CanReliveObj obj;
public static void main(String[] args) throws InterruptedException{
obj=new CanReliveObj();
obj=null; //可复活
System.gc();
Thread.sleep(1000);
if(obj==null){
System.out.println("obj 是 null");
}else{
System.out.println("obj 可用");
}
System.out.println("第二次gc");
obj=null; //不可复活
System.gc();
Thread.sleep(1000);
if(obj==null){
System.out.println("obj 是 null");
}else{
System.out.println("obj 可用");
}
}
@Override
//重写析构方法
protected void finalize() throws Throwable {
super.finalize();
System.out.println("CanReliveObj finalize called");
obj=this;
}
@Override
public String toString(){
return "I am CanReliveObj";
}
}
复制代码
对于用可达性分析法搜索不到的对象,GC并不必定会回收该对象。要彻底回收一个对象,至少须要通过两次标记的过程。
第一次标记:对于一个没有其余引用的对象,筛选该对象是否有必要执行finalize()方法,若是没有执行必要,则意味可直接回收。(筛选依据:是否复写或执行过finalize()方法;由于finalize方法只能被执行一次)。
第二次标记:若是被筛选断定位有必要执行,则会放入FQueue队列,并自动建立一个低优先级的finalize线程来执行释放操做。若是在一个对象释放前被其余对象引用,则该对象会被移除FQueue队列
Java中一种全局暂停的现象
全局停顿,全部Java代码中止,native代码能够执行,但不能和JVM交互
多半因为GC引发,也能够是Dump线程、死锁检查、堆Dump
适用于对吞吐量有较高要求, 多CPU、对应用响应时间无要求的中、大型应用。
举例:后台处理、科学计算 吞吐量:=运行用户代码时间/(运行用户代码时间+GC时间)
-XX:+UseParNewGC
-XX:ParallelGCThreads
限制线程数量
0.834: [GC 0.834: [ParNew: 13184K->1600K(14784K), 0.0092203 secs] 13184K->1921K(63936K), 0.0093401 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
复制代码
-XX:+UseAdaptiveSizePolicy
自适应调节策略是Parallel与ParNew的重要区别-XX:+UseParallelGC
-XX:+UseParallelOldGC
老年代不同而已
1.500: [Full GC [PSYounhttps://user-gold-cdn.xitu.io/2017/12/3/1601bd5a57d6924fen: 2682K->0K(19136K)] [ParOldGen: 28035K->30437K(43712K)] 30717K->30437K(62848K) [PSPermGen: 10943K->10928K(32768K)], 0.2902791 secs] [Times: user=1.44 sys=0.03, real=0.30 secs]
复制代码
-XX:MaxGCPauseMills
-XX:GCTimeRatio
适用于对响应时间有高要求,多CPU、对应用响应时间有较高要求的中、大型应用。
举例:Web服务器/应用服务器、电信交换、集成开发环境
特性
Concurrent Mark Sweep 并发标记清除(与用户线程一块儿执行 )
标记-清除算法(不是标记压缩)
并发阶段会下降吞吐量(?)
只是针对老年代收集器(新生代使用ParNew/或串行)
-XX:+UseConcMarkSweepGC
运行过程
优:
尽量下降停顿,在并发标记过程当中并不须要全局停顿
劣:
-XX:CMSInitiatingOccupancyFraction
设置触发GC的阈值-XX:+ UseCMSCompactAtFullCollection
Full GC后,进行一次整理
-XX:+CMSFullGCsBeforeCompaction
-XX:ParallelCMSThreads
参数名称 | 含义 | 备注 |
---|---|---|
-Xms | 初始堆大小 | 默认空余堆内存小于40%时,JVM就会增大堆直到-Xmx的最大限制 |
-Xmx | 最大堆大小 | 默认(MaxHeapFreeRatio参数能够调整)空余堆内存大于70%时,JVM会减小堆直到 -Xms的最小限制 |
-Xmn | 年轻代大小 | eden+ 2 survivor space,增大年轻代后,将会减少年老代大小,Sun官方推荐配置为整个堆的3/8 |
-XX:PermSize | 设置持久代(perm gen)初始值 | 持久代是方法区的一种实现 |
-XX:MaxPermSize | 设置持久代最大值 | |
-Xss | 每一个线程的栈大小 | JDK5.0之后每一个线程堆栈大小为1M,栈越大,线程越少,栈深度越深 |
-XX:NewRatio | 年轻代(包括Eden和两个Survivor区)与年老代的比值(除去持久代) | -XX:NewRatio=4表示年轻代与年老代所占比值为1:4,年轻代占整个堆栈的1/5,Xms=Xmx而且设置了Xmn的状况下,该参数不须要进行设置。 |
-XX:SurvivorRatio | Eden区与Survivor区的大小比值 | 设置为8,则两个Survivor区与一个Eden区的比值为2:8,一个Survivor区占整个年轻代的1/10 |
-XX:MaxTenuringThreshold | 垃圾最大年龄 | 该参数只有在串行GC时才有效 |
-XX:PretenureSizeThreshold | 对象超过多大是直接在旧生代分配 | 单位字节 新生代采用Parallel Scavenge GC时无效, 另外一种直接在旧生代分配的状况是大的数组对象,且数组中无外部引用对象. |
参数名称 | 含义 | 备注 |
---|---|---|
-XX:+UseParallelGC | 新生代使用Parallel收集器+ 老年代串行 | |
-XX:+UseParNewGC | 在新生代使用并行收集器 | |
-XX:ParallelGCThreads | 并行收集器的线程数 | 此值最好配置与处理器数目相等 也适用于CMS |
-XX:+UseParallelOldGC | 新生代使用Parallel收集器+ 老年代并行 | |
-XX:MaxGCPauseMillis | 每次年轻代垃圾回收的最长时间(最大暂停时间) | 若是没法知足此时间,JVM会自动调全年轻代大小,以知足此值 |
-XX:+UseAdaptiveSizePolicy | 自动选择年轻代区大小和相应的Survivor区比例 | 设置此选项后,并行收集器会自动选择年轻代区大小和相应的Survivor区比例,以达到目标系统规定的最低相应时间或者收集频率等,此值建议使用并行收集器时,一直打开. |
参数名称 | 含义 | 备注 |
---|---|---|
-XX:+UseConcMarkSweepGC | 使用CMS内存收集 | 新生代使用并行收集器ParNew,老年代使用CMS+串行收集器 |
-XX:CMSFullGCsBeforeCompaction | 多少次后进行内存压缩 | 因为并发收集器不对内存空间进行压缩,整理,因此运行一段时间之后会产生"碎片",使得运行效率下降.此值设置运行多少次GC之后对内存空间进行压缩,整理 |
-XX+UseCMSCompactAtFullCollection | 在FULL GC的时候, 对年老代的压缩 | CMS是不会移动内存的, 所以, 这个很是容易产生碎片, 致使内存不够用, 所以, 内存的压缩这个时候就会被启用。 增长这个参数是个好习惯。可能会影响性能,可是能够消除碎片 |
-XX:CMSInitiatingPermOccupancyFraction | 当永久区占用率达到这一百分比时,启动CMS回收 |
参数名称 | 含义 |
---|---|
-XX:+PrintGC | |
-XX:+PrintGCDetails | |
-XX:+PrintGCTimeStamps | |
-XX:+PrintGCApplicationStoppedTime | 打印垃圾回收期间程序暂停的时间.可与上面混合使用 |
-XX:+PrintHeapAtGC | 打印GC先后的详细堆栈信息 |
--Xloggc:filename | 把相关日志信息记录到文件以便分析 |
-XX:+HeapDumpOnOutOfMemoryError | |
-XX:HeapDumpPath | |
-XX:+PrintCommandLineFlags | 打印出已经被设置过的详细的 XX 参数的名称和值 |
项目 | 响应时间优先 | 吞吐量优先 |
---|---|---|
年轻代 | -Xmn尽可能大,直到接近系统的最低响应时间限制-XX:MaxGCPauseMillis,减小年轻代GC,减小到达老年代对象 | -Xmn尽可能大 |
年轻代垃圾回收器 | 并发收集器 | 并行收集器 |
年老代 | 若是堆设置小了,能够会形成内存碎 片,高回收频率以及应用暂停而使用传统的标记清除方式;若是堆大了,则须要较长的收集时间 | |
要参照年轻代和年老代垃圾回收时间与次数 | -XX:NewRatio 年老代设置小一些,这样能够尽量回收掉大部分短时间对象,减小中期的对象,而年老代尽存放长期存活对象 | |
年老代垃圾回收器 | 年老代使用并发收集器 | 由于对响应时间没有要求,垃圾收集能够并行进行,也能够串行 |
典型配置
-Xmx3800m -Xms3800m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:ParallelGCThreads=20
复制代码
年老代并行
-Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:ParallelGCThreads=20 -XX:+UseParallelOldGC
复制代码
设置每次年轻代垃圾回收的最长时间,若是没法知足此时间,JVM会自动调全年轻代大小,以知足此值
-Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:MaxGCPauseMillis=100
复制代码
-Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:ParallelGCThreads=20 -XX:+UseConcMarkSweepGC -XX:+UseParNewGC
复制代码
-XX:+UseConcMarkSweepGC
:设置年老代为并发收集 -XX:+UseParNewGC
:设置年轻代为并行收集
-Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseConcMarkSweepGC -XX:CMSFullGCsBeforeCompaction=5
-XX:+UseCMSCompactAtFullCollection
复制代码
-XX:CMSFullGCsBeforeCompaction
:因为并发收集器不对内存空间进行压缩、整理,因此运行一段时间之后会产生“碎片”,使得运行效率下降。此值设置运行多少次GC之后对内存空间进行压缩、整理。
-XX:+UseCMSCompactAtFullCollection
:打开对年老代的压缩。可能会影响性能,可是能够消除碎片
5.617: [GC 5.617: [ParNew: 43296K->7006K(47808K), 0.0136826 secs] 44992K->8702K(252608K), 0.0137904 secs]
[Times: user=0.03 sys=0.00, real=0.02 secs]
复制代码
解释
5.617(时间戳): [GC(Young GC) 5.617(时间戳):
[ParNew(使用ParNew做为年轻代的垃圾回收器): 43296K(年轻代垃圾回收前的大小)->7006K(年轻代垃圾回收之后的大小)(47808K)(年轻代的总大小), 0.0136826 secs(回收时间)]
44992K(堆区垃圾回收前的大小)->8702K(堆区垃圾回收后的大小)(252608K)(堆区总大小), 0.0137904 secs(回收时间)]
[Times: user=0.03(Young GC用户耗时) sys=0.00(Young GC系统耗时), real=0.02 secs(Young GC实际耗时)]
复制代码
[GC [DefNew: 3468K->150K(9216K), 0.0028638 secs][Tenured:
1562K->1712K(10240K), 0.0084220 secs] 3468K->1712K(19456K),
[Perm : 377K->377K(12288K)],
0.0113816 secs] [Times: user=0.02 sys=0.00, real=0.01 secs]
复制代码
Tenured:持久代/老年代
串行收集器:
DefNew:使用-XX:+UseSerialGC
(新生代,老年代都使用串行回收收集器)。
并行收集器:
ParNew:是使用-XX:+UseParNewGC
(新生代使用并行收集器,老年代使用串行回收收集器)或者-XX:+UseConcMarkSweepGC
(新生代使用并行收集器,老年代使用CMS)。
PSYoungGen:是使用-XX:+UseParallelOldGC
(新生代,老年代都使用并行回收收集器)或者-XX:+UseParallelGC
(新生代使用并行回收收集器,老年代使用串行收集器)
garbage-first heap:是使用-XX:+UseG1GC
(G1收集器)
触发条件就是某GC算法对应区域满了,或是预测快满了(好比该区使用比例达到必定比例-对并行/并发,或不够晋升)
针对HotSpot VM的实现,它里面的GC其实准确分类只有两大种:
Major GC一般是跟full GC是等价的,收集整个GC堆。最简单的分代式GC策略,按HotSpot VM的serial GC的实现来看,触发条件是:
触发条件复杂一些,不过大体的原理与串行GC同样。
例外: Parallel Scavenge(-XX:+UseParallelGC
新生代使用Parallel收集器)框架下,默认是在要触发full GC前先执行一次young GC,而且两次GC之间能让应用程序稍微运行一下,以期下降full GC的暂停时间(由于young GC会尽可能清理了young gen的死对象,减小了full GC的工做量)。控制这个行为的VM参数是-XX:+ScavengeBeforeFullGC
并发GC的触发条件就不太同样。以CMS GC为例,主要是定时去检查old gen的使用量,当使用量超过了触发比例就会启动一次CMS GC,对old gen作并发收集
-XX:CMSInitiatingOccupancyFraction=80 // old达到80%收集
复制代码
或者GC过程当中,因为预留的内存没法知足程序须要, 出现concurrent mode failure,临时使用serial old进行Full GC
G1 GC的initial marking(初始标记)的触发条件是Heap使用比率超过某值,收集时是按照回收价值的优先级,不按照young old区
G1 GC:Young GC + mixed GC(新生代,再加上部分老生代)+ Full GC for G1 GC算法(应对G1 GC算法某些时候的不赶趟,开销很大);
转为方法区数据结构 在Java堆中生成对应的java.lang.Class对象
tomcat和OSGi有作更改
example:类从上往下加载
在工程目录中添加A.java,自动编译生成A.class
又指定根加载目录path,-Xbootclasspath/a:path,从新放一个同名A.class
此时会加载指定根加载目录下的class文件
注意:以上是jdk默认的类加载模式,但tomcat和OSGi有本身的加载方式
Tomcat:Tomcat的WebappClassLoader 就会先加载本身的Class,找不到再委托parent
OSGi的ClassLoader造成网状结构,根据须要自由加载Class
直接在控制台输入命令,参数具体使用可以使用-help 命令
通常是第一步,方便后续其余命令调用
列出java进程,相似于ps命令
参数-q能够指定jps只输出进程ID ,不输出类的短名称
参数-m能够用于输出传递给Java进程(主函数)的参数
参数-l能够用于输出主函数的完整路径
参数-v能够显示传递给JVM的参数
查看进程参数 能够用来查看正在运行的Java应用程序的扩展参数,甚至支持在运行时,修改部分参数
-flag 进程ID:打印指定JVM的参数值
-flag [+|-] 进程ID:设置指定JVM参数的布尔值
-flag = 进程ID:设置指定JVM参数的值
生成Java应用程序的堆快照和对象的统计信息
num #instances #bytes class name
----------------------------------------------
1: 370469 32727816 [C
2: 223476 26486384 <constMethodKlass>
3: 260199 20815920 java.lang.reflect.Method
…..
8067: 1 8 sun.reflect.GeneratedMethodAccessor35
Total 4431459 255496024
复制代码
打印线程dump
-l
打印锁信息
-m
打印java和native的帧信息
-F
强制dump,当jstack没有响应时使用
Jdk1.6版本只有 –l选项
图形化监控工具
能够查看Java应用程序的运行概况,监控堆信息、永久区使用状况、类加载状况等
Visual VM是一个功能强大的多合一故障诊断和性能监控的可视化工具
堆+线程栈 +直接内存<= 操做系统可分配空间
public static void main(String args[]){
ArrayList<byte[]> list=new ArrayList<byte[]>();
for(int i=0;i<1024;i++){
list.add(new byte[1024*1024]);
}
}
复制代码
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at geym.jvm.ch8.oom.SimpleHeapOOM.main(SimpleHeapOOM.java:14)
复制代码
解决方法:增大堆空间,及时释放内存,分批处理
//生成大量的类
public static void main(String[] args) {
for(int i=0;i<100000;i++){
CglibBean bean = new CglibBean("geym.jvm.ch3.perm.bean"+i,new HashMap());
}
}
复制代码
Caused by: java.lang.OutOfMemoryError: 【PermGen space】
[Full GC[Tenured: 2523K->2523K(10944K), 0.0125610 secs] 2523K->2523K(15936K),
[Perm : 【4095K->4095K(4096K)】], 0.0125868 secs] [Times: user=0.02 sys=0.00, real=0.01 secs]
Heap
def new generation total 4992K, used 89K [0x28280000, 0x287e0000, 0x2d7d0000)
eden space 4480K, 2% used [0x28280000, 0x282966d0, 0x286e0000)
from space 512K, 0% used [0x286e0000, 0x286e0000, 0x28760000)
to space 512K, 0% used [0x28760000, 0x28760000, 0x287e0000)
tenured generation total 10944K, used 2523K [0x2d7d0000, 0x2e280000, 0x38280000)
the space 10944K, 23% used [0x2d7d0000, 0x2da46cf0, 0x2da46e00, 0x2e280000)
compacting perm gen total 4096K, used 4095K [0x38280000, 0x38680000, 0x38680000)
the space 4096K, 【99%】 used [0x38280000, 0x3867fff0, 0x38680000, 0x38680000)
ro space 10240K, 44% used [0x38680000, 0x38af73f0, 0x38af7400, 0x39080000)
rw space 12288K, 52% used [0x39080000, 0x396cdd28, 0x396cde00, 0x39c80000)
复制代码
解决方法:避免动态生成class,增大Perm区,容许Class回收
-Xmx1g -Xss1m
public static class SleepThread implements Runnable{
public void run(){
try {
Thread.sleep(10000000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String args[]){
for(int i=0;i<1000;i++){
new Thread(new SleepThread(),"Thread"+i).start();
System.out.println("Thread"+i+" created");
}
}
复制代码
Exception in thread "main" java.lang.OutOfMemoryError:
unable to create new native thread
复制代码
这里的栈溢出指,在建立线程的时候,须要为线程分配栈空间,这个栈空间是向操做系统请求的,若是操做系统没法给出足够的空间,就会抛出OOM
eg:堆空间1G,每一个线程栈空间1m
注意:堆+线程栈+直接内存 <= 操做系统可分配空间
ByteBuffer.allocateDirect()
:申请堆外的直接内存-Xmx1g -XX:+PrintGCDetails
//会抛出oom,但堆内存空间充足
for(int i=0;i<1024;i++){
ByteBuffer.allocateDirect(1024*1024);
System.out.println(i);
System.gc();
}
复制代码
public static List<Integer> numberList =new ArrayList<Integer>();
public static class AddToList implements Runnable{
int startnum=0;
public AddToList(int startnumber){
startnum=startnumber;
}
@Override
public void run() {
int count=0;
while(count<1000000){
numberList.add(startnum);
startnum+=2;
count++;
}
}
}
public static void main(String[] args) throws InterruptedException {
Thread t1=new Thread(new AddToList(0));
Thread t2=new Thread(new AddToList(1));
t1.start();
t2.start();
while(t1.isAlive() || t2.isAlive()){
Thread.sleep(1);
}
System.out.println(numberList.size());
}
复制代码
Exception in thread "Thread-0" java.lang.ArrayIndexOutOfBoundsException: 73
at java.util.ArrayList.add(Unknown Source)
at simpleTest.TestSome$AddToList.run(TestSome.java:27)
at java.lang.Thread.run(Unknown Source)
1000005
复制代码
ArrayList 不是线程安全的集合对象,在两个线程添加元素的过程当中,当数组填满,正在自动扩展时,另外一个线程却仍是在添加元素,在ArrayList底层就是不可变长的数组,则抛出下表越界异常
HotSpot虚拟机中,对象在内存中存储的布局能够分为三块区域:对象头(Header)、实例数据(Instance Data)和对齐填充(Padding)。
随着锁的竞争,锁能够从偏向锁升级到轻量级锁,再升级的重量级锁(可是锁的升级是单向的,也就是说只能从低到高升级,不会出现锁的降级)
大多数状况下锁不只不存在多线程竞争,并且老是由同一线程屡次得到,为了让线程得到锁的代价更低而引入了偏向锁。偏向锁只能在单线程下起做用。
偏向锁在锁对象的对象头中有个ThreadId字段,这个字段若是是空的,第一次获取锁的时候,就将自身的ThreadId写入到锁的ThreadId字段内,将锁头内的是否偏向锁的状态位置1.,这样下次获取锁的时候,直接检查ThreadId是否和自身线程Id一致,若是一致,则认为当前线程已经获取了锁,所以不需再次获取锁,略过了轻量级锁和重量级锁的加锁阶段。提升了效率。
-XX:+UseBiasedLocking
-XX:+UseBiasedLocking -XX:BiasedLockingStartupDelay=0
系统启动后,并不会当即开启偏向锁,而是会延迟,能够设置延迟时间为0
普通的锁处理性能不够理想,轻量级锁是一种快速的锁定方法
轻量级锁是为了在线程交替执行同步块时提升性能
若是对象没有被锁定
将对象头的Mark指针保存到锁对象中
将对象头设置为指向锁的指针(在线程栈空间中)
即对象和锁都互相保存引用
轻量级锁加锁
线程在执行同步块以前,JVM会先在当前线程的栈桢中建立用于存储锁记录的空间,并将对象头中的Mark Word复制到锁记录中,官方称为Displaced Mark Word。
而后线程尝试使用CAS将对象头中的Mark Word替换为指向锁记录的指针。若是成功,当前线程得到锁,若是失败,表示其余线程竞争锁,当前线程便尝试使用自旋来获取锁。
轻量级锁解锁
轻量级解锁时,会使用原子的CAS操做来将Displaced Mark Word替换回到对象头,若是成功,则表示没有竞争发生。
若是失败,表示当前锁存在竞争,锁就会膨胀成重量级锁
lock位于线程栈中
由上可知,判断一个线程是否持有轻量级锁,只要判断对象头的指针,是否在线程的栈空间范围内
特性
-XX:+UseSpinning
开启当发生争用时,若Owner线程能在很短的时间内释放锁,则那些正在争用线程(未阻塞)能够稍微等一等(自旋),在Owner线程释放锁后,争用线程可能会当即获得锁,从而避免线程阻塞
锁 | 优势 | 缺点 | 适用场景 |
---|---|---|---|
偏向锁 | 加锁和解锁不须要额外的消耗,和执行非同步方法比仅存在纳秒级的差距。 | 若是线程间存在锁竞争,会带来额外的锁撤销的消耗。 | 适用于只有一个线程访问同步块场景。 |
轻量级锁 | 竞争的线程不会阻塞,提升了程序的响应速度。 | 若是始终得不到锁竞争的线程使用自旋会消耗CPU。有竞争时会比重量级锁更慢 | 追求响应时间。同步块执行速度很是快。 |
重量级锁 | 线程竞争不使用自旋,不会消耗CPU。 | 线程阻塞,响应时间缓慢。 | 追求吞吐量。同步块执行速度较长。 |
偏向锁与轻量级锁理念上的区别:
轻量级锁:在无竞争的状况下使用CAS操做去消除同步使用的互斥量
偏向锁:在无竞争的状况下把整个同步都消除掉
连CAS操做都不作了?
同步范围减小
将大对象拆成小对象,增长并行度,下降锁竞争
偏向锁和轻量级锁成功率提升——粒度大,竞争激烈,偏向锁,轻量级锁失败几率就高
Segment<K,V>[] segments
HashEntry<K,V>
锁类型 | 读锁 | 写锁 |
---|---|---|
读锁 | 可访问 | 不可访问 |
写锁 | 不可访问 | 不可访问 |
若是对同一个锁不停的进行请求、同步和释放,其自己也会消耗系统宝贵的资源,反而不利于性能的优化
public void demoMethod(){
synchronized(lock){
//do sth.
}
//作其余不须要的同步的工做,但能很快执行完毕
synchronized(lock){
//do sth.
}
}
复制代码
直接扩大范围
public void demoMethod(){
//整合成一次锁请求
synchronized(lock){
//do sth.
//作其余不须要的同步的工做,但能很快执行完毕
}
}
复制代码
for(int i=0;i<CIRCLE;i++){
synchronized(lock){
}
}
//锁粗化
synchronized(lock){
for(int i=0;i<CIRCLE;i++){
}
}
复制代码
在即时编译器时,若是发现不可能被共享的对象,则能够消除这些对象的锁操做
锁不是由程序员引入的,JDK自带的一些库,可能内置锁
栈上对象,不会被全局访问的,没有必要加锁
public static void main(String args[]) throws InterruptedException {
long start = System.currentTimeMillis();
for (int i = 0; i < CIRCLE; i++) {
craeteStringBuffer("JVM", "Diagnosis");
}
long bufferCost = System.currentTimeMillis() - start;
System.out.println("craeteStringBuffer: " + bufferCost + " ms");
}
public static String craeteStringBuffer(String s1, String s2) {
//StringBuffer线程安全对象,内置锁
StringBuffer sb = new StringBuffer();
sb.append(s1);
sb.append(s2);
return sb.toString();
}
复制代码
-server -XX:+DoEscapeAnalysis -XX:+EliminateLocks
无锁的一种实现方式 CAS(Compare And Swap)
非阻塞的同步
CAS(V,E,N):if V==E then V=N
复制代码
CAS算法的过程: CAS(V,E,N)。V表示要更新的变量,E表示预期值,N表示新值。 仅当V值等于E值时,才会将V的值设为N,若是V值和E值不一样,则说明已经有其余线程作了更新,则当前线程什么都不作。
最后,CAS返回当前V的真实值。
CAS操做是抱着乐观的态度进行的,它老是认为本身能够成功完成操做。当多个线程同时使用CAS操做一个变量时,只有一个会胜出,并成功更新,其他均会失败。失败的线程不会被挂起,仅是被告知失败,而且容许再次尝试,固然也容许失败的线程放弃操做。基于这样的原理,CAS操做即时没有锁,也能够发现其余线程对当前线程的干扰,并进行恰当的处理。
java.util.concurrent.atomic包使用无锁实现,性能高于通常的有锁操做
当多个线程同时请求某个对象监视器时,对象监视器会设置几种状态用来区分请求的线程:
U4:无符号整型,4个字节
类型 | 名称 | 数量 | 备注 |
---|---|---|---|
u4 | magic | 1 | 0xCAFEBABE:表示java class文件类型 |
u2 | minor_version | 1 | Jdk编译版本 |
u2 | major_version | 1 | Jdk编译版本 |
u2 | constant_pool_count | 1 | |
cp_info | constant_pool | constant_pool_count - 1 | 链式引用基本类型-被各处引用-要减1 |
u2 | access_flags | 1 | 访问修饰符&class type |
u2 | this_class | 1 | 指向常量池的class |
u2 | super_class | 1 | 指向常量池的class |
u2 | interfaces_count | 1 | |
u2 | interfaces | interfaces_count | 每一个接口指向常量池CONSTANT_Class索引 |
u2 | fields_count | 1 | |
field_info | fields | fields_count | access_flags,name_index ,descriptor_index ,attributes_count,attribute_info attributes[attributes_count] |
u2 | methods_count | 1 | |
method_info | methods | methods_count | |
u2 | attribute_count | 1 | |
attribute_info | attributes | attributes_count |
线程帧栈中的数据:
-XX:CompileThreshold=1000
:执行超过一千次即为热点代码-XX:+PrintCompilation
:打印编译为机器码的代码-Xint
:解释执行-Xcomp
:所有编译执行-Xmixed
:默认,混合