JVM
2023-04-11 23:18:35 0 举报
JVM+类加载+JMM+GC优化+锁优化
作者其他创作
大纲/内容
或称为本地变量表,方法的形参、局部变量、返回值;特点:1、线程私有,编译期确定其大小,运行期不会改变其大小;2、局部变量必须赋值才能使用,否则编译不通过;3、虚拟机通过该表来传递参数值到参数变量列表;4、性能调优,被该表直接或间接引用的对象不会被回收
安全点
......
语法糖
JVM
并发/响应时间优先
CPU0
标记:收集器从引用根节点开始遍历,标记所有被引用的对象,在对象头中记录为可达对象清除:收集器对堆内存从头到尾进行线性遍历,找出对象头中没有被标记为可达的对象,将其回收 清除并不是正的置空,而是维护一份空闲列表,记录清除对象的空闲地址,新对象插入时判断空间是否够;特点:效率一般,需要维护空闲列表,会产生内存碎片。
old区是否有足够空间
包含了对象所有成员变量,大小由各个变量类型决定boolean、byte:1字节short、char:2字节int、float:4字节long、double:8字节reference:8字节(64位系统)
标记-清除Mark-Sweep
无锁状态
读取字符串文件的时候,调整桶的个数;考虑字符串是否入池(大量重复信息);
保证加载类的正确性,符合JVM规范:文件格式验证、元数据验证、字节码验证、符号引用验证;
其他线程join结束
回收机制
系统类加载器
被其他线程阻塞suspend() 已过时
解释器
垃圾回收线程
先标记所有被根节点引用的对象,将所有存活对象压缩到内存的一端,清除边界外的所有空间;特点:消除标记清除算法中内存碎片的问题,但效率低,如果移动对象被其他对象引用,还需调整引用的地址。
volatile:保证变量的可见性、禁止指令重排保证可见性:指被其修饰的变量,可以避免线程从自己的工作缓存中获取变量值,直接操作主存;指令重排:指JVM会在不影响正确的前提下调整语句的执行顺序double-checked-locking;
方法区
Length数组对象特有数组长度64 位系统:4字节
原子操作类:juc(java.util.concurrent)提供原子操作类,线程安全,底层采用的是CAS+volatile实现,如 AtomicIntrger、AtomicBoolean等。
局部变量表
成功
用户线程和垃圾回收线程同时执行,在垃圾回收时不会停顿用户线程,某些过程也会STW;尽可能让单次STW的时间最短
栈帧1
单线程,堆内存较小,适合个人电脑-XX:+UseSerialGC=Sreial + SerialOld使用SerialGC 新生代、复制 老年代、标记整理
H
static boolean run = true;psvm{ new Thread(()->{ while(true){ //.... } }).start(); Thread.sleep(1000); run = false;}
Thread2
double
栈帧2
程序计数器
JVM内存结构
访问同步块
睡眠时间过
获取偏向锁:一个线程访问同步块获取锁,会在对象头和栈帧中锁记录里存储偏向锁的线程ID,之后该线程再进入同步块检查对象头中是否有指向当前线程的ID,没有则检测MarkWord中偏向锁标识是否是1,不是则使用CAS竞争锁,是则使用CAS将对象头的偏向锁指向当前线程。
true
一些附加信息
while(true){ int 旧值 = 共享变量; int 结果 = 旧值 + 1; /* 此时若其他线程将共享变量变为其他值,本线程 的结果就作废,compareAndSwap返回false; 重新尝试,直到值为预期结果返回ture。 */ if(compareAndSwap(旧值,结果)){ //成功,退出循环 }}
CAS底层依赖于一个Unsafe类来直接调用操作系统底层的CAS指令,Unsafe对象只能通过反射获取。
GC-Root
即时编译器(just in time)
内存溢出和内存泄漏
运行
栈帧数量过多,递归;栈帧占用内存大
线程等到CPU时间片,获取CPU资源执行run()方法
解锁,将线程id设为空
链接
回收、分配原理:使用Unsafe对象完成直接内存的分配unsafe.allocateMemory(size),主动调用freeMemory()释放;
五种引用类型
多条垃圾收集线程并行工作,用户线程等待;多线程,堆内存较大,多核CPU;单位时间内,STW的时间最短
CAS替换MarkWord
垃圾回收算法
StringTable串池
虚拟机栈
reference
OutOfMemoryError
加载java核心类库,用于提供JVM自身所需的类;处于安全考虑,只加载java、javax、sun等开头的类
阻塞
并发标记
暂停线程
失败
本地方法接口
CPU占用过高
survivor
E
有序性:通过volatile关键字修饰变量,保证代码执行的顺序性;多线程下指令重排会影响结果的正确性
装载
-XX:+UseStringDedupLicationString s1 = new String(\"hello\
会STW,幸存对象复制到survivor
继承classload,重写findClass方法
synchronize:this-实例对象;类名.class-锁住类的方法,不能锁住类的属性和元数据;同步代码块:synchronize(任意对象){} 同步静态方法:锁定的是类名.class同步方法:所得是this
static final int i = 20;static final String s = \"Hello\";如果static变量是敷哪里的基本类型,以及字符串常量,name编译阶段值就确定了,赋值在准备阶段;static final Object o = new Object();若果被static、final修饰的引用类型,赋值在初始化阶段完成;
并发清理
概括得说,类初始化是【懒惰的】:*main 方法所在的类,总会被首先初始化*首次访问这个类的静态变量或静态方法时*子类初始化,如果父类还没初始化,会引发*子类访问父类的静态变量,只会触发父类的初始化*Class.forNamenew 会导致初始化不会导致类初始化的情况:*访问类的 static final 静态常量(基本类型和字符串)不会触发初始化*类对象.class 不会触发初始化*创建该类的数组不会触发初始化*类加载器的 loadClass 方法*Class.forName 的参数 2 为 false 时
对类的静态变量和静态代码块进行初始化操作
JDK1.7
Eden区部分对象移至Survivor区
run=true
自旋获取锁
堆区
并发标记类卸载JDK8u40
JDK1.8
FullGC
CAS修改MarkWord
栈帧n
S
Minor GC、Full GC、Major GC辨析?新生代收集: Minor GC、Young GC,只是新生代的垃圾收集; 触发,Eden区满,survivor区满不会触发;老年代收集: Major GC/Old GC,老年代垃圾收集; 触发,老年代空间不足触发轻GC,还不足,则触发 MajorGC,在不足,则OOM; CMS GC会有单独老年代垃圾收集;混合收集: Mix GC,对整个新生代和部分老年代的垃圾收集; 目前只有G1 GC 会有这种行为整堆收集: Full GC,堆整个堆和方法区进行垃圾收集; 触发: 1、调用System.gc(),建议系统执行,非务必执行; 2、老年代空间不足,方法区空间不足; 3、轻GC后移入老年代的对象所需内存大于老年代 剩余内存; 4、Eden区、from区想to区复制时,对象所需内存 大于to区可用内存,则吧对象转存到old区,且 old区可用内存小于对象大小。
阻塞Blocked等待队列线程状态Waiting
没有
1、由启动类加载的对象;2、本地方法栈内引用的对象;3、被同步锁synchronized持有的对象;4、虚拟机栈中引用的对象;5、方法区中常量引用的对象;
引用计数算法:对象被任一对象引用则+1,引用失效-1,对象引用计数为0时,则标记该对象为垃圾;存在计数器和计算加减的开销及循环引用问题:两对象相互引用
准备
javac编译器:词法分析器tokens流语法分析器语法树语义分析器注解抽象语法树字节码生成器
乐观锁:假设没有别的线程来修改共享变量,若有修改,在重试更新变量,指导修改成功,如CAS;悲观锁:假设一定有其他线程来修改共享变量,阻塞式同步修改变量,如synchronize。
方法区:类信息、常量、静态变量、JIT编译后的代码
子线程中的循环并不会停止;初始状态,子线程从主存读取run的值到工作内存,由于需频繁读取主存中的变量值,JIT编译器会把值缓存到自己的工作内存中,减少对主存的访问,提高效率,后续主线程修改对子线程不可见。使用volatile关键字修饰变量。
run方法执行完毕
运行running
自旋锁
线程2 i--
卸载
失败,因为线程2在争夺锁
重量锁
初始化
主内存 static int i = 0
主内存 static boolean run = true
工作内存
就绪Runnerable
当一个类所有的类都不在使用,则卸载它所加载的所有对象;-XX:+ClassUnloadingWithConcurrentMark
线程1
垃圾回收器
串行
synchronized
Object.wati();
执行同步体
YG跨代引用
指一个对象占用超过一半的region的空间;G1不会对其进行拷贝,回收是优先考虑;G1会跟踪老年代所有incoming引用,老年代引用为0时,巨型对象就可以在新生代垃圾回收时处理
Thread.sleep();
目标代码生成器
线程共享区域;用于存放已被虚拟机加载的类的类型信息、常量、即时编译后的代码缓存等;在HotspotJVM中又叫非堆,用于和堆区分;在JVM启动的时候被创建,决定了系统可以保存了多少个类回收包括两部分:常量池中废弃的常量和不在使用的类型常量没有任何引用则可以回收。JDK1.7及之后,静态变量和字符串常量池置于堆中JDK1.8及以后,永久代改成元空间
锁膨胀,修改为重量级锁
Compare and Swap乐观锁的思想,比较并替换;共享变量需要使用volatile修饰,保证可见性;使用CAS和volatile可以实现无锁并发;适用用于并发不高,多核CPU的场景;
font color=\"#323232\
线程共享区域,是java内存管理的核心区域;可以划分线程私有的缓冲区(TLAB)一个实例只有一个堆内存,物理上可以是不连续的内存空间,逻辑上视为连续的;分为新生代(eden区,survivor区),老年代、永久区,是GC的重点区域。
分代收集
空|0|01
调优
将类中符号引用转换为直接引用,解释器执行到该行的时候,才会去指向真实对象
撤销偏向锁
TLAB:让每个线程私有的内存区域来创建对象,即使多个线程创建对象时,在堆内存也不会产生干扰
t1.start();
案列
FullGC和MinorGC频繁?分析:可能是新生代初始内存空间过小,导致minorGC频繁,幸存区对象提前放入老年代,导致老年代存在大量生命周期短的对象,内存不足导致FullGC频发;处理:增减新生代的内存大小,调整survivor区的占比和晋升次数。
CPU1
JVM同步原理:JVM基于进入和退出Monitor对象来实现方法同步和代码块同步;代码块同步使用的是Monitorenter和Monitorexit指令实现,同步方法同样使用这两指令实现;Monitorenter指令在编译后充值入同步代码块的开始位置,Monitorexit指令插入在方法结束处或异常处;每个Monitorenter都有一个Monitorexit与之对应;任何对象都有一个Monitor对象与之关联,当且一个monitor被持有后,它处于锁定状态;线程执行到Monitorenter指令时,将会尝试获取monitor的所有权(尝试获取锁);
HSDB工具:查找对象,java -cp ./lib/sa-jdi.jar sun.jvm.hotspot.HSDB
.class文件
java对象头:锁存在java对象头里,对象是数组,用3个字宽(Word)来存储对象头,非数组用2个Word来存储;在32位系统中,1Word=4字节=32位;64位系统中,1Word=8字节=64位;、Mark Word 标记位class Metadata address 类元数据地址指针array length 数组长度Mark Word用于存储对象hashCode、分代年龄、锁标记位
强引用可以直接访问目标对象;强引用锁指向对象不会被系统回收;所有GCRoot对象都不通过【强引用】引用该对象,则该对象可被垃圾回收
协作式:t1.yield()当前线程让步
JVM进行FullGC
虚引用
FullGC前后内存占用:数据是不是太多,select * from 大表;数据表示是否太臃肿,对象大小最小16字节;是否存在内存泄漏;static Map map,只放不回收,考虑第三方缓存实现。
扩展类加载器
Humongous
操作数栈
新对象申请内存空间
被另一个线程恢复 resume()调用 已过时
死亡Dead
开始
old区
b style=\
类加载
永久代/方法区
内存溢出:程序申请内存时,没有足够的内存空间使用,且垃圾回收之后仍无法提供更多的内存,线程中子线程发生oom不会影响原有线程执行;内存泄漏:程序申请内存后,无法释放已申请的空间,即对象不会被程序用 到,但又不能被垃圾回收; 如单例程序中持有外部引用,这个对象是不能被回收的,会导致内存泄漏,一些提供和close方法的资源未关闭导致内存泄漏。
栈内存溢出
int
至针对对象生命周期采取不同的垃圾回收策略,目前大部分的GC都是采用分带收集;Young区,对象生命周期短、存活率低、回收频繁,使用复制算法效率高;Old区,对象生命周期长、存活率高、回收频率低,使用标记-清除或标记-整理算法;
...
垃圾回收
释放锁,并唤醒等待的线程
元空间和永久代(方法区):最大区别是元空间不再在虚拟机中设置内存,而是使用本地内存,也会发生内存溢出问题;内部空间调整:1.8类型信息、字段、方法、常量保存在本地内存的元空间,字符串常量和静态变量仍在堆中;
Garbage First同时注重吞吐量和低延迟,默认是暂停目标是200ms;超大堆内存,将堆划分为多个大小相等的regiion;整体上是标记整理算法,两个区域之间是复制算法
cpu时间片用完
重新标记
false
原子性:指该操作不可再分,同一时刻只有一个线程来操作,并且执行过程中不会被打断;eg:对共享变量的自增自减有两个线程同时进行,通过synchronize关键字进行同步处理;锁住的对象是同一个对象。
解锁:使用CAS操作将Displaced Mark Word替换到对象头,成功,则同步完成;失败,表示有其他线程尝试获取该锁,则释放锁的同时唤醒被挂起的线程。
Young区
增量收集
O
动态链接
强引用
Eden是否有足够空间
Container
线程2
CPU3
偏向锁
双亲委派机制:加载一个类,委派其父类加载器,父类还有父类则委派父类的父类,直到顶层加载器;父类能加载返回成功,父类加载不了,子类在尝试自己加载;好处: 1、保护程序安全,避免核心API被篡改 2、避免类重复加载,
run()结束
JMM
为什么年轻代需要survivor区,survivor为什么要分from区和to区?survivor区的存在起到以个缓冲作用,避免在Eden区每minorGC之后,将部分对象移至old区,导致old区很快被填满,old区内存不足时进行Major GC(Major GC一般伴随着Minor GC,也可以看成是触发了FullGC),而FullGC耗时长,对程序执行和响应有影响;Survivor的存在能减少被送往old区的对象;Survivor区的预筛选,只有经历16次minorGC还存活的对象才会被送往老年代;分from区,to区的好处是避免了内存span style=\
特性
只有弱引用引用该对象,在垃圾回收是会回收该对象;通过弱引用队列来释放自身内存;
代码优化器
将内存空间分为两块,每次只是用其中一块,垃圾回收时,将存活的对象复制到未使用的内存空间中,清除正在使用的内存中的所有对象,之后交换两个内存块的角色。特点:无内存碎片问题运行高效,但需要成倍的内存空间,对于G1收集器需要额外的维护region之间对象引用的开销
偏向锁撤销:等到竞争出现才释放锁;撤销需等待安全点,先暂停拥有偏向锁的线程,检查线程是否活着,不活跃,对象头设为无锁;活着,拥有偏向锁的栈会被执行遍历偏向对象的锁记录,栈中的锁记录和对象头的MarkWord要么重新偏向其他线程,要么恢复无锁或者标记对象不适合做为偏向锁,最后唤醒暂停线程。
使用:ByteBuffer bb = ByteBuffer.allocateDirect(1024*1024);ByteBuffer的实现内部类,使用了Cleaner(虚引用)来检测ByteBuffer对象,一但其被回收,那么就会由ReferenceHandler线程通过Cleaner的clean方法调用freeMemory来释放直接内存。
分配空间并复制MarkWord到栈
JVM中又叫对象监视器Monitor,包含竞争锁的队列和信号阻塞队列,前者做互斥,后者用于线程同步。
在某个线程获得锁之后,消除这个线程重入(CAS)的开销;大多数情况下锁不仅不存在多线程竞争,而且总是由同一线程多次获得;偏向锁的释放不需要做任何事情,加过偏向锁的Mark Value会一直保留,即同一个线程持续不断的加锁解锁,也是没有开销的;偏向锁在有不同线程申请锁时先退为无锁,再加轻量锁;对多线程加锁,但不存在锁竞争情况,会使用epoch优化;
将对象头中的线程id指向自己
JVM回收不活跃的对象
-Xmn:新生代越大,老年代越小,fullgc可能性更大,minorGC时间更长;oracle新生代建议25%-50%;新生代内存设置:并发量*(一次请求-响应内存占用);幸存区大到能保存当前活跃对象+需要晋升对象;晋升阈值配置得当,让长时间存活对象尽快晋升
阻塞Blocked
class文件
竞争,获取到到锁
巨型对象回收JDK8u40
run=false
本地变量表
数组实现,又称表达式栈,根据字节码指令入栈(写入)或出栈(读取);用于保存计算过程的中间结果或变量临时存储空间;若调用方法有返回值,其返回值会被压入当前栈帧的操作数栈中。java虚拟机的解释引擎是基于栈的执行引擎,其中栈及操作数栈。
加锁:线程执行同步代码块前,JVM会在当前线程的栈帧中创建用于存储锁记录的空间,并将对象头中的MarkWord复制到锁记录中(Displaced Mark Word);线程尝试使用CAS将对象头中的MarkWord替换为指向锁记录的指针,成功则获得锁,失败则自旋,自旋获取锁仍失败,表示有其他线程竞争锁,则轻量锁膨胀为重量锁。
0|10
notify();/notifyAll();
本地方法栈
-XX:+UseConcMarkSweepGC~-XX:+UseParNewGC~SerialOld使用CMS垃圾回收器,标记清除,并发失败后,退化SerialOld单线程执行标记整理-XX:ParallelGCThreads=n~-XX:ConcGCThreads=threads用户并行线程数,垃圾并发GC线程数,一般设为CPU的1/4-XX:CMSInitiatingOccuPancyFraction=percent垃圾收集器触发垃圾回收的百分比,一般设为75-80-XX:+CMSScavengeBeforeRemark在重新标记前先对新生代来及回收
MixedCollection
young Collection
新建new Thread()
Field theUnsafe = Unsafe.class.getDeclaredField(\"theUnsafe\");theUnsafe.setAccessible(true);Unsafe unsafe = (Unsafe)theUnsafe.get(null);
分区收集G1
堆内存溢出
Java memory model:JMM定义了一套在多线程读写共享数据时(成员变量、数组),对数据的可见性、有序性、原子性的规则和保障
T1|epoch|1|01
java对象内存布局
to/S1
可见性:多个线程操作同一变量,一个线程对变量写的修改,对其他读线程可见;不能保证原子性,适用于一个写多个读场景
轻量锁
主线程
标记-整理
线程1 i++
软引用
对象实例数据
阻塞Blocked同步队列线程状态Blocked
per-write barrier + satb_mark_queue并发标记时,对象的引用发生改变时,会给对象增加一个写屏障,写屏障指令会执行,将该对象加入队列中,标记该对象未处理,在重新标记时对队列中的对象重新标记,避免并发标记时对象被当做垃圾回收
jstack 线程id,查看打印出来的死锁问题
运行长时间没有结果-死锁
对象头中的MarkWord
恢复线程
线程诊断
新生代调优
为了保证对象大小为8字节的整数倍
本地方法库
直接内存
终结器引用,在垃圾回收时,终结器引用入队,再有Finalizer线程通过终结器引用找到被引用对象并调用其finalize()方法,在进行第二次GC时在能回收该对象;不建议手动调用对象finalize方法,因为执行线程优先级很低,对象不能及时回收。
结束
java虚拟机栈的构成部分,对应这个一个个方法的调用,每个方法运行时的内存空间
CMS的老年代内存越大越好;先不做调优,没有FullGC,现尝试新生代调优;观察FullGC时老年代内存占用将老年代预设值调大1/4~1/3
使用
CAS与原子类
synchronize优化
全限定类名获取到类的二进制字节流,将其代表的静态存储结构转换为方法区运行时数据结构在java堆中生成一个类的对象,作为方法区中这些数据的访问入口
short
检查对象头中是否存储了线程1
对齐填充
其他线程加入 join()
CPU2
设置:java6/7默认开启偏向锁,程序启动后会延迟几秒激活;-XX:BiasedLockingStartupDelay = 0 关闭延迟;-XX:-UseBiasedLocking=false 关闭偏向锁,默认进入轻量锁;
解析
线程私有,每个线程运行时所需要的内存,每个栈有多个栈帧组成,对应每次方法调用;栈是运行时单位,堆是存储单位;栈帧先进后出,弹栈(执行return指令或抛异常)
old
复制
t1.stop()被调用
Survivor是否有足够空间
本地方法栈:调用非Java代码接口,如遇外界、操作系统交互
from/S0
膨胀为重量级锁
判断垃圾
young Collection+Concurrent Mark
将一块大的内存空间分成若干个小的region区间,根据目标的停顿时间,每次回收若干个小区间,从而减少一次GC产生的停顿;每个小区间独立使用,独立回收,可以控制一次回收多少个区间
轻量级锁
G1垃圾收集器
加载环境变量或系统属性,类路径下的类库一般来说,java应用的类都是由其加载
垃圾回收后,内存占用仍很高?使用jvisualVM工具,执行GC后查看堆内存占用,进行堆dump查看是什么对象占用内存高
加载java扩展类库,加载/jre/lib/ext下的类库
高速缓存run = true
可达性分析算法:没有被根对象集合中的对象直接或间接的链接,则可以标记位垃圾;使用该算法须保证分析在一个快照中进行,保证一致性。
falseMinorGC
Eden区
方法返回地址
线程生命周期
老年代调优
初始标记:标记根对象,速度快,STW时间很短;并发标记:并发标记需垃圾回收对象;重新标记:并发标记用户线程也在执行,会有新的引用或引用改变,需重新标记;当程序运行到安全点后,开始阻塞式初始标记,到达下一安全点后,并发标记垃圾清除对象;下一安全点后STW重新标记,之后再并发清理;存在问题:并发清理的同时,用户线程也在运行,会产生新的垃圾对象?此时需要设置一个触发GC的阈值,避免垃圾回收的同时没有空间存放用户线程新产生的对象;新生代(对象会很多)引用老年代对象,可达性分时的时候,重新标记耗时会加长?新生代很对对象生命周期比较短,可以再重新标记时先进行一次新生代垃圾回收。
指向栈锁记录的指针|00
内存问题
堆
HashCode|age|0|01
线程被唤醒,重新争夺锁访问同步块
GC调优
-XX:+UseG1GCjdk8默认回收器不是G1,jdk9设为默认-XX:G1HeapRegionSize=size 一般设置成1248-XX:MaxGCPauseMillis=time
字符串延迟加载、变量拼接、编译期优化、intern()
java编译器将.java文件编译成.class文件,自动生成或转换一些代码;
非堆
运行时数据区
对象头
子线程
新对象内存申请成功
检查对象头中是否存储了线程2
堆:代表某个类的对象
slot槽:1、局部变量表的基本存储单位;2、可存放8种基本数据类型,应用类型(reference)、 returnAddress类型的变量;3、一个槽占4个字节,byte、short、char存储前被转换成int, boolean也转换成int,占一个槽,long和double占两个槽;4、JVM为每个槽分配一个索引,使用long类型变量访问前一个索引;5、当前帧有构造方法或实例方创建,对象引用this放在index=0的槽, static方法中不允许使用this,其余按顺序存放;6、槽可以重复利用,当某一局部变量过了作用域之后, 其槽位可能会被之后申明的变量使用。
类加载子系统
回收阶段
对象数据类型
自定义类加载器
卡表技术,将老年代的Region细分为多个卡。一般一个卡512k,存在引用的卡为脏卡,查找GCRoot的时候,区遍历脏卡查找即可,减少了GCRoot的时间;标记脏卡是异步操作: 卡表与Remember Set; 在引用变更时通过post-worite barrier + dirty card queue; concurrent refinement threads 更新 Remember Set;
为类的静态变量分配内存,并将其初始化默认值;
常量池与运行时常量池:字节码文件包含:类的基本信息、常量池、方法的定义(包含虚拟机指令)常量池:源文件编译成二进制字节码文件,在字节码文件中会维护一份常量池(就是一张表),虚拟机指令根据该表去找到要执行的类名,方法名、参数类型、字面量等信息。运行时常量池:当类被加载,其常量池会放入到运行时常量池,并把里面的符号地址,编程真实地址。符号引用和直接引用:符号引用:在源文件编译成字节码文件的时候,被调用的方法在编译期无法确定,使用符号引用来来替代;直接引用:将常量池中的符号引用转换成直接引用,直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄
指向运行时常量池的引用,将符号引用转换为调用方法的直接引用
缓解STW问题:垃圾收集线程和程序线程交替执行,每次只收集以小片区域的内存空间,直到垃圾回收完成;间断性执行程序线程,上下文切换线程切换的消耗,总体回收成本上升,系统吞吐量降低
线程阻塞和唤醒需要CPU从用户态转为核心态,是个重操作;自旋,让线程区执行一个无意义的循环,循环结束去竞争锁,竞争失败接着循环,期间线程处于running状态,但会让出时间片,其他线程仍可申请|释放锁;自旋需控制次数,否则变成“忙式等待”,超出次数,自旋锁升级为阻塞锁。
public static void main(String[] args) { String a = \"a\"; //懒惰的 String b = \"b\"; String ab = \"ab\"; //new StringBuilder().append(\"a\").append(\"b\
分析器
Survivor区部分对象移至old区
Survivor区
class point指向对象对应的元数据内存地址64位系统:8字节
CMS垃圾回收器
并行/吞吐量优先
young GC时进行GCroot初始标记,老年代占用堆比例到阈值,进行并发标记(不会STW)阈值设置-XX:InitiatingHeapOccuPancyPercent=percent,默认45%
被其他线程杀死
实例数据
Mark Word一系列标记位(哈希码、锁状态、分带年龄等)64位系统:8字节
1、一个线程内,按照程序顺序,前面的操作hb与后面的操作;2、对于一个锁的解锁hb与随后对这个锁的加锁(synchronize);3、对一个volatile域 的写,hb与任意后续对这个volatile域的读;4、传递性A-hb-B,B-hb-C,则A-hb-C;5、start()规则,主线程A启动子线程B,B能看到A启动B之前的操作;6、join()规则,线程A执行ThreadB.join(),那么B中任意操作hb与A从ThreadB.join()操作成功返回;7、线程A打断线程B前对变量的写,对于其他线程得知线程B被打断后的对变量的读可见;
调优领域:内存、锁竞争、CPU占用、io确定目标:低延迟、高吞吐量,合适的回收器(CMS、G1、ZGC;ParallelGC)
虚引用引用对象,在对象被回收后,会执行相关回收后的操作;如直接内存的使用,在ByteBuffer对象被回收后,会通过虚引用对象Cleaner执行clean方法,调用unsafe.freememery()释放直接内存
Eden
弱引用
HotSpot中,CMS垃圾回收器是基于Mark-Sweep实现,回收效率高;对于内存碎片问题,CMS采用基于标记-整理算法的Serial Old回收器作为补偿机制,当内存回收不佳时,采用Serial Old回收器进行Full GC;即先使用标记清除算法,标记清除算法回收效果不佳时,在采用标记整理MinorGC和FullGC都会引发STWSurvivor区中对象年龄为什么是15?对象的分带年龄记录在对象头中,占4bit,最大就是15,对象头每个部分都有大小限制。
老年代充裕的情况下,发生FullGC?(CMS、1.7)分析:可能是永久代的空间不足;处理:增加永久代内存空间
字符串常量池,实际是一个HashTable,不能扩容;JDK1.7/1.8,在堆内存中,JDK1.6,在永久代中;span style=\"font-size: inherit;\
线程阻塞
其他锁优化:减少上锁时间-同步带块短;减少锁的粒度-一个锁拆分成多个锁,concurrentHashMap,linkedBlockQueue;锁粗化-StringBuffer,循环外加锁;锁消除;读写分离-copyOnWriterArrayList、copyOnWriterSet
Thread t1 = new Thread();
请求高峰期发生FullGC,且时间长?(CMS)分析:高峰期会产生大量对象,而FullGC的重新标记阶段,对新的引用和引用改变的对象重新标记,大量对象生成,会造成这部分时间加长;处理:在重新标记前先对年轻代进行一次GC-XX:+CMSScavengeBeforeRemark
将MarkWord替换为轻量级锁
元空间
Thread1
寄存器,记录下一条jvm指令的执行地址;运行速度最快的存储区域,没有规定oom的区域;线程私有,CPU会不停切换任务,可以避免其他线程的干扰执行native方法则是未指定(undefined);
java堆
直接内存就是JVM可以读取的操作系统的内存;常用于NIO操作,用于数据缓冲区;分配回收成本高,单读写性能高;不收JVM内存管理;也会内存溢出:oomError:direct buffer memory
终结器引用
.java文件
指向重量级锁的指针|10
对E、S、O全面进行垃圾回收;最终标记、拷贝存活,会STW;对O会有选择的回收Old区,只选择回收价值最高的老年代region区;-XX:MaxGCParuseMillis=ms
所有new操作的内存分配非常廉价:TLAB;死亡对象的回收代价是0;大部分对象用过即死;minorGC时间远小于FullGC
中间代码生成器
只有弱引用引用该对象,在垃圾回收后空间仍不足时,会回收该对象;通过软引用队列来释放自身引用空间;
对象类型数据指针
引导类加载器
方法区:加载的类过多JDK1.7永久代内存溢出JDK1.8元空间内存溢出eg:spring、mybatis的动态代理
验证
初始标记
字符串去重JDK8u20
0 条评论
下一页