JVM问答
为什么是jvm
首先需要搞明白顺序,是先后的java,才有的jvm,又因为java语言的特性,导致必须出现一个像jvm这样的平台,才能满足java的跨平台特性。所以必须是jvm
为什么是class字节码文件
字节码文件是一个二进制文件,其实无论是什么文件都是二进制文件,class文件是一种遵守了jvm规范的二进制文件,所以可以被jvm解释执行,而这个规范才是字节码文件的文本。出现字节码文件是为了让jvm可以使用,而不是单纯的为了创建而创建。
class文件里面有什么
cafe babe 0000 0034 0010 0a00 0300 0d07
000e 0700 0f01 0006 3c69 6e69 743e 0100
0328 2956 0100 0443 6f64 6501 000f 4c69
6e65 4e75 6d62 6572 5461 626c 6501 0012
4c6f 6361 6c56 6172 6961 626c 6554 6162
6c65 0100 0474 6869 7301 001c 4c63 6f6d
2f63 6f6d 7061 6e79 312f 436c 6173 7343
6f64 6544 656d 6f3b 0100 0a53 6f75 7263
6546 696c 6501 0012 436c 6173 7343 6f64
6544 656d 6f2e 6a61 7661 0c00 0400 0501
001a 636f 6d2f 636f 6d70 616e 7931 2f43
6c61 7373 436f 6465 4465 6d6f 0100 106a
6176 612f 6c61 6e67 2f4f 626a 6563 7400
2100 0200 0300 0000 0000 0100 0100 0400
0500 0100 0600 0000 2f00 0100 0100 0000
052a b700 01b1 0000 0002 0007 0000 0006
0001 0000 0003 0008 0000 000c 0001 0000
0005 0009 000a 0000 0001 000b 0000 0002
000c
字节码认知
字节码文件就是一个约定,class和JVM约定好在什么位置的什么数据是什么含义,这样JVM就可以解析了
按照class执行原理,字节码中应该有的内容
- 是一个class文件
- 记录版本信息
- 记录类信息
- 名称
- 位置
- 类型,是否抽象
- 记录类接口与继承信息
- 记录变量信息
- 名称
- 类型
- 属性,是否时静态变量
- 是否被finnal修饰
- 变量的值
- 记录方法信息
- 名称
- 类型
- 属性,是否是静态方法,是否抽象
- 是否可以继承
- 返回值类型
- 返回值内容
- 参数类型
- 参数值
- 记录
第一步,我们要明白字节码文件的用途,他是为了可以让jvm认识从而编译出来的一种文件,既然需要认识,我们就要标识出来这是一个字节码文件,所以当你用二进制查看class文件的时候,很容易就能看到每个字节码文件的构成
1.开头部分都是一样,是cafe babe,这是16进制表示,这就是magic Number
2.然后是Minor version 小的版本号 占两个字节
3.0034占两个字节是Major Version java8==52
4.0010是constant_pool_count 常量池计数 16
**********************
常量池开始
**********************
#1 1.0a是CONSTANT_Methodref_info,该常量存在两个指向,都是两个字节,一个指向0003,指明声明方法的类或者接口(CONSTANT_Class_info),也可以理解为方法的拥有者,另一个指向字段描述符,指向CONSTANT_NameAndType的索引项,这里执行000a,也就是第十个常量。
#2 2.07是指向类的全限定类名,此处是000e索引位置,占两个字节
#3 3.07是指向父类的全限定类名,这里是000f索引的位置,占两个字节
#4 1.01是CONSTANT_Utf8_info,这里占用一个字节,指向00,内容是<init>
2.0006是长度,表示字符串长度是6,长度占用两个字节
3.3c69 6e69 743e是CONSTANT_Utf8_info的内容,这里占用6个字节
#5 1.01是CONSTANT_Utf8_info占一个字节,内容是,指向的内容是()V
2.0003是长度
3.28 2956是内容,占3个字节
#6 1.01是CONSTANT_Utf8_info占一个字节,内容是,指向的内容是Code
2.0004是长度
3.43 6f64 65是内容,占用4个字节
#7 1.01 内容 LineNumberTable
2.000f 长度
3.4c69 6e65 4e75 6d62 6572 5461 626c 65 15个字节
#8 1.01 内容 LocalVariableTable
2.0012 长度
3.4c6f 6361 6c56 6172 6961 626c 6554 6162 6c65 18个字节
#9 1.01 内容 this
2.0004 长度
3.74 6869 73 4字节
#10 1.01 内容 Lcom/company1/ClassCodeDemo;
2.001c 长度
3.4c63 6f6d 2f63 6f6d 7061 6e79 312f 436c 6173 7343 6f64 6544 656d 6f3b 28字节
#11 1.01 内容 SourceFile
2.00 0a 长度
3.53 6f75 7263 6546 696c 65 10字节
#12 1.01 内容 ClassCodeDemo.java
2.0012 长度
3.436c 6173 7343 6f64 6544 656d 6f2e 6a61 7661
#13 1. 0C CONSTANT_NameAndType 指向类的名字和描述符
2.0004 类的名字
3.0005 类的描述符
#14 1.01 内容 com/company1/ClassCodeDemo
2.001a 长度
3.636f 6d2f 636f 6d70 616e 7931 2f43 6c61 7373 436f 6465 4465 6d6f 26字节
#15 1.01 内容 java/lang/Object
2.0010 长度
3.6a 6176 612f 6c61 6e67 2f4f 626a 6563 74 16字节
*************************
常量池结束
*************************
1.0021 —— acess_flags 表示访问标识,
这里的值是许多属性相加的结果
ACC_PUBLIC 0X0001 public 属性
ACC_FINAL 0X0010 是否声明final属性,仅可以作用在类上
ACC_SUPER 0X0020 在jdk1.2之后都默认为真
ACC_INTERFACE 0X0200 这是一个接口
ACC_ABSTRACT 0X0400 这是一个抽象类
ACC_SYNTHETIC 0X1000 这个类不是用户产生的
ACC_ANNOTATION 0X2000 这是一个注解
ACC_ENUM 0X4000这是一个枚举类
我们这里是0021说明是0020+0001 即说明使用的类是public属性的
2.0002 —— this_class 指向常量池中的 #2 占2字节
3.0003 —— super_class 执行常量池中的 #3 占2字节
4.00 00 —— interfaces_count 接口数,这里是0 --如果有的话,会指向常量池的某一个值
5.00 00 —— 变量信息 --如果有的话,会指向常量池的某一个值
这里的值是许多属性相加的结果
ACC_PUBLIC 0X0001 public 属性
ACC_PRIVATE 0X0002 private 属性
ACC_PRITECTED 0X0004 protected 属性
ACC_STATIC 0X0008 static 属性
ACC_FINAL 0X0010 是否声明final属性,
ACC_VOLATILE 0X0040 volatile属性
ACC_TRANSIENT 0X0080 这是一个不需要被序列化的
ACC_SYSTHETIC 0X1000 这是一个加锁的
ACC_ENUM 0X4000这是一个枚举类
我们这里是0001说明是0001 即说明使用的变量是public属性的
6.0001 —— 方法数 这里是1
7.0001 —— 方法属性,access_flags 2字节
这里的值是许多属性相加的结果
ACC_PUBLIC 0X0001 public 属性
ACC_PRIVATE 0X0002 private 属性
ACC_PRITECTED 0X0004 protected 属性
ACC_STATIC 0X0008 static 属性
ACC_FINAL 0X0010 是否声明final属性,
ACC_SYNCHRONIZED 0X0020 加锁的方法
ACC_BRIDGE 0X0040 编译器产生的桥接方法
ACC_VARARGS 0X0080 是否存在可变参数
ACC_NATIVE 0X0100 这是本地方法
ACC_ABSTRACT 0X0400 静态方法
ACC_STRCTFP 0X0800 运算结果精度要求
ACC_SYNTHETIC 0X1000 编译器生成的方法
我们这里是0001说明是0001 即说明使用的方法是public属性的
8.0004 —— 方法名字,指向常量池中的 #4 2字节
9.0005 —— 方法描述符,参数、返回值信息,指向常量池中 #5 2字节
10.00 01 —— attibutes_count 2字节
11 00 06 —— attibutes 指向方法表
后面的需要我在捋捋才能明白
参考文档:
https://www.jianshu.com/p/8d5cc8ba5e9a
类加载过程
问题导入:我有一个class文件,我也明白这个文件内数据含义,现在怎么执行呢?第一步是什么,第二部是什么。。。。
类加载步骤简单描述
- 加载
- 连接
- 验证
- 准备
- 解析
- 初始化
- 使用
- 卸载
加载(类加载器)
所有的类都是被类加载器加载到内存里面,这点很重要!!!
类加载器到底是做什么的?
我们上面说到了类的加载步骤,第一步就是将class文件加载到内存,然后通过一个对象去操作这个class文件。从这里我们可以明白,类加载器作业的内容是加载class文件到内存中 ,在内存中指定一个区域产生该class加载区域的对象,这个对象就是这个class内存的入口
类加载器类型
- bootstrap 加载java核心类,基本都是 lib下面的类
- extension 加载额外的类,基本都在 jrelibext 文件下的类
- app 加载classpath中指定的jar包,这就是我们只能加载当前目录和lib目录下jar包的原因。因为其他jar包不存在这些目录里面
- 自定义类加载器,定义类加载位置,继承classload类
类加载器也是一个类,他们的加载器是bootstrapClassLoad
啥是双亲委派?为什么要用双亲委派?
双亲委派:当一个class文件需要加载到内存中,从上面我们了解到,系统中存在四种类加载器,所以我们针对需要加载的class文件,我们需要使用哪个类加载器呢?上面的类加载器是存在顺序的,当一个class文件被加载到内存,首先是按照按照以下顺序进行交付
自定义类加载器 -> appClassload -> extensionClassLoad -> bootstrapClassLoad
如果在上一级加载器可以加载,则直接返回结果,举个例子:
如果一个class被加载,首先会交给自定义类加载器,然后上一级,上一级、、、假设这个class文件可以在bootstrapClassLoad加载器中加载,此时就会直接返回结果,而不会再返回到 extensionClassLoad 进行加载。
这个过程实现的原理是每个加载内部有一个ClassLoad 属性的 parent 加载器,当需要加载类时,首先用 parent加载器进行加载
这样做的好处是: 安全
我们考虑没有这个机制的情况,如果没有这个机制,那么我们拿到的类就应该被第一个可以加载的类进行加载,如果有恶意开发者重写了java.lang.String的加载器,那么用户在使用字符串的时候都会使用重写的加载器,系统自带的会被覆盖,开发者通过类加载器拿到用户输入数据是很可怕的一件事。
同时这个方式还避免了重复加载的问题,因为只要有一个加载器加载了文件,其他的都不会在执行。
自定义类加载器
1.首先我们新建一个java类
package com.company1;
public class ClassDemo {
public void disPlay(){
System.out.println("Hi,definedLoader");
}
}
2.编译生成class文件
javac ClassDemo.java
3.编写自定义类加载器
package com.company;
import java.io.*;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class ClassLoaderTest extends ClassLoader {
@Override
public Class<?> findClass(String name){
//通过包名取得class文件
String path = name.replace(".","/");
//加载文件
File file = new File("D:/Load/"+path+".class");
try {
//加载文件流
FileInputStream fis = new FileInputStream(file);
//byte数组输出流
ByteArrayOutputStream bao = new ByteArrayOutputStream();
int len = -1;
while((len=fis.read())!=-1)
{
bao.write(len);
}
byte[] bytes = bao.toByteArray();
bao.close();
fis.close();
return defineClass(name,bytes,0,bytes.length);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
//初始化自定义类加载器
ClassLoader cl = new ClassLoaderTest();
//加载已经被转移到D盘的class文件
Class clazz = cl.loadClass("com.company1.ClassDemo");
//获取加载的类对象
Object classDemo = clazz.newInstance();
//通过反射获取到对象的方法
Method method = classDemo.getClass().getMethod("disPlay");
//执行方法
method.invoke(classDemo);
//输出此时的类加载器
System.out.println(clazz.getClassLoader());
//输出测试工具类的类加载器
System.out.println(ClassLoaderTest.class.getClassLoader());
}
}
//输出结果
Hi,definedLoader
com.company.ClassLoaderTest@4554617c
sun.misc.Launcher$AppClassLoader@18b4aac2
问:使用自定义类加载器加载的类对象不能直接使用,必须使用Object接收,那怎么调用对象的方法呢?
答:通过反射的方式获取对象的方法,然后再执行,以此来避开AppClassLoad加载器
双亲委派怎么实现的?打破双亲委派?
首先我们要知道双亲委派机制是怎么实现的?分析源码我们可以看到
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 {
//因为BootstrapClassLoadder不存在上层加载器,所以单独考虑
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();
//如果上面没能成功加载,调用自己的findClass尝试加载
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;
}
}
上面就是双亲委派的实现原理,下面我们尝试打破双亲委派,从上面的源码可以很清楚的看到双亲委派主要是 loadClass 方法再起作用,所以如果我们想要打破这个机制,需要重写 loadClass 方法,代码如下
package com.company;
import java.io.*;
public class CLassLoadDemo2 extends ClassLoader {
@Override
public Class<?> loadClass(String name)
throws ClassNotFoundException{
//通过重写load方法,加载自定义类加载里的findClass打破了系统原有的双亲委派机制
Class<?> c = new ClassLoaderTest().findClass(name);
return c;
}
public static void main(String[] args) throws ClassNotFoundException {
CLassLoadDemo2 classLoader = new CLassLoadDemo2();
Class clazz = classLoader.loadClass("com.company1.ClassDemo");
Class clazz2 = classLoader.loadClass("com.company1.ClassDemo");
System.out.println(clazz==clazz2);
}
}
//输出
false
连接
验证
验证阶段也是检查阶段,检查字节码文件是否存在异常,文件格式是否正确等
准备
给静态变量赋默认值,例如 int 的默认值 0 等等,
解析
将类的符号引用(JVM问答中 #14 1.01 内容 com/company1/ClassCodeDemo )变成直接引用(内存地址)
初始化
将静态变量默认值赋初始值
.java -> .class
jvm一般使用的是混合模式编译java文件
在这种模式下,一般使用解释器执行Java文件,当需要某一部分代码重复执行次数过高后(热点代码:设置属性 -XX: CompileThreshold 默认10000),会将这部分代码编译,提高执行效率,可以通过jvm参数指定mode
-Xint 解释型
-Xcomp 编译型
什么时候初始化呢?—— lazing init
在类加载过程我们知道了,一个类被加载到内存是有一系列的过程,那么这一系列的过程是以此完成的吗?答案很显然是否定的,因为在某些情况下,我们不需要初始化就可以使用类内对象了,例如,被 final 和 static 修饰的变量,在连接阶段中的准备阶段就完成赋值,因为这个值是确定的、不可变的。所以不需要初始化就可以赋值。可以得出类是有初始化时机的,总结如下
主动引用(发生初始化)
-
使用new关键字创建新对象
public class InitiaRef{ public static void main(String[] args){ A a = new A(); } } class A{ static { System.out.println("static"); } } //结果 D:msbLearn ote ote-master新建文件夹viewjava>java InitiaRef static
-
读取类的静态成员变量
public class InitiaRef{ public static void main(String[] args){ int m = A.b; } } class A{ static int b = 3; static { System.out.println("static"); } } //结果 D:msbLearn ote ote-master新建文件夹viewjava>java InitiaRef static
-
设置类的静态成员变量
public class InitiaRef{ public static void main(String[] args){ A.b = 10; } } class A{ static int b = 3; static { System.out.println("static"); } } //结果 D:msbLearn ote ote-master新建文件夹viewjava>java InitiaRef static
-
调用静态方法
public class InitiaRef{ static { System.out.println("static"); } public static void main(String[] args){ } } //结果 D:msbLearn ote ote-master新建文件夹viewjava>java InitiaRef static
-
子类初始化会初始化父类
public class InitiaRef extends A{ public static void main(String[] args){ } } class A{ static { System.out.println("static"); } } //结果 D:msbLearn ote ote-master新建文件夹viewjava>java InitiaRef static
-
使用反射初始化
public class InitiaRef{ public static void main(String[] args)throws Exception{ Class aa = Class.forName("A"); } } class A{ static { System.out.println("static"); } } //结果 D:msbLearn ote ote-master新建文件夹viewjava>java InitiaRef static
被动引用
- 通过子类使用父类静态属性(方法和变量),子类不会初始化
//静态变量
public class InitiaRef {
public static void main(String[] args)throws Exception{
int m = B.aa;
}
}
class A{
static int aa = 10;
static {
System.out.println("static A");
}
}
class B extends A{
static {
System.out.println("static B");
}
}
//静态方法
public class InitiaRef {
public static void main(String[] args)throws Exception{
B.display();
}
}
class A{
static int aa = 10;
static void display() {
System.out.println("static A");
}
}
class B extends A{
static {
System.out.println("static B");
}
}
//结果
D:msbLearn
ote
ote-master新建文件夹viewjava>java InitiaRef
static A
-
使用数组
public class InitiaRef { public static void main(String[] args)throws Exception{ A[] aa = new A[10]; } } class A{ static int aa = 10; static { System.out.println("static A"); } } //结果 无输出
-
最开始提到的无需初始化
public class InitiaRef { public static void main(String[] args)throws Exception{ System.out.println(A.aa); } } class A{ final static int aa = 10; static { System.out.println("static A"); } } //结果 D:msbLearn ote ote-master新建文件夹viewjava>java InitiaRef 10
JMM问答
DCL为什么要用volatile?什么是伪共享?缓存模型是什么样子的?缓存一致性怎么实现的(MESI)?。什么是合并写?
DCL 是什么
DCL意思是 Double Check Lock ,顾名思义,使用这种方式创建的单例模式需要两次检查,并且需要使用volatile关键字修饰类内对象。两次检查的问题我们放到 JUC 在讲,这里重点说一下为什么使用 volatile 关键字,关于这个问题,首先要知道的类初始化时的具体动作:
(1)给新建对象的实例分配内存
(2)调用对象的构造函数,初始化成员字段
(3)将新建对象指向分配的内存空间
0 new #2 <com/company/NewClass> (1)
3 dup
4 invokespecial #3 <com/company/NewClass.<init>> (2)
7 astore_1 (3)
8 return
// 代码
public class DCLDemo {
private volatile static DCLDemo decl = null;
private DCLDemo(){
}
private DCLDemo getDcl(){
//鉴定是否为空
if(decl!=null){
synchronized(DCLDemo.class){
//鉴定是否为空
if(decl!=null){
decl = new DCLDemo();
return decl;
}else{
return decl;
}
}
}else{
return decl;
}
}
}
背景
在计算机内部,各种元器件的速度差异巨大,例如CPU与加载内存数据等,由于该问题的存在,所以在CPU执行我们的程序的时候会发生指令重拍,简单理解就是CPU 会判定没有关系的两条语句进行优化,假设此时有两条指令,彼此没有引用关系,那么当第一条指令执行期间如果需要等待,例如加载数据之类,CPU会让第二条指令优先执行,用以提升计算机的运行效率
如此,便可以发现,如果上面 (2) ,(3) 两部发生了指令重拍,那么会导致新产生的对象没能成功引用到堆内对象,但是此时的引用已经不再为空,存在安全隐患。
内存模型
计算机内存模型:每个CPU有自己的 L1、L2 Cache 和共有的 L3 Cache ,三级缓存之外连接了内存
参考资料:https://blog.csdn.net/weixin_43767015/article/details/104856899
伪共享是什么
了解伪共享首先要知道内存是怎么加载数据到缓存中的。假设我们需要一个元素A,但是CPU并不会只加载A到缓存,这个时候一般都是加载A以及A在内存相邻地址的数据进入缓存,如果存在其他CPU将同一块内存在数据读走,这是两个CPU修改自己的目的数据,但是都会导致对方的一部分缓存失效,最终导致两个CPU都需要频繁的去主存中刷新最新的数据,降低CPU执行效率。
可再关键元素之前添加空白数据,使关键元素独占一个数据块,代码实现如下
package com.company;
public class CacheBlock {
private static class T {
//private volatile long l21,l22,l23,l24,l25,l26,l27; 没有这行,结果是123944200 加上后结果是 67458500 相差一个数量级
public volatile long x = 0L;
}
//
static T[] cb = new T[2];
static{
cb[0] = new T();
cb[1] = new T();
}
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(()->{
for(long i=0;i<10000000L;i++){
cb[0].x=i;
}
});
Thread t2 = new Thread(()->{
for(long i=0;i<10000000L;i++){
cb[1].x=i;
}
});
long ll = System.nanoTime();
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println((System.nanoTime()-ll));
}
}
缓存一致性
MESI 协议实现了各个高速Cache之间的一致性约束(缓存锁),总线锁(当缓存放不下的对象需要锁时,依旧需要总线锁),协议内容如下:
首字母缩略词MESI中的字母表示可以标记高速缓存行的四种独占状态(使用两个附加位编码):
1、修改(M)
高速缓存行仅存在于当前高速缓存中,并且是脏的 - 它已从主存储器中的值修改(M状态)。在允许对(不再有效)主存储器状态的任何其他读取之前,需要高速缓存在将来的某个时间将数据写回主存储器。回写将该行更改为共享状态(S)。
2、独家(E)
缓存行仅存在于当前缓存中,但是干净 - 它与主内存匹配。它可以随时更改为共享状态,以响应读取请求。或者,可以在写入时将其改变为修改状态。
3、共享(S)
表示此高速缓存行可能存储在计算机的其他高速缓存中并且是干净的 - 它与主存储器匹配。可以随时丢弃该行(更改为无效状态)。
4、无效(I)
表示此缓存行无效(未使用)。
对于任何给定的高速缓存对,给定高速缓存行的允许状态如图1所示。
当块标记为M(已修改)时,其他高速缓存中块的副本将标记为I(无效)。
怎么保证指令有序
硬件级别内存屏障 除了下面的内存屏障(X86),在硬件指令是还有lock锁实现屏障
- sfence
- lfence
- mfence
JVM 级别的内存屏障,底层时上面的内存屏障和lock共同实现
- LoadLoad
- LoadStore
- StoreLoad
- StoreStore
volatile是怎么实现指令有序的
分三个层面 1.在字节码层面添加volatile关键字 2.在jvm添加内存屏障 4.在计算机操作系统中使用sfence或lock指令
synchronized是怎么实现线程安全的
1.在字节码层面添加synchronized关键字 2.jvm使用C++调用内核lock函数 3.计算机操作系统使用总线锁或缓存锁
happens-before , as-if-seri 原则
对象
对象的创建
- load
- link
- 校验
- 准备
- 解析
- init
- 申请对象内存
- 成员变量赋默认值
- 构造方法
- 成员变量赋初始值
- 执行构造方法体
对象在内存
查看虚拟机配置
C:UsersXX >java -XX:+PrintCommandLineFlags -version -XX:InitialHeapSize=131932160 -XX:MaxHeapSize=2110914560 -XX:+PrintCommandLineFlags -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:-UseLargePagesIndividualAllocation -XX:+UseParallelGC java version "1.8.0_144" Java(TM) SE Runtime Environment (build 1.8.0_144-b01) Java HotSpot(TM) 64-Bit Server VM (build 25.144-b01, mixed mode) //参数详解 InitialHeapSize 初始堆大小 MaxHeapSize 最大堆大小 +UseCompressedClassPointers +使用 -不使用 此处是使用class引用压缩 +UseCompressedOops +使用 -不使用 对象头压缩 +UseParallelGC +使用Parallel Scavenge+Parallel Old (PS+PO)垃圾回收
对象在内存中的布局
1.普通对象
- 对象头
- 对象头信息
- 对象类型引用
- 对象到对象实例的指向
- 成员变量
- 对齐(是8的倍数)
- 对象头信息
2.数组对象
* 对象头
* 对象引用
* 数组长度
* 数组元素
* 对齐(是8的倍数)
对象头信息里面都包含什么
- hashCode (没有锁的情况下),对象年龄,是否是偏向锁,锁类型,GC标记
- 偏向锁情况下
- 线程id epoch重入次数,对象年龄
锁升级的原因
不升级没地方存放hashCode ,升级了可以放到monitor里面
对象定位
- 句柄池
- 句柄池包含对象实例和对象类型两种信息
- 直接引用
- 直接引用指向对象实例,对象实例指向对象类型
RunTime Data Area
每个线程有自己的栈,每个方法有自己的栈帧
本地方法有自己的栈
这个区域都有什么?
-
JVM Stack 栈空间 保存线程信息 线程拥有
-
frames 栈帧 栈帧的信息在method area中 每个方法
- local variable 局部变量
- operand Stacks 操作数栈
- dynamic linking 动态连接 方法内部使用变量的引用,例如method A中使用了methodB 此时methodB的引用在method区域,需要拿到这个引用
- return address 返回值 动态连接中methodB返回值放到 address中
i=i++ //解析** 考虑三个问题 1.++怎么实现的 2.数字相加是什么时候变成int类型的 3.++i和i++的区别是怎么实现的 ************************************************* //代码 public class AddAdd { public static void main(String[] args) { int i = 8; i=i++; System.out.println(i); } } ********************* // 汇编 0 bipush 8 //bipush 指令描述 The immediate byte is sign-extended to an int value. That value is pushed onto the operandstack. 作为一个int 类型数 2 istore_1 3 iload_1 4 iinc 1 by 1 7 istore_1 8 getstatic #2 <java/lang/System.out> 11 iload_1 12 invokevirtual #3 <java/io/PrintStream.println> 15 return **************************************************** 解析: 0 将8压入栈 2 弹出赋值到i 3 i压入栈 此时i=8 4 i执行++ 7 栈内元素弹出赋值到 i(此时栈内元素为8) ****************************************************
-
-
Program Counter 程序计数器,这个是JVM本身的程序计数器,不要和操作系统的程序计数器搞混,当调用native方法时,这里面的值会变成undefined 未定义,因为pc不知道程序执行到哪里了 线程拥有
-
native method statck 本地方法区,native方法用的 线程拥有
-
Direct Memory JVM直接访问的内核内存,在网络编程时的零拷贝就是利用这里区域内存来实现的,
-
method area 方法空间 各种类信息 1.8之前叫永久区/1.8之后叫元数据区 JVM拥有
- run_time constant pool 常量池
- static variable 静态变量
- class message 类信息
- method 方法信息
-
Heap 堆 JVM拥有
栈帧的执行顺序
//代码
public static void main(String[] args) {
int i = 8;
i=i++;
System.out.println(i);
}
//汇编
0 bipush 8 将8压入栈
2 istore_1 弹出赋值给i
3 iload_1 将i压入栈
4 iinc 1 by 1 i++
7 istore_1 栈内元素给i
8 getstatic #2 <java/lang/System.out> 常量池中2号位置的字段值是符号引用,该引用指向的是常量池中22号和23号位置,即java/lang/System类和java/io/PrintStream类中println的方法描述。如果该静态字段(#2号)所指向的类或接口没有被初始化,则指令执行过程将触发其初始化过程。
11 iload_1 将i压入栈
12 invokevirtual #3 <java/io/PrintStream.println> 此处调用java.io.PrintStream类中的println方法后,会自动从main的操作数栈中出栈相应的参数(即8)。然后方法执行来到了println方法中,于是新建此方法的栈帧,将当前栈帧切换到println栈帧上,将参数2入栈,在完成println方法后(输出2)再切换回到main的栈帧中。
15 return
//解析
bipush -- byte -> int push
istore_1 -- int store 局部变量1
iload_1 -- int load 局部变量1
iinc 1 by 1 int increament 局部变量1 add 1
getstatic #2 get #2常量池的 static 变量,没有则进行初始化
invokevirtual #3 执行常量池中#3的方法,调用实例方法,基于类进行分派,在操作数栈取参数,此处需要创建新栈帧。多态也是在这里实现的。
invoke 的几种方法
1.invokestatic
public class Invoke {
public static void main(String[] args) {
m();
}
public static void m(){
}
}
//
0 invokestatic #2 <com/company/Invoke.m>
3 return
2.invokevirtual
public class Invoke {
public static void main(String[] args) {
new Invoke().m();
}
public void m(){
}
}
//
0 new #2 <com/company/Invoke>
3 dup
4 invokespecial #3 <com/company/Invoke.<init>>
7 invokevirtual #4 <com/company/Invoke.m>
10 return
3.invokespecial
public class Invoke {
public static void main(String[] args) {
new Invoke().m();
}
private void m(){
}
}
//
0 new #2 <com/company/Invoke>
3 dup
4 invokespecial #3 <com/company/Invoke.<init>>
7 invokespecial #4 <com/company/Invoke.m>
10 return
4.invokeinteface
import java.util.ArrayList;
import java.util.List;
public class Invoke {
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
list.add("123");
}
}
//
0 new #2 <java/util/ArrayList>
3 dup
4 invokespecial #3 <java/util/ArrayList.<init>>
7 astore_1
8 aload_1
9 ldc #4 <123>
11 invokeinterface #5 <java/util/List.add> count 2
16 pop
17 return
5.invokedynamic
public class Invoke {
public static void main(String[] args) {
new Thread(()->{
});
}
}
//
0 new #2 <java/lang/Thread>
3 dup
4 invokedynamic #3 <run, BootstrapMethods #0>
9 invokespecial #4 <java/lang/Thread.<init>>
12 pop
13 return
GC Garbage Collection
什么是垃圾
没有引用就是垃圾,
怎么找到垃圾
引用计数器,但是引用计数器会出现循环引用,导致内存泄漏(有一块内存访问不到)
根可达算法,
- 根的概念
- JVM Stack 、native method stack、run-time constrant pool、static references in method area
当需要进行垃圾回收时,不是立刻进入STW状态,需要所有的线程需要进入safepoint(线程安全停止),保证所有的线程都安全了,然后进入STW进行垃圾回收。
垃圾回收算法
- 标记清理 回收之后空间不连续,产生碎片,需要两次标记,适合垃圾少的情况
- 第一遍标记需要回收的内存
- 第二遍清理
- 拷贝 需要额外空间存放拷贝内容,引用指针需要修改,适合存活对象少的情况
- 存活对象需要拷贝到额外空间
- 标记压缩 需要移动指针,需要两次处理
- 标记可用空间
- 将存活对象移动到可用空间
heap 内存分布
看下面这个图
对象从出生到消亡
- 首先尝试分配对象到栈上
- 分配到eden区域
- 经过垃圾回收在两个survivor区域来回跳转
- 经过一定次数的垃圾回收,进入old区域
- 被垃圾回收
对象分配
什么样的对象会分配到栈上? 调优基本涉及不到 好处是因为是在栈上,弹出就消失,不需要垃圾回收的参与,因此会提高效率
- 小对象,线程私有
- 不会逃逸 -XX:-DoEscapeAnalysis //关闭逃逸分析,栈上分配必须用
- 支持标量替换 -XX:-EliminateAllocations //关闭标量替换
什么样的对象会分配到TLAB (Thread local Allocation Buffer),好处这是每个线程在eden区域私有的一块内存,分配到这里不需要在Heap空间发生争用,效率较高
TLAB区域特点: -XX:-UseTLAB //关闭TLAB区域
1. 占用eden0.01区域
调优参数:默认这两部分都是打开的,可以手动关闭
-XX:-DoEscapeAnalysis //关闭逃逸分析,栈上分配必须用
-XX:-EliminateAllocations //关闭标量替换
-XX:-UseTLAB //关闭TLAB区域
对象什么时候进入老年代,参数怎么调整
在新生代的对象经过一定次数的垃圾回收后,一直没有被回收就会进入老年代,这个次数最大时15,因为每个对象的分代年龄只有4位表示,可以通过调整 下面参数进行调整
-XX:+MaxTenuringThreshold=num(设定的次数)
常见垃圾回收器及常用搭配
物理、逻辑都区分新生代和老年代的
serial、ParNew、Parallel Scavenge、CMS、Serial Old 、Parallel Old
只区分逻辑层的
G1
不分代的
ZGC、Shenandoah
调试专用
Epsilon
常用搭配 (新生代+老年代) 因为新生代存活对象少,通常用拷贝算法,而老年代通常对象存活时间长,所以使用标记整理或者标记清理算法
serial+Serial Old 单线程+单线程 Copy+Mark-compact
Parallel Scavenge+Parallel Old 多线程+多线程 Copy+Mark-compact
ParNew+CMS 多线程+多线程 Copy+Mark-sweep
CMS实现了什么,为什么这么重要
CMS算法全称是concurrent mark sweep(并发标记算法)
这里并发的意思并不是说没有了 STW 而是这是时间被大大缩减,我们首先看一下 CMS 的实现方式,共有4个步骤
-
inital mark(初始标记阶段)
这个阶段只是把根上的对象标记,不会继续向下标记
-
concurrent mark (并发标记)
这个阶段是找到所有的垃圾进行标记
-
remark(重新标记)
这个阶段是把2阶段执行中新产生的垃圾标记一下,这里有STW
-
concurrent sweep(并发清理)
这个阶段是清理标记好的垃圾,这个过程产生的新的垃圾叫浮动垃圾
CMD的缺点
-
产生大量的碎片,这是标记清理算法自身的限制
如果碎片过多,会启动FGC,但是在这之前会调用Seroal-OLd算法执行标记压缩,可以通过
-XX:+UseCMS-CompactAtFullCollection
参数设置,默认是开启的,但是这个标记压缩不是每次启动FGC就会执行的,可以通过设置
-XX:CMSFullGCsBefore-Compaction=1 参数是数量
设置,上面设置的结果是,每两次FGC就进行以此标记压缩
-
没有空间存放浮动垃圾
对于这个问题,可以使用
-XX:CMSInitiatingOccupancyFraction=70
设置在Heap内存使用超过70%时启动CMS,这样就有足够的空间来存放浮动垃圾了
remark阶段的三色标记算法
JVM调优
查找所有参数
java -XX:+PrintFlagsFinal -version 所有的非标准参数
JVM常见命令行参数
//设置初始堆大小
-Xms=40M
//查看初始堆大小
-XX: InitalHeapSize
//设置最大堆大小
-Xmx=40M
//查看最大堆大小
-XX: MaxHeapSize=40M
//设置新生代初始大小
-Xmn=10M //相当与同时设置-XX:NewSize(新生代初始大小) 和 -XX:MaxNewSize(新生代最大大小)
//打印GC日志信息
-XX:+PrintGC
//打印日志具体信息
-XX:+PrintGCDetails
//打印GC产生原因
-XX:+PrintGCCauses
GC日志分析
首先新建一个类,代码如下:(内存溢出)
package com.GC;
import java.util.LinkedList;
import java.util.List;
public class GCDemo1 {
public static void main(String[] args) {
List<byte[]> list = new LinkedList<byte[]>();
for(;;){
byte[] bytes = new byte[1024*1024];
list.add(bytes);
}
}
}
//使用下列参数执行
java -Xmn10M -Xms30M -Xmx40M -XX:+PrintCommandLineFlags -XX:+PrintGC GCDemo1
//结果如下
初始堆大小 最大堆大小 最大新生代大小
-XX:InitialHeapSize=31457280 -XX:MaxHeapSize=41943040 -XX:MaxNewSize=10485760
新生代大小 用户或者JVM设置过的详细的XX参数的名称和值
-XX:NewSize=10485760 -XX:+PrintCommandLineFlags
GC信息
-XX:+PrintGC
使用class引用压缩 对象引用压缩
-XX:+UseCompressedClassPointers -XX:+UseCompressedOops
给新生代更大的页 使用了什么垃圾回收器
-XX:-UseLargePagesIndividualAllocation -XX:+UseParallelGC
GC类型 GC原因(此处是新生代内存不足) GC前大小->GC后大小 用时
[GC (Allocation Failure) 7314K->5968K(29696K), 0.0018025 secs]
[GC (Allocation Failure) 13376K->13104K(29696K), 0.0019155 secs]
[GC (Allocation Failure) 20641K->20192K(29696K), 0.0026764 secs]
[Full GC (Ergonomics) 20192K->20092K(39936K), 0.0078830 secs]
[GC (Allocation Failure) 27448K->27484K(39936K), 0.0019121 secs]
[Full GC (Ergonomics) 27484K->27261K(39936K), 0.0055299 secs]
[Full GC (Ergonomics) 34600K->34433K(39936K), 0.0066184 secs]
[Full GC (Ergonomics) 37664K->37502K(39936K), 0.0019412 secs]
[Full GC (Allocation Failure) 37502K->37484K(39936K), 0.0068853 secs]
知道了上面的参数,我们需要明确调优目的
吞吐量:用户执行时间/用户执行时间+GC时间
响应时间:及时反馈
常用术语:
QPS(Query Per Second,即每秒请求、查询次数)
TPS(Transcantion Per Second,即每秒事务数)
调优实战
OOM 全程是 out of memory
首先我们需要使用下面的程序
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class FullGCPlayer {
private static class CardInfo {
BigDecimal price = new BigDecimal(0.0);
String name = "张三";
int age = 5;
Date birthdate = new Date();
public void m() {
}
}
private static ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(50,
new ThreadPoolExecutor.DiscardOldestPolicy());
public static void main(String[] args) throws Exception {
executor.setMaximumPoolSize(50);
for (;;){
modelFit();
Thread.sleep(100);
}
}
private static void modelFit(){
List<CardInfo> taskList = getAllCardInfo();
taskList.forEach(info -> {
// do something
executor.scheduleWithFixedDelay(() -> {
//do sth with info
info.m();
}, 2, 3, TimeUnit.SECONDS);
});
}
private static List<CardInfo> getAllCardInfo(){
List<CardInfo> taskList = new ArrayList<>();
for (int i = 0; i < 100; i++) {
CardInfo ci = new CardInfo();
taskList.add(ci);
}
return taskList;
}
}
执行
java -Xms200m -Xmx200m -XX:+PrintGC FullGCPlayer
top 命令查看当前进程
top - 19:10:05 up 58 min, 4 users, load average: 0.30, 0.13, 0.08
Tasks: 279 total, 1 running, 278 sleeping, 0 stopped, 0 zombie
%Cpu(s): 6.4 us, 2.5 sy, 0.0 ni, 91.1 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
KiB Mem : 1797060 total, 85136 free, 1028944 used, 682980 buff/cache
KiB Swap: 0 total, 0 free, 0 used. 591740 avail Mem
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
4179 root 20 0 2257256 146552 12604 S 13.9 8.2 0:46.74 java
4287 root 20 0 162400 2460 1596 R 0.7 0.1 0:00.46 top
773 root 20 0 324784 5724 4204 S 0.3 0.3 0:05.54 vmtoolsd
854 root 20 0 225932 4068 2588 S 0.3 0.2 0:00.06 abrt-watch-log
4288 root 20 0 161012 5660 4300 S 0.3 0.3 0:00.26 sshd
1 root 20 0 128376 6292 3544 S 0.0 0.4 0:02.14 systemd
2 root 20 0 0 0 0 S 0.0 0.0 0:00.00 kthreadd
4 root 0 -20 0 0 0 S 0.0 0.0 0:00.00 kworker/0:0H
6 root 20 0 0 0 0 S 0.0 0.0 0:02.16 ksoftirqd/0
7 root rt 0 0 0 0 S 0.0 0.0 0:00.00 migration/0
8 root 20 0 0 0 0 S 0.0 0.0 0:00.00 rcu_bh
top -Hp 4179 查看4179进程的具体线程信息
[root@chouchou ~]# top -Hp 5320
top - 19:28:07 up 1:16, 4 users, load average: 0.02, 0.23, 0.25
Threads: 60 total, 0 running, 60 sleeping, 0 stopped, 0 zombie
%Cpu(s): 1.0 us, 1.7 sy, 0.0 ni, 97.2 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
KiB Mem : 1797060 total, 225356 free, 965356 used, 606348 buff/cache
KiB Swap: 0 total, 0 free, 0 used. 647484 avail Mem
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
5330 root 20 0 2257256 42924 12480 S 1.3 2.4 0:00.32 pool-1-thread-1
5332 root 20 0 2257256 42924 12480 S 0.3 2.4 0:00.01 pool-1-thread-3
5337 root 20 0 2257256 42924 12480 S 0.3 2.4 0:00.01 pool-1-thread-8
5339 root 20 0 2257256 42924 12480 S 0.3 2.4 0:00.01 pool-1-thread-1
5340 root 20 0 2257256 42924 12480 S 0.3 2.4 0:00.01 pool-1-thread-1
5348 root 20 0 2257256 42924 12480 S 0.3 2.4 0:00.01 pool-1-thread-1
5350 root 20 0 2257256 42924 12480 S 0.3 2.4 0:00.01 pool-1-thread-2
5354 root 20 0 2257256 42924 12480 S 0.3 2.4 0:00.01 pool-1-thread-2
5356 root 20 0 2257256 42924 12480 S 0.3 2.4 0:00.01 pool-1-thread-2
5365 root 20 0 2257256 42924 12480 S 0.3 2.4 0:00.01 pool-1-thread-3
5366 root 20 0 2257256 42924 12480 S 0.3 2.4 0:00.01 pool-1-thread-3
5369 root 20 0 2257256 42924 12480 S 0.3 2.4 0:00.01 pool-1-thread-4
5371 root 20 0 2257256 42924 12480 S 0.3 2.4 0:00.01 pool-1-thread-4
5372 root 20 0 2257256 42924 12480 S 0.3 2.4 0:00.01 pool-1-thread-4
5375 root 20 0 2257256 42924 12480 S 0.3 2.4 0:00.01 pool-1-thread-4
5320 root 20 0 2257256 42924 12480 S 0.0 2.4 0:00.00 java
5321 root 20 0 2257256 42924 12480 S 0.0 2.4 0:00.17 java
5322 root 20 0 2257256 42924 12480 S 0.0 2.4 0:00.02 VM Thread
5323 root 20 0 2257256 42924 12480 S 0.0 2.4 0:00.00 Reference Handl
5324 root 20 0 2257256 42924 12480 S 0.0 2.4 0:00.00 Finalizer
5325 root 20 0 2257256 42924 12480 S 0.0 2.4 0:00.00 Signal Dispatch
5326 root 20 0 2257256 42924 12480 S 0.0 2.4 0:00.19 C2 CompilerThre
5327 root 20 0 2257256 42924 12480 S 0.0 2.4 0:00.08 C1 CompilerThre
jstack 查看具体进程内具体线程运行状况
2021-04-27 19:29:32
Full thread dump OpenJDK 64-Bit Server VM (25.292-b10 mixed mode):
"Attach Listener" #58 daemon prio=9 os_prio=0 tid=0x00007fa328002000 nid=0x1529 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
Locked ownable synchronizers:
- None
"pool-1-thread-50" #57 prio=5 os_prio=0 tid=0x00007fa3541e8000 nid=0x1503 waiting on condition [0x00007fa3401af000]
java.lang.Thread.State: WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x00000000f7afcf68> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2039)
at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:1088)
at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:809)
at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1074)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1134)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
Locked ownable synchronizers:
- None
"pool-1-thread-49" #56 prio=5 os_prio=0 tid=0x00007fa3541e6000 nid=0x1502 waiting on condition [0x00007fa3402b0000]
java.lang.Thread.State: WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x00000000f7afcf68> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2039)
at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:1088)
at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:809)
at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1074)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1134)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
Locked ownable synchronizers:
- None
通过上面的几条命令我们可以找到每个线程的运行状态,方便及时定位OOM问题,下面我们根据一个写一个死锁的小程序来分析上述参数。
top top-Hp jstack 实战
首先我们写一个死锁的小程序
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class DeadLock {
public static synchronized void inHere() throws InterruptedException{
System.out.println(Thread.currentThread().getName()+"in here");
Thread.sleep(10000L);
}
public static void main(String[] args) {
ExecutorService service = Executors.newFixedThreadPool(5);
for(int i = 0;i<5;i++){
service.submit(()->{
try {
inHere();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
}
}
从上面的程序我们可以很清楚的知道,系统会在inHere方法中睡眠10S,此时睡眠线程是占有锁的。
在执行过程中,我们使用
TOP指令
得到下面的数据,可以看到,我们的java程序进程号是2704
2369 root 20 0 640768 26168 19376 S 0.7 1.5 0:00.74 vmtoolsd
1147 root 20 0 222764 5472 3832 S 0.3 0.3 0:00.13 rsyslogd
2282 root 20 0 953812 44564 18468 S 0.3 2.5 0:01.44 gnome-software
2704 root 20 0 2458584 30168 12096 S 0.3 1.7 0:00.42 java
2704进程里面有哪些线程呢?我们使用
TOP -Hp 2704
得到了下面的线程记录,可以对线程进行简单的分析,例如CPU占用,内存占用之类的
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
2704 root 20 0 2458584 30168 12096 S 0.0 1.7 0:00.00 java
2705 root 20 0 2458584 30168 12096 S 0.0 1.7 0:00.10 java
2706 root 20 0 2458584 30168 12096 S 0.0 1.7 0:00.01 VM Thread
2707 root 20 0 2458584 30168 12096 S 0.0 1.7 0:00.00 Reference Handl
2708 root 20 0 2458584 30168 12096 S 0.0 1.7 0:00.00 Finalizer
2709 root 20 0 2458584 30168 12096 S 0.0 1.7 0:00.00 Signal Dispatch
2710 root 20 0 2458584 30168 12096 S 0.0 1.7 0:00.01 C2 CompilerThre
2711 root 20 0 2458584 30168 12096 S 0.0 1.7 0:00.01 C1 CompilerThre
2712 root 20 0 2458584 30168 12096 S 0.0 1.7 0:00.00 Service Thread
2713 root 20 0 2458584 30168 12096 S 0.0 1.7 0:00.31 VM Periodic Tas
2714 root 20 0 2458584 30168 12096 S 0.0 1.7 0:00.00 pool-1-thread-1
2715 root 20 0 2458584 30168 12096 S 0.0 1.7 0:00.00 pool-1-thread-2
2716 root 20 0 2458584 30168 12096 S 0.0 1.7 0:00.00 pool-1-thread-3
2717 root 20 0 2458584 30168 12096 S 0.0 1.7 0:00.00 pool-1-thread-4
2718 root 20 0 2458584 30168 12096 S 0.0 1.7 0:00.00 pool-1-thread-5
2752 root 20 0 2458584 30168 12096 S 0.0 1.7 0:00.00 Attach Listener
上面的这些线程都在干什么呢?我们使用、
JSTACK指令
得到线程的具体用途
2021-04-29 21:37:58 --生成时间
Full thread dump OpenJDK 64-Bit Server VM (25.292-b10 mixed mode): --JVM类型
-- 线程名字 守护线程 J优先级 OS优先级 JID OSID 状态,等待某个条件
"Attach Listener" #14 daemon prio=9 os_prio=0 tid=0x00007f2f64001000 nid=0xac0 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE --jstack命令执行线程
"DestroyJavaVM" #13 prio=5 os_prio=0 tid=0x00007f2f8c04b800 nid=0xa91 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE --如果线程出错,执行这个方法结束JVM
--等待锁资源
"pool-1-thread-5" #12 prio=5 os_prio=0 tid=0x00007f2f8c181000 nid=0xa9e waiting for monitor entry [0x00007f2f6949f000] --线程地址
java.lang.Thread.State: BLOCKED (on object monitor) --阻塞
at DeadLock.inHere(DeadLock.java:8) --阻塞位置
- waiting to lock <0x00000000e4869b98> (a java.lang.Class for DeadLock) --等待对象内存地址
at DeadLock.lambda$main$0(DeadLock.java:16)
at DeadLock$$Lambda$1/135721597.run(Unknown Source)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
"pool-1-thread-4" #11 prio=5 os_prio=0 tid=0x00007f2f8c17f800 nid=0xa9d waiting for monitor entry [0x00007f2f695a0000]
java.lang.Thread.State: BLOCKED (on object monitor)
at DeadLock.inHere(DeadLock.java:8)
- waiting to lock <0x00000000e4869b98> (a java.lang.Class for DeadLock)
at DeadLock.lambda$main$0(DeadLock.java:16)
at DeadLock$$Lambda$1/135721597.run(Unknown Source)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
"pool-1-thread-3" #10 prio=5 os_prio=0 tid=0x00007f2f8c17d800 nid=0xa9c waiting for monitor entry [0x00007f2f696a1000]
java.lang.Thread.State: BLOCKED (on object monitor)
at DeadLock.inHere(DeadLock.java:8)
- waiting to lock <0x00000000e4869b98> (a java.lang.Class for DeadLock)
at DeadLock.lambda$main$0(DeadLock.java:16)
at DeadLock$$Lambda$1/135721597.run(Unknown Source)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
"pool-1-thread-2" #9 prio=5 os_prio=0 tid=0x00007f2f8c17b800 nid=0xa9b waiting for monitor entry [0x00007f2f697a2000]
java.lang.Thread.State: BLOCKED (on object monitor)
at DeadLock.inHere(DeadLock.java:8)
- waiting to lock <0x00000000e4869b98> (a java.lang.Class for DeadLock)
at DeadLock.lambda$main$0(DeadLock.java:16)
at DeadLock$$Lambda$1/135721597.run(Unknown Source)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
"pool-1-thread-1" #8 prio=5 os_prio=0 tid=0x00007f2f8c17a000 nid=0xa9a waiting on condition [0x00007f2f698a3000]
java.lang.Thread.State: TIMED_WAITING (sleeping) --等待唤醒
at java.lang.Thread.sleep(Native Method) --在睡眠
at DeadLock.inHere(DeadLock.java:9) --执行睡眠的方法
- locked <0x00000000e4869b98> (a java.lang.Class for DeadLock) --锁定的对象
at DeadLock.lambda$main$0(DeadLock.java:16)
at DeadLock$$Lambda$1/135721597.run(Unknown Source)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
"Service Thread" #7 daemon prio=9 os_prio=0 tid=0x00007f2f8c119800 nid=0xa98 runnable [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"C1 CompilerThread1" #6 daemon prio=9 os_prio=0 tid=0x00007f2f8c116800 nid=0xa97 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"C2 CompilerThread0" #5 daemon prio=9 os_prio=0 tid=0x00007f2f8c115000 nid=0xa96 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"Signal Dispatcher" #4 daemon prio=9 os_prio=0 tid=0x00007f2f8c106000 nid=0xa95 runnable [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"Finalizer" #3 daemon prio=8 os_prio=0 tid=0x00007f2f8c0da800 nid=0xa94 in Object.wait() [0x00007f2f9016d000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x00000000e4808ee0> (a java.lang.ref.ReferenceQueue$Lock)
at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:144)
- locked <0x00000000e4808ee0> (a java.lang.ref.ReferenceQueue$Lock)
at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:165)
at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:216)
"Reference Handler" #2 daemon prio=10 os_prio=0 tid=0x00007f2f8c0d5800 nid=0xa93 in Object.wait() [0x00007f2f9026e000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x00000000e4806c00> (a java.lang.ref.Reference$Lock)
at java.lang.Object.wait(Object.java:502)
at java.lang.ref.Reference.tryHandlePending(Reference.java:191)
- locked <0x00000000e4806c00> (a java.lang.ref.Reference$Lock)
at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153)
"VM Thread" os_prio=0 tid=0x00007f2f8c0cc000 nid=0xa92 runnable
"VM Periodic Task Thread" os_prio=0 tid=0x00007f2f8c11c800 nid=0xa99 waiting on condition
JNI global references: 309 JNI(java native interface
jinfo pid
查看运行信息
还是以刚才的程序为例
Attaching to process ID 3329, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.292-b10
Java System Properties:
java.runtime.name = OpenJDK Runtime Environment
java.vm.version = 25.292-b10
sun.boot.library.path = /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.292.b10-1.el7_9.x86_64/jre/lib/amd64
java.vendor.url = https://www.redhat.com/
java.vm.vendor = Red Hat, Inc.
path.separator = :
file.encoding.pkg = sun.io
java.vm.name = OpenJDK 64-Bit Server VM
sun.os.patch.level = unknown
sun.java.launcher = SUN_STANDARD
user.country = CN
user.dir = /root
java.vm.specification.name = Java Virtual Machine Specification
java.runtime.version = 1.8.0_292-b10
java.awt.graphicsenv = sun.awt.X11GraphicsEnvironment
os.arch = amd64
java.endorsed.dirs = /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.292.b10-1.el7_9.x86_64/jre/lib/endorsed
java.io.tmpdir = /tmp
line.separator =
java.vm.specification.vendor = Oracle Corporation
os.name = Linux
sun.jnu.encoding = UTF-8
java.library.path = /usr/java/packages/lib/amd64:/usr/lib64:/lib64:/lib:/usr/lib
java.specification.name = Java Platform API Specification
java.class.version = 52.0
sun.management.compiler = HotSpot 64-Bit Tiered Compilers
os.version = 3.10.0-1127.10.1.el7.x86_64
user.home = /root
user.timezone =
java.awt.printerjob = sun.print.PSPrinterJob
file.encoding = UTF-8
java.specification.version = 1.8
user.name = root
java.class.path = .:/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.181-3.b13.el7_5.x86_64/lib/dt.jar:/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.181-3.b13.el7_5.x86_64/lib/tools.jar
java.vm.specification.version = 1.8
sun.arch.data.model = 64
sun.java.command = DeadLock
java.home = /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.292.b10-1.el7_9.x86_64/jre
user.language = zh
java.specification.vendor = Oracle Corporation
awt.toolkit = sun.awt.X11.XToolkit
java.vm.info = mixed mode
java.version = 1.8.0_292
java.ext.dirs = /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.292.b10-1.el7_9.x86_64/jre/lib/ext:/usr/java/packages/lib/ext
sun.boot.class.path = /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.292.b10-1.el7_9.x86_64/jre/lib/resources.jar:/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.292.b10-1.el7_9.x86_64/jre/lib/rt.jar:/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.292.b10-1.el7_9.x86_64/jre/lib/sunrsasign.jar:/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.292.b10-1.el7_9.x86_64/jre/lib/jsse.jar:/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.292.b10-1.el7_9.x86_64/jre/lib/jce.jar:/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.292.b10-1.el7_9.x86_64/jre/lib/charsets.jar:/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.292.b10-1.el7_9.x86_64/jre/lib/jfr.jar:/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.292.b10-1.el7_9.x86_64/jre/classes
java.vendor = Red Hat, Inc.
file.separator = /
java.vendor.url.bug = https://bugzilla.redhat.com/enter_bug.cgi?product=Red%20Hat%20Enterprise%20Linux%207&component=java-1.8.0-openjdk
sun.io.unicode.encoding = UnicodeLittle
sun.cpu.endian = little
sun.cpu.isalist =
VM Flags:
Non-default VM flags: -XX:CICompilerCount=2 -XX:InitialHeapSize=29360128 -XX:MaxHeapSize=461373440 -XX:MaxNewSize=153747456 -XX:MinHeapDeltaBytes=196608 -XX:NewSize=9764864 -XX:OldSize=19595264 -XX:+UseCompressedClassPointers -XX:+UseCompressedOops
Command line:
这个我就不分析了,累了
jmap
生产环境慎重导出文件分析,因为他会把线程停掉然后去导出