一、JVM体系结构
- jvm优化99%都是优化方法区和堆
二、类加载器
1.启动类(根)加载器 Bootstrap ClassLoader 默认会去加载JAVA_HOME/lib目录下的jar加载的是核心类
- 扩展类加载器 ExtClassLoader /jre/lib/ext 默认会去加载JAVA_HOME/lib/ex目录下的jar 加载的是拓展类
- 应用程序加载器 AppClassLoader 比如web应用,会去加载web程序下ClassPath下的类 加载的是应用类
- 用户自定义类加载器 User ClassLoader
加载:从字节码加载成二进制流的过程
验证:验证Class文件是否符合虚拟机规范
准备:为静态变量常量赋默认值
解析:把常量池中的符号引用替换为直接引用。符号引用就理解为一个标示,而在直接
引用直接指向内存中的地址。
初始化:执行static代码块和静态变量初始化(如果存在父类先对父类进行初始化)
类加载完成就是要对对象分配空间和初始化的过程
1.首先为对象分配合适大小的内存空间
2.为实例变量赋值
3.设置对象头,hash码、GC分代年龄、元数据等信息
4.执行构造函数(init)初始化
------------------------------------------
比较两个类是否“相等”,只有在两个类是由同一个类加载器加载的前提下才有意义,
否则,即使这两个类来源于同一个Class文件,被同一个Java虚拟机加载,只要
加载它们的类加载器不同,这两个类就一定不相同
三、双亲委派机制
-
类加载器收到类加载请求
-
将这个请求向上委托给父类加载器去完成,一直向上直到启动类加载器
-
启动类加载器若是可以加载直接加载,若是不能则通知子加载器进行加载
请求:APP->Ext->Root
加载:Root->Ext->APP
四、Native
-
凡是带了native关键字的,都是java作用不到的范围,会去调用C或者C++库,进入本地方法栈,调用本地方法接口JNI
-
JNI作用就是扩展java的用途,融合不同的语言为java所用
-
java在内存中专门开辟了一块内存,本地方法栈,登记native方法,在最终执行的时候,加载本地方法库中的方法通过JNI
五、运行时数据区
1.方法区
-
Method Area
-
1.8之后方法区用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译后的代码等数据。
2.程序计数器
-
Program Counter Register
-
当前线程所执行的字节码的行号指示器,个人感觉的他就是为多线程准备的,程序计数器是每个线程独有的,所以是线程安全的。它主要用于记录每个线程的执行情况。
3.堆
- Java Heap
- Java 虚拟机中内存最大的一块,是被所有线程共享的,几乎所有的对象实例都在这里分配内存;Java堆也叫GC堆,是垃圾收集器管理的主要区域,堆中可以细分为:新生代、老年代;再细致一点,新生代中又分为:Eden Space(伊甸园)、Survivor空间,Survivor空间又分为From区和to区。
4.虚拟机栈
-
Java Virtual Machine Stacks
-
线程私有,用于存储局部变量表(存储方法参数和局部变量)、操作数栈(局部变量表的变量在这里面操作)、动态链接(用于将符号引用表示的方法转换为实际的方法的直接引用)、方法出口等信息
5.本地方法栈
-
Native Stack
-
线程私有,登记native本地方法,调用本地方法接口
栈:
- 栈内存,主管程序的运行,生命周期和线程同步。
- 里面包括8大基本数据类型+对象引用+实例的方法
- 运行原理:栈帧。每个被压入栈的方法的引用或者其他都会有一个父帧和子帧,父帧指向上一个方法的栈帧,指向下一个方法栈帧,在栈顶的方法用完先弹出,则其下一个方法的子帧为空,直到栈底的父帧子帧都为空则用完释放缓存。
- 栈满了:StackOverFlowErrot
六、堆的结构
垃圾回收主要在伊甸园去和养老区,假设内存满了,jdk8以后永久存储区改了个名字(元空间)。
产生的对象实例会在Edan区,如果eden区满了就会触发young gc,回收垃圾,
回收那些没有人引用的对象(GC Root),把eden里的存活的对象,移到S0区
(复制算法);下次额扥又满了触发ygc,就把eden和S0区里面存活的对象,都通过
复制算法转移到S1区,同时清空垃圾对象,下次又ygc会把eden区和S1区里面存活的
对象放到S0区,这样保证每次都有一块survivor区是清空的
三种场景移到老年代:
第一种,当一个对象在年轻代熬过15次垃圾回收就会被认为是长期存活的对象,
转移到老年代里去;
第二种情况,就是ygc的时候,发现存活的对象很多,而幸存区空间少,直接移到老年代;
第三张就是,刚开始创建的对象特别大,这个很大的对象直接就放到老年代。
处理OOM:
-Xms8m -Xmx8m -XX:+PrintGCDetails
更改jvm最大内存和总内存- jprofiler工具
七、垃圾回收
GC算法
-
引用计数法:
-
复制算法
- 优点:没有内存碎片
- 缺点:浪费了内存空间:有一半幸存区空间永远是空的
- 最佳使用场景:对象存活度较低的时候:新生区
-
标记清除法
- 优点:不需要额外的空间
- 缺点:因为要遍历所有的GCROOT,清除的过程也要遍历堆中所有的对象,两次扫描效率低下,会产生内存碎片
-
标记整理:把存活的对象压到一边
GC就是分代收集算法
对于新生区:存活率低,用复制算法
对于老年区:存活率高,采用标记清楚+标记压缩混合实现
垃圾回收器
-
Serial
为单线程环境设计且只使用一个线程进行垃圾回收,会暂停所有的用户线程,所以不适合服务器环境。
-
parallel
多个垃圾收集线程并行工作,此时用户线程也是暂停的,适用于科学计算/大数据处理。
-
CMS(并发垃圾回收器)
并发标记清楚,用户线程和垃圾收集线程同时执行(不一定是并行,可能交替执行),不需要停顿用户线程,适用于对响应时间有要求的场景。
优点:并发进行停顿低
缺点:并发进行CPU压力大,会导致大量碎片
第一步:初始标记(CMS inital mark) 标记GC ROOT能关联到的对象 此时STW
第二步:并发标记(CMS concurrent mark )从GCROOTS的直接关联对象开始遍历整个对象图的过程
第三步:重新标记(CMS remark) 为了修正并发标记期间,因用户程序继续运作而导致标记产生改变的标记 此时STW
第四步:并发清除(CMS concurrent sweep)清除标记阶段判断死亡的对象
-
G1
默认的垃圾回收器有哪些
UseSerialGC:开启新生代和老年代的串行垃圾回收器
UseParNewGC:开启新生代的并行的垃圾回收器,而老年代仍是串行,已经不推荐使用
UseParallerGC:新生代和老年代都使用并行垃圾回收器,java8默认使用的垃圾回收器,重点在于,可控制的吞吐量,自适应调节策略。
UseParallelOldGC:新生代和老年代都使用并行垃圾回收器,与UseParallerGC互相激活
UseConcMarkSweepGC:老年代用CMS,年轻代用ParNew
UseSerialOldGC:1.8之后已经不在使用,只有在CMS出故障时候,老年代才会使用Serial Old
UseG1GC
八、JMM
(Java Memory Model)
是一个虚拟的内存模型,数据存储在主内存中,每个线程都有自己的工作内存,拷贝主内存的数据作为变量副本,对数据的操作不能在主内存,都是在自己的工作内存操作,然后刷到主内存。
九、GCRoots
GCRoots是给定一个集合的引用作为根节点,在堆中的数据,凡是可以追溯到GCRoots
的则叫做引用可达,不能追溯到GCRoots的则不可达,被当做垃圾等待回收
//可以作为GCRoots的对象有:
1.虚拟机栈(栈帧中的本地变量表)中引用的对象
2.方法区中的类静态属性引用的对象
3.方法区中常量引用的对象
4.本地方法栈中JNI(即Native方法)中引用的对象
十、JVM的参数类型
如何查看JVM参数值
jsp -l查看当前进程编号
jinfo -flags 进程编号
如何查看JVM初始默认值和修改后的值
java -XX:+PrintFlagsInitial//初始值
java -XX:+PrintFlagsfFinal//修改后的值
java -XX:+PrinCommandLineFlags //可以查看当前使用的垃圾回收器
- 标配参数
java -version,java - help
- X参数
-Xint:解释执行,-Xcomp:第一次使用就编译成本地代码,-Xmixed:混合模式
- XX参数
- -Xms
等价于-XX:InitialHeapSize//初始内存,默认是物理内存的1/64
- -Xmx
等价于-XX:MaxHeapSize//最大内存,默认是物理内存的1/4
- -Xss
等价于-XX:ThreadStackSize//单个线程栈的大小,一般512k-1024k
- -Xmn
设置年轻代大小
- -XX:MetaspaceSize
设置元空间大小
- -XX:+PrintGCDetails
打印GC信息
- -XX:SurvivorRatio
设置Eden区和s0s1区的比列,默认为8,ex:-XX:SurvivorRatio=4,则Eden:s0:s1=4:1:1
- -XX:NewRatio
设置老年代和年轻代的比列,默认为2,ex:-XX:NewRatio=4,则老年代:年轻代=4:1
- -XX:MaxTenuringThreshold
设置垃圾最大年龄,默认15
- -XX:+DisableExplicitGC
禁止运行期显式地调用 System.gc() 来触发fulll GC
- -Xms
十一、强、软、弱、虚引用
-
强引用
Demo demo = new Demo();
强引用不会被GC掉,即使内存满出现OOM
-
软引用
SoftReference
内存够的时候保留,内存不够的时候会被回收
-
弱引用
WeakReference
只要GC就会被回收
在读取大量的本地图片时,考虑用一个hashmap来保存图片路径和相应图片对象关联的软引用之间的映射干,内存不足的时候自动释放内存,从而避免OOM
WeakHashMap
-
虚引用
PhantomReference
引用在被回收的时候需要被放到ReferenceQueue保存一下
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5MTDGFjv-1662002138504)(https://pic-for-note.oss-cn-hangzhou.aliyuncs.com/pic-for-typora/%E8%99%9A%E5%BC%95%E7%94%A8.png)]
十二、OOM
-
StackOverFlowError
-
OutOfMemoryError:Java heap space
-
OutOfMemoryError:GC overhead limit exceeded
GC回收时间过长,效率低,用98%的时间却只回收了2%的内存,导致恶性的GC的循环。
-
OutOfMemoryError:Direct buffer memory
写NIO程序,一些读写本地数据的ByteBuffer方式,使用Native函数库直接分配堆外内存,通过在堆中的DirectByteBuffer对象作为这块内存的引用进行操作,这种方式显著提高性能,避免在java堆和native中来回复制数据。但若是不断分配本地内存,堆内存充足而本地内存用光,就会出现OutOfMemoryError:Direct buffer memory。
十三、Linux调优
-
查看整机性能
Top
其中load average:代表系统1分钟5分钟15分钟系统的平均负载值
uptime
:可以直接查看load average -
查看CPU
vmstat
mpstat -P ALL 2
查看所有cpu核信息pidstat -u 1 -p 进程编号
每个进程使用CPU的用量分解信息 -
查看内存
free -m
应用程序可用内存pidstat -p 进程编号 -r 采样间隔秒数
-
查看硬盘
df -h
-
查看磁盘IO
iostat -xdk 2 3
两次采样一秒采样3次pidstat -d 采样间隔秒数 -p 进程编号
-
查看网络IO
ifstat
14.CPU占用过高分析
第一步:TOP命令找到占用CPU最高的进程
第二步:ps -ef|grep java|grep -v grep
或者jps -l,进一步定位
第三步:定位到具体的线程或者代码 ps -mp 进程编号 -o THREAD,tid,time
第四步:jstack 进程编号 | grep tid(16进制线程ID小写英文) -A60
15、JVM调优
首先,JVM调优主要是为了减少STW的时间,也就是减少GC的次数,