• JVM详细教程


    前言

    还在完善中先发布
    JVM虚拟机厂家多钟多样,具体实现细节可能不一样,这里主要讲的是虚拟机的规范,以下内容融合了各个平台发布的内容和周志明老师的《深入理解java虚拟机》

    JVM概述

    如何理解jvm跨平台?

    编译成汇编代码的语言,例如C语言,编译后的二进制文件会因为操作系统的CPU和操作位数的不同无法做到通用。编译成.class的语言,不用关注操作系统的异同,不同的平台安装不同的JVM,代码层面不需要考虑操作系统的异同。JVM是解释运行.class文件的虚拟机,不止针对java。

    jvm虚拟机产品

    HotSpot VM
    KVM(Kilobyte)
    JRockit

    java的内存模型

    计算机内存

    内存模型:
    计算机的内存模型是指计算机内部存储数据的方式和结构。计算机的内存模型通常分为三个部分:寄存器、主存储器和缓存。
    寄存器:寄存器是CPU中的一组高速缓存,用于存储指令、数据和控制信息。寄存器的访问速度非常快,但是容量有限,只能存储少量的数据。
    主存储器:主存储器是计算机中的主要存储器,用于存储大量的数据和指令。主存储器的访问速度比寄存器慢,但是容量大,可以存储大量的数据。
    缓存:缓存是计算机中的一种高速缓存技术,用于缓存主存储器中的部分数据,以提高访问速度。缓存的容量通常比主存储器小,但是访问速度非常快。
    计算机的内存模型是指将数据和指令从主存储器中加载到寄存器和缓存中,并在CPU中进行处理和运算的过程。在计算机的内存模型中,数据和指令的访问速度取决于它们所存储的位置,从寄存器到主存储器再到缓存,访问速度逐渐变慢。因此,在编写高效的程序时,需要考虑如何优化数据和指令的访问方式,以提高程序的执行效率。
    计算机内存读取寻址过程:

    操作系统提供的可寻址空间:
    32位:2^32 4Gb的寻址范围
    64位:2^64的寻址范围
    计算机地址空间划分:
    内核空间和用户空间是计算机操作系统中的两个概念,它们用于划分计算机内存空间的不同部分。
    内核空间:内核空间是计算机内存空间的一部分,用于存储操作系统的核心程序和数据。内核空间是计算机操作系统的核心,它负责管理和控制计算机硬件资源,例如处理器、内存、磁盘、网络等。内核空间的访问权限非常高,只有操作系统本身才能访问。
    用户空间:用户空间是计算机内存空间的另一部分,用于存储用户程序和数据。用户空间是用户程序的运行环境,它提供了一组基本的系统调用接口,用于用户程序与内核空间进行交互。用户空间的访问权限相对较低,只有用户程序才能访问。
    在计算机操作系统中,内核空间和用户空间是通过内存保护机制实现的。内核空间和用户空间之间存在一个内存保护屏障,用于隔离内核空间和用户空间的数据和指令。只有操作系统本身才能通过内存保护屏障,从而访问内核空间和控制硬件资源。而用户程序只能通过系统调用接口,从而访问用户空间和执行操作系统提供的基本服务。

    Java在JVM当中运行内存模型(JVM规范的内存模型)

    [1]

    私有区域

    概述

    Java多线程执行是需要每个线程去请求CPU资源的,一个时刻就只能执行一个线程,因此需要每个线程都需要一段属于自己的内存空间,并保存自己内部的运行状况,这样在线程切换的时候才能继续执行。

    程序计数器(逻辑)(Program Counter Register)

    为什么它是线程私有的?当多线程运行,切换线程的时候如何知道线程自己的字节码运行到什么地方,程序计数器的作用就是如此:
    1.记录线程自己的字节码运行到哪一行,因此是线程私有的
    2.既然记录的行号,那么就可以改变程序计数器中的值让这个线程去选择读取什么地方下一行的数据

    java虚拟机栈(Stack)

    概述:线程的私有地址,每个方法封装为栈帧入栈,运行完出栈,
    虚拟机栈描述的是Java方法执行的线程内存模型:每个方法被执行的时候,Java虚拟机都会同步创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态连接、方法出口等信息。每一个方法被调用直至执行完毕的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程**[1]**。

    运行方式

    java方法执行的内存模型,从局部变量表中取数据,在这个栈内存中通过出栈和入栈的方式进行方法的执行由多个栈帧组成
    image.png
    栈帧包含:
    局部变量表(local variable):局部变量表存放了编译期可知的各种Java虚拟机基本数据类型(boolean、byte、char、short、int、 float、long、double)、对象引用(reference类型,它并不等同于对象本身,可能是一个指向对象起始地址的引用指针,也可能是指向一个代表对象的句柄或者其他与此对象相关的位置)和returnAddress 类型(指向了一条字节码指令的地址)。[1]
    操作栈(Operand Stack):本质上是执行引擎的工作区,就是执行
    动态链接(Dynamic Linking):java语言的特性多态
    返回地址(Return Address):正常返回(调用程序计数器中的地址作为返回)、异常的话(通过异常处理器表<非栈帧中的>来确定)
    …等组成

    局部变量表、操作数栈、程序计数器的关系

    本地方法栈

    和虚拟机栈类似,运行的是native本地方法

    java为什么会栈内存溢出?

    从上面线程虚拟方法栈就可以看出每调用一个方法就会有一个栈帧压栈,例如递归等方法,如果递归的层数太深,不断压栈,超过了栈的大小就会出现内存溢出

    共享区域

    堆(heap)

    存放几乎所有的对象实例和数组,所有线程的共享区域。垃圾回收机制也就是针对堆内存的回收,因此也被称为GC堆

    1.7推内存模型介绍

    执行过程:eden区域满了将进入survivor去,survivor区未被回收的对象转移到tenured区域,tenured的没有回收的对象进入永久区。
    Survivor区结构:两个相同的survivor区构成。运行时候只会使用一个,另外一个用做gc回收机制时复制对象使用。
    Perm:永久区一般存储class和file。一段时间tenured没有被回收将会进入这个区域。

    1.8内存模型介绍


    1.8版本取消永久区,使用元素区代替,元素区为本机内存不再jvm当中。
    Young区:两个survivor区+eden
    MetaSpace:ccs+codecach

    方法区(Method Area)

    存储已被虚拟机加载的类型信息,常量,静态变量,JIT编译后的代码缓存数据等。

    运行时常量池(Runtime Constant Pool)

    运行时常量池(Runtime Constant Pool):是方法区的一部分。Class文件中除了有类的版本、字 段、方法、接口等描述信息外,还有一项信息是常量池表(Constant Pool Table),用于存放编译期生成的各种字面量与符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中**[1]
    在jvm规范中,方法区除了存储类信息之外,还包含了运行时常量池。这里
    首先要来讲一下常量池的分类
    常量池可分两类:
    1、Class常量池(静态常量池)
    2、运行时常量池
    3、字符串常量池(没有明确的官方定义,其目的是为了更好的使用String ,真实的存储位置在堆)
    [1]**

    对象

    对象内存布局


    java代码编译的过程

    通过反射了解类是如何加载的

    最终调用一个本地方法库函数forName0,获取Class,同时传入了一个默认的ClassLoader类


    ClassLoader

    java的核心组件,所有的class都通过ClassLoader加载进入内存。ClassLoader通过读取字节码的二进制数据流到JVM当中,JVM再进行连接初始化。可以自定义ClassLoader读取指定的字节码文件,在这个过程中我们就可以对这个二进制流做相应的操作。加载的字节码可以在本地,jar包内,远程。
    一些核心的ClassLoader类举例:
    BootStrapClassLoader:加载核心库java.*
    ExtClassLoader:加载扩展库javaX.*
    AppClassLoader:加载程序所在目录
    自定义ClassLoader:定制化加载

    自定义一个ClassLoader了解双亲委派机制

    一句话就是不断去父类找,看那个父类加载过这个字节码,有就从父类返回,没有就从子类读取
    好处:系统不用重复加载字节码

    重写ClassLoader的步骤

    1.读取二进制流返回给findClass()
    2.加载二进制流loadClass()

    public class MyClassLoader extends ClassLoader {
    
        public static void main(String[] args) throws ClassNotFoundException {
            MyClassLoader myClassLoader = new MyClassLoader("/Users/alexyuan/Documents/codefile/giteemycode/mylearnrepository/jvmdemo/src/main/java/com/alexyuan/test/");
            Class<?> aClass = myClassLoader.loadClass("Test");
            System.out.println(aClass.getClassLoader());
            System.out.println(aClass.getClassLoader().getParent().getClass().getName());
            System.out.println(aClass.getClassLoader().getParent().getParent().getClass().getName());
            System.out.println(aClass.getClassLoader().getParent().getParent().getParent());
        }
    
        private String path;
    
        public MyClassLoader(String path) {
            this.path = path;
        }
    
        public MyClassLoader(String path, ClassLoader parentClassLoader) {
            super(parentClassLoader);
            this.path = path;
        }
    
        @Override
        public Class<?> loadClass(String name) throws ClassNotFoundException {
            // 先调用父类的loadClass方法
            Class<?> clazz = super.loadClass(name);
            // 自定义的类加载逻辑
            return clazz;
        }
    
        /**
         * 重写父类的findClass方法,在ClassLoader在执行 loadClass 方法时,
         * 如果父加载器不会加载类,就会调用当前重写的方法进行加载类
         */
        @Override
        protected Class<?> findClass(String name) throws ClassNotFoundException {
            BufferedInputStream bis = null;
            ByteArrayOutputStream baos = null;
            try {
                bis = new BufferedInputStream(new FileInputStream(path + name + ".class"));
                baos = new ByteArrayOutputStream();
                int len;
                byte[] data = new byte[1024];
                while ((len = bis.read(data)) != -1) {
                    baos.write(data, 0, len);
                }
                //获取内存中的完整的字节数组的数据
                byte[] classByteArray = baos.toByteArray();
                //将字节数组转换为Class的实例
                return defineClass(null, classByteArray, 0, classByteArray.length);
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                try {
                    if (null != baos) {
                        baos.close();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
                try {
                    if (null != bis) {
                        bis.close();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            return null;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    读取字节码文件,查看父类都有哪些


    BootstrapClassLoader:Java的启动类加载器,负责加载JRE的核心类库和启动时需要的类。
    ExtensionClassLoader:Java的扩展类加载器,负责加载JRE的扩展类库和用户自定义的扩展类库。
    SystemClassLoader:Java的系统类加载器,负责加载用户类路径中的类和资源。
    URLClassLoader:基于URL的类加载器,可以从指定的URL列表中加载类和资源。
    CustomClassLoader:自定义的类加载器,可以根据自己的需求实现自定义的类加载逻辑。

    类的加载过程

    1.加载:此阶段由类加载器从指定的位置加载类的二进制数据,包括从JAR文件、目录、网络等位置加载。在加载阶段,类加载器需要解析类的名称、父类、接口、方法、字段等元素,并将其转换为JVM可以识别的字节码格式。
    2.链接:此阶段由JVM对字节码进行验证、准备和解析。验证阶段会验证字节码是否符合JVM的规范,包括验证类的名称、父类、接口、字段、方法等元素;准备阶段会为类变量分配内存,并设置默认初始值;解析阶段会解析类中的静态变量、方法和类的调用关系。
    3.初始化:此阶段会初始化类中的静态变量和静态方法,并为类变量分配内存。在初始化阶段,如果类中存在静态代码块,会先执行静态代码块;如果类中存在静态方法,会先执行静态方法。在初始化阶段,JVM会为类变量分配内存,并将其初始化为默认初始值。

    加载方式

    隐示加载:new一个类初始化到内存中
    显示加载:Class.forName(),loadClass()
    显示加载的区别:
    loadClass()只到加载过程

    forName默认初始化是true,所有使用forName该类已经到初始化阶段,会运行类中的静态代码块

            MyClassLoader myClassLoader = new MyClassLoader("/Users/alexyuan/Documents/codefile/giteemycode/mylearnrepository/jvmdemo/src/main/java/com/alexyuan/test/");
            Class<?> aClass = myClassLoader.loadClass("Test");
     
           Class<?> aClass1 = Class.forName("/Users/alexyuan/Documents/codefile/giteemycode/mylearnrepository/jvmdemo/src/main/java/com/alexyuan/test/Teat.class");
            Object o = aClass1.newInstance();
    
    • 1
    • 2
    • 3
    • 4
    • 5

    Jstat查看堆内存使用情况

    命令可使用的参数

    命令格式:jstat –{选项} 进程id 间隔时间 查询次数

    Jmap使用对堆内存进行统计

    Jmap参数

    Mat工具对jdump文件分析

    1. 将内存情况保存为“.dat”文件
    2. 使用mat打开文件

    使用Jstack查看当前jvm中运行现场的情况

    线程状态介绍

    初始态:刚刚启动的状态,也就是刚刚调用start()函数的状态

    运行态:在CPU中运行

    就绪:等待CPU资源就可执行
    阻塞:没有获得资源一直请求,超时后进入阻塞队列,当获取的资源之后进入就绪

    等待态:需要获取的启动资源,属于被动等待

    超时等待:主动进入等待状态,如sleep函数

    终止态:线程运行结束

    使用jmx查看内存和线程使用状况

    在JDK中bin目录双击打开

    JVM——垃圾回收机制

    什么是垃圾回收

    程序在运行时需要申请内存,内存使用之后需要归还内存给系统,当一些无效对象一直占用内存就可能导致内存溢出,因此需要对无效对象进行垃圾回收。

    垃圾回收算法

    垃圾回收算法——引用计数算法

    概述:每个对象都设置一个计数器,如果有对象引用它计数器进行加一,引用结束减一,系统对对象的计数器进行判断,如果计数器为0者回收该对象
    缺点:无法回收循环引用,也就是只要是改对象被引用,这就不会被回收,无论这个引用是否指向null或者未被使用。

    垃圾回收算法——标记清除法

    概述:当系统做垃圾回收的时候暂停所有线程,遍历所有对象,标记处对象的引用关系,清楚root对象未被应用到的对象。
    缺点:性能低,由于对象可能在不同内存中,回收的内存很可能不连续,也就是内存碎片化严重。

    垃圾回收算法——标记压缩算法

    概述:由标记清楚法演变而来,在回收之前将有效对象压缩在一个连续的内存再回收,这样解决了回收内存碎片化的问题
    缺点:性能比优化之前更低

    垃圾回收算法——复制算法

    概述:将内存分为两块,一块使用,另一块闲置,当进行垃圾回收的时候将有效对象放在闲置内存当中,在将内存情况,两个内存空间交换角色,然后重复之前的操作
    缺点:运行时有一块内存空间没有使用因此有点资源浪费
    应用区域:young中的survivor由两块相同的内存空间构成就是使用的复制算法,这样垃圾回收的区域是完整的一块内存空间,没有碎片化。

    回收算法总结

    每一个算法都各有有点,根据不同区域的特性不同使用不同算法,年轻代对象少可以使用复制算法,gen区对象多适合标记清楚和标记压缩算法。

    收集器

    垃圾收集器以及内存分配

    算法只是实现方式,在java内部提供了一些垃圾回收器,垃圾回收器去完成垃圾的回收。
    image.png
    Parallel Scavenge:收集器也是一款新生代收集器,它同样是基于标记-复制算法实现的收集器。Parallel Scavenge收集器的特点是它的关注点与其他收集器不同,CMS等收集器的关注点是尽可能 地缩短垃圾收集时用户线程的停顿时间,而Parallel Scavenge收集器的目标则是达到一个可控制的吞吐量(Throughput)。所谓吞吐量就是处理器用于运行用户代码的时间与处理器总消耗时间的比值,
    image.png
    Parallel Old是Parallel Scavenge收集器的老年代版本,支持多线程并发收集,基于标记-整理算法实现。
    CMS收集器是**基于标记-清除算法实现,**一种以获取最短回收停顿时间为目标的收集器。
    1)初始标记(CMS initial mark)
    2)并发标记(CMS concurrent mark)
    3)重新标记(CMS remark)
    4)并发清除(CMS concurrent sweep)
    ParNew收集器实质上是Serial收集器的多线程并行版本
    image.png

    串行收集器

    只有一个线程执行收集器,一般不用

    并行收集器

    Parnew收集器

    将串行收集器改成并行而来

    ParallelGC收集器

    在parnew的基础上增加了吞吐量的设置

    CMS垃圾收集器

    算法:标记清除算法
    工作位置:老年带

    Stw:线程全部暂停,stop world
    标记时会触发stw

    G1垃圾回收器

    概述:取消了老年带,年轻代的概念,清理工作是将对象从一个区复制到另一个区。G1为因为巨形对象在拷贝的时候对回收器性能影响很大,因此具有一个humongous区域。

    G1垃圾回收器使用流程

    第一步:打开G1作为垃圾回收器
    第二步:设置最大停顿时间,也就是标记清除的时候
    第三步:设置堆最大内存

    YoungGC

    对年轻带进行GC,eden空间向survivor空间复制转移,也向old区转移

    MixGC

    主要GCyoung区,也GCold区

    G1垃圾回收如何找到根对象

    使用remember set方式,就是每个空间初始化的时候生成一个set集合,这个集合记录这片空间被引用的空间,通过这些set集合找到根对象,然后从

    使用G1垃圾回收器

    1.jvm参数:
    -XX:+UseG1GC
    -XX:MaxGCPauseMillis=100
    -Xmx256m
    -XX:+PrintGCDetails
    -XX:+PrintGCTimeStamps
    -XX:+PrintGCDateStamps
    -XX:+PrintHeapAtGC
    -Xloggc:E://test//gc.log
    GC历史文件在:E://test//gc.log目录下,打开https://gceasy.io/文件查看吞吐量

    jvm调优

    目的

    解决生产环境中日志不输出,死锁,cpu占用过高,如何分配线程数等问题,在于让程序跑起来也让程序跑的更快。

    JVM参数分类

    根据jvm参数开头可以区分参数类型,共三类:“-”、“-X”、“-XX”,
    标准参数“-”:每种类型的Jvm都会实现,-help,-version
    非标准参数“-X”:默认Jvm实现这些功能,但不是一定完全实现,-Xms,-Xmx等
    非Stable参数“-XX”:不稳定随着版本的不一致,参数也不一致

    -X参数


    ■ -Xms20m :设置jvm初始化堆大小为20m,一般与-Xmx相同避免垃圾回收完成后jvm重新分。
    ■ -Xmx20m:设置jvm最大可用内存大小为20m。
    ■ -Xmn10m:设置新生代大小为20m。
    ■ -Xss128k:设置每个线程的栈大小为128k。

    -XX参数

    Java -XX:+PrintFlagsFinal -version #打印系统XX参数
    
    • 1

    -XX:+PrintGCDetails:打印GC详情;
    -XX:+PrintGCTimeStamps:打印时间戳;
    -XX:CMSInitiatingOccupancyFraction=80 CMS gc,表示在老年代达到80%使用率时马上进行回收;
    -verbose:gc:可以输出每次GC的一些信息;
    -XX:-UseConcMarkSweepGC:使用CMS收集器;

    GC信息查看

    打印GC简单信息
    -verbose:gc
    -XX:+PrintGC
    打印详细GC信息
    -XX:+PrintGCDetails
    -XX:+PrintGCTimeStamps
    指定GC日志以文件输出
    -Xloggc:./gc.log

    使用jps和jinfo查看java进程运行状态


    Jinfo+pid 可以看到详细的进程运行参数,包括Jvm启动参数

    Jvm运行模式设置

    Server模式性能强,64位默认为该模式。Client模式性能较弱,32位系统可以使用该模式。

    full gc排查实践

    查看系统的垃圾回收器

    java -XX:+PrintCommandLineFlags -version
    jps查看进程PID
    image.png

    jinfo查看进程的启动参数

    VM Flags:
    Non-default VM flags: -XX:CICompilerCount=4 -XX:CompressedClassSpaceSize=260046848 -XX:GCLogFileSize=104857600 -XX:InitialHeapSize=268435456 -XX:+ManagementServer -XX:MaxHeapSize=268435456 -XX:MaxMetaspaceSize=268435456 -XX:MaxNewSize=267911168 -XX:MetaspaceSize=67108864 -XX:MinHeapDeltaBytes=524288 -XX:NewSize=267911168 -XX:NumberOfGCLogFiles=10 -XX:OldSize=524288 -XX:-OmitStackTraceInFastThrow -XX:+PrintGC -XX:+PrintGCTimeStamps -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseFastUnorderedTimeStamps -XX:+UseGCLogFileRotation -XX:+UseParallelGC
    Command line:
    -Xms256m JVM初始分配的内存大小为1024m
    -Xmx256m JVM最大分配的内存大小为1024
    -Xmn50m 设置年轻代的大小,设置越大,gc越少.
    设置年轻代大小为512m。整个JVM内存大小=年轻代大小 + 年老代大小 + 持久代大小。
    持久代一般固定大小为64m,所以增大年轻代,将会减小年老代大小。此值对系统性能影响较大,Sun官方推荐配置为整个堆的3/8
    -XX:MetaspaceSize=64m
    -XX:MaxMetaspaceSize=256m
    -Xloggc:/home/boco/vimp/nbd-device-manage/logs/gc-%t.log
    -XX:-OmitStackTraceInFastThrow
    -XX:+UseGCLogFileRotation
    -XX:GCLogFileSize=100M
    -XX:NumberOfGCLogFiles=10
    -Dloader.path=lib/
    -Dcom.sun.management.jmxremote.port=1111
    -Dcom.sun.management.jmxremote.authenticate=false
    -Dcom.sun.management.jmxremote.ssl=false

    使用JMap查看对应的PID的对内存大致使用情况

    image.png

    导出dump文件

    根据dump文件查看堆内存情况

    自动

    # 1.线上环境如果有流量需要在启动服务脚本中加入如下JVM参数,表示在发生fullgc的时候自动dump
    -XX:HeapDumpBeforeFullGC
    # 2.与第一个JVM参数配套使用,指定dump文件的保存路径,便于排查问题,路径也可以是相对路径
    -XX:HeapDumpPath=保存dump文件的文件绝对路径
    
    # 说明:如果加入这两个jvm参数还是没有dump下来文件,可能是你的jvm的参数中有其他的参数导致dump失败,排查看是否有如下参数,如果有去掉即可
    -XX:+DisableExplicitGC
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    手动

    使用jdk自带的jmap工具
    image.png

    # 导出内存dump文件
    jmap -dump:live,format=b,file=heap.bin <pid>
    
    ##### 3.3把dump文件从线上主机下载到本地
    命令格式:  
    scp local_file remote_username@remote_ip:remote_folder  
    或者
    scp local_file remote_username@remote_ip:remote_file  
    或者
    scp local_file remote_ip:remote_folder  
    或者
    scp local_file remote_ip:remote_file
    
    ##### 3.4通过JDK自带的jvisualvm工具分析或者下载三方软件jprofiler来分析dump文件即可
    
    ##### 3.5最重要的一点,要把fullgc发生时刻的dump文件和正常没有发生fullgc时间的dump文件都下载到本地,然后对比观察分析方便找到问题的原因
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    jmap -dump:live,format=b,file=heap.hprof 22209

    • live 只dump存活的对象,如果不加则会dump所有对象
    • format=b 表示以二进制格式
    • file=filepath 输出到某个文件中,文件名可以使txt,bin,hprof等

    stack链接一个JVM进程去查看进程内部的运行情况

    image.png

    JVM进程内部是如何通信的

    attach 就是jvm提供一种jvm进程间通信的能力,能让一个进程传命令给另外一个进程,并让它执行内部的一些操作:
    VirtualMachine.attach: 位于tools.jar,jstack和jhipcup的attach使用的是VirtualMachine.attach,
    Serviceability Agent,简称SA 继承Tool/HotSpotAgent.attach。SA 在JDK中是以Jar文件的形式提供的,位于JAVA_HOME/lib/sa-jdi.jar ,和一般的Jar文件执行一样。SA工具一般使用ptrace方法来进行attach和detach。
    j

    参考文献


    1. 《深入理解java虚拟机:周志明》
  • 相关阅读:
    火山引擎 DataTester 应用故事:一个A/B测试,将产品DAU提升了数十万
    Python AI 之Stable-Diffusion-WebUI
    使用 LangChain 和 Elasticsearch 对私人数据进行人工智能搜索
    经验之谈:AntD级联选择动态加载数据
    zookeeper选举leader源码剖析
    使用mqtt.fx向EMQX服务器发送消息
    BMS电池管理系统理论基础
    阿里云购买磁盘,挂载到/root/XXXX 详细的步骤
    每日一题|2022-10-28|907. 子数组的最小值之和|今天不想学,后面补上
    java中的集合框架基础-5
  • 原文地址:https://blog.csdn.net/Alex_yuan666/article/details/132334734