JVM调优
jvm
java语言的优势,有jvm虚拟机,通过字节码文件,可以在任意的环境运行,其他语言都需要根据不同的操作系统(win、macos、linux)形成各自的机器码文件,在jdk1.8的源码中有根据不同的环境形成了不同的指令集,所以java通过jvm这个适配器,去适配各个系统。
java ———> 字节码 ————–> 不同的操作系统对接jvm后对接一个字节码文件
其他语言————-> 机器码 ————> 不同机器码对应不同的系统
jvm体现了适配器设计模式、体现了控制反转和依赖导致的设计原则,jvm去适配不同的系统又体现了开闭的原则
1.字节码
package com.test;
/** * @Author: * @Date: 2022/01/18/22:24 * @Description: **/
public class JVMHelloDemo {
public static void main(String[] args) {
System.out.println("hello,world");
}
}
2 解析JVM运行时数据区
2.1 方法区(Method Area)
方法区是所有线程共享的内存区域,它用于存储已被Java虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。它有个别命叫Non-Heap(非堆)。当方法区无法满足内存分配需求时,抛出OutOfMemoryError异常。
2.2 Java堆(Java Heap)
ava堆是java虚拟机所管理的内存中最大的一块,是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例。在Java虚拟机规范中的描述是:所有的对象实例以及数组都要在堆上分配。java堆是垃圾收集器管理的主要区域,因此也被成为“GC堆”。从内存回收角度来看java堆可分为:新生代和老生代。从内存分配的角度看,线程共享的Java堆中可能划分出多个线程私有的分配缓冲区。无论怎么划分,都与存放内容无关,无论哪个区域,存储的都是对象实例,进一步的划分都是为了更好的回收内存,或者更快的分配内存。根据Java虚拟机规范的规定,java堆可以处于物理上不连续的内存空间中。当前主流的虚拟机都是可扩展的(通过 -Xmx 和 -Xms 控制)。如果堆中没有内存可以完成实例分配,并且堆也无法再扩展时,将会抛出OutOfMemoryError异常。
2.3 程序计数器(Program Counter Register)
程序计数器是一块较小的内存空间,它可以看作是:保存当前线程所正在执行的字节码指令的地址(行号)
由于Java虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的,一个处理器都只会执行一条线程中的指令。因此,为了线程切换后能恢复到正确的执行位置,每条线程都有一个独立的程序计数器,各个线程之间计数器互不影响,独立存储。称之为“线程私有”的内存。程序计数器内存区域是虚拟机中唯一没有规定OutOfMemoryError情况的区域。
总结:也可以把它叫做线程计数器
3.字节码文件结构
cafe babe: magic 是java的字节码文件
0000 :minor_version 0
0034:major_version 52
cp_info {
u1 tag;
u1 info[];
}
0a:tag CONSTANT_Methodref
CONSTANT_Methodref_info {
u1 tag;
u2 class_index;
u2 name_and_type_index;
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-E4egWycx-1643246498549)(/Users/zhaokaijie/Library/Application Support/typora-user-images/image-20220119103625185.png)]
cafe babe java
0000 minor version
0034 major version
001d 常量池大小 29 0-28
0a 0006=6 000f=15
Methodref class_index name_and_type_index
09 0010=16 0011=17
Fieldref class_index name_and_type_index
08 0012=18
String string_index
0a 0013=19 0014=20
Methodref class_index name_and_type_index
07 0015=21
Class name_index
07 0016=22
Class name_index
01 0006 3c 696e 6974 3e
Utf-8 length bytes[length]
01 0003 28 2956
01 0004 43 6f64 65
01 000f 4c 696e 654e 756d 6265 7254 6162 6c65
01 0004 6d 6169 6e
01 0016 28 5b4c 6a61 7661 2f6c 616e 672f53 7472 696e 673b 2956
01 000a 53 6f75 7263 6546 696c 65
01 0011 4a 564d 4865 6c6c 6f44 656d 6f2e 6a61 7661
0c 0007 0008
07 0017
0c 0018 0019
01 000d 68 656c 6c6fefbc 8c77 6f72 6c64
07 001a
0c 001b 001c
01 0015 63 6f6d 2f74 6573 742f 4a56 4d48 656c 6c6f 4465 6d6f
01 0010 6a 6176 612f 6c61 6e67 2f4f 626a 6563 74
01 0010 6a 6176 612f 6c61 6e67 2f53 7973 7465 6d
01 0003 6f 7574
01 0015 4c 6a61 7661 2f69 6f2f 5072 696e 7453 7472 6561 6d3b
01 0013 6a 6176 612f 696f 2f50 7269 6e74 5374 7265 616d
01 0007 70 7269 6e74 6c6e
01 0015 28 4c6a 6176 612f 6c61 6e67 2f53 7472 696e
673b 2956 0021 0005 0006 0000 0000 0002
0001 0007 0008 0001 0009 0000 001d 0001
0001 0000 0005 2ab7 0001 b100 0000 0100
0a00 0000 0600 0100 0000 0800 0900 0b00
0c00 0100 0900 0000 3900 0200 0200 0000
1503 3c1b 8401 0184 0101 1b60 3cb2 0002
1203 b600 04b1 0000 0001 000a 0000 0012
0004 0000 000a 0002 000b 000c 000c 0014
000d 0001 000d 0000 0002 000e
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dpMBJE07-1643246498553)(/Users/zhaokaijie/Library/Application Support/typora-user-images/image-20220118224245237.png)]
所以说更加理解了方法区存放常量,堆中存储对象
有了字节码文件之后需要通过类加载器把字节码文件加载到jvm虚拟机中
初始化完成就完成了一个对象实例的创建,就可以使用了
1.Class Loader
由子类classloader到应用级到扩展级到系统级
1.1双亲委派的原因
1.1.1java遵循单继承的原则,保证了在一个jvm进程的全链路的类只会出现一次
同一个类名出现多次也是有可能出现的,在容器中,eg:tomcat和javaEE出现包名一致,类名一致但是也不是全路径的一致
出现同包名类名的情况:比如加载一个二方包和本地源码时,
1.报错
2.先找到谁就用谁
3.按照规则决定使用哪个
4.重复的后面的覆盖前面的
5.随机
儿子永远相信父亲是对的
import org.junit.Test;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
public class ClassLoaderTest {
@Test
public void test1() {
//1.获取一个系统类加载器 App ClassLoader
ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader(); //Application
System.out.println(systemClassLoader);
//2.获取系统类加载器的父类加载器,即扩展类加载器
ClassLoader extensionClassLoader = systemClassLoader.getParent();
System.out.println(extensionClassLoader);
//3.获取扩展类加载器的父类加载器,即引导类加载器
ClassLoader bootstapClassLoader = extensionClassLoader.getParent();
//引导类加载器用于加载java核心库,无法直接获取,故输出null
System.out.println(bootstapClassLoader);
//4.测试当前类由哪个类加载器进行加载
ClassLoader classLoader = null;
try {
classLoader = Class.forName("com.jvm.classload.ClassLoaderTest").getClassLoader();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
System.out.println(classLoader);//结果为系统类加载器
//5.测试JDK提供的Object类由哪个类加载器完成
ClassLoader objClassLoader = null;
try {
objClassLoader = Class.forName("java.lang.Object").getClassLoader();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
System.out.println(objClassLoader);//结果为null(说明是用的引导类加载器,我们无法获取)
//6.1 关于类加载器的一个主要方法:getResourceAsStream(String str):获取路径下的指定文件的输入流
InputStream is = null;
is = this.getClass().getClassLoader().getResourceAsStream("application.properties");
System.out.println(is);
Properties properties = new Properties();
//6.2 读取配置文件
try {
//从输入流中读取属性列表(键和元素对)
properties.load(is);
} catch (IOException e) {
e.printStackTrace();
}
//匹配对应key的属性,获取key对应的元素值
String name = properties.getProperty("spring.application.name");
String port = properties.getProperty("server.port");
System.out.println("name = " + name + " , port = " + port);
}
}
1.2tomcat破坏双亲委派原则
Tomcat 中最小的单位是 *.war
ClassLoad 最小单位是 *.war
可见Tomcat中.war需要依附于一个ClassLoader,这样同样全路径的包也可以区分,叫做WebAppClassLoader
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
c = findClass(name);
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
类加载的过程也是满足多线程的加载方式的,可以多个线程一起加载
在源码中 根据类的名字把字节码文件读进来,开始解析开启字节码文件的流,字节码文件对不对,属于哪个域,上下文,线程全正确的情况下赋值给上下文
GC
发现垃圾的方法
1.reference count 引用计数法
原理:看一个对象被引用的次数,有一个计数器来记录引用的个数,如果没用被引用,计数器的个数变为0的时候,就会被回收
缺点:无法判断循环引用
2.根可达算法
原理:就是从根开始看,达到的是有用的,其他的是垃圾
root:JVM stack,native,method stack,run-time,constant,pool,static references in method area,Clazz
清除垃圾的算法
1.Mark-Sweep(标记清除算法)
原理:将标记出来的垃圾直接清除
弊端:会产生碎片的空闲空间,较小的空间无法被大的对象使用
2.Copying(拷贝算法)
原理:一般的空间空余,一半的空间用于存储,将标记无用的回收后,复制到预留的区域
弊端:浪费存储空间
3.Mark-Compact(标记压缩算法)
原理:将无用的对象先标记出来,整理后合并空闲空间存储
弊端:算法复杂,效率最低
垃圾收集器
1.分代模型:
年轻代:ParNew、Serial、Parallel Scavenge
老年代: Scavenge、CMS、Serial Old、Parallel Old
Epsilon 11.0之后的垃圾回收器,不干任何事的
1.8默认的垃圾回收器
默认使用的PS+PO Parallel Scavenge+Parallel Old
新生代 YGC 老年代 MGC 整体 FGC
流程:
一个新的对象创建之后会被存储到eden中,再次使用会采用Copying回收方法复制到surivor中,根据不同的算法,默认15代(有4bit的存储空间记录年代的信息也就是说最大15)之后,CMS为6次,直接静如老年代,老年代满了之后会采用Mark Compact、Mark Sweep方法进行回收。
算法的选择:
年轻代使用的是Copying的算法,这是的大多数的对象是指用一次就被销毁的对象,只有少部分的会多次使用进入S1、S2,就可以整块的清除Eden和S2(S1),使用Copying算法需要分配一部分空间多次引用的对象、此时的空间不需要很大,就减少了Copying方法的弊端。
对象创建到消亡的全过程
1.放入栈
对象创建后最优先考虑是是否可以随着方法放入栈中而不是直接进入对存储区中,满足:
(1)、逃逸分析
若某对象A只在方法Amethod()中被使用,而没有被其他的对象所使用,那么就可以直接将该对象放到Amethod()所在的栈中
(2)、标量替换
对象中存储的类似于一个坐标{x,y} x=1,y=2,可以直接吧x,y存储到Amethod()中
这样A对象随着、Amethod()从栈中移除而回收,不需要GC的过程,大大提高了效率。
2.不适合栈且对象过大
直接进老年代,具体多大的对象直接静如老年代,可以指定参数的大小
3.小对象
小的对象会进入TLAB后获得JVM分配的在Eden中独立的存储区域
一个新的对象创建之后会被存储到eden中,再次使用会采用Copying回收方法复制到surivor中,根据不同的算法,默认15代之后,CMS为6次,直接静如老年代,老年代满了之后会采用Mark Compact、Mark Sweep方法进行回收。
1.Serial模型
原理:ABC分别是不同对象,当Serial进行回收的时候,所有创建的在eden上的线程会全部停到一个safe point上,在stop-the-world中停顿时间由Serial垃圾收集器进行Copying算法类型的**单线程的(Single)**垃圾收集处理后再继续进行
2.Serial Old
原理:ABC分别是不同对象,当Serial进行回收的时候,所有创建的在old上的线程会全部停到一个safe point上,在stop-the-world中停顿时间由Serial垃圾收集器进行Copying算法类型的**单线程的(Single)**垃圾收集处理后再继续进行
3.Parallel Scavenge
原理:ABC分别是不同对象,当Serial进行回收的时候,所有创建的在old上的线程会全部停到一个safe point上,在stop-the-world中停顿时间由Serial垃圾收集器进行Copying算法类型的**多线程的(Parallel)**垃圾收集处理后再继续进行
4.Parallel Old
原理:ABC分别是不同对象,当Serial进行回收的时候,所有创建的在old上的线程会全部停到一个safe point上,在stop-the-world中停顿时间由Serial垃圾收集器进行Copying算法类型的**多线程的(Parallel)**垃圾收集处理后再继续进行
5.CMS
产生的原因:随着内存JVM的存储空间逐增加,线程在逐渐增加,线程增加到一定的程度之后,会增加上下文的切换导致占用大量的内存,对外提供的新能就会下降不足,STW的时间间隔也会持续增加
原则:CMS,不再有STW,GC线程和业务线程并发执行,
产生的问题:
1.业务线程产生对象A,GC扫描到A的时候,A对象是有引用的线程,但是之后A编程了垃圾对象,GC线程是否能感应到A的变化。
2.更严重的是GC线程扫描的时候A是垃圾对象,但是之后又被业务线程引用了,GC线程能否知道
三色标记法
如何标记已经扫描过的对象采用的是三色标记法
出现的问题
1.无所谓:因为引用关系的变化导致部分节点扫描不到
情况描述:A已经扫描完成,且A引用的B也扫描完成,但是B引用的对象D没有扫描到,此时A被标记为黑色,B为灰色,D为白色,接着业务线程取消了D和B的引用关系,GC线程根据上下文的关系直接由B扫描被他引用的对象,但是扫描不到D,本次漏扫了叫做没关系,无所谓,下次扫就可以了,B也会标黑
环的情况:遇见黑色停止
2.错标:会导致引用的对象被清除
情况描述:A已经扫描完成,且A引用的B也扫描完成,但是B引用的对象D没有扫描到,此时A被标记为黑色,B为灰色,D为白色,接着业务线程取消了D和B的引用关系,新建了A和D的引用关系,GC线程根据上下文的关系直接由B扫描被他引用的对象,但是扫描不到D,也不会再次从A去扫描D,D会被当作垃圾直接标记后清除
解决方法:CMS采用的是Incremental Update方法,简单说就是黑色直接连白色的情况,将黑色标记为灰色,通过鞋屏障在A.x = D的时候,如果A是黑色,D是白色,标A为灰
问题:怎么看到A-D的情况
所以解决方案是现在CMS在最后remark阶段会重新扫描一遍,此时会STW重新扫描,会停掉业务线程,但是好在之前大部分都标记过,只需要针对可能漏标都点进行扫描
6.ParNew
原理:专门为CMS设计的,类似于Parallel Scavenge的收集器,ABC分别是不同对象,当Serial进行回收的时候,所有创建的在old上的线程会全部停到一个safe point上,在stop-the-world中停顿时间由Serial垃圾收集器进行Copying算法类型的**多线程的(Parallel)**垃圾收集处理后再继续进行
2.分区模型
G1、ZGC、Shenandoah
实战
每一个java命令是开启一个JVM虚拟机
查看使用的垃圾回收器
java -XX:+PrintCommandLineFlags -version
使用arthus工具进行监控
位置在 Users/zhaokaijie下
dashboard
需要调优的情况描述:
设定以一个内存空间之后,每次没有可以分配的空间时候进行GC,程序有问题导致内存不断增加,随着空余空间的不断缩小,会引起Full GC,此时回收的空间越来越小,频率越来也频繁,最后就会抛出OOM异常
OOM:内存溢出
ML:内存泄漏
JDK自带的调优参数jmap -histo
jmap -histo + pid 查看有哪些对象比较占用空间属于哪个类
jmap -histo + pid | head -10
如果有问题就会有某几个类持续的增加
jmap -dump:format=b,file=xxoo.prof port
下载下来使用jdk自带的jvvm分析,当然也可以用jvvm链接服务器动态的监控,但是不可以,1.多开端口会产生潜在的危险。2.摘掉一台在做监控,但是没有流量,也只能dump出来再看
jmap在生产环境是万万不可用的 是对堆的巨大冲击
好的回答:复制机,摘掉一台用流量复制重现
这就用到了arthus
Arthus 常用方法
1.thread
查看线程状况
2.thread pid
查看某一线程的具体情况
3.thread -b
查看是否有死锁线程
4.jad prog_name
1.可以看到是什么类加载器加载进来的。
2.可以查看引用的版本号
5.
6.trace 链路追踪
某一个服务发布前压力测试可以抗100w tps,上线后100就超时了 main->a()->b
分别追踪每一个方法
互联网三高
1.高性能
1.1吞吐 并发高
1.2低延时
2.高可用 断1、2台机器问题不大
3.高扩容 随时扩缩容
今天的文章jvm垃圾回收参数设置_java编写软件工具[通俗易懂]分享到此就结束了,感谢您的阅读。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/85181.html