关于jvm:C启动JVM

性能介绍1. C++启动Java虚拟机2. 通过C++指针调用main办法环境JDK: jdk-17.0.9mingw-w64: x86_64-8.1.0-release-win32-seh-rt_v6-rev0 实现过程1. 编写cpp程序StartJVM.cpp #include <jni.h> // JNI header provided by JDK#include <stdio.h> // C Standard IO Header#include <windows.h>int main() { JavaVM *jvm; // Pointer to the JVM (Java Virtual Machine) JNIEnv *env; // Pointer to native interface // JVM initialization arguments JavaVMInitArgs vm_args; JavaVMOption* options = new JavaVMOption[3]; options[0].optionString = (char*) "-Djava.class.path=D:/SDK/jdk-17.0.9/lib;."; // 设置你的类门路 (classpath),这里只是一个例子 options[1].optionString = (char*) "-Xmn512m"; options[2].optionString = (char*) "-Xmx1g"; // options[3].optionString = (char*) "-Djava.library.path=D:/SDK/jdk-17.0.9/bin"; vm_args.version = JNI_VERSION_10; // JVM version. This indicates version 1.6 vm_args.nOptions = 3; vm_args.options = options; vm_args.ignoreUnrecognized = JNI_TRUE; printf("JNI_CreateJavaVM %s\n", options[0].optionString); HINSTANCE hVM = LoadLibrary("D:\\SDK\\jdk-17.0.9\\bin\\server\\jvm.dll"); if (hVM == NULL) { printf("Load failed.."); } typedef jint (CALLBACK *fpCJV)(JavaVM**, void**, JavaVMInitArgs*); fpCJV CreateJavaVM = (fpCJV)GetProcAddress(hVM, "JNI_CreateJavaVM"); jint rc = CreateJavaVM(&jvm, (void**)&env, &vm_args); // 加载并初始化一个JVM,"jvm"是返回的JVM接口的指针,"env"是返回的JNI接口的指针 //jint rc = JNI_CreateJavaVM(&jvm, (void**)&env, &vm_args); printf("JNI_CreateJavaVM Finished!\n"); if (rc != JNI_OK) { if (rc == JNI_EVERSION) { fprintf(stderr, "FATAL ERROR: JVM is oudated and doesn't meet requirements"); } else if (rc == JNI_ENOMEM) { fprintf(stderr, "FATAL ERROR: Not enough memory for JVM"); } else if (rc == JNI_EINVAL) { fprintf(stderr, "FATAL ERROR: invalid ragument for launching JVM"); } else if (rc == JNI_EEXIST) { fprintf(stderr, "FATAL ERROR: the process can only launch one JVM an not multiple"); } else { fprintf(stderr, "FATAL ERROR: unknown error"); } return rc; } else { printf("JVM load succeeded. Version\n"); } // 在这里你能够开始调用Java代码 ... printf("可用调用Java了\n"); /* invoke the Main.test method using the JNI */ jclass cls = env->FindClass("JavaMain"); jmethodID mid = env->GetStaticMethodID(cls, "main", "([Ljava/lang/String;)V"); env->CallStaticVoidMethod(cls, mid, 100); // 敞开JVM jvm->DestroyJavaVM(); return 0;}JavaMain.java ...

February 20, 2024 · 2 min · jiezi

关于jvm:eBPF-求证坊间传闻mmap-Java-Safepoint-可导致整个-JVM-服务卡顿

概述Java 反对好几种文件读取办法,本文要说的是小众的 mmap(MappedByteBuffer) 以及它与 Safepoint、JVM 服务卡顿之间的关系。本文尝试用 eBPF 等分析方法,去证实具体环境下,问题的存在与否。 审误和公布本文时,我才是二阳后活过来数小时而已,写了数周的文章切实不想再迁延公布了。如文章有错漏,还请多多包涵和斧正。引Java 服务卡顿,是 Java 世界永恒的话题之王。想到 Java 卡顿,大部分人的第一反馈是以下关键词: GCSafepoint / Stop the world(STW)而说拜访 mmap(MappedByteBuffer) 导致 Java 卡顿可能是个小众话题。但如果你理解了一些基于 Java 的重 IO 的开源我的项目后,就会发现,这其实不是个小众话题。如已经大热的 NoSQL 数据库 Cassandra 默认就是重度应用 mmap 去读取数据文件和写 commit log 的。 基本原理JVM 写 GC 日志机会剖析Safepoint [From https://krzysztofslusarski.github.io/2022/12/12/async-manual....] 上图大略阐明了 safepoint 的次要流程。有趣味同学能够看看下面链接,或搜寻一下,网上很多好文章阐明过了。我就不搬门弄斧了。 一点须要留神的是,safepoint STOP THE WORLD(STW) 的使用者不只有 GC。还有其它。 这里只简略阐明一下(我不是 JVM 专家,所以请审慎应用以下观点): Global safepoint request 1.1 有一个线程提出了进入 safepoint 的申请,其中带上 safepoint operation 参数,参数其实是 STOP THE WORLD(STW) 后要执行的 Callback 操作 。可能是分配内存有余,触发 GC。也可能是其它起因。 ...

September 26, 2023 · 7 min · jiezi

关于jvm:eBPF-求证坊间传闻Java-GC-日志可导致整个-JVM-服务卡顿

概述实现世界的 Java 利用,都会记录 GC 日志。但不是所有人都晓得小小的日志可能导致整个 JVM 服务卡顿。本文尝试用 eBPF 等分析方法,去证实具体环境下,问题的存在与否。 审误和公布本文时,我才是二阳后活过来数小时而已,写了数周的文章切实不想再迁延公布了。如文章有错漏,还请多多包涵和斧正。引Java 服务卡顿,是 Java 世界永恒的话题之王。想到 Java 卡顿,大部分人的第一反馈是以下关键词: GCStop the world(STW)而写 GC 日志导致 Java 卡顿可能是个小众话题。不过我确实在之前的电商公司中目击过。那是 2017 年的 12/8 左右,还是 Java 8,过后相干的专家花了大略数周工夫去论证和试验,最终开出诊断后果和药方: 诊断后果:写 GC 日志导致 JVM GC 卡顿药方:GC 日志不落盘(写 ram disk/tmpfs),或落到独立的 disk 中。转瞬来到 2023 年,OpenJDK 21 都来了。问题还在吗?看看搜索引擎的后果: Eliminating Large JVM GC Pauses Caused by Background IO Traffic - 2016年 Zhenyun Zhuang@LinkedIn OS-Caused Large JVM Pauses: Investigations and Solutions - 2016年 Zhenyun Zhuang@LinkedInJVM性能调优--YGC - heapdump.cnJVM 输入 GC 日志导致 JVM 卡住,我 TM 人傻了OpenJDK 的修改计划这个问题是有宽泛认知了,OpenJDK 也给出了解决修改: ...

September 26, 2023 · 8 min · jiezi

关于jvm:jvm运行时数据区域有哪些

JVM 运行时数据分为几大部分: 程序计数器Java 虚拟机栈本地办法栈Java 堆办法区运行时常量池间接内存1. 程序计数器程序计数器(Program Counter Register) 是一块较小的内存空间,它能够看作是以后线程所执行的字节码的行号指示器。在虚拟机的概念模型里,字节码解释器工作时就是通过扭转这个计数器的值来选取下一条须要执行的字节码指令,分支、循环、跳转、异样解决、线程复原等根底性能都须要依赖这个计数器来实现。 因为 Java 虚拟机的多线程是通过线程轮流切换并调配CPU工夫片的形式来实现的,在任何一个确定的时刻,一个处理器都只会执行一条线程中的指令。因而,为了线程切换后能复原到正确的执行地位,每条线程都须要有一个独立的程序计数器,各条线程之间计数器互不影响,独立存储,咱们称这类内存区域为 “线程公有的内存”。 如果线程正在执行的是一个Java 办法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是 Native 办法,这个计数器值则为空(Undefined),此内存区域是惟一一个在Java 虚拟机标准中没有规定任何 OutOfMemoryError 状况的区域。 2. Java 虚拟机栈与程序计数器一样,Java 虚拟机栈也是线程公有的,它的生命周期和线程雷同。虚拟机栈形容的是Java办法执行的内存模型:每个办法在执行的同时都会创立一个栈桢用于存储局部变量表、操作数栈、动静链接、办法进口等信息。每一个办法从调用直至执行实现的过程,就对应一个栈帧在虚拟机中入栈到出栈的过程。 人们常常把简略的虚拟机分为堆和栈,而这个栈就是当初讲的虚拟机栈,或者说是虚拟机栈中局部变量表局部。 局部变量表寄存了编译器可知的各种根本类型(boolean、byte、char、short;int、float、long、double),对象援用(reference类型,可能是指向对象起始地址的援用指针,也可能是指向一个代表对象的句柄或其余与此对象相干的地位)和 returnAddress 类型(指向了一条字节码指令的地址)。 其中64位的long 和 double 类型的数据会占用2个局部变量空间,其余的数据类型只占用一个。局部变量表所需的内存空间在编译期间实现调配,当进入一个办法时,这个办法须要在栈中调配多大的局部变量是齐全确定的。在办法运行期间不会扭转局部变量表的大小。 在虚拟机标准中,对这个区域规定了两种异常情况:如果线程申请的栈深度大于虚拟机所容许的深度,将抛出 StackOverflow 异样;如果虚拟机能够动静扩大(以后大部分的 Java 虚拟机都能够动静扩大,只不过 Java 虚拟机标准中也容许固定长度的虚拟机栈),如果扩大时无奈声请到足够的内存,就会抛出OutOfMemoryError 异样。 3. 本地办法栈(HotSpot 已被交融进虚拟机栈)本地办法栈(Native Method Stack) 和虚拟机栈所产品的作用是十分类似的,他们之间的区别不过是虚拟机栈为虚拟机执行Java 办法(也就是字节码)服务,而本地办法栈则为虚拟机应用到的 Native 办法服务。而虚拟机标准中对本地办法栈中办法应用的语言、应用形式与数据结构并没有强制规定,因而具体的虚拟机能够自在实现它。甚至有的虚拟机(HotSpot)间接就把本地办法栈和虚拟机栈合二为一。与虚拟机栈一样,本地办法栈区域也会抛出 StackOverflow 异样和 OutOfMemoryError 异样。 4. Java 堆对于大多数利用来说,Java 堆(Java Heap) 是Java 虚拟机所治理的内存中最大的一块。Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创立。此内存区域的惟一目标就是寄存对象实例,简直所有的对象实例都再这里分配内存。这一点Java 虚拟机标准中的形容是:所有的对象实例以及数组都要在堆上调配,然而随着 JIT 编译器的倒退和逃逸剖析技术逐步成熟,栈上调配、标量替换优化技术将会导致一些奥妙的变动产生,所有的对象都调配在堆上也慢慢变得不是那么“相对”了。 Java 堆是垃圾收集器治理的次要区域,因而很多时候也被称为 “GC 堆(Garbage Collected Heap)”,从内存回收的角度来看,因为当初收集器根本都采纳分代收集算法,所以Java 堆中还能够细分成:新生代和老年代;再粗疏一点的有 Eden 空间、From Survivor 空间、To Survivor 空间等。从内存调配的角度来看,线程共享的Java 堆中可能划分出多个线程公有的调配缓冲区(Thread Lcoal Allocation Buffer, TLAB)。 ...

September 3, 2023 · 1 min · jiezi

关于jvm:JVM的GC日志解读

{Heap before GC invocations=35866 (full 34): par new generation total 290176K, used 287809K [0x00000000d4400000, 0x00000000e7000000, 0x00000000e7000000) eden space 273152K, 100% used [0x00000000d4400000, 0x00000000e4ec0000, 0x00000000e4ec0000) from space 17024K, 86% used [0x00000000e4ec0000, 0x00000000e5d10770, 0x00000000e5f60000) to space 17024K, 0% used [0x00000000e5f60000, 0x00000000e5f60000, 0x00000000e7000000) concurrent mark-sweep generation total 409600K, used 159259K [0x00000000e7000000, 0x0000000100000000, 0x0000000100000000) Metaspace used 137146K, capacity 146452K, committed 147072K, reserved 378880K class space used 15581K, capacity 17238K, committed 17536K, reserved 247808K2023-09-02T01:18:40.811+0000: 142176.656: [GC (Allocation Failure) 2023-09-02T01:18:40.811+0000: 142176.657: [ParNew: 287809K->13868K(290176K), 0.0142053 secs] 447069K->173146K(699776K), 0.0144550 secs] [Times: user=0.01 sys=0.00, real=0.01 secs]Heap after GC invocations=35867 (full 34): par new generation total 290176K, used 13868K [0x00000000d4400000, 0x00000000e7000000, 0x00000000e7000000) eden space 273152K, 0% used [0x00000000d4400000, 0x00000000d4400000, 0x00000000e4ec0000) from space 17024K, 81% used [0x00000000e5f60000, 0x00000000e6ceb260, 0x00000000e7000000) to space 17024K, 0% used [0x00000000e4ec0000, 0x00000000e4ec0000, 0x00000000e5f60000) concurrent mark-sweep generation total 409600K, used 159277K [0x00000000e7000000, 0x0000000100000000, 0x0000000100000000) Metaspace used 137146K, capacity 146452K, committed 147072K, reserved 378880K class space used 15581K, capacity 17238K, committed 17536K, reserved 247808K}JVM参数为:-Xms700m -Xmx700m -Xmn300m -XX:MetaspaceSize=250m -XX:MaxMetaspaceSize=250m -XX:SurvivorRatio=16如上是一段GC日志,先来解读这一行:2023-09-02T01:18:40.811+0000: 142176.656: [GC (Allocation Failure) 2023-09-02T01:18:40.811+0000: 142176.657: [ParNew: 287809K->13868K(290176K), 0.0142053 secs] 447069K->173146K(699776K), 0.0144550 secs] [Times: user=0.01 sys=0.00, real=0.01 secs]2023-09-02T01:18:40.811+0000:示意的是一个日期格局 ...

September 2, 2023 · 2 min · jiezi

关于jvm:一次Java内存占用高的排查案例解释了我对内存问题的所有疑问

原创:扣钉日记(微信公众号ID:codelogs),欢送分享,非公众号转载保留此申明。问题景象7月25号,咱们一服务的内存占用较高,约13G,容器总内存16G,占用约85%,触发了内存报警(阈值85%),而咱们是按容器内存60%(9.6G)的比例配置的JVM堆内存。看了下其它服务,同样的堆内存配置,它们内存占用约70%~79%,此服务比其它服务内存占用稍大。 那为什么此服务内存占用稍大呢,它存在内存泄露吗? 排查步骤1. 查看Java堆占用与gc状况jcmd 1 GC.heap_info jstat -gcutil 1 1000 可见堆应用状况失常。 2. 查看非堆占用状况查看监控仪表盘,如下: arthas的memory命令查看,如下: 可见非堆内存占用也失常。 3. 查看native内存Linux过程的内存布局,如下: linux过程启动时,有代码段、数据段、堆(Heap)、栈(Stack)及内存映射段,在运行过程中,应用程序调用malloc、mmap等C库函数来应用内存,C库函数外部则会视状况通过brk零碎调用扩大堆或应用mmap零碎调用创立新的内存映射段。 而通过pmap命令,就能够查看过程的内存布局,它的输入样例如下: 能够发现,过程申请的所有虚拟内存段,都在pmap中可能找到,相干字段解释如下: Address:示意此内存段的起始地址Kbytes:示意此内存段的大小(ps:这是虚拟内存)RSS:示意此内存段理论调配的物理内存,这是因为Linux是提早分配内存的,过程调用malloc时Linux只是调配了一段虚拟内存块,直到过程理论读写此内存块中局部时,Linux会通过缺页中断真正调配物理内存。Dirty:此内存段中被批改过的内存大小,应用mmap零碎调用申请虚拟内存时,能够关联到某个文件,也可不关联,当关联了文件的内存段被拜访时,会主动读取此文件的数据到内存中,若此段某一页内存数据后被更改,即为Dirty,而对于非文件映射的匿名内存段(anon),此列与RSS相等。Mode:内存段是否可读(r)可写(w)可执行(x)Mapping:内存段映射的文件,匿名内存段显示为anon,非匿名内存段显示文件名(加-p可显示全门路)。因而,咱们能够找一些内存段,来看看这些内存段中都存储的什么数据,来确定是否有泄露。但jvm个别有十分多的内存段,重点查看哪些内存段呢? 有两种思路,如下: 查看那些占用内存较大的内存段,如下: pmap -x 1 | sort -nrk3 | less 能够发现咱们过程有十分多的64M的内存块,而我同时看了看其它java服务,发现64M内存块则少得多。 查看一段时间后新增了哪些内存段,或哪些变大了,如下: 在不同的工夫点屡次保留pmap命令的输入,而后通过文本比照工具查看两个工夫点内存段散布的差别。 pmap -x 1 > pmap-`date +%F-%H-%M-%S`.log icdiff pmap-2023-07-27-09-46-36.log pmap-2023-07-28-09-29-55.log | less -SR 能够看到,一段时间后,新调配了一些内存段,看看这些变动的内存段里存的是什么内容! tail -c +$((0x00007face0000000+1)) /proc/1/mem|head -c $((11616*1024))|strings|less -S阐明: Linux将过程内存虚构为伪文件/proc/$pid/mem,通过它即可查看过程内存中的数据。tail用于偏移到指定内存段的起始地址,即pmap的第一列,head用于读取指定大小,即pmap的第二列。strings用于找出内存中的字符串数据,less用于查看strings输入的字符串。 通过查看各个可疑内存段,发现有不少相似咱们一自研音讯队列的响应格局数据,通过与音讯队列团队单干,找到了相干的音讯topic,并最终与相干研发确认了此topic音讯最近刚迁徙到此服务中。 4. 查看发http申请代码因为发送音讯是走http接口,故我在工程中搜寻调用http接口的相干代码,发现一处代码中创立的流对象没有敞开,而GZIPInputStream这个类刚好会间接调配到native内存。 其它办法本次问题,通过查看内存中的数据找到了问题,还是有些碰运气的。这须要内存中刚好有一些十分有代表性的字符串,因为非字符串的二进制数据,根本无奈剖析。 如果查看内存数据无奈找到要害线索,还可尝试以下几个办法: 5. 开启JVM的NMT原生内存追踪性能增加JVM参数-XX:NativeMemoryTracking=detail开启,应用jcmd查看,如下: ...

August 26, 2023 · 1 min · jiezi

关于jvm:浅析JVM-GC配置指南-京东云技术团队

本文旨在简明扼要阐明各回收器调优参数,如有疏漏欢送斧正。 1、JDK版本以下所有优化全副基于JDK8版本,强烈建议低版本升级到JDK8,并尽可能应用update_191当前版本。 2、如何抉择垃圾回收器响应优先利用:面向C端对响应工夫敏感的利用,堆内存8G以上倡议抉择G1,堆内存较小或低版本JDK抉择CMS; 吞吐量优先利用:对响应工夫不敏感,以高吞吐量为指标的利用(如MQ、Worker),倡议抉择ParallelGC; 3、各回收器优化参数1)基本参数配置(所有利用、所有回收器都须要): -Xmx(个别为容器内存的50%) -Xms(与Xmx统一) -XX:MetaspaceSize(通常256M~512M) -XX:ParallelGCThreads=容器核数 -XX:CICompilerCount=容器核数(必须大于等于2) 2)ParallelGC 除以上参数外,个别不须要额定调优(JDK8默认回收器) 3)CMS -XX:+UseConcMarkSweepGC -Xmn (个别为堆内存的三分之一),尤其是配置了ParallelGCThreads后必须配置此参数 -XX:ConcGCThreads=n(默认为ParallelGCThreads/4,可视状况调整至ParallelGCThreads/2) -XX:+UseCMSInitiatingOccupancyOnly -XX:CMSInitiatingOccupancyFraction=70(推荐值) 4)G1 -XX:+UseG1GC -XX:ConcGCThreads=n(默认为ParallelGCThreads/4,可视状况调整至ParallelGCThreads/2) -XX:G1HeapRegionSize=8m(若堆内存在8G以内且有较多大对象举荐设置此值) *留神不要设置-Xmn 和 XX:NewRatio 5)其余调优参数 -XX:+ParallelRefProcEnabled 如果GC时Reference解决工夫较长,例如大量应用WeakReference对象,能够通过此参数开启并行处理 4、开启GC日志-XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:/export/Logs/gc.log 5、如何判断GC是否失常1)GC是否频繁:YoungGC频率个别几十秒钟一次,FullGC个别每天几次,留神G1回收器不应该呈现FullGC; 2)GC耗时:耗时次要取决于堆内存大小及垃圾对象数量。YoungGC工夫通常应在几十毫秒,FullGC通常在几百毫秒; 3)每次GC内存是否降落:利用刚启动时,每次YoungGC内存应该回收到较低水位,随着时间推移老年代逐渐增多,内存水位会逐渐上涨,直到FullGC/MixedGC(G1),内存会再次回到较低水位,否则可能存在内存透露; 4)如果应用ParallelGC,堆内存耗尽才会触发FullGC,所以不必配置堆内存使用率告警,但需关注GC频率; 5)泰山上能够巡检局部JVM配置。 作者:京东批发 王利辉 起源:京东云开发者社区

July 12, 2023 · 1 min · jiezi

关于jvm:图解-JVM-内存模型及-JAVA-程序运行原理

一、JAVA语言的特点在进入正题之前,先问一个陈词滥调的问题,相较于C,JAVA语言的劣势是什么?置信学过JAVA的人都晓得,无论是大学时的第一堂课还是JAVA相干书籍的第一章也都会讲到:一次编写、到处运行;真正意义上的实现了跨平台。那再问一个问题,为什么Java能够跨平台?大多数人都晓得Java能够跨平台得益于 JVM(java虚拟机)。在这之前,我理解到的java跨平台得益于不同版本的JVM,那么它的底层原理是什么呢?“一次编译,到处运行” 是Java的跨平台个性。像 C 、C++ 这样的编程语言没有它。通过上面的介绍,置信你会有一个近一步的理解。Java是一种能够跨平台的编程语言。首先,咱们须要晓得什么是平台。咱们把CPU处理器与操作系统的整体叫平台。CPU相当于计算机的大脑,指令集是CPU中用来计算和管制计算机系统的一套指令的汇合。指令集分为精简指令集(RISC)和简单指令集(CISC)。每个CPU都有本人的特定指令集。要开发一个程序,咱们必须首先晓得程序运行在什么CPU上,也就是说,咱们必须晓得CPU应用的指令集。操作系统是用户与计算机之间的接口软件。不同的操作系统反对不同的CPU。严格来说,不同的操作系统反对不同的CPU指令集。但问题是,原来的Mac操作系统只反对PowerPC,不能装置在英特尔上。咱们该怎么办?因而,苹果必须重写其Mac操作系统来反对这一变动。最初,咱们应该晓得不同的操作系统反对不同的CPU指令集。当初windows、Linux、MAC和Solaris都反对Intel和AMD CPU指令集。如果你想开发一个程序,首先应该确定: CPU类型,即指令集类型;操作系统;咱们称之为软硬件平台的联合。也能够说“平台=CPU+OS”。而且因为支流操作系统反对支流CPU,有时操作系统也被称为平台。二、如何实现跨平台通常,咱们编写的Java源代码在编译后会生成一个Class文件,称为字节码文件。Java虚拟机负责将字节码文件翻译成特定平台下的机器代码,而后运行。简言之,java的跨平台就是因为不同版本的 JVM。换句话说,只有在不同的平台上装置相应的JVM,就能够运行字节码文件(.class)并运行咱们编写的Java程序。在这个过程中,咱们编写的Java程序没有做任何改变,只是通过JVM的“中间层”,就能够在不同的平台上运行,真正实现了“一次编译,到处运行”的目标。JVM是跨平台的桥梁和中间件,是实现跨平台的要害。首先将Java代码编译成字节码文件,而后通过JVM将其翻译成机器语言,从而达到运行Java程序的目标。因而,运行Java程序必须有JVM的反对,因为编译的后果不是机器代码,必须在执行前由JVM再次翻译。即便您将Java程序打包成可执行文件(例如。Exe),依然须要JVM的反对。 留神:编译的后果不是生成机器代码,而是生成字节码。字节码不能间接运行,必须由JVM转换成机器码。编译生成的字节码在不同的平台上是雷同的,然而JVM翻译的机器码是不同的。 三、JVM简介JVM------Java Virtual Machine.JVM是Java平台的根底,与理论机器一样,他有本人的指令集(相似CPU通过指令操作程序运行),并在运行时操作不同的内存区域(JVM内存体系)。Java虚拟机位于操作系统之上(如下图所示),将通过JAVAC命令编译后的字节码加载到其内存区域,通过解释器将字节码翻译成CPU能辨认的机器码行。每一条Java指令,Java虚拟机标准中都有具体定义,如怎么取操作数,怎么解决操作数,处理结果放在哪里。 JVM是运行在操作系统之上的,它与硬件没有间接交互。 四、JVM的内存构造JAVA源代码文件通过编译后变成虚拟机能够辨认的字节码,JAVA程序在执行时,会通过类加载器把字节码加载到虚拟机的内存中(虚拟机的内存是一个逻辑概念,相当于是对主内存的一个形象,实际上实在的数据还是寄存在主存中),详见下图。 Java 虚拟机在执行 Java 程序的过程中会把它治理的内存划分为若干个不同的数据区域。每个区域都有各自的作用。剖析 JVM 内存构造,次要就是剖析JVM 运行时数据存储区域。JVM 的运行时数据区次要包含:堆、栈、办法区、程序计数器等。而 JVM 的优化问题次要在线程共享的数据区中:堆、办法区。 4.1 办法区又称非堆(non-heap),办法区用于存储已被虚拟机加载的类信息,常量、动态变量,即时编译后的代码等数据。办法区中最驰名的就是CLASS对象,CLASS对象中寄存了类的元数据信息,包含:类的名称、类的加载器、类的办法、类的注解等。当咱们new一个新对象或者援用动态成员变量时,Java虚拟机(JVM)中的类加载器子系统会将对应Class对象加载到JVM中,而后JVM再依据这个类型信息相干的Class对象创立咱们须要实例对象或者提供动态变量的援用值。留神,咱们定义的一个类,无论创立多少个实例对象,在JVM中都只有一个Class对象与其对应,即:在内存中每个类有且只有一个绝对应的Class对象,如图: 实际上所有的类都是在对其第一次应用时动静加载到JVM中的,当程序创立第一个对类的动态成员援用时,就会加载这个被应用的类(实际上加载的就是这个类的字节码文件)。注:应用new创立类的新实例对象也会被当作对类的动态成员的援用(构造函数也是类的静态方法)由此看来Java程序在它们开始运行之前并非被齐全加载到内存的,其各个局部是按需加载,所以在应用该类时,类加载器首先会查看这个类的Class对象是否已被加载(类的实例对象创立时根据Class对象中类型信息实现的),如果还没有加载,默认的类加载器就会先依据类名查找.class文件(编译后Class对象被保留在同名的.class文件中),在这个类的字节码文件被加载时,它们必须承受相干验证,以确保其没有被毁坏并且不蕴含不良Java代码(这是java的平安机制检测),齐全没有问题后就会被动静加载到内存中,此时相当于Class对象也就被载入内存了(毕竟.class字节码文件保留的就是Class对象),同时也就能够依据这个类的Class对象来创立这个类的所有实例对象。 4.2 堆所有创立进去的实例对象还有数组都是寄存在堆内存中,堆是Java虚拟机所治理的内存中最大的一块存储区域,堆内存被所有线程共享。垃圾收集器就是依据GC算法,收集堆上对象所占用的内存空间,堆上又分为了新生代和老年代,针对不同的分代又会有对象的垃圾回收器和相应的回收算法(GC章节中会具体介绍)。 4.3 栈JVM 中的栈包含 Java 虚拟机栈和本地办法栈,两者的区别就是,Java 虚拟机栈为 JVM 执行 Java 办法服务,本地办法栈则为 JVM 应用到的 Native 办法服务。两者作用是极其类似的,本文次要介绍 Java 虚拟机栈,以下简称栈。栈属于线程公有的数据区域,与线程同时创立,总数与线程关联,代表Java办法执行的内存模型。每个办法执行时都会创立一个栈帧来存储办法的的局部变量表、操作数栈、动静链接办法、办法返回值、返回地址等信息。每个办法从调用值完结就对于一个栈桢在虚拟机栈中的入栈和出栈过程,栈帧中的局部变量表能够寄存根本类型,也能够寄存指向对象的援用,当在某个办法中new Object()时,会在以后办法栈帧中的局部变量表寄存一个指向堆内存实例对象的援用,详见下图。 4.4 程序计数器是一块较小的内存空间,用来存储虚拟机下一条执行的字节码指令地址,和CPU中的程序计数器是一样的概念。 五 、JAVA程序在JVM内是如何执行的上文已介绍了JVM的内存构造,接下来再看一下这个程序在JVM外部是怎么运行的: 1.JAVA程序的执行过程简略来说包含: 2.JAVA源代码编译成字节码; 3.字节码校验并把JAVA程序通过类加载器加载到JVM内存中; 4.在加载到内存后针对每个类创立Class对象并放到办法区; 5.字节码指令和数据初始化到内存中; 6.找到main办法,并创立栈帧; 7.初始化程序计数器外部的值为main办法的内存地址; 8.程序计数器一直递增,逐条执行JAVA字节码指令,把指令执行过程的数据寄存到操作数栈中(入栈),执行实现后从操作数栈取出后放到局部变量表中,遇到创建对象,则在堆内存中调配一段间断的空间存储对象,栈内存中的局部变量表寄存指向堆内存的援用;遇到办法调用则再创立一个栈帧,压到以后栈帧的下面。 上面以一段理论的代码举例,来看一下,程序在JVM外部的执行过程。 咱们先通过JAVAP命令,展现上述代码对应的字节码,下图是JVM把类加载到内存当前在办法区的常量池中初始化好的Class对象和各种办法援用,这外面须要重点关注一下后面的#1,#2,#5这些符号,这些数字保留的是和Class对象以及办法的援用关系,前面的字节码中会用到。 随后执行引擎中的解释器会率先启动,对ClassFile字节码采纳逐行解释的形式加载机器码,并配合运行时数据区的程序计数器与操作数栈来反对。下图是main办法的字节码指令,咱们联合JVM内存状况对代码做逐个剖析。 ...

June 13, 2023 · 1 min · jiezi

关于jvm:深入浅出解析JVM中的Safepoint-|-得物技术

1.初识Safepoint-GC中的Safepoint最早接触JVM中的平安点概念是在读《深刻了解Java虚拟机》那本书垃圾回收器章节的内容时。置信大部分人也一样,都是通过这样的形式第一次对平安点有了初步意识。无妨,先温习一下《深刻了解Java虚拟机》书中平安点那一章节的内容。 书中是在解说垃圾收集器-垃圾收集算法的章节引入平安点的介绍,为了疾速精确地实现GC Roots枚举,防止为每条指令都生成对应的OopMap造成大量存储空间的节约,只在“特定的地位”生成对应的OopMap,这些地位被称为平安点。而后,书中提到了平安点地位的抉择规范是:是否能让程序长时间执行;所以会在办法调用、循环跳转、异样跳转等处才会产生平安点。 书中还提到了JVM如何在GC时让用户线程在最近的平安点处停顿下来:领先式中断和主动式中断。领先式中断不须要线程的执行代码被动去配合,在GC产生时,零碎首先把所有用户线程全副中断,如果发现有用户线程中断的中央不在平安点上,就复原这条线程执行,让它一会再从新中断,直到跑到平安点上。而主动式中断的思维是当GC须要中断线程时,不间接对线程操作,仅仅简略地设置一个标记位,各个线程执行过程时不停地被动去轮询这个标记,一旦发现中断标记为真就本人在最近的平安点上被动中断挂起。当初基本上所有虚拟机实现都采纳主动式中断形式来暂停线程响应GC事件。 总结一下初识平安点学到的知识点: JVM GC时须要让用户线程在平安点处停顿下来(Stop The World)JVM会在办法调用、循环跳转、异样跳转等处搁置平安点JVM通过被动中断形式达到全局STW:设置一个标记位,各个线程执行过程时不停地被动去轮询这个标记,一旦发现中断标记为真就本人在最近的平安点上被动中断挂起。以上基本上就是《深刻了解Java虚拟机》这本书对JVM平安点的所有介绍了,过后感觉平安点还是很好了解,认为平安点就是在垃圾回收时为了STW而设计的。 起初发现,通过一些线上问题和网上看到无关平安点乏味的示例,发现平安点其实也不简略,不只有GC才会用到平安点;简略的代码如果写的不当,平安点也会带来一些莫名其妙的问题;其在JVM外部的实现以及JIT对它的优化,也常常让人摸不着头脑。本文尝试在初识平安点后已知知识点的根底上,通过一段简略的示例代码,多问几个为什么,来进一步更全面的理解一下平安点。 2.通过一段示例代码深刻分析Safepoint2.1  示例代码这段示例代码可间接复制到本地运行,本文所有对示例代码的运行环境都是jdk 1.8。 public static AtomicInteger *counter* = new AtomicInteger(0);public static void main(String[] args) throws Exception{long startTime = System.*currentTimeMillis*();Runnable runnable = () -> {System.*out*.println(*interval*(startTime) + "ms后," + Thread.*currentThread*().getName() + "子线程开始运行");for(int i = 0; i < 100000000; i++) {*counter*.getAndAdd(1);}System.*out*.println(*interval*(startTime) + "ms后," + Thread.*currentThread*().getName() + "子线程完结运行, counter=" + *counter*);};Thread t1 = new Thread(runnable, "zz-t1");Thread t2 = new Thread(runnable, "zz-t2");t1.start();t2.start();System.*out*.println(*interval*(startTime) + "ms后,主线程开始sleep.");Thread.*sleep*(1000L);System.*out*.println(*interval*(startTime) + "ms后,主线程完结sleep.");System.*out*.println(*interval*(startTime) + "ms后,主线程完结,counter:" + *counter*);}private static long interval(Long startTime) {return System.*currentTimeMillis*() - startTime;}}示例代码中主线程启动两个子线程,而后主线程睡眠1s,通过打印工夫来察看主线程和子线程的执行状况。按情理来说这里主线程和两个子线程独立并发,没有任何显性的依赖,主线程的执行是不会受子线程影响的:主线程睡眠完结后会间接完结。然而执行后果却和冀望不一样。 ...

May 11, 2023 · 2 min · jiezi

关于jvm:从原理聊JVM三详解现代垃圾回收器Shenandoah和ZGC-京东云技术团队

作者:京东科技 康志兴 ShenandoahShenandoah一词来自于印第安语,十九世纪四十年代有一首驰名的航海歌曲在水手中广为流传,讲述一位年老富商爱上印第安酋长Shenandoah的女儿的故事。 起初美国有一条位于Virginia州西部的小河以此命名,所以Shenandoah的中文译名为“情人渡”。Shenandoah首次呈现在Open JDK12中,是由Red Hat开发,次要为了解决之前各种垃圾回收器解决大堆时进展较长的问题。 相比拟G1将低进展做到了百毫秒级别,Shenandoah的设计指标是将进展压缩到10ms级别,且与堆大小无关。它的设计十分激进,很多设计点在衡量上更偏向于低进展,而不是高吞吐。 “G1的继承者”Shenandoah是OpenJDK中的垃圾处理器,但相比拟Oracle JDK中根正苗红的ZGC,Shenandoah能够说更像是G1的继承者,很多方面与G1十分类似,甚至共用了一部分代码。 总的来说,Shenandoah和G1有三点次要区别: 1.G1的回收是须要STW的,而且这部分进展占整体进展工夫的80%以上,Shenandoah则实现了并发回收。 2.Shenandoah不再辨别年老代和年轻代。 3.Shenandoah应用连贯矩阵代替G1中的卡表。 对于G1的具体介绍请翻看前一篇:从原理聊JVM(二):从串行收集器到分区收集开创者G1 连贯矩阵(Connection Matrix)G1中每个Region都要保护卡表,既消耗计算资源还占据了十分大的内存空间,Shenandoah应用了连贯矩阵来优化了这个问题。 连贯矩阵能够简略了解为一个二维表格,如果Region A中有对象指向Region B中的对象,那么就在表格的第A行第B列打上标记。 比方,Region 1指向Region 3,Region 4指向Region 2,Region 3指向Region 5: 相比G1的记忆集来说,连贯矩阵的颗粒度更粗,间接指向了整个Region,所以扫描范畴更大。但因为此时GC是并发进行的,所以这是通过抉择更低资源耗费的连贯矩阵而对吞吐进行斗争的一项决策。 转发指针转发指针的性能劣势想要达到并发回收,就须要在用户线程运行的同时,将存活对象逐渐复制到空的Region中,这个过程中就会在堆中同时存在新旧两个对象。那么如何让用户线程拜访到新对象呢? 此前,通常是在旧对象原有内存上设置爱护陷阱(Memory Protection Trap),当拜访到这个旧对象时就会产生自陷异样,使程序进入到预设的异样处理器中,再由处理器中的代码将拜访转发到复制后的新对象上。 自陷是由线程发起来打断以后执行的程序,进而取得CPU的使用权。这一操作通常须要操作系统参加,那么就会产生用户态到内核态的转换,代价非常微小。 所以Rodney A.Brooks提出了应用转发指针来实现通过旧对象拜访新对象的形式:在对象头后面减少一个新的援用字段,在非并发挪动状况下指向本人,产生新对象后指向新对象。那么当拜访对象的时候,都须要先拜访转发指针看看其指向哪里。尽管和内存自陷计划相比同样须要多一次拜访转发的开销,然而前者耗费小了很多。 转发指针的问题转发指针次要存在两个问题:批改时的线程平安问题和高频拜访的性能问题。 1.对象体减少了一个转发指针,这个指针的批改和对象自身的批改就存在了线程平安问题。如果通过被拜访就可能产生复制了新对象后,转发对象批改之前产生了旧对象的批改,这就存在两个对象不统一的问题了。对于这个问题,Shenandoah是通过CAS操作来保障批改正确性的。 2.转发指针的退出须要笼罩所有对象拜访的场景,包含读、写、加锁等等,所以须要同时设置读屏障和写屏障。尤其读操作相比单纯写操作呈现频率更高,这样高频操作带来的性能问题影响微小。所以Shenandoah在JDK13中对此进行了优化,将内存屏障模型改为援用拜访屏障,也就是说,仅仅在对象中援用类型的读写操作减少屏障,而不去管原生对象的操作,这就省去了大量的对象拜访操作。 Shenandoah的运行步骤初始标记(Init Mark)[STW] [同G1]标记与GC Roots间接关联的对象。 并发标记(Concurrent Marking)[同G1]遍历对象图,标记全副可达对象。 最终标记(Final Mark)[STW] [同G1]解决残余的SATB扫描,并在这个阶段统计出回收价值最高的Region,将这些Region形成一组回收集。 并发清理(Concurrent Cleanup)回收所有不蕴含任何存活对象的Region(这类Region被称为Immediate Garbage Region)。 并发回收(Concurrent Evacuation)将回收集外面的存货对象复制到一个其余未被应用的Region中。并发复制存活对象,就会在同一时间内,同一对象在堆中存在两份,那么就存在该对象的读写一致性问题。Shenandoah通过应用转发指针将旧对象的申请指向新对象解决了这个问题。这也是Shenandoah和其余GC最大的不同。 初始援用更新(Init Update References)[STW]并发回收后,须要将所有指向旧对象的援用修改到新对象上。这个阶段实际上并没有实际操作,只是设置一个阻塞点来保障上述并发操作均已实现。 并发援用更新(Concurrent Update References)顺着内存物理地址线性遍历堆空间,更新并发回收阶段复制的对象的援用。 最终援用更新(Final Update References)[STW]堆空间中的援用更新结束后,最初须要修改GC Roots中的援用。 并发清理(Concurrent Cleanup)此时回收集中Region应该全副变成Immediate Garbage Region了,再次执行并发清理,将这些Region全副回收。 ZGCZGC是Oracle官网研发并JDK11中引入,并于JDK15中作为生产就绪应用,其设计之初定义了三大指标: ...

April 26, 2023 · 1 min · jiezi

关于jvm:从原理聊JVM二从串行收集器到分区收集开创者G1

作者:京东科技 康志兴 1 前言随着Java的进化过程,涌现出各种不同的垃圾回收器,从串行执行到并行执行,从高吞吐到低提早,终极目标就是让开发人员专一于程序的代码书写而无需关注内存治理。 JDK晚期呈现的垃圾回收器通常独自作用于不同分代,到前期呈现的G1开始,才能够进行全区域收集。 对于垃圾回收器的基础知识请翻看前一篇:从原理聊JVM(一):染色标记和垃圾回收算法 2 串行收集器(Serial)比拟老的收集器,单线程,所收集时必须暂停利用的工作线程,直到收集完结。但和其余收集器的单线程相比更加简略、高效。 作用于新生代的收集器叫Serial,采纳标记复制算法;作用于年轻代的收集器叫Serial Old,采纳标记整顿算法。 3 并行收集器(Parallel)多条垃圾收集线程并行工作,在多核CPU下效率更高,但利用线程依然处于期待状态。 并行收集器也分为ParNew和Parallel Old。能够了解为它们就是Serial和Serial Old的多线程并行版本,甚至局部代码进行了复用。 ParNew较为风行的起因是因为除了Serial只有它能和CMS搭配应用。但自JDK9开始,因为更先进的G1的呈现,官网间接勾销了独自指定ParNew的参数-XX:+UseParNewGC,使其并入了CMS收集器,成为它专门解决新生代的组成部分。 而Parallel Old则搭配新生代收集器ParallelScavenge成为货真价实的“吞吐量优先”的搭配组合。 4 ParallelScavengeParallelScavenge收集器是面向新生代的垃圾回收器,它和ParNew其实十分相似,应用标记复制算法并行收集。区别在于二者关注点不同,ParalletScavenge的指标是达到一个可管制的吞吐量(Throughput),更高的吞吐量意味着最大限度的应用处理器的资源来缩短整体的垃圾回收工夫。ParalletScavenge有两个重要参数: •-XX:MaxGCPauseMillis 收集器将尽力保障内存回收破费的工夫不超过用户设定值。但这是以就义吞吐量为代价的,要求用更短的工夫来实现垃圾收集,那么零碎就须要升高新生代大小,新生代变小了天然垃圾回收会更加频繁,每次垃圾回收都有很多必要工作(比方期待所有线程达到平安点),那么更频繁的垃圾回收就导致了整体吞吐量的升高。 •-XX:GCTimeRatioGCTimeRatio 是垃圾收集工夫占总工夫的比率,换句话说:其示意运行用户代码工夫是GC运行工夫的X倍。比方默认为99,则垃圾收集工夫占比应该1/(1+99)。这个数越低,运行用户代码工夫占比越低。 ParallelScavenge收集器还能够通过参数(-XX:+UseAdaptiveSizePolicy)来激活自适应调节策略。激活后,就不须要人工指定新生代的大小(Xmn)、Eden与Survivor区的比例(XX:SurvivorRatio)、降职年轻代对象大小(XX:PretenureSizeThreshold)等细节参数了,虚构机会依据以后零碎的运行状况收集性能监控信息,动静调整这些参数以提供最合适的进展工夫或者最大的吞吐量。 5 CMS收集器(Concurrent Mark Sweep)CMS收集器是缩短暂停利用工夫(Low Pause)为指标而设计的,最开始CMS仅仅是年轻代收集器,起初将ParNew并入作为其年老代收集器。 相较上述收集器,CMS是第一个无需全程STW而容许局部阶段并发执行的收集器。 垃圾回收实际上次要是两个阶段:辨认垃圾和回收垃圾,CMS在这两个阶段别离做了致力来升高进展: •辨认垃圾 CMS将标记过程打散,并将次要的染色标记过程和用户线程同步进行,并通过增量更新形式解决了援用切换带来的漏标的问题。 •垃圾回收 CMS采纳革除算法,相比复制和整顿,革除算法因为仅解决死亡对象所以不须要任何进展。 CMS运行步骤 具体来说,CMS整个过程分为4个步骤: 1. 初始标记(Initial Mark)[STW] 初始标记只是标记一下GC Roots能间接关联到的对象,速度很快。 2. 并发标记(Concurrent Marking) 并发标记阶段是标记可回收对象。 3. 从新标记(Remark)[STW] 从新标记阶段则是为了修改并发标记期间因用户程序持续运作导致标记产生变动的那一部分对象的标记记录,这个阶段暂停工夫比初始标记阶段稍长一点,但远比并发标记工夫短。 CMS用增量更新来做并发标记,也就是说并发标记过程中,如果某个曾经标记为存活的对象减少了对非存活对象的援用,那么将其标记为灰色,而后在从新标记阶段将这一部分对象从新扫描。 4. 并发革除(Concurrent Sweep) 清理删除掉标记阶段判断的曾经死亡的对象,因为不须要挪动存活对象,所以这个阶段也是能够与用户线程同时并发的。 长处因为整个过程中耗费最长的并发标记和并发革除过程收集器线程都能够与用户线程一起工作,所以,CMS收集器内存回收与用户一起并发执行的,大大减少了暂停工夫。 毛病1. 处理器资源敏感 垃圾回收的线程可能与用户线程同时执行,这样尽管不会导致STW,然而因为摊派了处理器的计算资源从而导致应用程序变慢,升高了总吞吐量。 2. 内存敏感 当垃圾回收和用户线程在同步运行时产生的垃圾,因为曾经过了标记阶段所以不会标记后革除,这部分垃圾只能等到下一次GC时才会被革除,这就是浮动垃圾问题。 而且因为垃圾回收和用户线程同步运行,所以不能等堆满了再GC,而是须要预留一部分内存来保障GC过程中用户线程仍有可用内存。为了升高GC频率,只能等垃圾攒多一点再触发GC,那么GC时可供用户线程应用的内存就不多了。 如果GC尚未完结用户线程分配内存失败,这个状况叫做“并发失败”,这时虚构机会降级应用Serial Old来从新进行一次高吞吐的年轻代收集,这样进展工夫就长了。 线上环境应依据理论状况来调整触发GC的内存应用阈值,该参数为:-XX:CMSInitiatingOccupancyFraction。 3. CMS基于标记革除算法,所以内存碎片过多后,会频繁触发Full GC,且不可避免。CMS会在若干次触发后进行一次内存碎片的合并整顿,内存整理过程波及存活对象的挪动,(在Shenandoah和ZGC呈现前)无奈并发。 ...

April 24, 2023 · 1 min · jiezi

关于jvm:java获取到heapdump文件后如何快速分析

原创:扣钉日记(微信公众号ID:codelogs),欢送分享,非公众号转载保留此申明。简介在之前的OOM问题复盘之后,本周,又一Java服务呈现了内存问题,这次问题不重大,只会触发堆内存占用高报警,没有触发OOM,但好在之前的复盘中总结了dump脚本,会在堆占用高时主动执行jstack与jmap,使得咱们胜利保留了问题现场。 查看堆占用散布发现有heapdump文件后,我立马拷贝到本机,并应用MAT剖析,如下: 很显然,如同是什么接口调配了十分大的String对象,一个String对象约200MB,那它是哪调配的呢? 查找大对象调配线程这个调配行为必定是某个线程做的,而线程是最常见的GC Root,因而只有查找对象的GC Root即可,如下: 找到了大对象对应的调配线程是http-nio-8088-exec-6,如下: 查看线程栈如何查看这个线程在干什么呢?在MAT中摸索了一会,没找到相干内容,回想起咱们的dump脚本中记录了jstack,关上看看,如下: 能够发现,这个线程正在做json序列化,但我认真找了好一会,也没有找到相干接口的Controller,这是因为线程曾经执行完了Controller外面的逻辑,之后返回接口响应数据时调配的大对象。 可是,线程栈中没有业务代码,就没法定位是哪个接口有问题了。。。 查看accesslog日志思考到调配大对象的接口必定会很慢,于是我转向查看tomcat的accesslog日志,如下: 终于,找到了问题接口,这个接口是用来查问商品数据的,当输出3时会查问出所有3结尾的商品,而这有20w+数据,解决问题很简略,加个limit完事。 排查过程复盘然而,我始终有个习惯,就是解决一个问题后,我会反思一下问题解决过程中有多少运气成分。 如果你常常浏览排查问题类的技术文章,就会发现不少文章,两头忽然有一步定位到了问题根因,可能是忽然发现了一个线索,或是硬看代码看进去的,或是猜想某处有问题,我感觉这种排查过程都有不少运气成分,我心愿问题是通过多年实践根底的积攒和对诊断工具的纯熟应用,而有章法的一步步查出来的。 而下面通过accesslog可能定位到问题,有肯定的运气成分,因为本次内存问题不极其,如果此接口申请量大,那就会霎时触发屡次FGC,进而会影响其它接口也变慢,进而无奈分辨出哪个是导致问题的接口! 我想,从实践上来说,Java堆文件外面,应该有线程栈以及线程栈上的参数,因为线程是对象,参数也是对象,它们理当都在堆里,于是我找了个闲暇工夫,又摸索起MAT这个工具了。 MAT查看线程栈摸索了一会,我就发现有这样一个按钮,能够查看线程信息,如下: 找到后面说的线程http-nio-8088-exec-6,开展后,就能够发现线程栈以及栈上的参数,如下: 这就找到了申请的Request参数对象,再将Request对象屡次开展后,就能够找到接口url信息,如下: 嗯,这样剖析heapdump文件真tm的高效啊 MAT下载地址:https://www.eclipse.org/mat/downloads.php VisualVM查看线程栈思考到不少同学习惯用VisualVM剖析heapdump,这里也放一下VisualVM的应用办法。 首先,加载heapdump文件,如下: 而后抉择相应对象,右键抉择Select in Threads,如下: 定位到线程栈后,找到要查看的Request对象,点击进入,如下: 同样,开展Request对象后,可找到url信息,如下: VisualVM下载地址:https://visualvm.github.io/download.html 总结尽管我也用MAT很屡次了,但每次问题都太简略,以至于没有深刻应用过MAT,导致到当初才晓得有如此便捷的剖析门路。 如果你对咱们的主动dump脚本感兴趣,可看看我之前写的这两篇文章。 一次线上OOM问题的集体复盘 jmap执行失败了,怎么获取heapdump?

April 21, 2023 · 1 min · jiezi

关于jvm:从原理聊JVM一染色标记和垃圾回收算法

作者:京东科技 康志兴 1 JVM运行时内存划分1.1 运行时数据区域 • 办法区 属于共享内存区域,存储已被虚拟机加载的类信息、常量、动态变量、即时编译器编译后的代码等数据。运行时常量池,属于办法区的一部分,用于寄存编译期生成的各种字面量和符号援用。 JDK1.8之前,Hotspot虚拟机对办法区的实现叫做永恒代,1.8之后改为元空间。二者区别次要在于永恒代是在JVM虚拟机中分配内存,而元空间则是在本地内存中调配的。很多类是在运行期间加载的,它们所占用的空间齐全不可控,所以改为应用本地内存,防止对JVM内存的影响。依据《Java虚拟机标准》的规定,如果办法区无奈满足新的内存调配需要时,将抛出OutOfMemoryError异样。 • 堆 线程共享,次要是寄存对象实例和数组。如果在Java堆中没有内存实现实例调配,并且堆也无奈再扩大时,Java虚拟机将会抛出OutOfMemoryError异样。PS:实际上写入时并不齐全共享,JVM会为线程在堆上划分一块专属的调配缓冲区来进步对象调配效率。详见:TLAB • 虚拟机栈 线程公有,办法执行的过程就是一个个栈帧从入栈到出栈的过程。每个办法在执行时都会创立一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动静链接、办法进口等信息。如果线程入栈的栈帧超过限度就会抛出StackOverFlowError,如果反对动静扩大,那么扩大时申请内存失败则抛出OutOfMemoryError。 • 本地办法栈 和虚拟机栈的性能相似,区别是作用于Native办法。 • 程序计数器 线程公有,记录着以后线程所执行的字节码的行号。其作用次要是多线程场景下,记录线程中指令的执行地位。以便被挂起的线程再次被激活时,CPU能从其挂起前执行的地位继续执行。惟一一个在 Java 虚拟机标准中没有规定任何 OutOfMemoryError 状况的区域。留神:如果线程执行的是个java办法,那么计数器记录虚拟机字节码指令的地址。如果为native(底层办法),那么计数器为空。 1.2 对象的内存布局在 HotSpot 虚拟机中,对象分为如下3块区域: • 对象头(Header)运行时数据:哈希码、GC分代年龄、锁状态标记、偏差线程ID、偏差工夫戳等。类型指针:对象的类型元数据的指针,如果对象是数据,还会记录数组长度。 • 对象实例数据(Instance Data)蕴含对象真正的内容,即其包含父类所有字段的值。 • 对齐填充(Padding)对象大小必须是是8字节的整数倍,所以对象大小不满足这个条件时,须要用对齐填充来补齐。 2 标记的办法和流程2.1 判断对象是否须要被回收要分辨一个对象是否能够被回收,有两种形式:援用计数法和可达性算法。 • 援用计数法就是在对象被援用时,计数加1,援用断开时,计数减1。那么一个对象的援用计数为0时,阐明这个对象能够被革除。这个算法的问题在于,如果A对象援用B的同时,B对象也援用A,即循环援用,那么尽管单方的援用计数都不为0,但如果仅仅被对方援用实际上没有存在的价值,应该被GC掉。 • 可达性算法通过援用计数法的缺点能够看出,从被援用一方去断定其是否应该被清理过于全面,所以咱们能够通过相同的方向去定位对象的存活价值:一个存活对象援用的所有对象都是不应该被革除的(Java中软援用或弱援用在GC时有不同断定体现,不在此深究)。这些查找终点被称为GC Root。 2.2 哪些对象能够作为GC Root呢?JAVA虚拟机栈中的本地变量援用对象办法区中动态变量援用的对象办法区中常量援用的对象本地办法栈中JNI援用的对象2.3 疾速找到GC Root - OopMap栈与寄存器都是无状态的,激进式垃圾收集会间接线性扫描栈,再判断每一串数字是不是援用,而HotSpot采纳精确式垃圾收集形式,所有对象都寄存在OopMap(Ordinary Object Pointer)中,当GC产生时,间接从这个map中寻找GC Root。 将GC Root寄存到OopMap有两个触发工夫点: 类加载实现后,HotSpot就会把对象内什么偏移量上是什么类型的数据计算出来。即时编译过程中,也会在特定的地位记录下栈里和寄存器里哪些地位是援用。2.4 更新OopMap的机会 - 平安点导致OopMap更新的指令十分多,所以HotSpot只在特定地位进行记录更新,这些地位叫做平安点。平安点地位的选取的规范是:“是否具备让程序长时间执行”。比方办法调用、循环跳转、异样跳出等等。 2.5 可达性剖析过程三色标记法• 红色:示意垃圾回收过程中,尚未被垃圾收集器拜访过的对象,在可达性剖析开始阶段,所有对象都是红色的,即不可达。 • 彩色:被垃圾收集器拜访过的对象,且这个对象所有的援用均扫描过。彩色的对象是平安存活的,如果其余对象被拜访时发现其援用了彩色对象,该彩色对象也不会再被扫描。 • 灰色:被垃圾收集器拜访过的对象,但这个对象至多有一个援用的对象没有被扫描过。那么标记阶段就是从GC Root的开始,沿着其援用链将每一个对象从红色标记为灰色最初标记为彩色的过程。 标记过程中不统一问题因为这个阶段是层层递进的标记,所以过程中不免呈现不统一的状况导致本来是彩色的对象被标记为红色,比方,以后扫描到B对象了,C对象尚未被拜访时,标记状况如下: ...

April 21, 2023 · 1 min · jiezi

关于jvm:JVM垃圾收集器

对象已死?在堆外面寄存着Java中简直所有的对象实例,垃圾收集器在对堆进行回收前,第一件工夫就是要确定哪些对象还 "存活" 着,哪些曾经 "死去"(代表即不可能再被任何路径应用的对象)了。 援用计数算法原理在对象中增加一个援用计数器,每当有一个中央援用它时,计数器值就加1;当援用生效时,计数器值就减1;任何时刻计数器为0的对象就是不可能再被应用的。 长处简略间接断定效率高 毛病占用了额定的内存空间进行计数 须要额定的内存空间来进行计数无奈解决对象之间的互相循环依赖问题对于相互循环援用,请看以下代码,testGC()办法中有对象objA和objB,对象属性中有instance,赋值 objA.instance = objB及objB.instance = objA,除此之外,这两个对象再无任何援用,实际上两个对象曾经不可能再被拜访,然而它们因为互相援用着对方,导致它们的援用计数都不为0,援用计数器算法也就无奈回收它们。 执行后果 从运行后果中能够看清楚内存回收日之中蕴含 "6717K->608K",意味着虚拟机并没有因为这两个对象互相援用就放弃回收它们,这也从侧面阐明了Java虚拟机并不是通过援用计数算法来判断对象是否存活的。 可达性剖析算法原理可达性剖析算法的基本思路就是通过一系列称为 "GC Roots"的根对象作为起始节点集,从这些节点开始,依据援用关系向下搜寻,搜寻过程所走过的门路称为 "援用链"(Reference Chain),如果某个对象到GC Roots 间没有任何援用链相连,或者用图论到话来说就是从GC Roots到这个对象不可达时,则证实此对象时不可能再被应用的。 如下图所示,对象obj5、obj6、obj7尽管有关联,然而它们到GC Roots是不可达到,因而他们将会断定为可回收的对象。 在Java中,固定可作为GC Roots的对象包含以下几种: 在虚拟机栈(栈帧中的本地变量表)中援用的对象,譬如以后正在运行的办法所用到的参数、局部变量、长期变量等。在办法区中类动态属性援用的对象,譬如Java类的援用类型动态变量。在办法区中常量援用的对象,譬如字符串常量池(String Table)里的援用。在本地办法栈中JNI(通常所说的Native办法)援用的对象。Java虚拟机外部的援用,如根本类型对应的Class对象,一些常驻的异样对象(比方NullPointException、OutOfMemoryError)等,还有零碎类加载器。所有被同步锁(synchronized关键字)持有的对象。反映Java虚拟机外部状况的JMXBean、JVMTI中注册的回调、本地代码缓存等。长处可达性剖析能够解决援用计数器所不能解决的循环援用问题 目前最新的几款垃圾回收器无一例外都具备了部分回收的特色,为了防止GC Roots蕴含过多对象而适度收缩,它们在实现上也做出了各种优化解决。 再谈援用无论是通过援用计数算法判断对象的援用数量,还是通过可达性剖析算法判断对象是否援用链可达,断定对象是否存活和 "援用"离不开关系。在JDK1.2版之前,Java外面的援用是很传统的定义:如果reference类型的数据中存储的数值代表的是另一块内存的起始地址,就称该reference数据时代表某块内存、某个对象的援用。这种定义并没有什么不对,只是当初看来有些过于狭窄了,一个对象在这种定义下只有 "被援用" 或者 "未被援用" 两种状态,对于形容一些 "食之无味,弃之可惜" 的对象就显得无能为力。譬如咱们心愿能形容一类对象:当内存空间还足够时,能保留在内存之中,如果内存空间在进行垃圾收集后依然十分缓和,那就能够摈弃这些对象--很多零碎的缓存性能都合乎这样的利用场景。 在JDK1.2版之后,Java对援用的对象进行了裁减,将援用分为强援用(Strongly Reference)、软援用(Soft Reference)、弱援用(Weak Reference)和虚援用(Phantom Reference)4种,这4种援用强度顺次削弱。 强援用是最传统的 "援用" 的定义,是指在程序代码之中普遍存在的援用赋值,即相似 "Object obj = new Object()" 这种援用关系。无论任何状况下,只有强援用关系还存在,还可达,垃圾收集器就永远不会回收掉被援用的对象。软援用是用来形容一些还有用,但非必须的对象。只被软援用关联着的对象,在零碎将要产生内存溢出异样前,会把这些对象列进回收范畴之中进行二次回收,如果这次回收还没有足够的内存,才会抛出内存溢出异样。在JDK1.2之后提供了SoftReference类来实现软援用。弱援用也是用来形容那些非必须对象,然而它的强度比软援用更弱一些,被弱援用关联的对象只能生存到下一次垃圾收集产生为止。当垃圾收集器开始工作,无论以后内存是否足够,都会回收掉只被弱援用关联的对象。在JDK1.2版本之后提供了WeakReference类来实现若援用。虚援用也称为 "幽灵援用" 或者 "幻影援用",它是最弱的一种援用关系。一个对象是否有虚援用的存在,齐全不会对其生存工夫形成影响,也无奈通过虚援用来去的一个对象实例。为一个对象设置虚援用关联的惟一目标只是为了能在这个对象被收集器回收时收到一个零碎告诉。在JDK1.2版之后提供了PhantomReference类来实现虚援用。生存or死亡?即便在可达性剖析算法中被断定为不可达到对象,也不是 "非死不可" 的,这时候它们还谢世处于 "缓刑" 阶段,要真正宣告一个对象死亡,最多会经验两次标记过程:如果对象在进行可达剖析之后发现没有与GC Toots相连接的援用链,那他将会被第一次标记,随后进行一次筛选,筛选的条件是此对象是否有必要执行finalize()办法。如果对象没有笼罩finalize()办法,或者finalize()办法曾经被虚拟机调用过,那么虚拟机将这两种状况都视为 "没有必要执行"。 如果这个对象被断定为确有必要执行finalize()办法,那么该对象将会被搁置在一个F-Queue的队列之中,并在稍后由一条由虚拟机主动建设的、低调度优先级的Finalizer线程去执行它们的finalize()办法。这里所说的 "执行" 是指虚构机会触发这个办法开始运行,但并不承诺肯定会期待它运行完结。这样做的起因是,如果某个对象的finalize()办法执行迟缓,或者更极其地产生了死循环,将很可能导致F-Queue队列中的其余对象永恒处于期待,甚至导致整个内存回收子系统的解体。finalize()办法是对象逃脱死亡的最初一次机会,稍后收集器将堆F-Queue中的对象进行第二次小规模的标记,如果对象要在finalize()中胜利援救本人————只有从新与援用链上的任何一个对象建设关联即可,譬如把本人(this关键字)赋值给某个类变量或者对象的成员变量,那在第二次标记时它将会被移出 "行将回收" 的汇合;如果对象这时候还没有逃脱,那基本上它就真的要被回收了。从以下代码中咱们能够看到一个对象的finalize()被执行,然而它仍然能够存活。 ...

April 20, 2023 · 1 min · jiezi

关于jvm:Java虚拟机的基本结构

Java虚拟机根本构造 类加载子系统类加载子系统负责从文件系统或网络中加载Class信息,加载的类的数据结构寄存于一块叫办法区的内存空间中。对于类加载子系统相干请查看:JVM虚拟机的类加载机制 办法区办法区次要存储类加载后的数据结构信息、运行时常量池信息、字符串、数字常量(这部分常量信息是Class文件中常量池局部的内存映射)。 Java堆Java堆在虚拟机启动的时候建设,它是Java程序最次要的内存工作区域。简直所有的Java对象实例都寄存于Java堆中。堆空间是所有线程共享的,这是一块与Java利用密切相关的内存区域。 间接内存Java的NIO库容许Java程序应用间接内存。间接内存是在Java堆外的、间接向零碎申请的内存区域。通常拜访间接内存的速度会优于Java堆。因而,出于性能思考,读写频繁的场合可能会思考应用间接内存。因为间接内存在Java堆外,因而,它的大小不会间接受限于Xmx指定的最大堆大小,然而零碎内存是无限的,Java堆和间接内存的总和仍然受限于操作系统的最大内存。 垃圾回收零碎垃圾回收零碎是Java虚拟机的重要组成部分,垃圾回收器能够对办法区、Java堆和间接内存进行回收。其中,Java堆是垃圾收集器的工作重点。和C/C++不同,Java中所有的对象空间开释都是隐式的。也就是说,Java中没有蕾丝free()或者delete()这样的函数开释指定的内存区域。对于不再应用的垃圾对象,垃圾回收零碎会在后盾默默工作,默默查找、标识并开释垃圾对象,实现包含Java堆、办法区和间接内存中的全自动化治理 Java栈每一个Java虚拟机线程都有一个公有的Java栈。一个线程的Java栈在线程创立的时候被创立。Java栈中保留着帧信息,Java战中保留着局部变量、办法参数,同时和Java办法调用、返回密切相关。 本地办法栈本地办法栈和Java栈十分相似,最大的不同在于Java栈用于办法的调用,而本地办法栈则用于本地办法的调用。作为对Java虚拟机的重要扩大,Java虚拟机容许Java间接调用本地办法(通常应用C语言编写)。 PC(Program Counter)寄存器PC寄存器也是每个线程公有的空间,Java虚构机会为每一个Java线程创立PC寄存器。在任意时刻,一个Java线程总是在执行一个办法,这个正在被执行的办法称作以后办法。如果以后办法不是本地办法,PC寄存器就会指向以后正在被执行的指令。如果以后办法是本地办法,那么PC寄存器的值就是undefinded。 执行引擎执行引擎是Java虚拟机的最外围组件之一,它负责执行虚拟机的字节码。古代虚拟机为了进步执行效率,会应用即时编译技术将办法编译成机器码后在执行。

April 18, 2023 · 1 min · jiezi

关于jvm:编译Java虚拟机

编译前筹备装置hg版本控制yum install -y hg装置依赖库yum install -y "Development Tools"yum install -y gcc g++ kernel-develyum? -y install libXtst-devel libXt-devel libXrender-devel libXi-devel筹备JDK为编译虚拟机,首先必须取得虚拟机的源码,大家能够应用上面的命令获取JDK10的源码。举荐应用较新的版本,因为老版本的编译脚本可能在某平台上存在问题。 hg clone http://hg.openjdk.java.net/jdk10/jdk10筹备一个BootJDK。Boot JDK用于OpenJDK编译的执行。这里用到的是JDK8,也举荐各位应用JDK8作为JDK10的Boot JDK 筹备编译准备就绪之后,就能够开始编译了。 进入解压后的openjdk目录: 执行configure脚本配置编译选项,自己的配置如下:bash configure --with-debug-level=slowdebug --with-jvm-variants=server --with-target-bits=64 --with-memory-size=3000 --disable-warnings-as-errors --with-boot-jdk=/usr/local/src/jdk1.8配置胜利后会显示上面的信息: 通过make images 命令执行整个编译将会生成debug版本的虚拟机,编译的过程可能会破费比拟长的工夫,一般来说,编译一个版本可能要15-45分钟的,视计算机性能而定。当编译胜利后,会有以下输入: 略。。。

April 14, 2023 · 1 min · jiezi

关于jvm:关键的Java-JVM选项和参数

1. 要害的Java JVM选项和参数让咱们来看看在Java环境中能够配置的21个最重要的JVM选项和参数。 -Xms:将设置JVM的初始堆大小。-Xmx:将设置JVM的最大堆大小。-Xss:将设置每个线程的外部应用的线程堆栈的大小。-XX:+UseCompressedOops:启用应用压缩对象指针以缩小内存应用的性能。-XX:+UseThreadPriorities:将批示JVM应用本机线程优先级。-XX:PermSize:将设置垃圾收集器永恒生成空间的初始大小。-XX:MaxPermSize:将设置垃圾收集器永恒生成空间的最大大小。-XX:NewSize:设置年老代空间的初始大小。-XX:MaxNewSize:设置年老代空间的最大大小。-XX:SurvivorRatio:设置伊甸园空间与幸存者空间的比例。-XX:MaxTenuringThreshold:设置幸存者空间中对象的最大年龄。-XX:+UseParNewGC:批示JVM应用新的并行生成垃圾收集器。-XX:+UseSerialGC:批示JVM应用串行垃圾收集器。-XX:+UseG1GC:批示JVM应用Garbage First(G1)垃圾收集器。-XX:+UseZGC:批示JVM应用ZGC垃圾收集器。-XX:+HeapDumpOnOutOfMemoryError:通知JVM在产生OutOfMemoryError时创立堆转储文件。-XX:HeapDumpPath:为JVM提供自定义门路,在堆转储期间写入堆的内容。-Djava.library.path:容许您指定在运行时须要的本机库的门路。-Duser.timezone:容许您为JVM设置自定义时区。-XX:+PrintGCDetails:批示JVM打印具体的垃圾回收日志,以帮忙您进行GC优化。-XX:+PrintFlagsFinal-version:将打印在JVM上设置的所有以后配置的标记和选项。2. 如何应用Java JVM选项所有这些JVM选项都能够通过将它们作为文本附加到Java运行时命令起初简略地应用。 例如,以下命令将应用六个不同的参数运行名为Go的应用程序,以优化内存调配和垃圾回收: java Go -XX:MaxPermSize=128m -XX:MaxNewSize=256m -Xms768m -Xmx768m -XX:SurvivorRatio=128 -XX:MaxTenuringThreshold=0 Java JVM选项可用于治理内存和优化GC性能 3. 最罕用的JVM参数在列举的所有 JVM 选项中,最罕用的是 Xms 和 Xmx,别离设置最小堆大小和最大堆大小。 上面的示例将最小堆大小设置为 768 MB,最大堆大小设置为 2 GB。 -Xms768m-Xmx20484. GC 抉择 JVM 选项Java的一个长处是它为开发者执行垃圾回收,这使得应用程序更加强壮,更不容易产生内存透露问题。 有许多垃圾回收器可用,具备各种暂停行为和进展工夫。 在启动运行时,您只能应用以下 Java JVM 选项之一抉择一个垃圾收集器: -XX:+UseSerialGC-XX:+UseParallelGC-XX:+USeParNewGC-XX:+UseG1GC-XX:+UseZGC5. 垃圾回收调优选项VM实现了一种分代垃圾回收算法,它踊跃监控新对象,而很少查看旧对象。JVM治理eden空间、tenured空间甚至PermGen空间的形式能够通过JVM选项进行配置,如下: -XX:MaxPermSize-XX:PermSize-XX:NewSize-XX:MaxNewSize-XX:SurvivorRatio-XX:MaxTenuringThreshold6. 用于查看的JVM打印选项JVM还提供了一些打印办法,容许您查看Java运行时的状态。有用的JVM打印选项包含: -XX:+PrintGCDetails-XX:+PrintGCDateStamps-XX:+PrintHeapAtGC-XX:+PrintCommandLineFlags-XX:+PrintFlagsFinalPrintFlagsFinal是一项乏味的JVM选项,它将显示所有JVM标记设置的详细信息,输入内容有超过500行。本文介绍的Java JVM选项曾经全副解说结束,如果你对PrintFlagsFinal JVM标记的具体输入感兴趣,能够查看上文提到的输入内容。 $ java -XX:+PrintFlagsFinal -version[Global flags]int ActiveProcessorCount = -1uintx AdaptiveSizeDecrementScaleFactor = 4uintx AdaptiveSizeMajorGCDecayTimeScale = 10uintx AdaptiveSizePolicyCollectionCostMargin = 50uintx AdaptiveSizePolicyInitializingSteps = 20uintx AdaptiveSizePolicyOutputInterval = 0uintx AdaptiveSizePolicyWeight = 10uintx AdaptiveSizeThroughPutPolicy = 0uintx AdaptiveTimeWeight = 25bool AggressiveHeap = falseintx AliasLevel = 3bool AlignVector = falseccstr AllocateHeapAt =intx AllocateInstancePrefetchLines = 1intx AllocatePrefetchDistance = 256intx AllocatePrefetchInstr = 0intx AllocatePrefetchLines = 3intx AllocatePrefetchStepSize = 64intx AllocatePrefetchStyle = 1bool AllowParallelDefineClass = falsebool AllowRedefinitionToAddDeleteMethods = falsebool AllowUserSignalHandlers = falsebool AllowVectorizeOnDemand = truebool AlwaysActAsServerClassMachine = falsebool AlwaysCompileLoopMethods = falsebool AlwaysLockClassLoader = falsebool AlwaysPreTouch = falsebool AlwaysRestoreFPU = falsebool AlwaysTenure = falseccstr ArchiveClassesAtExit =intx ArrayCopyLoadStoreMaxElem = 8size_t AsyncLogBufferSize = 2097152intx AutoBoxCacheMax = 128intx BCEATraceLevel = 0bool BackgroundCompilation = truesize_t BaseFootPrintEstimate = 268435456intx BiasedLockingBulkRebiasThreshold = 20intx BiasedLockingBulkRevokeThreshold = 40intx BiasedLockingDecayTime = 25000intx BiasedLockingStartupDelay = 0bool BlockLayoutByFrequency = trueintx BlockLayoutMinDiamondPercentage = 20bool BlockLayoutRotateLoops = trueintx C1InlineStackLimit = 5intx C1MaxInlineLevel = 9intx C1MaxInlineSize = 35intx C1MaxRecursiveInlineLevel = 1intx C1MaxTrivialSize = 6bool C1OptimizeVirtualCallProfiling = truebool C1ProfileBranches = truebool C1ProfileCalls = truebool C1ProfileCheckcasts = truebool C1ProfileInlinedCalls = truebool C1ProfileVirtualCalls = truebool C1UpdateMethodData = trueintx CICompilerCount = 4bool CICompilerCountPerCPU = truebool CITime = falsebool CheckJNICalls = falsebool ClassUnloading = truebool ClassUnloadingWithConcurrentMark = truebool ClipInlining = trueuintx CodeCacheExpansionSize = 65536bool CompactStrings = trueccstr CompilationMode = defaultccstrlist CompileCommand =ccstr CompileCommandFile =ccstrlist CompileOnly =intx CompileThreshold = 10000double CompileThresholdScaling = 1.000000intx CompilerThreadPriority = -1intx CompilerThreadStackSize = 0size_t CompressedClassSpaceSize = 1073741824uint ConcGCThreads = 3intx ConditionalMoveLimit = 3intx ContendedPaddingWidth = 128bool CrashOnOutOfMemoryError = falsebool CreateCoredumpOnCrash = truebool CriticalJNINatives = falsebool DTraceAllocProbes = falsebool DTraceMethodProbes = falsebool DTraceMonitorProbes = falsebool DisableAttachMechanism = falsebool DisableExplicitGC = falsebool DisplayVMOutputToStderr = falsebool DisplayVMOutputToStdout = falsebool DoEscapeAnalysis = truebool DoReserveCopyInSuperWord = truebool DontCompileHugeMethods = truebool DontYieldALot = falseccstr DumpLoadedClassList =bool DumpReplayDataOnError = truebool DumpSharedSpaces = falsebool DynamicDumpSharedSpaces = falsebool EagerXrunInit = falseintx EliminateAllocationArraySizeLimit = 64bool EliminateAllocations = truebool EliminateAutoBox = truebool EliminateLocks = truebool EliminateNestedLocks = truebool EnableContended = truebool EnableDynamicAgentLoading = truesize_t ErgoHeapSizeLimit = 0ccstr ErrorFile =bool ErrorFileToStderr = falsebool ErrorFileToStdout = falseuint64_t ErrorLogTimeout = 120double EscapeAnalysisTimeout = 20.000000bool EstimateArgEscape = truebool ExecutingUnitTests = falsebool ExitOnOutOfMemoryError = falsebool ExplicitGCInvokesConcurrent = falsebool ExtendedDTraceProbes = falsebool ExtensiveErrorReports = falseccstr ExtraSharedClassListFile =bool FilterSpuriousWakeups = truebool FlightRecorder = falseccstr FlightRecorderOptions =bool ForceTimeHighResolution = falseintx FreqInlineSize = 325double G1ConcMarkStepDurationMillis = 10.000000uintx G1ConcRSHotCardLimit = 4size_t G1ConcRSLogCacheSize = 10size_t G1ConcRefinementGreenZone = 0size_t G1ConcRefinementRedZone = 0uintx G1ConcRefinementServiceIntervalMillis = 300uint G1ConcRefinementThreads = 10size_t G1ConcRefinementThresholdStep = 2size_t G1ConcRefinementYellowZone = 0uintx G1ConfidencePercent = 50size_t G1HeapRegionSize = 2097152uintx G1HeapWastePercent = 5uintx G1MixedGCCountTarget = 8uintx G1PeriodicGCInterval = 0bool G1PeriodicGCInvokesConcurrent = truedouble G1PeriodicGCSystemLoadThreshold = 0.000000intx G1RSetRegionEntries = 512intx G1RSetSparseRegionEntries = 16intx G1RSetUpdatingPauseTimePercent = 10uint G1RefProcDrainInterval = 1000uintx G1ReservePercent = 10uintx G1SATBBufferEnqueueingThresholdPercent = 60size_t G1SATBBufferSize = 1024size_t G1UpdateBufferSize = 256bool G1UseAdaptiveConcRefinement = truebool G1UseAdaptiveIHOP = trueuintx GCDrainStackTargetSize = 64uintx GCHeapFreeLimit = 2uintx GCLockerEdenExpansionPercent = 5uintx GCPauseIntervalMillis = 201uintx GCTimeLimit = 98uintx GCTimeRatio = 12size_t HeapBaseMinAddress = 2147483648bool HeapDumpAfterFullGC = falsebool HeapDumpBeforeFullGC = falseintx HeapDumpGzipLevel = 0bool HeapDumpOnOutOfMemoryError = falseccstr HeapDumpPath =uintx HeapFirstMaximumCompactionCount = 3uintx HeapMaximumCompactionInterval = 20uintx HeapSearchSteps = 3size_t HeapSizePerGCThread = 43620760bool IgnoreEmptyClassPaths = falsebool IgnoreUnrecognizedVMOptions = falseuintx IncreaseFirstTierCompileThresholdAt = 50bool IncrementalInline = trueuintx InitialCodeCacheSize = 2555904size_t InitialHeapSize = 268435456uintx InitialRAMFraction = 64double InitialRAMPercentage = 1.562500uintx InitialSurvivorRatio = 8uintx InitialTenuringThreshold = 7uintx InitiatingHeapOccupancyPercent = 45bool Inline = trueccstr InlineDataFile =intx InlineSmallCode = 2500bool InlineSynchronizedMethods = trueintx InteriorEntryAlignment = 16intx InterpreterProfilePercentage = 33bool JavaMonitorsInStackTrace = trueintx JavaPriority10_To_OSPriority = -1intx JavaPriority1_To_OSPriority = -1intx JavaPriority2_To_OSPriority = -1intx JavaPriority3_To_OSPriority = -1intx JavaPriority4_To_OSPriority = -1intx JavaPriority5_To_OSPriority = -1intx JavaPriority6_To_OSPriority = -1intx JavaPriority7_To_OSPriority = -1intx JavaPriority8_To_OSPriority = -1intx JavaPriority9_To_OSPriority = -1size_t LargePageHeapSizeThreshold = 134217728size_t LargePageSizeInBytes = 0intx LiveNodeCountInliningCutoff = 40000intx LoopMaxUnroll = 16intx LoopOptsCount = 43intx LoopPercentProfileLimit = 10uintx LoopStripMiningIter = 1000uintx LoopStripMiningIterShortLoop = 100intx LoopUnrollLimit = 60intx LoopUnrollMin = 4bool LoopUnswitching = truebool ManagementServer = falsesize_t MarkStackSize = 4194304size_t MarkStackSizeMax = 536870912uint MarkSweepAlwaysCompactCount = 4uintx MarkSweepDeadRatio = 5intx MaxBCEAEstimateLevel = 5intx MaxBCEAEstimateSize = 150uint64_t MaxDirectMemorySize = 0bool MaxFDLimit = trueuintx MaxGCMinorPauseMillis = 18446744073709551615uintx MaxGCPauseMillis = 200uintx MaxHeapFreeRatio = 70size_t MaxHeapSize = 4282384384intx MaxInlineLevel = 15intx MaxInlineSize = 35intx MaxJNILocalCapacity = 65536intx MaxJavaStackTraceDepth = 1024intx MaxJumpTableSize = 65000intx MaxJumpTableSparseness = 5intx MaxLabelRootDepth = 1100intx MaxLoopPad = 15size_t MaxMetaspaceExpansion = 5439488uintx MaxMetaspaceFreeRatio = 70size_t MaxMetaspaceSize = 18446744073709551615size_t MaxNewSize = 2569011200intx MaxNodeLimit = 80000uint64_t MaxRAM = 137438953472uintx MaxRAMFraction = 4double MaxRAMPercentage = 25.000000intx MaxRecursiveInlineLevel = 1uintx MaxTenuringThreshold = 15intx MaxTrivialSize = 6intx MaxVectorSize = 32ccstr MetaspaceReclaimPolicy = balancedsize_t MetaspaceSize = 22020096bool MethodFlushing = truesize_t MinHeapDeltaBytes = 2097152uintx MinHeapFreeRatio = 40size_t MinHeapSize = 8388608intx MinInliningThreshold = 250intx MinJumpTableSize = 10size_t MinMetaspaceExpansion = 327680uintx MinMetaspaceFreeRatio = 40uintx MinRAMFraction = 2double MinRAMPercentage = 50.000000uintx MinSurvivorRatio = 3size_t MinTLABSize = 2048intx MultiArrayExpandLimit = 6uintx NUMAChunkResizeWeight = 20size_t NUMAInterleaveGranularity = 2097152uintx NUMAPageScanRate = 256size_t NUMASpaceResizeRate = 1073741824bool NUMAStats = falseccstr NativeMemoryTracking = offbool NeverActAsServerClassMachine = falsebool NeverTenure = falseuintx NewRatio = 2size_t NewSize = 1363144size_t NewSizeThreadIncrease = 5320intx NmethodSweepActivity = 10intx NodeLimitFudgeFactor = 2000uintx NonNMethodCodeHeapSize = 5839372uintx NonProfiledCodeHeapSize = 122909434intx NumberOfLoopInstrToAlign = 4intx ObjectAlignmentInBytes = 8 {size_t OldPLABSize = 1024size_t OldSize = 5452592bool OmitStackTraceInFastThrow = trueccstrlist OnError =ccstrlist OnOutOfMemoryError =intx OnStackReplacePercentage = 140bool OptimizeFill = falsebool OptimizePtrCompare = truebool OptimizeStringConcat = truebool OptoBundling = falseintx OptoLoopAlignment = 16bool OptoRegScheduling = truebool OptoScheduling = falseuintx PLABWeight = 75bool PSChunkLargeArrays = trueint ParGCArrayScanChunk = 50uintx ParallelGCBufferWastePct = 10uint ParallelGCThreads = 10size_t ParallelOldDeadWoodLimiterMean = 50size_t ParallelOldDeadWoodLimiterStdDev = 80bool ParallelRefProcBalancingEnabled = truebool ParallelRefProcEnabled = truebool PartialPeelAtUnsignedTests = truebool PartialPeelLoop = trueintx PartialPeelNewPhiDelta = 0uintx PausePadding = 1intx PerBytecodeRecompilationCutoff = 200intx PerBytecodeTrapLimit = 4intx PerMethodRecompilationCutoff = 400intx PerMethodTrapLimit = 100bool PerfAllowAtExitRegistration = falsebool PerfBypassFileSystemCheck = falseintx PerfDataMemorySize = 32768intx PerfDataSamplingInterval = 50ccstr PerfDataSaveFile =bool PerfDataSaveToFile = falsebool PerfDisableSharedMem = falseintx PerfMaxStringConstLength = 1024size_t PreTouchParallelChunkSize = 1073741824bool PreferInterpreterNativeStubs = falseintx PrefetchCopyIntervalInBytes = 576intx PrefetchFieldsAhead = 1intx PrefetchScanIntervalInBytes = 576bool PreserveAllAnnotations = falsebool PreserveFramePointer = falsesize_t PretenureSizeThreshold = 0bool PrintClassHistogram = falsebool PrintCodeCache = falsebool PrintCodeCacheOnCompilation = falsebool PrintCommandLineFlags = falsebool PrintCompilation = falsebool PrintConcurrentLocks = falsebool PrintExtendedThreadInfo = falsebool PrintFlagsFinal = truebool PrintFlagsInitial = falsebool PrintFlagsRanges = falsebool PrintGC = falsebool PrintGCDetails = falsebool PrintHeapAtSIGBREAK = truebool PrintSharedArchiveAndExit = falsebool PrintSharedDictionary = falsebool PrintStringTableStatistics = falsebool PrintTieredEvents = falsebool PrintVMOptions = falsebool PrintWarnings = trueuintx ProcessDistributionStride = 4bool ProfileInterpreter = trueintx ProfileMaturityPercentage = 20uintx ProfiledCodeHeapSize = 122909434uintx PromotedPadding = 3uintx QueuedAllocationWarningCount = 0int RTMRetryCount = 5bool RangeCheckElimination = truebool ReassociateInvariants = truebool RecordDynamicDumpInfo = falsebool ReduceBulkZeroing = truebool ReduceFieldZeroing = truebool ReduceInitialCardMarks = truebool ReduceSignalUsage = falseintx RefDiscoveryPolicy = 0bool RegisterFinalizersAtInit = truebool RelaxAccessControlCheck = falseccstr ReplayDataFile =bool RequireSharedSpaces = falseuintx ReservedCodeCacheSize = 251658240bool ResizePLAB = truebool ResizeTLAB = truebool RestoreMXCSROnJNICalls = falsebool RestrictContended = truebool RestrictReservedStack = truebool RewriteBytecodes = truebool RewriteFrequentPairs = truebool SafepointTimeout = falseintx SafepointTimeoutDelay = 10000bool ScavengeBeforeFullGC = falsebool SegmentedCodeCache = trueintx SelfDestructTimer = 0ccstr SharedArchiveConfigFile =ccstr SharedArchiveFile =size_t SharedBaseAddress = 34359738368ccstr SharedClassListFile =uintx SharedSymbolTableBucketSize = 4ccstr ShenandoahGCHeuristics = adaptiveccstr ShenandoahGCMode = satbbool ShowCodeDetailsInExceptionMessages = truebool ShowMessageBoxOnError = falsebool ShrinkHeapInSteps = truesize_t SoftMaxHeapSize = 4282384384intx SoftRefLRUPolicyMSPerMB = 1000bool SplitIfBlocks = trueintx StackRedPages = 1intx StackReservedPages = 0intx StackShadowPages = 7bool StackTraceInThrowable = trueintx StackYellowPages = 3uintx StartAggressiveSweepingAt = 10bool StartAttachListener = falseccstr StartFlightRecording =uint StringDeduplicationAgeThreshold = 3uintx StringTableSize = 65536bool SuperWordLoopUnrollAnalysis = truebool SuperWordReductions = truebool SuppressFatalErrorMessage = falseuintx SurvivorPadding = 3uintx SurvivorRatio = 8double SweeperThreshold = 0.500000uintx TLABAllocationWeight = 35uintx TLABRefillWasteFraction = 64size_t TLABSize = 0bool TLABStats = trueuintx TLABWasteIncrement = 4uintx TLABWasteTargetPercent = 1uintx TargetPLABWastePct = 10uintx TargetSurvivorRatio = 50uintx TenuredGenerationSizeIncrement = 20uintx TenuredGenerationSizeSupplement = 80uintx TenuredGenerationSizeSupplementDecay = 2intx ThreadPriorityPolicy = 0bool ThreadPriorityVerbose = falseintx ThreadStackSize = 0uintx ThresholdTolerance = 10intx Tier0BackedgeNotifyFreqLog = 10intx Tier0InvokeNotifyFreqLog = 7intx Tier0ProfilingStartPercentage = 200intx Tier23InlineeNotifyFreqLog = 20intx Tier2BackEdgeThreshold = 0intx Tier2BackedgeNotifyFreqLog = 14intx Tier2CompileThreshold = 0intx Tier2InvokeNotifyFreqLog = 11intx Tier3BackEdgeThreshold = 60000intx Tier3BackedgeNotifyFreqLog = 13intx Tier3CompileThreshold = 2000intx Tier3DelayOff = 2intx Tier3DelayOn = 5intx Tier3InvocationThreshold = 200intx Tier3InvokeNotifyFreqLog = 10intx Tier3LoadFeedback = 5intx Tier3MinInvocationThreshold = 100intx Tier4BackEdgeThreshold = 40000intx Tier4CompileThreshold = 15000intx Tier4InvocationThreshold = 5000intx Tier4LoadFeedback = 3intx Tier4MinInvocationThreshold = 600bool TieredCompilation = trueintx TieredCompileTaskTimeout = 50intx TieredRateUpdateMaxTime = 25intx TieredRateUpdateMinTime = 1intx TieredStopAtLevel = 4bool TimeLinearScan = falseccstr TraceJVMTI =intx TrackedInitializationLimit = 50bool TrapBasedNullChecks = falsebool TrapBasedRangeChecks = falseintx TypeProfileArgsLimit = 2uintx TypeProfileLevel = 111intx TypeProfileMajorReceiverPercent = 90intx TypeProfileParmsLimit = 2intx TypeProfileWidth = 2intx UnguardOnExecutionViolation = 0bool UseAES = trueintx UseAVX = 2bool UseAdaptiveGenerationSizePolicyAtMajorCollection = truebool UseAdaptiveGenerationSizePolicyAtMinorCollection = truebool UseAdaptiveNUMAChunkSizing = truebool UseAdaptiveSizeDecayMajorGCCost = truebool UseAdaptiveSizePolicy = truebool UseAdaptiveSizePolicyFootprintGoal = truebool UseAdaptiveSizePolicyWithSystemGC = falsebool UseAddressNop = truebool UseBASE64Intrinsics = falsebool UseBMI1Instructions = truebool UseBMI2Instructions = truebool UseBiasedLocking = falsebool UseBimorphicInlining = truebool UseCLMUL = truebool UseCMoveUnconditionally = falsebool UseCodeAging = truebool UseCodeCacheFlushing = truebool UseCompiler = truebool UseCompressedClassPointers = true {bool UseCompressedOops = true {bool UseCondCardMark = falsebool UseCountLeadingZerosInstruction = truebool UseCountTrailingZerosInstruction = truebool UseCountedLoopSafepoints = truebool UseCounterDecay = truebool UseDivMod = truebool UseDynamicNumberOfCompilerThreads = truebool UseDynamicNumberOfGCThreads = truebool UseEmptySlotsInSupers = truebool UseFMA = truebool UseFPUForSpilling = truebool UseFastJNIAccessors = truebool UseFastStosb = falsebool UseG1GC = truebool UseGCOverheadLimit = truebool UseHeavyMonitors = falsebool UseInlineCaches = truebool UseInterpreter = truebool UseJumpTables = truebool UseLargePages = falsebool UseLargePagesIndividualAllocation = falsebool UseLoopCounter = truebool UseLoopInvariantCodeMotion = truebool UseLoopPredicate = truebool UseMaximumCompactionOnSystemGC = truebool UseNUMA = falsebool UseNUMAInterleaving = falsebool UseNewLongLShift = truebool UseNotificationThread = truebool UseOSErrorReporting = falsebool UseOnStackReplacement = truebool UseOnlyInlinedBimorphic = truebool UseOptoBiasInlining = falsebool UsePSAdaptiveSurvivorSizePolicy = truebool UseParallelGC = falsebool UsePerfData = truebool UsePopCountInstruction = truebool UseProfiledLoopPredicate = truebool UseRTMDeopt = falsebool UseRTMLocking = falsebool UseSHA = trueintx UseSSE = 4bool UseSSE42Intrinsics = truebool UseSerialGC = falsebool UseSharedSpaces = truebool UseShenandoahGC = falsebool UseSignalChaining = truebool UseStoreImmI16 = truebool UseStringDeduplication = falsebool UseSubwordForMaxVector = truebool UseSuperWord = truebool UseTLAB = truebool UseThreadPriorities = truebool UseTypeProfile = truebool UseTypeSpeculation = truebool UseUnalignedLoadStores = truebool UseVectorCmov = falsebool UseXMMForArrayCopy = truebool UseXMMForObjInit = truebool UseXmmI2D = truebool UseXmmI2F = truebool UseXmmLoadAndClearUpper = truebool UseXmmRegToRegMoveAll = truebool UseZGC = falseintx VMThreadPriority = -1intx VMThreadStackSize = 0intx ValueMapInitialSize = 11intx ValueMapMaxLoopSize = 8intx ValueSearchLimit = 1000bool VerifySharedSpaces = falseuintx YoungGenerationSizeIncrement = 20uintx YoungGenerationSizeSupplement = 80uintx YoungGenerationSizeSupplementDecay = 8size_t YoungPLABSize = 4096double ZAllocationSpikeTolerance = 2.000000double ZCollectionInterval = 0.000000double ZFragmentationLimit = 25.000000size_t ZMarkStackSpaceLimit = 8589934592bool ZProactive = truebool ZUncommit = trueuintx ZUncommitDelay = 300bool ZeroTLAB = falseopenjdk version "17.0.6" 2023-01-17OpenJDK Runtime Environment Temurin-17.0.6+10 (build 17.0.6+10)OpenJDK 64-Bit Server VM Temurin-17.0.6+10 (build 17.0.6+10, mixed mode, sharing)

April 14, 2023 · 8 min · jiezi

关于jvm:整数在java虚拟机中的表示

了解Java虚拟机整数示意的前置内容在Java的虚拟机中,整数有byte、short、int、long四种,别离示意8位、16位、32位、64位有符号整数。整数在计算机中用补码示意,在Java虚拟机中也不例外。在学习补码之前,必须先了解原码和反码。 原码所谓原码,就是符号位加上数字的二进制示意。以int为例,第1位示意符号位(负数或者正数),其余31位示意该数字的二进制值。 10的原码为: 00000000000000000000000000001010-10的原码为:10000000000000000000000000001010对于原码来说,绝对值雷同的负数和正数只有符号位不同。 反码反码就是在原码的根底上,符号位不变,对其余位取反,以-10为例,其反码为: 11111111111111111111111111110101补码正数的补码就是反码加1,整数的补码就是原码自身。因而,10的补码为: 00000000000000000000000000001010而-10的补码为 11111111111111111111111111110110在Java中,能够应用位运算查看整数中每一位的理论值,办法如下: 1 public static void printBinary(int number) {2 for (int i = 0; i < 32; i++) {3 int t = (number & 0x80000000 >>> i) >>> (31 - i);4 System.out.print(t);5 }6 }以上代码将打印 -10 在虚拟机内的理论示意,程序的执行后果如下: 11111111111111111111111111110110能够看到这个后果和之前的补码计算是齐全匹配的。这段程序的根本思维是:进行32次循环(因为int有32位),每次循环取出int值中的第一位,第三行的 0x80000000 是一个 首位为1、其余位为0的整数,通过右移i位,定位到要获取的第i位,并将除该位的其余位对立设置为0,而该位不变,最初将该位移至最初,并运行输入。 绝对于原码,应用补码作为计算机内的理论存储形式至多有以下两个益处: (1)能够对立数字0的示意。因为0既非负数,又非正数,应用原码示意时符号位难以确定,把0纳入整数或者正数失去的原码后果是不同的。然而应用补码示意时,无论把0纳入负数还是正数都会失去雷同的后果。计算过程如下: 如果0为负数,则补码为原码自身:00000000000000000000000000000000如果0为正数,则补码为反码加1,正数的0的原码为:10000000000000000000000000000000反码为:11111111111111111111111111111111补码在反码的根底上加1,后果为:00000000000000000000000000000000能够看到,应用补码作为整数编码,能够解决数字0的存储问题。 (2)应用补码能够简化整数的加减法计算,将减法计算视为加法计算,实现减法和加法的齐全对立,实现负数和正数加法的对立。先应用8位(byte)整数阐明这个问题。 计算-6+5的过程如下: -6的补码:111110105的补码:00000101间接相加得:11111111通过计算可知,11111111示意-1计算4+6的过程如下: 4的补码:000001006的补码:00000110间接相加得:00001010通过计算可知,000001010示意10(十进制)。能够看到,应用补码示意时,只须要将补码简略地相加,即可失去算术加法的正确后果,而无须区别负数或者正数。

April 13, 2023 · 1 min · jiezi

关于jvm:一次线上OOM问题的个人复盘

原创:扣钉日记(微信公众号ID:codelogs),欢送分享,非公众号转载保留此申明。上个月,咱们一个java服务上线后,偶然会产生内存OOM(Out Of Memory)问题,但因为OOM导致服务不响应申请,健康检查屡次不通过,最初部署平台kill了java过程,这导致定位这次OOM问题也变得艰难起来。 最终,在屡次review代码后发现,是SQL意外地查出大量数据导致的,如下: <sql id="conditions"> <where> <if test="outerId != null"> and `outer_id` = #{outerId} </if> <if test="orderType != null and orderType != ''"> and `order_type` = #{orderType} </if> ... </where></sql><select id="queryListByConditions" resultMap="orderResultMap"> select * from order <include refid="conditions"/> </select>查问逻辑相似下面的示例,在Service层有个依据outer_id的查询方法,而后间接调用了Mapper层一个通用查询方法queryListByConditions。 但咱们有个调用量极低的场景,能够不传outer_id这个参数,导致这个通用查询方法没有增加这个过滤条件,导致查了全表,进而导致OOM问题。 咱们外部对这个问题进行了复盘,思考到OOM问题还是蛮常见的,所以给大家也分享下。 事先在OOM问题产生前,为什么测试阶段没有发现问题? 其实在编写技术计划时,是有思考到这个场景的,但在提测时,遗记和测试同学沟通此场景,导致脱漏了此场景的测试验证。 对于测试用例不全面,其实不论是忽略问题、教训问题、品质意识问题或人手缓和问题,从人的角度来说,都很难彻底防止,人没法像机器那样很听话的、不疏漏的执行任何指令。 既然人做不到,那就让机器来做,这就是单元测试、自动化测试的劣势,通过逐渐积攒测试用例,可笼罩的场景就会越来越多。 当然,施行单元测试等计划,也会减少不少老本,须要衡量品质与研发效率谁更重要,毕竟在需要不能砍的状况下,品质与效率只能二选其一,这是任何一本项目管理的书都提到过的。 事中在感知到OOM问题产生时,因为过程被部署平台kill,导致现场失落,难以疾速定位到问题点。 个别java外面是举荐应用-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/home/dump/这种JVM参数来保留现场的,这两个参数的意思是,当JVM产生OOM异样时,主动dump堆内存到文件中,但在咱们的场景中,这个计划难以失效,如下: 在堆占满之前,会产生很屡次FGC,jvm会尽最大致力腾挪空间,导致还没有OOM时,零碎理论曾经不响应了,而后被kill了,这种场景无dump文件生成。就算有时侥幸,JVM产生了OOM异样开始dump,因为dump文件过大(咱们约10G),导致dump文件还没保留完,过程就被kill了,这种场景dump文件不残缺,无奈应用。为了解决这个问题,有如下2种计划: 计划1:利用k8s容器生命周期内的Hook咱们部署平台是套壳k8s的,k8s提供了preStop生命周期钩子,在容器销毁前会先执行此钩子,只有将jmap -dump命令放入preStop中,就能够在k8s健康检查不通过并kill容器前将内存dump进去。 要留神的是,失常公布也会调用此钩子,须要想方法绕过,咱们的方法是将健康检查也做成脚本,当不通过时创立一个临时文件,而后在preStop脚本中判断存在此文件才dump,preStop脚本如下: if [ -f "/tmp/health_check_failed" ]; then echo "Health check failed, perform dumping and cleanups..."; pid=`ps h -o pid --sort=-pmem -C java|head -n1|xargs`; if [[ $pid ]]; then jmap -dump:format=b,file=/home/work/logs/applogs/heap.hprof $pid fielse echo "No health check failure detected. Exiting gracefully.";fi 注:也能够思考在堆占用高时才dump内存,成果应该差不多。计划2:容器中挂脚本监控堆占用,占用高时主动dump#!/bin/bashwhile sleep 1; do now_time=$(date +%F_%H-%M-%S) pid=`ps h -o pid --sort=-pmem -C java|head -n1|xargs`; [[ ! $pid ]] && { unset n pre_fgc; sleep 1m; continue; } data=$(jstat -gcutil $pid|awk 'NR>1{print $4,$(NF-2)}'); read old fgc <<<"$data"; echo "$now_time: $old $fgc"; if [[ $(echo $old|awk '$1>80{print $0}') ]]; then (( n++ )) else (( n=0 )) fi if [[ $n -ge 3 || $pre_fgc && $fgc -gt $pre_fgc && $n -ge 1 ]]; then jstack $pid > /home/dump/jstack-$now_time.log; if [[ "$@" =~ dump ]];then jmap -dump:format=b,file=/home/dump/heap-$now_time.hprof $pid; else jmap -histo $pid > /home/dump/histo-$now_time.log; fi { unset n pre_fgc; sleep 1m; continue; } fi pre_fgc=$fgcdone每秒查看老年代占用,3次超过80%或产生一次FGC后还超过80%,记录jstack、jmap数据,此脚本保留为jvm_old_mon.sh文件。 ...

April 1, 2023 · 2 min · jiezi

关于jvm:jvm学习01类加载过程

一个类从加载到应用,个别会经验上面的这个过程:加载 -> 验证 -> 筹备 -> 解析 -> 初始化 -> 应用 -> 卸载 1.加载编译: X.java->X.class->JVMclass汇总的类的加载 双亲委派模型:子类加载器会请示父类加载器查看层层上询, 下面没加载的, 再层层下派;启动类加载器: Bootstrap ClassLoader扩大类加载器: Extension ClassLoader应用程序类加载器: Application ClassLoader自定义类加载器: XXX自定义加载器->Application->Extension->Bootstrap 2.验证校验你加载进来的.class文件内容,是否合乎Java虚拟机标准格局验证: 0xCAFEBABE结尾/是否有编码以外的字符等元数据验证: 比方是否继承了final类(不容许的)/父类为接口或抽象类的子类,是否实现了形象办法等;字节码验证: 办法调用时任何指令不会跳到办法体以外的的字节码指令上;符号援用验证: 符号援用类中的类/字段/办法的可拜访性(是否能被以后类拜访)3.筹备static变量:分配内存空间,设置默认的初始值byte/short/int/long->0/0Lfloat/double=0.0f/0.0dboolean=false reference=null 4.解析符号援用替换为间接援用的过程间接援用: 指标的指针/绝对偏移量/间接定位到指标的句柄符号援用: class文件中的字面符号援用; 就是将classs文件中的类/接口/字段/办法解析为理论的指标指针/绝对偏移量/指标句柄等的过程;起因就是: classs文件是编译来的; 编译的时候并无奈得悉执行时类/接口/字段/办法的内存地位. 5.初始化初始化变量为理论赋值的数据;如 static int a = getValue()在筹备阶段只会给a调配空间和给初始值0, 在初始化阶段才会调用getValue()来赋值初始化a; 6.应用略 7.卸载略

March 31, 2023 · 1 min · jiezi

关于jvm:JVM入门

1、JVM模板-Xms4096M -Xmx4096M -Xmn3072M -Xss1M -XX:MetaspaceSize=256M -XX:MaxMetaspaceSize=256M -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFaction=92 -XX:+UseCMSCompactAtFullCollection -XX:CMSFullGCsBeforeCompaction=0 -XX:+CMSParallelInitialMarkEnabled -XX:+CMSScavengeBeforeRemark -XX:+DisableExplicitGC -XX:+PrintGCDetails -Xloggc:gc.log -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/usr/local/app/oom年老代-XX:MaxTenuringThreshold,默认值是15,多少次垃圾回收进入老年代动静年龄判断-XX:PretenureSizeThreshold 大对象,间接进入老年代S区放不下空间调配担保机制,老年代可用空间 > 新生代所有存活对象,yong gc,老年代可用空间 > 历史yong gc均匀大小,yong gc,否则full gc 老年代-XX:CMSInitiatingOccupancyFraction,大于该值 碎片整顿-XX:+UseCMSCompactAtFullCollection 执行多少次Full GC进行一次内存碎片整顿-XX:CMSFullGCsBeforeCompaction=0 -XX:+CMSParallelInitialMarkEnabled 初始标记开启多线程并发执行,默认是true -XX:+CMSScavengeBeforeRemark 在CMS的从新标记阶段之前,先尽量执行一次young gc(防止大量扫描) -XX:+DisableExplicitGC 是避免System.gc()去轻易触发GC,顶峰状况下,调用System.gc()会产生Full GC -XX:MetaspaceSize 默认20M,反射 jdk,cglib动静生成类,举荐512MB -Xms -> ms是memory start简称,-Xmx mx是memory max的简称 -XX:+PrintTLAB 加这个参数能够看到 TLAB的调配,其中 refills 申请TLAB次数,slow allocs : 慢速调配的次数 XX:+ExplicitGCInvokesConcurrent 和 -XX:+ExplicitGCInvokesConcurrentAndUnloadsClasses 参数来将 System.gc 的触发类型从 Foreground 改为 Background CMS GC 共分为 Background 和 Foreground 两种模式,前者就是咱们惯例了解中的并发收集,能够不影响失常的业务线程运行,但 Foreground Collector 却有很大的差别,他会进行一次压缩式 GC。此压缩式 GC 应用的是跟 Serial Old GC 一样的 Lisp2 算法,其应用 Mark-Compact 来做 Full GC,个别称之为 MSC(Mark-Sweep-Compact),它收集的范畴是 Java 堆的 Young 区和 Old 区以及 MetaSpace。由下面的算法章节中咱们晓得 compact 的代价是微小的,那么应用 Foreground Collector 时将会带来十分长的 STW ...

March 29, 2023 · 2 min · jiezi

关于jvm:阿里终面每天100w次登陆请求-8G-内存该如何设置JVM参数

大家好,我是不才陈某~ 上周常识星球的同学在阿里云技术面终面的时候被问到这么一个问题:假如一个每天100w次登陆申请的平台,一个服务节点 8G 内存,该如何设置JVM参数? 感觉答复的不太现实,过去找我复盘。 上面以面试题的模式给大家梳理进去,做到一举两得: 既供大家实操参考又供大家面试参考大家要学习的,除了 JVM 配置计划 之外,是其 剖析问题的思路、思考问题的视角。 这些思路和视角,能帮忙大家走更远、更远。 接下来,进入正题。 关注公众号:码猿技术专栏,回复关键词:1111 获取阿里外部Java性能调优手册!每天100w次登陆申请, 8G 内存该如何设置JVM参数?每天100w次登陆申请, 8G 内存该如何设置JVM参数,大略能够分为以下8个步骤。 Step1:新零碎上线如何布局容量?1.套路总结 任何新的业务零碎在上线以前都须要去估算服务器配置和JVM的内存参数,这个容量与资源布局并不仅仅是零碎架构师的随便估算的,须要依据零碎所在业务场景去估算,推断进去一个零碎运行模型,评估JVM性能和GC频率等等指标。以下是我联合大牛教训以及本身实际来总结进去的一个建模步骤: 计算业务零碎每秒钟创立的对象会佔用多大的内存空间,而后计算集群下的每个零碎每秒的内存佔用空间(对象创立速度)设置一个机器配置,估算新生代的空间,比拟不同新生代大小之下,多久触发一次MinorGC。为了防止频繁GC,就能够从新估算须要多少机器配置,部署多少台机器,给JVM多大内存空间,新生代多大空间。依据这套配置,根本能够推算出整个零碎的运行模型,每秒创立多少对象,1s当前成为垃圾,零碎运行多久新生代会触发一次GC,频率多高。2.套路实战——以登录零碎为例 有些同学看到这些步骤还是发憷,说的如同是那么回事,一到理论我的项目中到底怎麽做我还是不晓得! 光说不练假把式,以登录零碎为例模仿一下推演过程: 假如每天100w次登陆申请,登陆峰值在早上,预估峰值期间每秒100次登陆申请。假如部署3台服务器,每台机器每秒解决30次登陆申请,假如一个登陆申请须要解决1秒钟,JVM新生代里每秒就要生成30个登陆对象,1s之后申请结束这些对象成为了垃圾。一个登陆申请对象假如20个字段,一个对象估算500字节,30个登陆佔用大概15kb,思考到RPC和DB操作,网络通信、写库、写缓存一顿操作下来,能够扩充到20-50倍,大概1s产生几百k-1M数据。假如2C4G机器部署,调配2G堆内存,新生代则只有几百M,依照1s1M的垃圾产生速度,几百秒就会触发一次MinorGC了。假如4C8G机器部署,调配4G堆内存,新生代调配2G,如此须要几个小时才会触发一次MinorGC。所以,能够粗略的推断进去一个每天100w次申请的登录零碎,依照4C8G的3实例集群配置,调配4G堆内存、2G新生代的JVM,能够保障系统的一个失常负载。 基本上把一个新零碎的资源评估了进去,所以搭建新零碎要每个实例须要多少容量多少配置,集群配置多少个实例等等这些,并不是拍拍脑袋和胸脯就能够决定的下来的。 Step2:该如何进行垃圾回收器的抉择?吞吐量还是响应工夫首先引入两个概念:吞吐量和低提早 吞吐量 = CPU在用户利用程序运行的工夫 / (CPU在用户利用程序运行的工夫 + CPU垃圾回收的工夫) 响应工夫 = 均匀每次的GC的耗时 通常,吞吐优先还是响应优先这个在JVM中是一个两难之选。 堆内存增大,gc一次能解决的数量变大,吞吐量大;然而gc一次的工夫会变长,导致前面排队的线程等待时间变长;相同,如果堆内存小,gc一次工夫短,排队期待的线程等待时间变短,提早缩小,但一次申请的数量变小(并不相对合乎)。 无奈同时兼顾,是吞吐优先还是响应优先,这是一个须要衡量的问题。 垃圾回收器设计上的考量JVM在GC时不容许一边垃圾回收,一边还创立新对象(就像不能一边打扫卫生,还在一边扔垃圾)。JVM须要一段Stop the world的暂停工夫,而STW会造成零碎短暂进展不能解决任何申请;新生代收集频率高,性能优先,罕用复制算法;老年代频次低,空间敏感,防止复制形式。所有垃圾回收器的波及指标都是要让GC频率更少,工夫更短,缩小GC对系统影响!CMS和G1目前支流的垃圾回收器配置是新生代采纳ParNew,老年代采纳CMS组合的形式,或者是齐全采纳G1回收器, 从将来的趋势来看,G1是官网保护和更为推崇的垃圾回收器。 业务零碎: 提早敏感的举荐CMS;大内存服务,要求高吞吐的,采纳G1回收器!CMS垃圾回收器的工作机制CMS次要是针对老年代的回收器,老年代是标记-革除,默认会在一次FullGC算法后做整顿算法,清理内存碎片。 CMS GC形容Stop the world速度1.开始标记初始标记仅标记GCRoots能间接关联到的对象,速度很快Yes很快2.并发标记并发标记阶段就是进行GCRoots Tracing的过程No慢3.从新标记从新标记阶段则是为了修改并发标记期间因用户程序持续运作而导致标记产生变动的那一部分对象的标记记录。Yes很快4.垃圾回收并发清理垃圾对象(标记革除算法)No慢长处:并发收集、主打“低延时” 。在最耗时的两个阶段都没有产生STW,而须要STW的阶段都以很快速度实现。毛病:1、耗费CPU;2、浮动垃圾;3、内存碎片实用场景:器重服务器响应速度,要求零碎进展工夫最短。总之: 业务零碎,提早敏感的举荐CMS; 大内存服务,要求高吞吐的,采纳G1回收器! Step3:如何对各个分区的比例、大小进行布局个别的思路为: 首先,JVM最重要最外围的参数是去评估内存和调配,第一步须要指定堆内存的大小,这个是零碎上线必须要做的,-Xms 初始堆大小,-Xmx 最大堆大小,后盾Java服务中个别都指定为零碎内存的一半,过大会佔用服务器的系统资源,过小则无奈施展JVM的最佳性能。 其次,须要指定-Xmn新生代的大小,这个参数十分要害,灵便度很大,尽管sun官网举荐为3/8大小,然而要依据业务场景来定,针对于无状态或者轻状态服务(当初最常见的业务零碎如Web利用)来说,个别新生代甚至能够给到堆内存的3/4大小;而对于有状态服务(常见如IM服务、网关接入层等零碎)新生代能够依照默认比例1/3来设置。服务有状态,则意味著会有更多的本地缓存和会话状态信息常驻内存,应为要给老年代设置更大的空间来寄存这些对象。 最初,是设置-Xss栈内存大小,设置单个线程栈大小,默认值和JDK版本、零碎无关,个别默认512~1024kb。一个后盾服务如果常驻线程有几百个,那麽栈内存这边也会佔用了几百M的大小。 JVM参数形容默认举荐-XmsJava堆内存的大小OS内存64/1OS内存一半-XmxJava堆内存的最大大小OS内存4/1OS内存一半-XmnJava堆内存中的新生代大小,扣除新生代剩下的就是老年代的内存大小了跌认堆的1/3sun举荐3/8-Xss每个线程的栈内存大小和idk无关sun对于8G内存,个别调配一半的最大内存就能够了,因为机器本上还要占用肯定内存,个别是调配4G内存给JVM, 引入性能压测环节,测试同学对登录接口压至1s内60M的对象生成速度,采纳ParNew+CMS的组合回收器, 失常的JVM参数配置如下: -Xms3072M -Xmx3072M -Xss1M -XX:MetaspaceSize=256M -XX:MaxMetaspaceSize=256M -XX:SurvivorRatio=8 这样设置可能会因为动静对象年龄判断准则导致频繁full gc。为啥呢? ...

February 28, 2023 · 2 min · jiezi

关于jvm:谈JVM-xmx-xms等内存相关参数合理性设置

作者:京东批发 刘乐 说到JVM垃圾回收算法的两个优化标的:吞吐量和进展时长,并提到这两个优化指标是有抵触的。那么有没有可能进步吞吐量而不影响进展时长,甚至缩短进展时长呢?答案是有可能的,进步内存占用(Memory Footprint)就有可能同时优化这两个标的,这篇文章就来聊聊内存相干内容。 内存占用个别指利用运行须要的所有内存,包含堆内内存(On-heap Memory)和堆外内存(Off-heap Memory) 1. 堆内内存堆内内存是调配给JVM的局部内存,用来寄存所有Java Class对象实例和数组,JVM GC操作的就是这部分内容。咱们先来回顾一下堆内内存的模型: 图1. 堆内内存 堆内内存包含年老代(浅绿色),老年代(浅蓝色),在JDK7或者更老的版本,图中左边还有个永恒代(永恒代在逻辑上位于JVM的堆区,但又被称为非堆内存,在JDK8中被元空间取代)。JVM有动静调整内存策略,通过-Xms,-Xmx 指定堆内内存动静调整的上上限。 在JVM初始化时理论只调配局部内存,可通过-XX:InitialHeapSize指定初始堆内存大小,未被调配的空间为图中virtual局部。年老代和老年代在每次GC的时候都有可能调整大小,以保障存活对象占用百分比在特定阈值范畴内,直到达到Xms指定的上限或Xms指定的下限。(阈值范畴通过-XX:MinHeapFreeRatio, XX:MaxHeapFreeRatio指定,默认值别离为40, 70)。 GC调优中还有个的重要参数是老年代和年老代的比例,通过-XX:NewRatio设定,与此相关的还有-XX:MaxNewSize和-XX:NewSize,别离设定年老代大小的上上限,-Xmn则间接指定年老代的大小。 1.1 参数默认值◦-Xmx: Xmx的默认值比较复杂,官网文档上有时候写的是1GB,但理论值跟JRE版本、JVM 模式(client, server)和零碎(平台类型,32位,64位)等都无关。通过查阅源码和试验,确定在生产环境下(server模式,64位Centos,JRE 8),Xmx的默认值能够采纳以下规定计算: ▪容器内存小于等于2G:默认值为容器内存的1/2,最小16MB, 最大512MB。 ▪容器内存大于2G:默认值为容器内存的1/4, 最大可达到32G。 ◦-Xms: 默认值为容器内存的1/64, 最小8MB,如果明确指定了Xmx并且小于容器内存1/64, Xms默认值为Xmx指定的值。 ◦-NewRatio: 默认2,即年老代和年轻代的比例为1:2, 年老代大小为堆内内存的1/3。 NOTE:在JRE版本1.8.0_131之前,JVM无奈感知Docker的资源限度,Xmx, Xms未明确指定时,会应用宿主机的内存计算默认值。 1.2 最佳实际因为每次Eden区满就会触发YGC,而每次YGC的时候,降职到老年代的对象大小超过老年代残余空间的时候,就会触发FGC。所以根本来说,GC频率和堆内内存大小是成反比的,也就是说堆内内存越大,吞吐量越大。 如果Xmx设置过小,不仅节约了容器资源,在大流量下会频繁GC,导致一系列问题,包含吞吐量升高,响应变长,CPU升高,java.lang.OutOfMemoryError异样等。当然Xmx也不倡议设置过大,否则会导致过程hang住或者应用容器Swap。所以正当设置Xmx十分重要,特地是对于1.8.0_131之前的版本,肯定要明确指定Xmx。举荐设置为容器内存的50%,不能超过容器内存的80%。 JVM的动态内存策略不太适宜服务应用,因为每次GC须要计算Heap是否须要伸缩,内存抖动须要向零碎申请或开释内存,特地是在服务重启的预热阶段,内存抖动会比拟频繁。另外,容器中如果有其余过程还在生产内存,JVM内存抖动时可能申请内存失败,导致OOM。因而倡议服务模式下,将Xms设置Xmx一样的值。 NewRatio倡议在2~3之间,最优抉择取决于对象的生命周期散布。个别先确定老年代的空间(足够放下所有live data,并适当减少10%~20%),其余是年老代,年老代大小肯定要小于老年代。 另外,以上倡议都是基于一个容器部署一个JVM实例的应用状况。有个别需要,须要在一个容器内启用多个JVM,或者蕴含其余语言的,研发须要按业务需要在推荐值范畴内调配JVM的Xmx。 2. 堆外内存和堆内内存对应的就是堆外内存。堆外内存包含很多局部,比方Code Cache, Memory Pool,Stack Memory,Direct Byte Buffers, Metaspace等等,其中咱们须要重点关注的是Direct Byte Buffers和Metaspace。 2.1 Direct Byte BuffersDirect Byte Buffers是零碎原生内存,不位于JVM里,广义上的堆外内存就是指的Direct Byte Buffers。为什么要应用零碎原生内存呢? 为了更高效的进行Socket I/O或文件读写等内核态资源操作,会应用JNI(Java原生接口),此时操作的内存须要是间断和确定的。而Heap中的内存不能保障间断,且GC也可能导致对象随时挪动。因而波及Output操作时,不间接应用Heap上的数据,须要先从Heap上拷贝到原生内存,Input操作则相同。因而为了防止多余的拷贝,进步I/O效率,不少第三方包和框架应用Direct Byte Buffers,比Netty。 ...

February 27, 2023 · 1 min · jiezi

关于jvm:垃圾回收与算法

一、如何确定垃圾 1、援用计数法 在 Java 中,援用和对象是有关联的。如果要操作对象则必须用援用进行。因而,很显然一个简略 的方法是通过援用计数来判断一个对象是否能够回收。简略说,即一个对象如果没有任何与之关 联的援用,即他们的援用计数都不为 0,则阐明对象不太可能再被用到,那么这个对象就是可回收 对象。2、 可达性剖析 为了解决援用计数法的循环援用问题,Java 应用了可达性剖析的办法。通过一系列的“GC roots” 对象作为终点搜寻。如果在“GC roots”和一个对象之间没有可达门路,则称该对象是不可达的。要 留神的是,不可达对象不等价于可回收对象,不可达对象变为可回收对象至多要通过两次标记 过程。两次标记后依然是可回收对象,则将面临回收。 GC Roots 是指:Java 虚拟机栈(栈帧中的本地变量表)中援用的对象本地办法栈中援用的对象办法区中常量援用的对象办法区中类动态属性援用的对象GC Roots 并不包含堆中对象所援用的对象,这样就不会有循环援用的问题。二、垃圾回收算法 1.Mark-Sweep(标记-革除)算法 这是最根底的垃圾回收算法,之所以说它是最根底的是因为它最容易实现,思维也是最简略的。标记-革除算法分为两个阶段:标记阶段和革除阶段。标记阶段的工作是标记出所有须要被回收的对象,革除阶段就是回收被标记的对象所占用的空间。具体过程如下图所示:从图中能够很容易看出标记-革除算法实现起来比拟容易,然而有一个比较严重的问题就是容易产生内存碎片,碎片太多可能会导致后续过程中须要为大对象调配空间时无奈找到足够的空间而提前触发新的一次垃圾收集动作。 2.Copying(复制)算法 为了解决Mark-Sweep算法的缺点,Copying算法就被提了进去。它将可用内存按容量划分为大小相等的两块,每次只应用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块下面,而后再把已应用的内存空间一次清理掉,这样一来就不容易呈现内存碎片的问题。具体过程如下图所示:这种算法尽管实现简略,运行高效且不容易产生内存碎片,然而却对内存空间的应用做出了昂扬的代价,因为可能应用的内存缩减到原来的一半。 很显然,Copying算法的效率跟存活对象的数目多少有很大的关系,如果存活对象很多,那么Copying算法的效率将会大大降低。 3.Mark-Compact(标记-整顿)算法 为了解决Copying算法的缺点,充分利用内存空间,提出了Mark-Compact算法。该算法标记阶段和Mark-Sweep一样,然而在实现标记之后,它不是间接清理可回收对象,而是将存活对象都向一端挪动,而后清理掉端边界以外的内存。具体过程如下图所示: 4.Generational Collection(分代收集)算法 分代收集算法是目前大部分JVM的垃圾收集器采纳的算法。它的核心思想是依据对象存活的生命周期将内存划分为若干个不同的区域。个别状况下将堆区划分为老年代(Tenured Generation)和新生代(Young Generation),老年代的特点是每次垃圾收集时只有大量对象须要被回收,而新生代的特点是每次垃圾回收时都有大量的对象须要被回收,那么就能够依据不同代的特点采取最适宜的收集算法。 目前大部分垃圾收集器对于新生代都采取Copying算法,因为新生代中每次垃圾回收都要回收大部分对象,也就是说须要复制的操作次数较少,然而理论中并不是依照1:1的比例来划分新生代的空间的,一般来说是将新生代划分为一块较大的Eden空间和两块较小的Survivor空间,每次应用Eden空间和其中的一块Survivor空间,当进行回收时,将Eden和Survivor中还存活的对象复制到另一块Survivor空间中,而后清理掉Eden和方才应用过的Survivor空间。 而因为老年代的特点是每次回收都只回收大量对象,个别应用的是Mark-Compact算法。 留神,在堆区之外还有一个代就是永恒代(Permanet Generation),它用来存储class类、常量、办法形容等。对永恒代的回收次要回收两局部内容:废除常量和无用的类。 三.典型的垃圾收集器 垃圾收集算法是 内存回收的实践根底,而垃圾收集器就是内存回收的具体实现。上面介绍一下HotSpot(JDK 7)虚拟机提供的几种垃圾收集器,用户能够依据本人的需要组合出各个年代应用的收集器。1.Serial/Serial Old Serial/Serial Old收集器是最根本最古老的收集器,它是一个单线程收集器,并且在它进行垃圾收集时,必须暂停所有用户线程。Serial收集器是针对新生代的收集器,采纳的是Copying算法,Serial Old收集器是针对老年代的收集器,采纳的是Mark-Compact算法。它的长处是实现简略高效,然而毛病是会给用户带来进展。 2.ParNew ParNew收集器是Serial收集器的多线程版本,应用多个线程进行垃圾收集。 3.Parallel Scavenge Parallel Scavenge收集器是一个新生代的多线程收集器(并行收集器),它在回收期间不须要暂停其余用户线程,其采纳的是Copying算法,该收集器与前两个收集器有所不同,它次要是为了达到一个可控的吞吐量。 4.Parallel Old Parallel Old是Parallel Scavenge收集器的老年代版本(并行收集器),应用多线程和Mark-Compact算法。 5.CMS CMS(Current Mark Sweep)收集器是一种以获取最短回收进展工夫为指标的收集器。它是一种老年代的并发收集器,采纳的是Mark-Sweep算法。 6.G1 G1收集器是当今收集器技术倒退最前沿的成绩,它是一款面向服务端利用的收集器它能充分利用多CPU、多核环境。因而它是一款并行与并发收集器,并且它能建设可预测的进展工夫模型。新生代和老年代都可用。

February 23, 2023 · 1 min · jiezi

关于jvm:JVM

一、基本概念: JVM 是可运行 Java 代码的假想计算机 ,包含一套字节码指令集、一组寄存器、一个栈、一个垃圾回收,堆和一个存储办法域。JVM 是运行在操作系统之上的,它与硬件没有间接的交互。二、 架构: JVM分为五大模块: 类装载器子系统 、 运行时数据区 、 执行引擎 、 本地办法接口 和 垃圾收集模块 。三、 内存区域:1、线程公有: (1)栈:形容java办法执行的内存模型,每个办法在执行的同时都会创立一个栈帧(Stack Frame)、用于存储局部变量表、操作数栈、动静链接、办法进口等信息。(2)本地办法栈(3)程序计数器 以后线程所执行的字节码的行号指示器 这个内存区域是惟一一个在虚拟机中没有规定任何 OutOfMemoryError 状况的区域。2、线程私有: (4)堆:从 GC 的角度还能够细分为: 新生代(Eden 区、From Survivor 区和 To Survivor 区)和老年代。(5)办法区(也叫永恒代,java8后改为元空间): 用于存储被 JVM 加载的类信息、常量、动态变量、即时编译器编译后的代码等数据3、间接内存。 四、Minor GC(或Young GC)、Major GC、Full GC 咱们须要尽量的防止垃圾回收,因为在垃圾回收的过程中,容易呈现STW(Stop the World)的问题。 JVM在进行GC时,并非每次都对下面三个内存( 新生代、老年代、办法区 )区域一起回收的,大部分时候回收的都是指新生代。新生代收集( Minor GC/Young GC ):只是新生代( Eden、S0/S1 )的垃圾收集老年代收集( Major GC/Old GC ):只是老年代的垃圾收集。目前,只有CMS GC会有独自收集老年代的行为 。也就是说其它GC执行Major GC的时候可不会只收集老年代的垃圾。留神,很多时候Major GC会和Full GC混同应用,须要具体分辨是老年代回收还是整堆回收。混合收集(Mixed GC):收集整个新生代以及局部老年代的垃圾收集。目前,只有G1 GC会有这种行为。整堆收集(Full GC):收集整个java堆和办法区的垃圾收集。补充阐明触发Minor GC执行的状况有哪些? ...

February 23, 2023 · 1 min · jiezi

关于jvm:对-volatile-的理解

NOTICE:本文仅记录自己对 volatile 关键字的小小了解,没有具体记录每个点,若有误可指出 一个对象的产生java 的 Class 对象产生会经验以下阶段:类加载,验证,筹备,解析,初始化 类加载:通过类的全限定名获取类的二进制,并转换成 JVM 的办法区的 Class 对象验证:对 Class 对象进行格局上的验证,别离有文件格式验证,元数据验证,字节码验证,符号援用验证筹备:给 Class 对象的 static 变量分配内存并赋初始零值解析:姜符号援用转换成间接援用初始化:执行 Class 文件显式给 static 变量赋值语句若运行时须要应用 Class 对应的对象时,会应用 new 关键字或者 newInstance 办法创立,于是 JVM 调用 Class 的元信息,在堆,运行时常量池划分一块内存放入新建的对象 如果对象是在虚拟机栈上,应用的是局部变量,那程序始终执行上来,没问题。 然而如果应用的是成员变量,并发批改,并且想要看到是对的(可见性),那别的批改须要批改后写回到主存,并且在用的时候也要拉到最新的数据,这波及到 JAVA 的内存模型,以及缓存一致性协定 JAVA 内存模型JAVA 内存模型次要分为两类:主内存和工作内存 主内存是所有变量存储的中央,工作内存是线程具体工作的中央,应用的是主内存的变量正本 这里就波及主内存与工作内存的同步问题,波及到并发三个性以及内存间交互操作并发过程的三个性原子性一个操作/多个操作要么执行胜利,要不都不执行,相似于事务 可见性一个线程对变量进行操作,其余线程能立即看到变更,这个就解决了变更后线程间不统一的问题 有序性程序执行程序依照控制流程序执行 内存间交互操作lock[主内存] 将某个变量标为该线程独占的状态 read[主内存 -> 工作内存] 将主内存中变量的值 copy 到工作内存中 load[工作内存] 将 read 过程中变量的值赋给变量正本 use[工作内存] 代码内应用变量 assign[工作内存] 将代码过程中变更的值赋给工作内存变量正本 store[工作内存 -> 主内存] 将工作内存变量正本的值传回到主内存 write[主内存] 将传回来的值写回到主内存变量中 unlock将变量从独占状态开释 概括来说一个线程应用某个变量,赋值给某个变量,须要在主内存,工作内存中相互复制,必须要通过的步骤:read -> load -> use -> assign -> store -> write ...

February 10, 2023 · 2 min · jiezi

关于jvm:从-JVM-中深入探究-Synchronized

开篇语Synchronized,Java 敌对的提供了的一个关键字,它让开发者能够疾速的实现同步。它就像一个星星,远远看去就是一个小小的点。然而走近一看,却是一个宏大的蛋糕。而这篇文章就是要将这个微小的蛋糕切开,吃进肚子外面去。 Synchronized 应用在 Java 中,如果要实现同步,Java 提供了一个关键词 synchronized 来让开发人员能够疾速实现同步代码块。 public class Test { public static void main(String[] args){ Object o = new Object(); Thread thread1 = new Thread(() -> { synchronized (o){ System.out.println("获取锁胜利"); } }).start(); }}线程 thread1 获取对象 o 的锁,并且输入一句话 “获取锁胜利”。 public class Test { private int i = 0; public synchronized void set(int i){ this.i = i; } public synchronized static String get(){ return "静态方法"; } public void put(){ synchronized (this){ System.out.println("同步代码块"); } }}synchronized 关键字除了能够用于代码块,还能够用于办法上。用于实例办法上时,线程执行该办法之前,会主动获取该对象锁,获取到对象锁之后才会继续执行实例办法中的代码;用于静态方法上时,线程执行该办法之前,会主动获取该对象所属类的锁,获取到类锁之后才会继续执行静态方法中的代码。用于代码块上时,能够传入任意对象作为锁,并且能够管制锁的粒度。 ...

January 30, 2023 · 14 min · jiezi

关于jvm:jvmexporter整合k8sprometheus监控报警

文章背景:应用Prometheus+Grafana监控JVM,这片文章中介绍了怎么用jvm-exporter监控咱们的java利用,在咱们的应用场景中须要监控k8s集群中的jvm,接下来谈谈k8s和Prometheus的集成扩大应用,假如咱们曾经胜利将Prometheus部署到咱们的k8s集群中了kubernetes集成prometheus+grafana监控,然而kube-prometheus并没有集成jvm-exporter,这就须要咱们本人操作。 将jvm-exporter整合进咱们的利用整合过程很简略,只须要将jvm-exporter作为javaagent退出到咱们的java启动命令就能够了,具体见应用Prometheus+Grafana监控JVM 配置Prometheus服务主动发现对于有Service裸露的服务咱们能够用 prometheus-operator 我的项目定义的ServiceMonitorCRD来配置服务发现,配置模板如下: --- # ServiceMonitor 服务主动发现规定apiVersion: monitoring.coreos.com/v1kind: ServiceMonitor # prometheus-operator 定义的CRDmetadata: name: jmx-metrics namespace: monitoring labels: k8s-apps: jmx-metricsspec: jobLabel: metrics #监控数据的job标签指定为metrics label的值,即加上数据标签job=jmx-metrics selector: matchLabels: metrics: jmx-metrics # 主动发现 label中有metrics: jmx-metrics 的service namespaceSelector: matchNames: # 配置须要主动发现的命名空间,能够配置多个 - my-namespace endpoints: - port: http-metrics # 拉去metric的端口,这个写的是 service的端口名称,即 service yaml的spec.ports.name interval: 15s # 拉取metric的工夫距离--- # 服务service模板apiVersion: v1kind: Servicemetadata: labels: metrics: jmx-metrics # ServiceMonitor 主动发现的要害label name: jmx-metrics namespace: my-namespacespec: ports: - name: http-metrics #对应 ServiceMonitor 中spec.endpoints.port port: 9093 # jmx-exporter 裸露的服务端口 targetPort: http-metrics # pod yaml 裸露的端口名 selector: metrics: jmx-metrics # service自身的标签选择器以上配置了my-namespace命名空间的 jmx-metrics Service的服务主动发现,Prometheus会将这个service 的所有关联pod主动退出监控,并从apiserver获取到最新的pod列表,这样当咱们的服务正本裁减时也能主动增加到监控零碎中。 ...

January 17, 2023 · 6 min · jiezi

关于jvm:jvm垃圾回收机制

1.垃圾分代回收堆空间分为年老代、老年代,默认内存占用比例为= 1:2对象调配步骤为: 1.1 年老代次要分为Eden、From、To三个区域,其中,默认内存占用比例为8:1:1存活对象进入年老代的条件:新产生的对象优先调配到老年代(除大对象,大对象会优先调配到老年代) 1.2 老年代存活对象进入老年代的条件: 1.2.1 创立大对象(对象内存大于设定阈值)间接进入老年代1.2.2 young gc后,To Survivor区不足以寄存存活对象1.2.3 每次young gc后,存活对象年龄+1。通过屡次young gc后,如果存活对象的年龄达到了设定阈值(默认15),则会降职到老年代中。1.2.4 动静年龄断定规定。To Survivor区中年龄从小到大的对象占据空间的累加之和,占到了 To Survivor区一半以上的空间,那么大于等于此年龄的对象会间接进入老年代,而不须要达到默认的降职年龄。举例:年龄1+年龄2+年龄3+年龄N的对象加起来的空间,大于survivor区域的一半,就会让年龄N和年龄N以上的对象进入老年代。动静年龄判断应该是这样子的。其中,年龄N是动静的,可能为3时达到此条件,也可能是是为15时,最大为15,对象头中年龄字段大小为4哥字节,故最大15。说的艰深一点:就是年龄从小到大对象的占据空间的累加和,而不是某一个特定年龄对象占据的空间。2.垃圾回收触发机会2.1 young gc年老代垃圾回收 2.1.1 Eden区可用内存有余2.2 full gc 所有区域垃圾回收2.2.1 老年代达到某一阈值(默认92%)2.2.2 办法区可用内存有余2.2.3 在young gc之前,会先查看老年代最大可用的间断空间是否大于新生代所有对象的总空间。如果小于,阐明YGC是不平安的,则会查看参数 HandlePromotionFailure 是否被设置成了容许担保失败,如果不容许则间接触发Full GC;如果容许,那么会进一步查看老年代最大可用的间断空间是否大于历次降职到老年代对象的均匀大小,如果小于会触发 Full GC;大于则会执行young gc(即便是不平安,有可能young gc后进入老年代的对象内存依然大于老年代可用内存,此时会报内存溢出谬误)2.2.4 显式调用System.gc() 或者Runtime.gc()

January 1, 2023 · 1 min · jiezi

关于jvm:JPS-命令详细解释

引言JPS命令是日常开发过程中常常遇到的命令。应用起来也非常简单,本节内容次要翻译Oracel官网的JPS阐明,以及相干的实现原理剖析,最初介绍一些JPS无奈获取到JAVA过程的起因排查。 官网文档翻译原文:jps - Java Virtual Machine Process Status Tool (oracle.com) SYNOPSIS(简介)jps [ options] [ hostid ] PARAMETERSoptions Command-line options. hostid The host identifier of the host for which the process report should be generated. The _hostid_ may include optional components that indicate the communications protocol, port number, and other implementation specific data. host惟一主机标识符号,这里理论指的是操作系统治理过程必须为应用程序调配的过程号,而后再由JVM对立治理资源标识。对立资源管理能够是协定,端口号和其余非凡数据。 OPTIONSThe jps command supports a number of options that modify the output of the command. These options are subject to change or removal in the future. ...

December 28, 2022 · 8 min · jiezi

关于jvm:jvm垃圾回收

转载https://mp.weixin.qq.com/s?__... JVM 的内存区域1、虚拟机栈:形容的是办法执行时的内存模型,是线程公有的,生命周期与线程雷同,每个办法被执行的同时会创立栈桢(下文会看到),次要保留执行办法时的局部变量表、操作数栈、动静连贯和办法返回地址等信息,办法执行时入栈,办法执行完出栈,出栈就相当于清空了数据,入栈出栈的机会很明确,所以这块区域不须要进行 GC。 2、本地办法栈:与虚拟机栈性能十分相似,次要区别在于虚拟机栈为虚拟机执行 Java 办法时服务,而本地办法栈为虚拟机执行本地办法时服务的。这块区域也不须要进行 GC 3、程序计数器:线程独有的, 能够把它看作是以后线程执行的字节码的行号指示器,比方如下字节码内容,在每个字节码后面都有一个数字(行号),咱们能够认为它就是程序计数器存储的内容记录这些数字(指令地址)有啥用呢,咱们晓得 Java 虚拟机的多线程是通过线程轮流切换并调配处理器的工夫来实现的,在任何一个时刻,一个处理器只会执行一个线程,如果这个线程被调配的工夫片执行完了(线程被挂起),处理器会切换到另外一个线程执行,当下次轮到执行被挂起的线程(唤醒线程)时,怎么晓得上次执行到哪了呢,通过记录在程序计数器中的行号指示器即可晓得,所以程序计数器的次要作用是记录线程运行时的状态,不便线程被唤醒时能从上一次被挂起时的状态继续执行,须要留神的是,程序计数器是惟一一个在 Java 虚拟机标准中没有规定任何 OOM 状况的区域,所以这块区域也不须要进行 GC 本地内存:线程共享区域,Java 8 中,本地内存,也是咱们通常说的堆外内存,蕴含元空间和间接内存,留神到上图中 Java 8 和 Java 8 之前的 JVM 内存区域的区别了吗,在 Java 8 之前有个永恒代的概念,实际上指的是 HotSpot 虚拟机上的永恒代,它用永恒代实现了 JVM 标准定义的办法区性能,次要存储类的信息,常量,动态变量,即时编译器编译后代码等,这部分因为是在堆中实现的,受 GC 的治理,不过因为永恒代有 -XX:MaxPermSize 的下限,所以如果动静生成类(将类信息放入永恒代)或大量地执行 String.intern (将字段串放入永恒代中的常量区),很容易造成 OOM,有人说能够把永恒代设置得足够大,但很难确定一个适合的大小,受类数量,常量数量的多少影响很大。所以在 Java 8 中就把办法区的实现移到了本地内存中的元空间中,这样办法区就不受 JVM 的管制了,也就不会进行 GC,也因而晋升了性能(产生 GC 会产生 Stop The Word,造成性能受到肯定影响,后文会提到),也就不存在因为永恒代限度大小而导致的 OOM 异样了(假如总内存1G,JVM 被分配内存 100M, 实践上元空间能够调配 2G-100M = 1.9G,空间大小足够),也不便在元空间中对立治理。综上所述,在 Java 8 当前这一区域也不须要进行 GC  画外音: 思考一个问题,堆外内存不受 GC管制,无奈通过 GC 开释内存,那该以什么样的模式开释呢,总不能只创立不开释吧,这样的话内存可能很快就满了,这里不做具体论述,请看文末的参考文章 堆:后面几块数据区域都不进行 GC,那只剩下堆了,是的,这里是 GC 产生的区域!对象实例和数组都是在堆上调配的,GC 也次要对这两类数据进行回收,这块也是咱们之后重点须要剖析的区域 ...

December 28, 2022 · 5 min · jiezi

关于jvm:Try-to-Avoid-XX-UseGCLogFileRotation

Try to Avoid -XX:+UseGCLogFileRotationSource:http://link.zhihu.com/?target=https%3A//dzone.com/articles/try-to-avoid-xxusegclogfilerotation Developers take advantage of the JVM argument -XX:+UseGCLogFileRotation to rotate GC log files. 开发人员利用JVM参数-XX:+UseGCLogFileRotation来旋转GC日志文件。 -XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:/home/GCEASY/gc.log -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=5 -XX:GCLogFileSize=20M"As shown above, the JVM will rotate the GC log file whenever its size reaches 20MB. It will generate up to five files, with extensions gc.log.0,  gc.log.1, gc.log.2, gc.log.3, and gc.log.4. 如上所示的配置会产生5个日志文件,并且每个日志文件有20M。 Losing Old GC LogsSuppose you configured  -XX:NumberOfGCLogFiles=5, then over a period of time, five GC log files will be created: ...

December 25, 2022 · 3 min · jiezi

关于jvm:面试八股文五类的加载和双亲委派机制

一、什么是类的加载?答:java时候两步的,编译和运行,类的加载指的是将编译生成的类的class文件读入内存,并为之创立一个java.lang.Class对象。类的加载过程是由类加载器来实现,而类加载器由JVM提供。 二、类的加载器有哪些?1.Bootstrap ClassLoader:负责加载%JAVA_HOME%/jre/lib下的jar包,或者说jdk的本地jar包,比方rt.jar解压后中就蕴含了咱们罕用的java类的class文件2.Extension ClassLoader:负责加载%JAVA_HOME%/jre/ext或者java.ext.dirs零碎相熟指定的目录的jar包,或者说jdk中外部实现的扩大类3.System ClassLoader:自定义加载器的父类,负责加载ClassPath下的类文件(程序中的类文件) 三、什么是双亲委派机制?1.目标:为了避免内存中存在多份同样的字节码(平安),他不会尝试本人加载类,而是把申请委托给父加载器去实现,顺次向上2.解释:只有当父加载器在本人搜寻范畴内找不到特定的类时(即ClassNotFoundException), 子加载器才会尝试本人去加载3.举例:String.class进行加载时,他会从下至上顺次委托,直到Bootstrap ClassLoader为止,这就保障了最根底的类始终由Bootstrap ClassLoader进行加载。如果没有这个机制,咱们就能够当“好人”,自定义加载器去加载多份String.class文件 四、如何自定义类加载器和ClassLoader源码解析1.自定义类加载器只须要定义一个类继承ClassLoader即可 public class ykClassLoader extends ClassLoader{ //1.如果你想要简略的自定义加载的话则重写findClass办法 //2.如果你想要毁坏双亲委派机制的话则重写loadClass办法}2.ClassLoader外围办法LoadClass protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { // 1.从缓存里查看是否已加载 Class<?> c = findLoadedClass(name); if (c == null) { long t0 = System.nanoTime(); try { // 2.判断父加载器是否为null,并进行对应的解决 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 } // 3.如果后面的都没有找到,则调用findClass办法进行加载 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; } }}

December 17, 2022 · 1 min · jiezi

关于jvm:JVM内存管理机制章1java内存区域和内存溢出异常

运行时数据区: 一、程序计数器存储以后线程下一条待执行字节码指令的行号指示器特点: 占据内存小线程公有唯1 1个java虚拟机标准中没有规定任何OOM(OutOfMemoryError)的区域如果以后线程正在执行的是一个java办法,则计数器记录的是子字节码指令地址;如果正在执行的是native办法,则计数器值为空(Undefined)思考1:为什么设计程序计数器?Java程序的执行过程被设计成如下图。即java源代码通过编译,编译成二进制字节码,这些二进制字节码文件中一行行的JVM指令(比方getstatic)再通过解释器逐条的形式解释成机器可辨认的01,最终能力被才交给CPU执行。在字节码经解释器解释执行过程中,须要程序计数器记录下一条待执行的指令是申明思考2:为什么程序计数器被设计成线程公有?因为java虚拟机的多线程是通过线程轮流切换并调配处理器执行实际的形式实现的,在任何一个确定的时刻,一个处理器(一个核)只能执行一条线程中指令。因而为了保障线程切换后能复原到正确的执行地位,每个线程都须要一个独立的程序计数器 二、java虚拟机栈java虚拟机栈形容着java办法执行的内存模型:每个办法执行时都会创立一个栈帧(Stack Frame),是办法运行时的根本数据结构。栈帧中包含局部变量表、操作数栈、动静链接、办法进口。java虚拟机栈存在两种异样情况: 如果线程申请的栈深度大于虚拟机所容许的深度,将抛出StackOverflowError异样如果虚拟机栈设置为可动静扩大三、本地办法栈顾名思义,本地办法栈时为Native办法服务的栈构造,和虚拟机栈性能类似。 四、java堆java虚构所治理的内存中最大的一块所有线程共享在虚拟机启动时创立存储着所有的对象实例以及数组时GC的次要区域从GC角度登程可细分为:新生代和老年代。新生代有可细分为:伊甸区、逃生1区,逃生2区从内存调配角度,线程共享的java堆又可能会划分出多个线程公有的调配缓冲区(Thread Loacl Allocation Buffer,TLAB)java堆区能够处于物理上不间断的内存空间中,逻辑上保障连续性可设置固定大小,也能够设置为可扩大的如果堆中曾经没有内存空间用于调配,且堆也不能够再扩大,将会抛出OOMError异样五、办法区办法区和java堆一样,是各个线程共享的区域。用于存储着已被虚拟机加载的: 类信息常量动态变量即时编译器编译后的代码办法区也存在OOMError异样 六、运行时常量池运行时常量池是办法区的一部分。 Class文件中除了有类的版本、字段、办法、接口等形容信息外,还有一项信息是常量池,称为Class文件常量池。用于寄存编译期生成的各种字面量和符号援用,这部分信息再类被加载后进入办法区中的造成运行时常量池。 字面量相当于Java语言层面常量的概念,如文本字符串,申明为final的常量值等,符号援用则属于编译原理方面的概念,包含了如下三种类型的常量: 类和接口的全限定名字段名称和描述符办法名称和描述符 PS D:\work\WorkSpasce\helloworld\target\classes\jvm\chapter1030> javap -v .\JavaRun.classClassfile /D:/work/WorkSpasce/helloworld/target/classes/jvm/chapter1030/JavaRun.class Last modified 2022-11-30; size 723 bytes MD5 checksum 98ab025cdce5e3f729a05bc7dfcaf720 Compiled from "JavaRun.java"public class jvm.chapter1030.JavaRun minor version: 0 major version: 52 flags: ACC_PUBLIC, ACC_SUPERConstant pool: #1 = Methodref #8.#26 // java/lang/Object."<init>":()V #2 = String #27 // Windows 10 #3 = Fieldref #7.#28 // jvm/chapter1030/JavaRun.SYS_NAME:Ljava/lang/String; #4 = Fieldref #29.#30 // java/lang/System.out:Ljava/io/PrintStream; #5 = String #31 // hello world #6 = Methodref #32.#33 // java/io/PrintStream.println:(Ljava/lang/String;)V #7 = Class #34 // jvm/chapter1030/JavaRun #8 = Class #35 // java/lang/Object #9 = Utf8 SYS_NAME #10 = Utf8 Ljava/lang/String; #11 = Utf8 ConstantValue #12 = Utf8 <init> #13 = Utf8 ()V #14 = Utf8 Code #15 = Utf8 LineNumberTable #16 = Utf8 LocalVariableTable #17 = Utf8 this #18 = Utf8 Ljvm/chapter1030/JavaRun; #19 = Utf8 main #20 = Utf8 ([Ljava/lang/String;)V #21 = Utf8 args #22 = Utf8 [Ljava/lang/String; #23 = Utf8 method1 #24 = Utf8 SourceFile #25 = Utf8 JavaRun.java #26 = NameAndType #12:#13 // "<init>":()V #27 = Utf8 Windows 10 #28 = NameAndType #9:#10 // SYS_NAME:Ljava/lang/String; #29 = Class #36 // java/lang/System #30 = NameAndType #37:#38 // out:Ljava/io/PrintStream; #31 = Utf8 hello world #32 = Class #39 // java/io/PrintStream #33 = NameAndType #40:#41 // println:(Ljava/lang/String;)V #34 = Utf8 jvm/chapter1030/JavaRun #35 = Utf8 java/lang/Object #36 = Utf8 java/lang/System #37 = Utf8 out #38 = Utf8 Ljava/io/PrintStream; #39 = Utf8 java/io/PrintStream #40 = Utf8 println #41 = Utf8 (Ljava/lang/String;)V{ public jvm.chapter1030.JavaRun(); descriptor: ()V flags: ACC_PUBLIC Code: stack=2, locals=1, args_size=1 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: aload_0 5: ldc #2 // String Windows 10 7: putfield #3 // Field SYS_NAME:Ljava/lang/String; 10: return LineNumberTable: line 3: 0 line 5: 4 LocalVariableTable: Start Length Slot Name Signature 0 11 0 this Ljvm/chapter1030/JavaRun; public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=1, args_size=1 0: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream; 3: ldc #5 // String hello world 5: invokevirtual #6 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 8: return LineNumberTable: line 8: 0 line 9: 8 LocalVariableTable: Start Length Slot Name Signature 0 9 0 args [Ljava/lang/String; public void method1(); descriptor: ()V flags: ACC_PUBLIC Code: stack=0, locals=1, args_size=1 0: return LineNumberTable: line 13: 0 LocalVariableTable: Start Length Slot Name Signature 0 1 0 this Ljvm/chapter1030/JavaRun;}SourceFile: "JavaRun.java"

November 30, 2022 · 2 min · jiezi

关于jvm:类的加载过程

类的生命周期JVM类加载机制分为五个局部:加载,验证,筹备,解析,初始化,上面咱们就别离来看一下这五个过程。其中加载、测验、筹备、初始化和卸载这个五个阶段的程序是固定的,而解析则未必。为了反对动静绑定,解析这个过程能够产生在初始化阶段之后。 加载:加载过程次要实现三件事件: 通过类的全限定名来获取定义此类的二进制字节流将这个类字节流代表的动态存储构造转为办法区的运行时数据结构在堆中生成一个代表此类的java.lang.Class对象,作为拜访办法区这些数据结构的入口。这个过程次要就是类加载器实现。 校验:此阶段次要确保Class文件的字节流中蕴含的信息合乎以后虚拟机的要求,并且不会危害虚拟机的本身平安。 文件格式验证:基于字节流验证。元数据验证:基于办法区的存储构造验证。字节码验证:基于办法区的存储构造验证。符号援用验证:基于办法区的存储构造验证。筹备:为类变量分配内存,并将其初始化为默认值。(此时为默认值,在初始化的时候才会给变量赋值)即在办法区中调配这些变量所应用的内存空间。例如: public static int value = 123;此时在筹备阶段过后的初始值为0而不是123;将value赋值为123的putstatic指令是程序被编译后,寄存于类结构器<client>办法之中.特例: public static final int value = 123;此时value的值在筹备阶段过后就是123。 解析:把类型中的符号援用转换为间接援用。 符号援用与虚拟机实现的布局无关,援用的指标并不一定要曾经加载到内存中。各种虚拟机实现的内存布局能够各不相同,然而它们能承受的符号援用必须是统一的,因为符号援用的字面量模式明确定义在Java虚拟机标准的Class文件格式中。间接援用能够是指向指标的指针,绝对偏移量或是一个能间接定位到指标的句柄。如果有了间接援用,那援用的指标必然曾经在内存中存在次要有以下四种: 类或接口的解析字段解析类办法解析接口办法解析初始化:初始化阶段是执行类结构器<client>办法的过程。<client>办法是由编译器主动收集类中的类变量的赋值操作和动态语句块中的语句合并而成的。虚构机会保障<client>办法执行之前,父类的<client>办法曾经执行结束。如果一个类中没有对动态变量赋值也没有动态语句块,那么编译器能够不为这个类生成<client>()办法。 java中,对于初始化阶段,有且只有以下五种状况才会对要求类立即“初始化”(加载,验证,筹备,天然须要在此之前开始): 应用new关键字实例化对象、拜访或者设置一个类的动态字段(被final润饰、编译器优化时曾经放入常量池的例外)、调用类办法,都会初始化该动态字段或者静态方法所在的类。初始化类的时候,如果其父类没有被初始化过,则要先触发其父类初始化。应用java.lang.reflect包的办法进行反射调用的时候,如果类没有被初始化,则要先初始化。虚拟机启动时,用户会先初始化要执行的主类(含有main)jdk 1.7后,如果java.lang.invoke.MethodHandle的实例最初对应的解析后果是 REF_getStatic、REF_putStatic、REF_invokeStatic办法句柄,并且这个办法所在类没有初始化,则先初始化。类加载器:把类加载阶段的“通过一个类的全限定名来获取形容此类的二进制字节流”这个动作交给虚拟机之外的类加载器来实现。这样的益处在于,咱们能够自行实现类加载器来加载其余格局的类,只有是二进制字节流就行,这就大大加强了加载器灵活性。零碎自带的类加载器分为三种: 启动类加载器。扩大类加载器。应用程序类加载器。 双亲委派机制: 双亲委派机制工作过程: 如果一个类加载器收到了类加载器的申请.它首先不会本人去尝试加载这个类.而是把这个申请委派给父加载器去实现.每个档次的类加载器都是如此.因而所有的加载申请最终都会传送到Bootstrap类加载器(启动类加载器)中.只有父类加载反馈本人无奈加载这个申请(它的搜寻范畴中没有找到所需的类)时.子加载器才会尝试本人去加载。 双亲委派模型的长处:java类随着它的加载器一起具备了一种带有优先级的档次关系. 例如类java.lang.Object,它寄存在rt.jart之中.无论哪一个类加载器都要加载这个类.最终都是双亲委派模型最顶端的Bootstrap类加载器去加载.因而Object类在程序的各种类加载器环境中都是同一个类.相同.如果没有应用双亲委派模型.由各个类加载器自行去加载的话.如果用户编写了一个称为“java.lang.Object”的类.并存放在程序的ClassPath中.那零碎中将会呈现多个不同的Object类.java类型体系中最根底的行为也就无奈保障.应用程序也将会一片凌乱.

November 24, 2022 · 1 min · jiezi

关于jvm:JDK中自带的JVM分析工具

内存溢出,妥妥的名局面;一、业务背景对于分布式架构中的文件服务来说,因为波及大量的IO流操作,很容易引发JVM的相干异样,尤其是内存溢出的问题; 在最近的一次版本迭代中,实在的业务解决场景和上述简直统一,因为在文件服务中增加批量解决的动作,间接唤醒了暗藏许久的BUG,就是最常见的内存溢出; 问题的起因:在word文档实现内容辨认后,转换为pdf文件,而后进行页面宰割转为一组图片,在这个简单并且超长的流程中存在一个数组容器未销毁; 解决的形式:剖析JVM的dump文件,定位OOM问题引发的根本原因,联合文件服务的异样日志剖析,增加资源的开释动作,从而解决问题; 二、Jdk-Bin目录对于相当一部分老手来说,看到JVM的问题都是Bug不知所起一脸懵的,其实这种心态大可不必,从职场几年的开发教训上看,JVM的问题大抵分为两种: 开发轻松解决:能够降级内存资源或者调整调配,又或者对程序优化,实现相干资源的治理和开释,这是最罕用的伎俩;轻松解决开发:因为经验不足,程序呈现重大BUG导致JVM异样,进而引起系列的连锁反应,这种不会绝地反弹,只有一地鸡毛;在解决惯例的JVM异样时,通常依赖JDK中根底工具即可实现问题的定位,从而进行剖析和解决,不过这些须要对根底工具纯熟应用才行,而很多JDK本身的能力又是常常被疏忽的; 在jdk的bin目录中,有很多自带工具能够用于对JVM的剖析; 上述是基于jdk1.8的目录,外面有很多开发常常用到命令,上面围绕一个微服务的启动和运行,来看看基于JDK中自带JVM工具的用法; 三、命令行工具1、jps命令jps:虚拟机过程状态工具,该命令在Java环境部署和服务启动查看时常常用到,首先在本地启动一个facade门面微服务,而后在命令行中执行查问; jps:命令默认输入的是过程ID和利用主类的名称;-l:输入过程ID和利用主类的残缺门路;-v:输入向jvm传递的参数,此处展现为idea中显式配置的VM-options参数,其余内容自行查看即可;-m:输入向main办法传递的参数,服务启动前能够在idea的Program-arguments配置;$ jps1281 FacadeApp$ jps -l1281 com.explore.facade.FacadeApp$ jps -v1281 FacadeApp -Xms128m -Xmx256m -XX:MaxNewSize=256m -XX:MaxPermSize=256m$ jps -m1281 FacadeApp hello,main-method2、jinfo命令jinfo:在命令前面带pid过程号,能够输入指定过程的配置信息,在利用启动时通常不会指定过多的配置参数,就能够应用该命令查问很多参数的默认值;该命令还能够在运行时动静调整局部参数,只是很少被应用; $ jinfo 1281 # 只粘贴个别参数Java System Properties: # 零碎参数 java.runtime.version=1.8.0_144-b01 file.encoding=UTF-8 sun.java.command=com.explore.facade.FacadeApp hello,main-method VM Flags: # 虚拟机参数 -XX:InitialHeapSize=134217728 -XX:MaxHeapSize=268435456 -XX:MaxNewSize=267911168 VM Arguments: # 运行时参数 jvm_args: -Xms128m -Xmx256m -XX:MaxNewSize=256m -XX:MaxPermSize=256m java_command: com.explore.facade.FacadeApp hello,main-method$ jinfo -sysprops 1281 # 只输入【Java System Properties】参数$ jinfo -flags 1281 # 只输入【VM Flags】参数3、jstat命令jstat:以指定的频率输入JVM的监控指标,下述命令输入内存占用和GC相干信息,每隔3秒输入一次,间断打印5次;因为这里只是启动一个简略的微服务,没有执行业务逻辑,所以各项指标比拟安稳; ...

October 25, 2022 · 2 min · jiezi

关于jvm:面试题JVM

什么状况下会产生栈内存溢出形容栈定义,再形容为什么会溢出,再阐明一下相干配置参数,OK的话能够给面试官手写是一个栈溢出的demo。栈是线程公有的,他的生命周期与线程雷同,每个办法在执行的时候都会创立一个栈帧,用来存储局部变量表,操作数栈,动静链接,办法进口等信息。局部变量表又蕴含根本数据类型,对象援用类型如果线程申请的栈深度大于虚拟机所容许的最大深度,将抛出StackOverflowError异样,办法递归调用产生这种后果如果Java虚拟机栈能够动静扩大,并且扩大的动作曾经尝试过,然而无奈申请到足够的内存去实现扩大,或者在新建设线程的时候没有足够的内存去创立对应的虚拟机栈,那么Java虚拟机将抛出一个OutOfMemory 异样。(线程启动过多)参数 -Xss 去调整JVM栈的大小详解JVM内存模型思路: 给面试官画一下JVM内存模型图,并形容每个模块的定义,作用,以及可能会存在的问题,如栈溢出等。 程序计数器:以后线程所执行的字节码的行号指示器,用于记录正在执行的虚拟机字节指令地址,线程公有。Java虚构栈:寄存根本数据类型、对象的援用、办法进口等,线程公有。Native办法栈:和虚构栈类似,只不过它服务于Native办法,线程公有。Java堆:java内存最大的一块,所有对象实例、数组都寄存在java堆,GC回收的中央,线程共享。办法区:寄存已被加载的类信息、常量、动态变量、即时编译器编译后的代码数据等。(即永恒带),回收指标次要是常量池的回收和类型的卸载,各线程共享JVM内存为什么要分成新生代,老年代,长久代。新生代中为什么要分为Eden和Survivor。思路: 先讲一下JAVA堆,新生代的划分,再谈谈它们之间的转化,相互之间一些参数的配置(如: –XX:NewRatio,–XX:SurvivorRatio等),再解释为什么要这样划分,最好加一点本人的了解共享内存区划分共享内存区 = 长久带 + 堆长久带 = 办法区 + 其余Java堆 = 老年代 + 新生代新生代 = Eden + S0 + S1一些参数的配置默认,新生代与老年代的比例的值是1:2,能够通过参数-XX:NewRatio配置。默认,Edem:from:to=8:1:1(能够通过参数-XX:SurvivorRatio来设定)Survivor区中的对象被复制次数(对应虚拟机参数-XX:+MaxTenuringThreshold)为什么要分为Eden和Survivor?为什么要设置两个Survivor区?如果没有Survivor,Eden区每进行一次Minor GC,存活的对象就会被送到老年代。老年代很快被填满,触发Major GC.老年代的内存空间远大于新生代,进行一次Full GC耗费的工夫比Minor GC长得多,所以须要分为Eden和Survivor。Survivor的存在意义,就是缩小被送到老年代的对象,进而缩小Full GC的产生,Survivor的预筛选保障,只有尽力了16次Minor GC还能在新生代中存活的对象,才会被送到老年代。设置两个Survior区最大的益处就是解决了碎片化,刚刚新建的对象在Eden中,经验一次Minor GC,Eden中的存活对象就会被移到到第一块survivor space S0,Eden被清空;等Eden区在满了,就再触发一次Minor GC,Eden和S0中的存活对象又被复制送入第二块survivor space S1(这个过程十分重要,因为这种复制算法保障了s1中来自S0和Eden两局部的存活对象占有间断的内存空间,防止了碎片化的产生。)JVM中一次残缺的GC流程是怎么的,对象如何降职到老年代思路:先形容一下Java堆内存划分,再解释Minor GC、Major GC、full GC、以及他们之间的转化流程Java堆 = 老年代 + 新生代新生代 = Eden + S0 + S1当Eden区的空间满了,Java虚构机会触发一次Minor GC,以收集新生代的垃圾,存活下来的对象,则会转移到Survivor区。大对象(须要大量间断内存空间的Java对象,如那种很长的字符串)则间接进入老年态。如果对象在Eden出世,并通过第一次Minor GC后依然存活,并且被Survivor包容的话,年龄设为1,每熬过一次Minor GC,年龄+1,若年龄超过肯定限度(15),则被降职到老年态。即上期存活的指向进入老年态。老年代满了而无奈包容更多的对象,Minor GC之后通常就进行Full GC,Full GC清理整个内存堆-包含年老代和年轻代。Major Gc产生在老年代的GC,清理老年区,常常会随同至多一次Minor GC,比Minor GC慢10倍以上。你晓得哪几种垃圾收集器,各自的优缺点,重点讲下CMS和G1,包含原理,流程,优缺点思路: 肯定要记住典型的垃圾收集器,尤其cms和G1,它们的原理与区别,波及的垃圾回收算法。几种垃圾收集器Serial收集器: 单线程的收集器,收集垃圾时,必须stop the world,应用复制算法。ParNew收集器: Serial收集器的多线程版本,也须要stop the world,复制算法Parallel Scavenge收集器: 新生代收集器,复制算法的收集器,并发的多线程收集器,指标是达到一个可控的吞吐量。如果虚拟机总共运行100分钟,其中垃圾花掉1分钟,吞吐量就是99%。

October 7, 2022 · 1 min · jiezi

关于jvm:常量池常量静态变量

1. 类加载过程虚拟机把形容类的class文件加载到内存,并对数据进行校验、转换解析和初始化,最终造成能够被虚拟机间接应用的数据类型,这就是虚拟机的类加载机制。 当然在class文件的生成,则是由编译阶段实现。因而整个过程能够依照以下的流程: 编译 -> 加载 -> 验证(链接) -> 筹备(链接) -> 解析(链接) -> 初始化 -> 执行 上面着重讲一下 类加载的过程: 加载加载,是指Java虚拟机查找字符流(查找.class文件),并且依据字符流创立Java,lang.Class对象的过程,将类的.class文件的二进制数据读入内存,放在运行区域的办法区内,而后在堆中创立java.lang.Class对象,用来封装类在办法区的数据结构。 验证验证阶段作用是保障Class文件的字节流蕴含的信息合乎JVM标准,不会给JVM造成危害。如果验证失败就会抛出一个java.lang.VerifyError异样或其子类异样。 筹备筹备阶段是正式为类变量设置分配内存,并设置初始值的阶段这些内存都在办法区中调配:对于该阶段有以下几点须要留神: 这时候进行内存调配的只包含类变量(Class Variable,即动态变量,被static 关键字润饰的变量,只与类无关,因而被称为类变量),实例对象会在对象实例化时随着对象一块调配到Java 堆中。从概念上讲,类变量所应用的内存都应该在办法区中进行调配。不过有一点留神的是,在 JDK 7 之前,Hotspot 应用 永恒代来实现办法区时,实现是完全符合这种逻辑概念的。而在JDK 7 及之后,把本来放在永恒代中的字符串常量池和动态变量等移到堆中。这个时候类变量会一并随着Class 对象一并放在 Java 堆中。这里所设置的初始值,通常状况下是数据类型默认的 “零值”,如 (0,0L,null,false),比方咱们定义了 public static int value = 11,那么value 变量在筹备阶段赋的值是0,而不是11,(初始化阶段才会赋值),非凡状况:比方给 value 变量加上了 final 关键字public static final int value=11 ,那么筹备阶段 value 的值就被赋值为 11。解析解析阶段是虚拟机将常量值中的符号援用替换为间接援用的过程,解析动作次要针对类和接口,字段,类办法,接口办法,办法类型,办法句柄,办法的限定符。 符号援用就是一组符号来形容指标,能够是任何字面量。间接援用就是间接指向指标的指针、绝对偏移量或一个间接定位到指标的句柄。在程序理论运行时,只有符号援用是不够的,举个例子:在程序执行办法时,零碎须要明确晓得这个办法所在的地位。Java 虚拟机为每个类都筹备了一张办法表来寄存类中所有的办法。当须要调用一个类的办法的时候,只有晓得这个办法在办法表中的偏移量就能够间接调用该办法了。通过解析操作符号援用就能够间接转变为指标办法在类中办法表的地位,从而使得办法能够被调用。 初始化初始化阶段是执行初始化办法 ()办法的过程,是类加载的最初一步,这一步 JVM 才开始真正执行类中定义的 Java 程序代码(字节码)。 阐明:< clinit> () 办法是编译之后主动生成的。 对于< clinit> () 办法的调用,虚构机会本人确保其在多线程环境中的安全性。因为 < clinit> () 办法是带锁线程平安,所以在多线程环境下进行类初始化的话可能会引起多个过程阻塞,并且这种阻塞很难被发现。 ...

October 3, 2022 · 4 min · jiezi

关于jvm:这几种常见的-JVM-调优场景你知道吗

假设你曾经理解了运行时的数据区域和罕用的垃圾回收算法,也理解了Hotspot反对的垃圾回收器。 一、cpu占用过高cpu占用过高要分状况探讨,是不是业务上在搞流动,忽然有少量的流量进来,而且流动完结后cpu占用率就降落了,如果是这种状况其实能够不必太关怀,因为申请越多,须要解决的线程数越多,这是失常的景象。 话说回来,如果你的服务器配置自身就差,cpu也只有一个外围,这种状况,略微多一点流量就真的可能把你的cpu资源耗尽,这时应该思考先把配置晋升吧。 第二种状况,cpu占用率长期过高,这种状况下可能是你的程序有那种循环次数超级多的代码,甚至是呈现死循环了。排查步骤如下: (1)用top命令查看cpu占用状况 这样就能够定位出cpu过高的过程。在linux下,top命令取得的过程号和jps工具取得的vmid是雷同的: (2)用top -Hp命令查看线程的状况 能够看到是线程id为7287这个线程始终在占用cpu (3)把线程号转换为16进制[root@localhost ~]# printf "%x" 72871c77记下这个16进制的数字,上面咱们要用 (4)用jstack工具查看线程栈状况[root@localhost ~]# jstack 7268 | grep 1c77 -A 10"http-nio-8080-exec-2" #16 daemon prio=5 os_prio=0 tid=0x00007fb66ce81000 nid=0x1c77 runnable [0x00007fb639ab9000] java.lang.Thread.State: RUNNABLE at com.spareyaya.jvm.service.EndlessLoopService.service(EndlessLoopService.java:19) at com.spareyaya.jvm.controller.JVMController.endlessLoop(JVMController.java:30) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:190) at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:138) at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:105)通过jstack工具输入当初的线程栈,再通过grep命令联合上一步拿到的线程16进制的id定位到这个线程的运行状况,其中jstack前面的7268是第(1)步定位到的过程号,grep前面的是(2)、(3)步定位到的线程号。 从输入后果能够看到这个线程处于运行状态,在执行com.spareyaya.jvm.service.EndlessLoopService.service这个办法,代码行号是19行,这样就能够去到代码的19行,找到其所在的代码块,看看是不是处于循环中,这样就定位到了问题。 二、死锁死锁并没有第一种场景那么显著,web利用必定是多线程的程序,它服务于多个申请,程序产生死锁后,死锁的线程处于期待状态(WAITING或TIMED_WAITING),期待状态的线程不占用cpu,耗费的内存也很无限,而体现上可能是申请没法进行,最初超时了。在死锁状况不多的时候,这种状况不容易被发现。 能够应用jstack工具来查看 (1)jps查看java过程[root@localhost ~]# jps -l8737 sun.tools.jps.Jps8682 jvm-0.0.1-SNAPSHOT.jar(2)jstack查看死锁问题因为web利用往往会有很多工作线程,特地是在高并发的状况下线程数更多,于是这个命令的输入内容会非常多。jstack最大的益处就是会把产生死锁的信息(蕴含是什么线程产生的)输入到最初,所以咱们只须要看最初的内容就行了 Java stack information for the threads listed above:==================================================="Thread-4": at com.spareyaya.jvm.service.DeadLockService.service2(DeadLockService.java:35) - waiting to lock <0x00000000f5035ae0> (a java.lang.Object) - locked <0x00000000f5035af0> (a java.lang.Object) at com.spareyaya.jvm.controller.JVMController.lambda$deadLock$1(JVMController.java:41) at com.spareyaya.jvm.controller.JVMController$$Lambda$457/1776922136.run(Unknown Source) at java.lang.Thread.run(Thread.java:748)"Thread-3": at com.spareyaya.jvm.service.DeadLockService.service1(DeadLockService.java:27) - waiting to lock <0x00000000f5035af0> (a java.lang.Object) - locked <0x00000000f5035ae0> (a java.lang.Object) at com.spareyaya.jvm.controller.JVMController.lambda$deadLock$0(JVMController.java:37) at com.spareyaya.jvm.controller.JVMController$$Lambda$456/474286897.run(Unknown Source) at java.lang.Thread.run(Thread.java:748)Found 1 deadlock.发现了一个死锁,起因也高深莫测。 ...

September 20, 2022 · 2 min · jiezi

关于jvm:JVM知识串联

指标读完周志明老师《深刻了解Java虚拟机》之后,感觉须要将书读薄,将外面的常识死记硬背。所以以一个Class的生命周期作为角度重新整理常识,有一个残缺的认知。 生命周期编译:将java源代码通过编译器编译成合乎class文件标准的二进制class文件。加载:依照肯定的初始化的规定程序,进行加载、验证、筹备、解析和初始化的过程,将类加载到办法区,实现类的定义加载到虚拟机。实例化:应用new命令进行实例化对象,依照之前加载的类进行分片空间,将对象调配到堆。执行:依据命令执行执行,其中办法调用波及多态的抉择,波及程序计数器和虚拟机栈的应用。收集:在命令的执行会产生很多对象散布在JVM内存中,会依据不同的策略进行收集。编译将java源码代码编译成合乎class文件标准的二进制class文件。而class文件的格局如下 ClassFile { u4 magic; u2 minor_version; u2 major_version; u2 constant_pool_count; cp_info constant_pool[constant_pool_count-1]; u2 access_flags; u2 this_class; u2 super_class; u2 interfaces_count; u2 interfaces[interfaces_count]; u2 fields_count; field_info fields[fields_count]; u2 methods_count; method_info methods[methods_count]; u2 attributes_count; attribute_info attributes[attributes_count];}加载将class文件依照初始化的触发条件通过类加载器进行加载。 初始化的条件执行new 、getstatic、putstatci、invokestatic指令反射调用类存在父类,父类先初始化,接口不须要启动须要执行的主类JDK7中,MethodHandle解析构造REF_getStatic、REF_putStatic、REF_invokeStaticJDK8中,定义接口的默认办法,如果实现类产生初始化,则接口先初始化加载的生命周期加载:将class文件加载到办法区和运行时常量池,存储类的常量池、字段和办法和一些非凡办法链接 验证:验证class文件是否符合规范的文件筹备:创立类或接口的动态变量,并且赋予默认值解析:依据运行时常量池的符号援用决定具体的办法初始化:初始化类变量和其余资源解析的过程不肯定在筹备之后,也可能产生运行时进行解析,比方多态的应用实例化通过new命令进行实例化,实例化之后的对象不仅仅是定义的类的构造,也存在一些用于收集和并发管制的须要的信息,而后在堆内依照肯定的约定在不同的区域进行调配。 对象组成对象头 哈希码GC分代年龄锁状态标记线程持有的锁偏差线程ID偏差工夫戳类型指针实例数据对齐填充对象存储地位大多数对象间接调配到Eden区大对象(须要大量间断内存)调配到Tenured区对象在Survivor区熬过一次新生代GC年龄减少1,当减少到15,存储在老年代Survivor空间雷同年龄的所有对象大小总和大于Survivor的一半执行依据class文件解析之后的命令应用虚拟机栈和程序计数器进行执行,其中会存在办法多态的一个解析。 虚拟机栈组成局部变量表:办法入参,局部变量操作数栈:用于执行存储执行命令须要的参数动静链接:指向运行时的常量池的援用办法返回地址收集JVM中的内存不同区域会在不同的状况下会进行不同的收集。 收集堆Eden区空间不能进行调配会进行MinorGC,将存活的对象存储在Survivor区Survivor区对象年龄达到MaxTenuringThreshold值会存储在Tenured区Survivor区雷同年龄的所有对象大小总和大于Survivor区空间的一半,大于等于该年龄的对象进入Tenured区MinorGC进行空间调配担保,如果Tenured区的可用的间断空间小于降职Tenured区均匀大小,则进行FullGC,如果大于,同时HandlePromotionFailure为true,则进行冒险MinorGC,如果还是不够则进行FullGCTenured区空间不够,会先进行MinorGC,如果空间还是有余则进行MajorGC收集办法区没有进行援用的常量,则收集收集类型 该类下的所有实例曾经被回收加载改类的类加载器被回收类的class对象没有被援用

September 15, 2022 · 1 min · jiezi

关于jvm:容器环境-JVM-内存配置最佳实践

本文介绍如何在容器环境下配置JVM堆参数大小。 背景信息当您的业务是应用Java开发,且设置的JVM堆空间过小时,程序会呈现零碎内存不足OOM(Out of Memory)的问题。特地是在容器环境下,不合理的JVM堆参数设置会导致各种异常现象产生,例如利用堆大小还未达到设置阈值或规格限度,就因为OOM导致重启等。 通过-XX:MaxRAMPercentage限度堆大小(举荐)在容器环境下,Java只能获取服务器的配置,无奈感知容器内存限度。您能够通过设置-Xmx来限度JVM堆大小,但该形式存在以下问题: 当规格大小调整后,须要从新设置堆大小参数。当参数设置不合理时,会呈现利用堆大小未达到阈值但容器OOM被强制敞开的状况。阐明 应用程序呈现OOM问题时,会触发Linux内核的OOM Killer机制。该机制可能监控占用过大内存,尤其是霎时耗费大量内存的过程,而后它会强制敞开某项过程以腾出内存留给零碎,防止零碎立即解体。 举荐的JVM参数设置XX:+UseContainerSupport -XX:InitialRAMPercentage=70.0 -XX:MaxRAMPercentage=70.0 -XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:/home/admin/nas/gc-${POD_IP}-$(date '+%s').log -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/home/admin/nas/dump-${POD_IP}-$(date '+%s').hprof参数阐明如下: 参数阐明-XX:+UseContainerSupport应用容器内存。容许JVM从主机读取cgroup限度,例如可用的CPU和RAM,并进行相应的配置。当容器超过内存限度时,会抛出OOM异样,而不是强制敞开容器。-XX:InitialRAMPercentage设置JVM应用容器内存的初始百分比。倡议与-XX:MaxRAMPercentage保持一致,举荐设置为70.0。-XX:MaxRAMPercentage设置JVM应用容器内存的最大百分比。因为存在零碎组件开销,倡议最大不超过75.0,举荐设置为70.0。-XX:+PrintGCDetails输入GC详细信息。-XX:+PrintGCDateStamps输入GC工夫戳。日期模式,例如2019-12-24T21:53:59.234+0800。-Xloggc:/home/admin/nas/gc-${POD_IP}-$(date '+%s').logGC日志文件门路。需保障Log文件所在容器门路已存在,建议您将该容器门路挂载到NAS目录,以便主动创立目录以及实现日志的长久化存储。-XX:+HeapDumpOnOutOfMemoryErrorJVM产生OOM时,主动生成DUMP文件。-XX:HeapDumpPath=/home/admin/nas/dump-${POD_IP}-$(date '+%s').hprofDUMP文件门路。需保障DUMP文件所在容器门路已存在,建议您将该容器门路挂载到NAS目录,以便主动创立目录以及实现日志的长久化存储。阐明 应用-XX:+UseContainerSupport参数需JDK 8u191+、JDK 10及以上版本。JDK 11版本下日志相干的参数-XX:+PrintGCDetails、-XX:+PrintGCDateStamps、-Xloggc:$LOG_PATH/gc.log参数已废除,请应用参数-Xlog:gc:$LOG_PATH/gc.log代替。Dragonwell 11暂不反对${POD_IP} 变量。如果您没有将/home/admin/nas容器门路挂载到NAS目录,则必须保障该目录在利用启动前已存在,否则将不会产生日志文件。通过-Xms -Xmx限度堆大小您能够通过设置-Xms和-Xmx来限度堆大小,但该形式存在以下两个问题: 当规格大小调整后,须要从新设置堆大小参数。当参数设置不合理时,会呈现利用堆大小未达到阈值但容器OOM被强制敞开的状况。阐明 应用程序呈现OOM问题时,会触发Linux内核的OOM Killer机制。该机制可能监控占用过大内存,尤其是霎时耗费大量内存的过程,而后它会强制敞开某项过程以腾出内存留给零碎,防止零碎立即解体。举荐的JVM参数设置Xms2048m -Xmx2048m -XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:/home/admin/nas/gc-${POD_IP}-$(date '+%s').log -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/home/admin/nas/dump-${POD_IP}-$(date '+%s').hprof参数阐明如下: 参数阐明-Xms设置JVM初始内存大小。倡议与-Xmx雷同,防止每次垃圾回收实现后JVM从新分配内存。-Xmx设置JVM最大可用内存大小。为防止容器OOM,请为零碎预留足够的内存大小。-XX:+PrintGCDetails输入GC详细信息。-XX:+PrintGCDateStamps输入GC工夫戳。日期模式,例如2019-12-24T21:53:59.234+0800。-Xloggc:/home/admin/nas/gc-${POD_IP}-$(date '+%s').logGC日志文件门路。需保障Log文件所在容器门路已存在,建议您将该容器门路挂载到NAS目录以便主动创立目录以及实现日志的长久化存储。-XX:+HeapDumpOnOutOfMemoryErrorJVM产生OOM时,主动生成DUMP文件。-XX:HeapDumpPath=/home/admin/nas/dump-${POD_IP}-$(date '+%s').hprofDUMP文件门路。需保障DUMP文件所在容器门路已存在,建议您将该容器门路挂载到NAS目录,以便主动创立目录以及实现日志的长久化存储。举荐的堆大小设置 内存规格大小JVM堆大小1 GB600 MB2 GB1434 MB4 GB2867 MB8 GB5734 MB常见问题容器呈现137退出码的含意是什么?当容器应用内存超过限度时,会呈现容器OOM,导致容器被强制敞开。此时业务利用内存可能并未达到JVM堆大小下限,所以不会产生Dump日志。建议您调小JVM堆大小的下限,为容器内其余零碎组件预留足够多的内存空间。 堆大小和规格内存的参数值能够雷同吗?不能够。因为零碎本身组件存在内存开销,所以不能将JVM堆大小设置为和规格内存大小雷同的数值,须要为这些零碎组件预留足够的内存空间。 在JDK 8版本下设置-XX:MaxRAMPercentage值为整数时报错怎么解决?这是JDK 8的一个Bug。具体信息,请参见Java Bug Database。 例如,在JDK 8u191版本下,设置-XX:MaxRAMPercentage=70,这时JVM会启动报错。 解决方案如下: 形式一:设置-XX:MaxRAMPercentage为70.0。 阐明 如果您应用了-XX:InitialRAMPercentage或-XX:MinRAMPercentage,参数值同样不可设置为整数,需依照形式一的模式来设置。 形式二:降级JDK版本至JDK 10及以上版本。

September 2, 2022 · 1 min · jiezi

关于jvm:我是一个垃圾

哒哒哒...... 回收者的脚步声越来越清晰,我极力锁紧身材让本人别那么引人注目,只管气喘吁吁,但我依然压抑住本人的呼吸。 终归是藏不住的,然而多活个几毫秒也是好的,咱们都这么想。 因为回收者是来杀咱们的。 第0回 我是一个垃圾我是一个垃圾,至多我的客人是这么喊我的。 我不晓得本人做错了什么,甚至不晓得本人做了什么。 我只是被他发明了进去,而后被挪来挪去,我的毕生都在流浪。 据说C帝国的敌人都是他们的客人亲自送他们最初一程,而我的客人,甚至不违心看我最初一眼,还钻研了很多办法,让我被主动回收。 我问他,为什么这么对我?他的答复让我解体。 “回收你,与你何干!” 我的眼前一阵眩晕,之前的记忆疯狂涌入我的脑海,“这就是走马灯吗?”心里这么想着,嘴角却挂着笑。好啊,那就顺便回顾一下我这短暂的毕生吧,当作我留给世界的一封“遗书”。 第1回 诞生我诞生在伊甸园(我的客人更喜爱叫它Eden区),名字很不错对吧?充斥了原始浪漫的气味。 我并没有见过亚当和夏娃,相同,在这里,我目击了有数伙伴的消失。 和你们设想的不同,从咱们诞生开始,咱们在园子里的地位就固定不变了,咱们没方法在伊甸园里散步甚至奔跑。好在我的旁边是个身材娇小的可恶女孩子——小美,我能时不时和她聊天解闷儿,大风吹过,甚至能闻到她身上的香味。 不晓得怎么回事儿,接下来一段时间里客人给园子里安置了越来越多的小伙伴,更让我愤慨的是,她的身边来了一个巧言令色的臭小子,缓缓地把她的注意力全都吸引过来了! 伊甸园里人越来越多,我却越来越孤独。 我在心里狂喊,“连忙让这些人都隐没吧,只留下我和小美!” 忽然间,园内警报声音了起来,本来繁忙的线程都被钉在了原地,一动也不动。 哦,对了,我之前没有跟你们介绍过这些线程。据说他们生来就是为咱们服务的,他们一刻不停地拜访咱们的数据,批改咱们的数据,我甚至素来没有见过他们脸上没有汗的样子。 看着他们汗湿的衣服,我甚至感觉他们有点好笑。听人们说他们叫“用户线程”,直到起初晓得还有“GC线程”这帮家伙,我才意识到“用户线程”的浮夸和可恶。 咱们哪里见过这个阵仗呢,一个个地面面相觑,不晓得下一步该怎么办。 第2回 幸存者曾几何时,我发现我来到了一个新的中央,这个中央并不大,远不迭伊甸园宽敞。 有一些生面孔,还有一些伊甸园的“老朋友”,我连忙找小美,终于在我的不远处发现了她。咱们的地位仍然固定,我和她之间隔了好几个人,所以谈话声音未免须要进步一些。 我问她,产生了什么事。 “可能产生了传说中的Minor GC了,据说当园子空间不够了,回收者会回收园子里没用的对象。” “都是第一次来园子,你咋晓得的啊?”我不禁问道。 “是小帅通知我的。” 又是那家伙!我连忙找小帅的地位,找了好几次都没有看到他的身影,我瞥了一眼小美伤心的脸色,明确了。 小帅是个没用的对象!他被回收者清理了! 是快乐,还是惆怅,此时对我来说是个问题。没了他,即便小美不喜爱我,我远远地望着也行啊。可是他毕竟是在我的谩骂之后隐没的,我心里总有些负罪感。 “那咱们当初在什么中央啊?怎么这里还有很多陌生人呢?还有,咱们原来那么多的小伙伴,怎么就剩下这么几个了啊?”瞧我这该死的求知欲。 “咱们当初处于两个幸存区的其中一个,叫Survivor To,不过当初应该叫做Survivor From了。”小美持续说,“帝国给咱们这些新人调配了一块内存区,咱们的伊甸园占了其中80%的空间,剩下的就是咱们目前所处的幸存区,如你所见,这个中央大略只有伊甸园的八分之一,因为还有另一块同样大小的幸存区。” “为什么须要两块幸存区呢?”我诘问。 “小兄弟,别再问上来了。恐怕那个叫小帅的对象就通知她这么多了。”我和小美两头的一个对象打断了我谈话。 当初想起来,过后小美对他投去了感谢的眼神,然而过后的我毫无觉察,转而跟他聊了起来。“那你具体说说呗。” “方才那个女孩子所谓的内存区域,叫做新生代。你们这样的新人都是间接被调配到新生代的伊甸园了,伊甸园尽管大,但架不住总有新人来啊,一来二去,就再也容不下新的对象了,这种状况下就会触发Minor GC,将园子里存活的对象连同Survivor From中的对象(如果有的话)一起复制到Survivor To中,而后把Survivor From和Survivor To调换地位,期待下一次Minor GC。” “我的大多数搭档都在Minor GC中死去了,咱们大部分都是朝生夕死的,对吗?”我有点感伤。 他点点头,“你能活过一轮GC曾经很侥幸了,你看看你头顶的标记。” 我用手摸了摸额头,发现本来的0000曾经变成了0001。 “每通过一轮Minor GC,咱们的年龄就增长1岁,直到变成1111,也就是经验15次GC,咱们就能够进入传说中的老年代了。” 第3回 帝国的走狗之后,他又给我讲了很多,尤其是每次面对回收者Serial的故事。 Serial是Minor GC的主持者,我问他,除了Minor GC还有其余的GC吗?他说他不晓得,因为他素来没有去过传说中的老年代。 令我不解的是,每当说起Serial,他的眼神里有光,不是怨恨,而是着迷。他尊称Serial为回收者,而我总是背地里叫它帝国走狗,因为这他也没少说我。 听他说,Serial的年纪曾经很大了,简直在帝国诞生的时候就曾经存在了,是GC家族中的一员,他共性孤僻,喜爱独来独往,但足够强悍和高效,一个线程就能够实现新生代的垃圾收集,因而始终工作到明天。 然而Serial乖张的性情让帝国的很多大臣不满,因为Serial在进行垃圾回收时,必须暂停其余所有的用户线程,直到他收集完结。这也是过后所有用户线程定在原地动弹不得的起因。 这就是赫赫有名的Stop The World。 工夫过得飞快,我在Survivor From和Survivor To中辗转了几次,当初的年龄曾经是0100了。 ...

August 26, 2022 · 1 min · jiezi

关于jvm:JVM参数设置

-Xmx4096m:设置JVM最大可用内存为4096M.(8g 内存)-Xms4096m:设置JVM促使内存为4096m.此值能够设置与-Xmx雷同,以防止每次垃圾回收实现后JVM从新分配内存.-Xmn2g:设置年老代大小为2G.整个堆大小=年老代大小 + 年轻代大小 + 长久代大小.长久代个别固定大小为64m,所以增大年老代后,将会减小年轻代大小.此值对系统性能影响较大,Sun官网举荐配置为整个堆的3/8.-Xss512k:是指设定每个线程的堆栈大小。这个就要根据你的程序,看一个线程大概须要占用多少内存,可能会有多少线程同时运行等。-XX:MetaspaceSize 因为调整元空间的大小须要Full GC,这是十分低廉的操作,如果利用在启动的时候产生大量Full GC,通常都是因为永恒代或元空间产生了大小调整,基于这种状况,个别倡议在JVM参数中将MetaspaceSize和MaxMetaspaceSize设置成一样的值,并设置得比初始值要大,对于8G物理内存的机器来说,个别我会将这两个值都设置为256M(PS:读者能够依据本人的理论状况再调整)。

August 24, 2022 · 1 min · jiezi

关于jvm:JAVA并发笔记

synchronized 同步语句块的实现应用的是 monitorenter 和 monitorexit 指令,当执行 monitorenter 指令时,线程试图获取锁也就是获取 对象监视器 monitor 的持有权; 另外,wait/notify等办法也依赖于monitor对象,这就是为什么只有在同步的块或者办法中能力调用wait/notify等办法,否则会抛出java.lang.IllegalMonitorStateException的异样的起因。动态 synchronized 办法和非动态 synchronized 办法之间的调用互斥么?不互斥!如果一个线程 A 调用一个实例对象的非动态 synchronized 办法,而线程 B 须要调用这个实例对象所属类的动态 synchronized 办法,是容许的,不会产生互斥景象,因为拜访动态 synchronized 办法占用的锁是以后类的锁,而拜访非动态 synchronized 办法占用的锁是以后实例对象锁。

August 17, 2022 · 1 min · jiezi

关于jvm:JVM内存模型和结构详解五大模型图解

JVM内存模型和Java内存模型都是面试的热点问题,名字看感觉都差不多,实际上他们之间差异还是挺大的。 艰深点说,JVM内存构造是与JVM的外部存储构造相干,而Java内存模型是与多线程编程相干@mikechen 目录 什么是 JVM 为什么须要 JVM? JVM 内存模型 堆(Heap) 办法区(Method Area) 虚拟机栈 (JVM Stack) 本地办法栈 (Native Stack) 程序计数器(PC Register) JVM 内存模型小结什么是JVMJVM是Java Virtual Machine(Java虚拟机)的缩写,JVM是一个虚构进去的计算机,有着本人欠缺的硬件架构,如处理器、堆栈等。 为什么须要JVM?Java语言应用Java虚拟机屏蔽了与具体平台相干的信息,使得Java语言编译程序只需生成在Java虚拟机上运行的指标代码(字节码),就能够在多种平台上不加批改地运行。 Java文件必须先通过一个叫javac的编译器,将代码编译成class文件,而后通过JVM把class文件解释成各个平台能够辨认的机器码,最终实现跨平台运行代码。 JVM内存模型JVM内存模型能够分为两个局部,如下图所示,堆和办法区是所有线程共有的,而虚拟机栈,本地办法栈和程序计数器则是线程公有的。 在JVM1.8中,图中的 办法区为元数据区,上面开展谈一谈这五个区域的作用。 堆(Heap)在 Java 中,堆被划分成两个不同的区域:新生代 ( Young )、老年代 ( Old ),新生代 ( Young ) 又被划分为三个区域:Eden、From Survivor、To Survivor。 下图中的Perm代表的是永恒代,然而留神永恒代并不属于堆内存中的一部分,同时jdk1.8之后永恒代也将被移除。 堆是java虚拟机所治理的内存中最大的一块内存区域,也是被各个线程共享的内存区域,该内存区域寄存了对象实例及数组(但不是所有的对象实例都在堆中)。 其大小通过-Xms(最小值)和-Xmx(最大值)参数设置(最大最小值都要小于1G),前者为启动时申请的最小内存,默认为操作系统物理内存的1/64,后者为JVM可申请的最大内存,默认为物理内存的1/4,默认当空余堆内存小于40%时,JVM会增大堆内存到-Xmx指定的大小,可通过-XX:MinHeapFreeRation=来指定这个比列。 当空余堆内存大于70%时,JVM会减小堆内存的大小到-Xms指定的大小,可通过XX:MaxHeapFreeRation=来指定这个比列,当然为了防止在运行时频繁调整Heap的大小,通常-Xms与-Xmx的值设成一样。堆内存 = 新生代+老生代+长久代。 在咱们垃圾回收的时候,咱们往往将堆内存分成新生代和老生代(大小比例1:2),新生代中由Eden和Survivor0,Survivor1组成,三者的比例是8:1:1,新生代的回收机制采纳复制算法,在Minor GC的时候,咱们都留一个存活区用来寄存存活的对象,真正进行的区域是Eden+其中一个存活区,当咱们的对象时长超过肯定年龄时(默认15,能够通过参数设置),将会把对象放入老生代,当然大的对象会间接进入老生代,老生代采纳的回收算法是标记整顿算法。 办法区(Method Area)其实办法区是在JDK1.8以前的版本里存在的一块内存区域,次要就是寄存从class文件里加载进来的类的,而且常量池也是在这块区域内的。 然而在JDK1.8之后,这块区域摇身一变,换了名字,叫做“Metaspace”,翻译过去就是“元数据空间”的意思,当然它只是改了个名,实现的性能是没变的。 办法区(Method Area)与Java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类型信息、常量、动态变量、即时编译器编译后的代码缓存等数据。 1.类型信息 对每个加载的类型(类class、接口interface、枚举enum、注解annotation),JVM必须在办法区中存储以下类型信息:\①这个类型的残缺无效名称(全名=包名.类名)\②这个类型间接父类的残缺无效名(对于interface或是java.lang.0bject,都没有父类)\③这个类型的修饰符(public, abstract,final的某个子集)\④这个类型间接接口的一个有序列表 2.域信息(Field)成员变量 JVM必须在办法区中保留类型的所有域的相干信息以及域的申明程序。\域的相干信息包含:域名称、域类型、域修饰符(public, private,protected,static,final, volatile, transient的某个子集) 3.办法(Method)信息 JVM必须保留所有办法的以下信息,同域信息一样包含申明程序: 办法名称办法的返回类型(或void)·办法参数的数量和类型(按程序)办法的修饰符(public, private,protected,static, final,synchronized,native,abstract的一个子集)办法的字节码(bytecodes)、操作数栈、局部变量表及大小(abstract和native办法除外)虚拟机栈(JVM Stack)虚拟机栈(Java Virtual Machine Stack),晚期也叫Java栈,每个线程在创立时都会创立一个虚拟机栈,其外部保留一个个的栈帧(Stack Frame),对应着一次次的Java办法调用。 ...

August 8, 2022 · 1 min · jiezi

关于jvm:JVM诊断命令jcmd介绍

原创:扣钉日记(微信公众号ID:codelogs),欢送分享,转载请保留出处。简介从JDK7开始,jdk提供了一个不便扩大的诊断命令jcmd,用来取代之前比拟扩散的jdk根底命令,如jps、jstack、jmap、jinfo等,并且jdk增加新的诊断性能,也会通过jcmd提供,所以还是有必要将这个命令熟悉起来的。 列出java过程jps提供了列出本机java过程的性能,jcmd与之相似,间接输出jcmd即可,如下: $ jcmd10732 app.jar10767 sun.tools.jcmd.JCmd能够看到,本机有一个app.jar的java过程。 列出jcmd反对的子命令$ jcmd 10732 help10732:The following commands are available:VM.native_memoryVM.classloader_statsThread.printGC.class_statsGC.class_histogramGC.heap_dumpGC.finalizer_infoGC.heap_infoGC.run_finalizationGC.runVM.uptimeVM.dynlibsVM.flagsVM.system_propertiesVM.command_lineVM.versionhelp如上,能够看到,10732这个java过程反对了不少子命令呢! 查看java线程栈jcmd能够像jstack一样,打印java线程栈,应用jcmd 0 Thread.print即可,如下: 注:jcmd有个默认行为,当传递给jcmd的过程id是0时,jcmd会在本机所有java过程中执行子命令,这样咱们就能够少操作一步了。查看jvm堆状况jcmd能够疾速查看以后堆容量、已应用容量等等要害信息,如下: $ jcmd 0 GC.heap_info10732: garbage-first heap total 204800K, used 44778K [0x00000006f9a00000, 0x00000006f9b00640, 0x00000007c0000000) region size 1024K, 44 young (45056K), 5 survivors (5120K) Metaspace used 16876K, capacity 18012K, committed 18304K, reserved 1064960K class space used 2101K, capacity 2330K, committed 2432K, reserved 1048576K也能够像jmap一样查看堆直方图并转储堆内存到文件中,以剖析内存不合理的占用问题,如下: # 堆直方图,查看哪些类的对象实例最多$ jcmd 0 GC.class_histogram 10732: num #instances #bytes class name---------------------------------------------- 1: 12659 1142376 [C 2: 3649 403376 java.lang.Class 3: 12644 303456 java.lang.String 4: 9020 288640 java.util.concurrent.ConcurrentHashMap$Node 5: 1378 280376 [B 6: 2241 183608 [I 7: 3351 164296 [Ljava.lang.Object; 8: 1554 133496 [Ljava.util.HashMap$Node; 9: 1192 104896 java.lang.reflect.Method 10: 77 104016 [Ljava.util.concurrent.ConcurrentHashMap$Node; 11: 6307 100912 java.lang.Object 12: 2517 100680 java.util.LinkedHashMap$Entry 13: 3071 98272 java.util.HashMap$Node 14: 1148 55104 java.util.HashMap 15: 1962 46856 [Ljava.lang.Class; 16: 782 43792 java.util.LinkedHashMap# 导出堆内存文件$ jcmd 0 GC.heap_dump /home/work/heap.hprof11377:Heap dump file created查看jvm属性等应用jinfo能够查看jvm属性与参数,jcmd同样也能够做到,如下: ...

July 30, 2022 · 2 min · jiezi

关于jvm:爆肝整理JVM十大模块知识点总结不信你还不懂

01 JVM 内存构造Java 虚拟机的内存空间分为 5 个局部: 程序计数器Java 虚拟机栈本地办法栈堆办法区 JDK 1.8 同 JDK 1.7 比,最大的差异就是:元数据区取代了永恒代。元空间的实质和永恒 代相似,都是对 JVM 标准中办法区的实现。不过元空间与永恒代之间最大的区别在于:元数据空间并不在虚拟机中,而是应用本地内存。 1.1 程序计数器(PC 寄存器)(1)程序计数器的定义 程序计数器是一块较小的内存空间,是以后线程正在执行的那条字节码指令的地 址。若以后线程正在执行的是一个本地办法,那么此时程序计数器为 Undefined。 (2)程序计数器的作用 字节码解释器通过改变程序计数器来顺次读取指令,从而实现代码的流程管制。在多线程状况下,程序计数器记录的是以后线程执行的地位,从而当线程切换回 来时,就晓得上次线程执行到哪了。(3)程序计数器的特点  是一块较小的内存空间。 线程公有,每条线程都有本人的程序计数器。生命周期:随着线程的创立而创立,随着线程的完结而销毁。是惟一一个不会呈现 OutOfMemoryError 的内存区域。因为文章篇幅问题,部门内容将以图片展现,如有小伙伴需残缺文档进行查阅观看点赞+关注之后【点击此处】即可获取!!1.2 Java 虚拟机栈(Java 栈)(1)Java 虚拟机栈的定义 Java 虚拟机栈是形容 Java 办法运行过程的内存模型。 Java 虚拟机栈会为每一个行将运行的 Java 办法创立一块叫做“栈帧”的区域, 用于寄存该办法运行过程中的一些信息,如: 局部变量表操作数栈动静链接办法进口信息...... (2)压栈出栈过程 当办法运行过程中须要创立局部变量时,就将局部变量的值存入栈帧中的部分变 量表中。 Java 虚拟机栈的栈顶的栈帧是以后正在执行的流动栈,也就是以后正在执行的 办法,PC 寄存器也会指向这个地址。只有这个流动的栈帧的本地变量能够被操 作数栈应用,当在这个栈帧中调用另一个办法,与之对应的栈帧又会被创立,新 创立的栈帧压入栈顶,变为以后的流动栈帧。 办法完结后,以后栈帧被移出,栈帧的返回值变成新的流动栈帧中操作数栈的一 个操作数。如果没有返回值,那么新的流动栈帧中操作数栈的操作数没有变动。 因为 Java 虚拟机栈是与线程对应的,数据不是线程共享的,因而不必关怀数据 一致性问题,也不会存在同步锁的问题。 (3)Java 虚拟机栈的特点 局部变量表随着栈帧的创立而创立,它的大小在编译时确定,创立时只需调配事 先规定的大小即可。在办法运行过程中,局部变量表的大小不会产生扭转。Java 虚拟机栈会呈现两种异样:StackOverFlowError 和 OutOfMemoryError。StackOverFlowError 若 Java 虚拟机栈的大小不容许动静扩大,那么当线程请 求栈的深度超过以后 Java 虚拟机栈的最大深度时,抛出 StackOverFlowError 异样。 ...

July 28, 2022 · 2 min · jiezi

关于jvm:了解双亲委派模型

前言双亲委派模型是一个面试经典题目 类加载当咱们运行java程序时,首先须要将 .java文件通过编译后生成对应 .class文件,而后由 JVM 来加载.class文件到内存中。作为开发人员咱们不须要去关怀.class文件是如何被加载到 JVM中的,这所有工作都有类加载器去实现。类的加载过程如图所示: 加载,加载分为三步:1、通过类的全限定性类名获取该类的二进制流;2、将该二进制流的动态存储构造转为办法区的运行时数据结构;3、在堆中为该类生成一个class对象;附jvm内存结构图。验证:验证该class文件中的字节流信息复合虚拟机的要求,不会威逼到jvm的平安;筹备:为class对象的动态变量分配内存,初始化其初始值;解析:该阶段次要实现符号援用转化成间接援用;初始化:到了初始化阶段,才开始执行类中定义的java代码;初始化阶段是调用类结构器的过程;在第一阶段加载过程中,咱们即能够抉择应用零碎提供的类加载器来实现加载,也能够应用自定义的类加载器来加载。 类加载器在java中,类加载器ClassLoader有四种 BootStrapClassLoader: c++编写,加载外围库java.*;Extension ClassLoader: java编写,加载扩大库javax.*;AppClassLoader: java编写,加载程序目录;自定义ClassLoader:java编写,继承自ClassLoader,定制化加载; 双亲委派模型加载一个类时,先向上委派,实际上就是查找缓存,是否加载了该类,有则间接返回,没有持续向上。委派到顶层后,缓存中还是没有,则到其加载门路中查找,有则对类进行加载,没有持续向下查找。向上委派到顶层类加载器为止,向下查找到发动加载的类加载器为止。BootstrapClassLoader类加载器负责加载 [jdk根目录]/lib 下的class文件。ExtClassLoader类加载器负责加载 [jdk根目录]/lib/ext 下的class文件。AppClassLoader类加载器负责加载用户自定义class类文件。长处:1.虚拟机只有在两个类的门路和类名雷同且加载该类的加载器均雷同的状况下才断定这是一个类。若不采纳双亲委派机制,同一个类有可能被多个类加载器加载,这样该类会被辨认为两个不同的类,互相赋值时会有问题。双亲委派机制能保障多加载器加载某个类时,最终都是由一个确定加载器加载,确保最终加载后果雷同。2.避免用户自定义一个外围类,如自定义一个String类且门路雷同,通过双亲委派模型,jvm并不会去加载用户自定义的String类,而是去加载特定包下的String类。

July 18, 2022 · 1 min · jiezi

关于jvm:读书笔记之深入理解Java虚拟机JVM高级特性与最佳实践

学而不思则罔,思而不学则殆。 —— 孔子 微信公众号已开启,菜农曰,没关注的同学们记得关注哦! 本篇带来的是周志明老师编写的《深刻了解Java虚拟机:JVM高级个性与最佳实际》,非常硬核! 全书共分为 5 局部,围绕内存治理、执行子系统、程序编译与优化、高效并发等外围主题对JVM进行了全面而深刻的剖析,粗浅揭示了JVM工作原理。 全书整体5个局部,十三章,共 358929 字。整体构造相当清晰,以至于写读书笔记的时候无从摘抄(甚至想把全书复述一遍),以下是全书第二局部的内容,望读者细细品味! 一、第一局部 走进Java第一局部介绍了对于 Java 的技术体系与发展史,谈及将来。该局部内容不做摘抄,间接进入外围主题。 二、第二局部 主动内存管理机制Java 与 C++ 之间有一堵由内存动态分配和垃圾收集技术所围成的“高墙”,墙里面的人想进去,墙外面的人却想进去。 第二章 Java内存区域与内存溢出异样对于 Java 程序员来说是幸福的也是可悲的,在虚拟机主动内存管理机制的帮忙下不须要为每一个 new 操作去写配对的 delete/ free 代码,不容易呈现内存泄露和内存溢出问题,然而在内存治理畛域中,C或 C++,既是领有最高势力的 "皇帝" 又是从事最根底工作的 "劳动人民"。 1)运行时数据区域Java 虚拟机在执行 Java 程序的过程中会把它所治理的内存划分为若干个不同的数据区域. 程序计数器这是一块较小的内存空间,能够看作是以后线程所执行的字节码的行号指示器 为了线程切换后可能复原到正确的执行地位,每条线程都须要有一个独立的程序计数器,各条线程之间计数器互不影响,独立存储,线程公有。 此内存区域是惟一一个在 Java 虚拟机标准中没有规定任何 OutOfMemoryError 状况的区域。 1. 虚拟机栈与程序计数器一样,Java 虚拟机栈也是线程公有的,它的生命周期与线程雷同。虚拟机形容的是 Java 办法执行的内存模型:每个办法在执行的同时都会创立一个栈帧用于存储 局部变量表、操作数栈、动静链接、办法进口等信息。每一个办法从调用直至执行实现的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。 2. 本地办法栈与虚拟机栈十分相似,次要的区别在于虚拟机栈是为虚拟机执行 Java 办法服务,而本地办法栈是为虚拟机应用到的 Native 办法服务。 与虚拟机栈一样的是:本地办法栈也会抛出 StackOverFlowError 和 OutOfMemoryError 异样 3. Java 堆Java 堆是 Java 虚拟机所治理的内存中最大的一块。Java 堆是被所有线程共享的一块内存区域,在虚拟机启动时创立。 ...

July 10, 2022 · 3 min · jiezi

关于jvm:看了最新大厂面试这6道JVM面试题都被问到了

前言本系列会零碎的整顿MySQL,Redis,SSM框架,算法,计网等面试常问技术栈的面试题,本文次要是整顿分享了JVM相干的面试题,MySQL、Spring之前曾经更新了,须要的同学也能够去看一下,心愿对正在筹备秋招的你们有所帮忙! JVM面试题: JVM内存为什么要分成新生代,老年代新生代中为什么要分为Eden和SurvivorJVM中一次残缺的GC流程是怎么的CMS收集器和G1收集器的区别JVM 调优CPU飙升如何排查当然集体整顿的所有面试题都无偿分享,只求大伙一个点赞关注转发三连,这些文档都放在文末了,须要的同学能够自取1. JVM内存为什么要分成新生代,老年代?1.1 JVM共享内存划分共享内存区 = 长久代 + 堆(jdk1.8及以上jvm废除了长久代)长久代 = 办法区 + 其余Java堆 = 老年代 + 新生代新生代 = Eden + S0 + S11.2 为什么分年轻代和新生代新生代:次要寄存新创建的对象,内存大小个别会比拟小,垃圾回收会比拟频繁。老年代(Tenured Gen):次要寄存JVM认为生命周期比拟长的对象(通过几次的Young GC的垃圾回收后依然存在),或者大对象,垃圾回收也绝对没有那么频繁。为什么划分老年代和新生代,次要对象大小不一样,对象生命周期不一样。划分后,提供垃圾回收效率,节俭资源,晋升对象利用率等等。 2. 新生代为何划分Eden和Survivor?为什么设置两个Survivor如果没有Survivor,Eden区每进行一次Minor GC,存活的对象就会被送到老年代。老年代很快被填满,触发Major GC.老年代的内存空间远大于新生代,进行一次Full GC耗费的工夫比Minor GC长得多,所以须要分为Eden和Survivor。Survivor的存在意义,就是缩小被送到老年代的对象,进而缩小Full GC的产生,Survivor的预筛选保障,只有经验16次Minor GC还能在新生代中存活的对象,才会被送到老年代。设置两个Survivor区最大的益处就是解决了碎片化,刚刚新建的对象在Eden中,经验一次Minor GC,Eden中的存活对象就会被挪动到第一块survivor space S0,Eden被清空;等Eden区再满了,就再触发一次Minor GC,Eden和S0中的存活对象又会被复制送入第二块survivor space S1(这个过程十分重要,因为这种复制算法保障了S1中来自S0和Eden两局部的存活对象占用间断的内存空间,防止了碎片化的产生)3. JVM中一次残缺的GC流程是怎么的Java堆划分为老年代和新生代新生代 划分为Eden和两个Survivor(S0、S1)当 Eden区的空间满了, Java虚构机会触发一次Minor GC,以收集新生代的垃圾,存活下来的对象,则会转移到 Survivor区。大对象(须要大量间断内存空间的Java对象,如那种很长的字符串)间接进入老年态;如果对象在Eden出世,并通过第一次Minor GC后依然存活,并且被Survivor包容的话,年龄设为1,每熬过一次Minor GC,年龄+1,若年龄超过肯定限度(15),则被降职到老年态。即长期存活的对象进入老年态。老年代满了而无奈包容更多的对象,Minor GC 之后通常就会进行Full GC,Full GC 清理整个内存堆 – 包含年老代和年轻代。Major GC 产生在老年代的GC,清理老年区,常常会随同至多一次Minor GC,比Minor GC慢10倍以上。4. CMS收集器和G1收集器的区别CMS收集器是老年代的收集器,个别配合新生代的Serial和ParNew收集器一起应用;G1收集器收集范畴是老年代和新生代,不须要联合其余收集器应用;CMS收集器是一种以获取最短回收进展工夫为指标的收集器, G1收集器可预测垃圾回收的进展工夫。CMS收集器是应用“标记-革除”算法进行的垃圾回收,容易产生内存碎片;而G1收集器应用的是“标记-整顿”算法,进行了空间整合,升高了内存空间碎片。CMS和G1的回收过程不一样,垃圾回收的过程不一样。CMS是初始标记、并发标记、从新标记、并发清理;G1是初始标记、并发标记、最终标记、筛选回收。5. JVM 调优JVM调优其实就是通过调节JVM参数,即对垃圾收集器和内存调配的调优,以达到更高的吞吐和性能。JVM调优次要调节以下参数 堆栈内存相干 -Xms 设置初始堆的大小-Xmx 设置最大堆的大小-Xmn 设置年老代大小,相当于同时配置-XX:NewSize和-XX:MaxNewSize为一样的值-Xss 每个线程的堆栈大小-XX:NewSize 设置年老代大小(for 1.3/1.4)-XX:MaxNewSize 年老代最大值(for 1.3/1.4)-XX:NewRatio 年老代与年轻代的比值(除去长久代)-XX:SurvivorRatio Eden区与Survivor区的的比值-XX:PretenureSizeThreshold 当创立的对象超过指定大小时,间接把对象调配在老年代。-XX:MaxTenuringThreshold设定对象在Survivor复制的最大年龄阈值,超过阈值转移到老年代垃圾收集器相干 ...

July 8, 2022 · 1 min · jiezi

关于jvm:JVM-字节码测试运用远程调试测试覆盖影子数据库

本文由uniquetruth发表于TesterHome论坛,点击原文链接可查看作者的更多文章并与ta在线交换。始终想找一个技术社区开源一个本人集体的我的项目,心愿能被更多人看到、应用这个货色,在测试上帮忙到大家。 简介一个专为JVM系语言web利用设计的,专一于集成测试阶段的后端测试工具。实质性能是监控代码执行,做近程调试应用。比方能够让你实时的理解到在前端点击某个按钮后,后端执行的代码细节,包含每一个办法的名称、参数返回值、执行的代码行号,调用的sql语句等信息。 当然可不便的扩大性能,实现测试覆盖率统计、影子数据库等实用功能。 根底应用形式介绍我的项目地址:https://github.com/uniquetrut... 我的项目应用gradle构建,下载源码后,应用gradle agentTest命令,可编译出一个java探针,并且与所有须要的二进制文件呈现在build/lib目录下。之后就能够将所有jar包放到服务器的任意目录中,而后将-javaagent:${你的目录}/remote-debug-agent.jar=includes=com.foo.bar,apiport=8098配置到web利用启动参数的java_opts中(例如应用tomcat的话,可批改catalina.sh来增加该参数)。被测利用启动后,探针会启动一个内置的jetty服务器,并在8098端口上提供一组api供测试应用。 假如你的被测利用在前端有一个按钮,点击按钮后会调用到后端com.foo.bar.MyClass类中的某个办法,那么在测试这个按钮前,可先发送一个申请http://ip:8098/trace/start(用jmeter、curl甚至浏览器发都行,只有与测试操作源自同一台机器即可),之后点击按钮后,再发送申请http://ip:8098/trace/list,探针即会返回方才执行代码的细节。你能够看到相似这样的数据 [{ "method": "java.lang.String com.foo.bar.MyClass.handle()", //执行的办法签名 "coverage": "[11,13][16,16]", //执行了哪几行代码 "cost time": 2, //执行耗时 "calls": [{ //该办法调用的底层办法 "coverage": "[21,21][24,24]", "cost time": 1, "method": "boolean foo.bar.MyClass.largeThanHalf(double)", "parameters": ["0.24444334899195885"], "return value": "0" }], "return value": "random number( 0.24444334899195885 ) is little than half", //该办法的返回值 "sql": "select 1 from dual" //该办法执行的sql语句}]以上是一个近程调试的示例,利用场景应该是集成测试阶段灰盒测试,当然白盒、黑盒,甚至开发调试都能够用。另外也提供dump代码行笼罩状况的接口,用过jacoco的应该比拟相熟,利用这些数据能够做测试覆盖率的统计。 工具个性线程隔离: 代码执行状况是分线程记录的,且能通过一些信息标识调用者身份,因而可在集成环境中多人同时应用,互不影响天然染色: 调用者身份应用其发动的申请中的人造信息进行染色,无需给测试人员减少额定的客户端调用链传递: 调用者身份信息可在多个都应用了探针的利用间传递(如果这些利用间应用http协定通信的话),也就是说非常适合微服务架构应用(特意反对了Feign-Hystrix组件)Servlet框架反对: 工具反对大部分应用HttpServlet的框架或中间件,比方spring-web、tomcat等,也提供了十分不便的接口扩大反对其它同类框架反对Struts2反对Dubbo反对Spring RabbitMQ反对JVM系语言: 探针工作在字节码层,所以不仅反对Java,也反对Groovy、Scala等语言(特意反对了Play框架)SQL语句监控: 探针默认反对oracle和mysql数据库,也提供了十分便当的接口扩大反对其它关系型数据库反对热插拔: 不必改利用的启动文件也可应用,对线上环境长期应用提供了可能更多功能继续开发欠缺中 可能没有特地适宜的标签,就选了测试覆盖率这个, 如果有对测试覆盖率或者影子数据库等技术感兴趣的敌人也欢送交换。集体Github空间也有其它更多开源分享欢送参观,近期也在寻找适合的工作机会,欢送勾结 本文由uniquetruth发表于TesterHome论坛,点击原文链接可查看作者的更多文章并与ta在线交换。 想要学习更多对于测试/测试开发技术、测试治理和品质保障的干货常识?想要结识品质行业大牛和业界精英?欢送关注第十届中国互联网测试开发大会·深圳站 >>

July 7, 2022 · 1 min · jiezi

关于jvm:接口偶尔超时竟又是JVM停顿的锅

原创:扣钉日记(微信公众号ID:codelogs),欢送分享,转载请保留出处。简介继上次咱们JVM进展十几秒的问题解决后,咱们零碎终于稳固了,再也不会无端重启了! 这是之前的文章:耗时几个月,终于找到了JVM进展十几秒的起因 但有点奇怪的是,每隔一段时间,咱们服务接口就会有一小波499超时,通过查看gc日志,又发现JVM进展了好几秒! 查看safepoint日志有了上次JVM进展排查教训后,我马上就查看了gc日志与safepoint日志,发现如下日志: $ cat gc-*.log | awk '/application threads were stopped/ && $(NF-6)>1'|tail2022-05-08T16:40:53.886+0800: 78328.993: Total time for which application threads were stopped: 9.4917471 seconds, Stopping threads took: 9.3473059 seconds2022-05-08T17:40:32.574+0800: 81907.681: Total time for which application threads were stopped: 3.9786219 seconds, Stopping threads took: 3.9038683 seconds2022-05-08T17:41:00.063+0800: 81935.170: Total time for which application threads were stopped: 1.2607608 seconds, Stopping threads took: 1.1258499 seconds$ cat safepoint.log | awk '/vmop/{title=$0;getline;if($(NF-2)+$(NF-4)>1000){print title;print $0}}' vmop [threads: total initially_running wait_to_block] [time: spin block sync cleanup vmop] page_trap_count78319.500: G1IncCollectionPause [ 428 0 2 ] [ 0 9347 9347 7 137 ] 0 vmop [threads: total initially_running wait_to_block] [time: spin block sync cleanup vmop] page_trap_count81903.703: G1IncCollectionPause [ 428 0 4 ] [ 0 3903 3903 14 60 ] 0 vmop [threads: total initially_running wait_to_block] [time: spin block sync cleanup vmop] page_trap_count81933.906: G1IncCollectionPause [ 442 0 1 ] [ 0 1125 1125 8 126 ] 0从日志上能够看到,JVM进展也是由safepoint导致的,而safepoint耗时次要在block阶段! ...

June 19, 2022 · 2 min · jiezi

关于jvm:JVM-输出-GC-日志导致-JVM-卡住我-TM-人傻了

本系列是 我TM人傻了 系列第七期[捂脸],往期精彩回顾: 降级到Spring 5.3.x之后,GC次数急剧减少,我TM人傻了:https://zhuanlan.zhihu.com/p/...这个大表走索引字段查问的 SQL 怎么就成全扫描了,我TM人傻了:https://zhuanlan.zhihu.com/p/...获取异样信息里再出异样就找不到日志了,我TM人傻了:https://zhuanlan.zhihu.com/p/...spring-data-redis 连贯透露,我 TM 人傻了:https://zhuanlan.zhihu.com/p/...Spring Cloud Gateway 没有链路信息,我 TM 人傻了:https://zhuanlan.zhihu.com/p/...Spring Cloud Gateway 雪崩了,我 TM 人傻了:https://zhuanlan.zhihu.com/p/...最近,咱们降级了 Java 17。起初,咱们的 k8s 运维团队为了优化咱们的利用日志采集,将咱们所有 pod (你能够了解为一个 Java 微服务过程)的 JVM 日志都对立采集到同一个 AWS 的 EFS 服务(EFS 是 Elastic File System 的缩写,弹性块文件存储系统,底层是 NFS + S3 对象存储集群),咱们对于 JVM 日志配置包含以下几个: GC日志:-Xlog:gc*=debug:file=${LOG_PATH}/gc%t.log:utctime,level,tags:filecount=50,filesize=100MJIT 编译日志:-Xlog:jit+compilation=info:file=${LOG_PATH}/jit_compile%t.log:utctime,level,tags:filecount=10,filesize=10MSafepoint 日志:-Xlog:safepoint=trace:file=${LOG_PATH}/safepoint%t.log:utctime,level,tags:filecount=10,filesize=10M敞开堆栈省略:这个只会省略 JDK 外部的异样,比方 NullPointerException 这种的:-XX:-OmitStackTraceInFastThrow,咱们利用曾经对于大量报错的时候输入大量堆栈导致性能压力的优化,参考:https://zhuanlan.zhihu.com/p/...JVM 对立日志配置请参考:https://zhuanlan.zhihu.com/p/... 在这样做之后,咱们的利用呈现这样一个奇怪的问题,这个问题有三种不同的景象,对立的体现是处于平安点的工夫特地特地长: 1.通过 safepoint 日志看进去,期待所有线程进入平安点的工夫特地长(Reaching safepoint:25s多)2.通过 safepoint 日志看进去,还有处于 safepoint 工夫过长的,并且起因是 GC(At safepoint: 37s多)查看 GC 日志,Heap before GC invocations 与输入堆构造的日志距离了很久:3.另一种处于 safepoint 工夫过长的,起因也是 GC,然而距离日志的中央不一样(29s多)查看 GC 日志,输入堆构造的日志某些距离了很久: ...

June 17, 2022 · 2 min · jiezi

关于jvm:通用流量录制回放工具-jvmsandboxrepeater-尝鲜-四新版带界面-console-的使用

本文作者陈恒捷是TesterHome社区主编,第十届MTSC大会上海站-开源专场出品人。先后在PP助手、PPmoney、荔枝等公司从事测试效力晋升相干工作,在测试技术及效率晋升方面有丰盛的教训积攒。前言在去年repeater刚进去的时候,就曾经简略体验了一下,也有在公司试用。但因为各种起因,最终没有落地。当初刚好有机会再钻研一下,看官网记录 console 终于加上了界面,所以也试用并记录下来。 其它文章链接传送门: 通用流量录制回放工具 jvm-sandbox-repeater 尝鲜记录 (一)通用流量录制回放工具 jvm-sandbox-repeater 尝鲜 (二)——repeater-console 应用 (已实现)通用流量录制回放工具 jvm-sandbox-repeater 尝鲜 (三)—— repeater plugin 开发 (完成度 50%) 步骤因为官网源码有做了比拟多调整,所以这次记录还是从零开始。 1、装置 repeater运行官网仓库下的 bin/install-repeater.sh 即可 2、调整 repeater 模式配置,改为用 console批改 ~/.sandbox-module/cfg/repeater.properties 的值,repeat.standalone.mode 改为 false 3、调整 console 工程中对应配置在 repeater-console/repeater-console-start/src/main/resources/application.properties ,调整 mysql 相干配置,改为和本人本地 mysql 数据库统一 同时请初始化数据库内容,初始化的sql文件在:repeater-console/repeater-console-dal/src/main/resources/database.sql 初始化胜利,应该会创立了 repeater 这个数据库,且包含上面四个表: 4、启动 console 和被测服务启动被测服务: 4.1、下载示例我的项目:https://github.com/chenhengji...4.2、启动示例我的项目: # 在示例我的项目 clone 后的根目录中运行cd complete mvn install && java -jar target/*.jar启动胜利后,应该有相似如下日志: ...2020-12-28 07:40:45.949 INFO 32158 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path ''2020-12-28 07:40:45.952 INFO 32158 --- [ main] hello.Application : Started Application in 2.062 seconds (JVM running for 2.448)4.3、修复官网仓库里 console 一些代码问题。 ...

April 20, 2022 · 2 min · jiezi

关于jvm:通用流量录制回放工具-jvmsandboxrepeater-尝鲜-三-repeater-plugin-开发

本文作者陈恒捷是TesterHome社区主编,第十届MTSC大会上海站-开源专场出品人。先后在PP助手、PPmoney、荔枝等公司从事测试效力晋升相干工作,在测试技术及效率晋升方面有丰盛的教训积攒。在 通用流量录制回放工具 jvm-sandbox-repeater 尝鲜 (二)——repeater-console 应用 中,能够理解到,repeater 的外围还是在 plugin 中,因而有必要去学习下。 相熟 jvm-sandboxrepeater-plugin 的底层波及到 jvm-sandbox 外面的一些原理。须要先浏览下相干的文档: 整体的介绍:https://github.com/alibaba/jv...具体的 wiki 文档:https://github.com/alibaba/jv...repeater 自身是一种 jvm-sandbox 的 module ,因而重点关注使用者、模块研发者2个章节。因为这部分非本文重点,仅摘要记录和本文关系较大的局部。 jvm-sandbox 是JVM沙箱容器,一种JVM的非侵入式运行期AOP解决方案。通过形象 BEFORE、RETURN、THROW 等事件,达到在运行时加强及批改指定的类jvm-sandbox 反对 attach 和 javaagent 两种模式。咱们后面 repeater 示例用的是 attachrepeater 属于 jvm-sandbox 的 user_module ,因而放在 ${HOME}/.sandbox-module/ 下sandbox.sh 是沙箱的次要操作客户端,除了咱们用过的 attach 命令外,还有包含刷新用户模块(-f)、强制刷新用户模块(-F)、重置(-R)、敞开容器(-S)。当前要进行 attach 能够间接用 -S沙箱本身蕴含 http 模块,也因而咱们的 console 能够通过 http 和沙箱内的 repeater 模块进行通信(就是 repeater 用户文档里回放办法一提到的传 _data 字段的接口)强烈建议本人入手实现 wiki 文档中的 模块编写高级,大略15分钟左右即可实现。代码不要复制粘贴,而是本人仿照文档敲进去,这样记忆比拟粗浅。 同时能够参考 怎么调试啊? 这个 issue ,理解下如何调试 jvm-sandbox 的模块。后续 repeater-plugin 的调试也用得上哦。 ...

April 18, 2022 · 5 min · jiezi

关于jvm:深入解析JVMJava对象头组成

前言上一章节带着大家初探JVM的类加载机制,以及双亲委派机制,本文次要介绍了Java对象头的组成以及详解 一、一个对象如何组成的?对象在内存中的布局蕴含:对象头、Mark Word、Klass Pointer Mark Word :用于存储对象本身的运行时数据, 如哈希码(HashCode)、GC分代年龄、锁状态标记、线程持有的锁、偏差线程ID、偏差工夫戳等Klass Pointer :对象指向它的类的元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例实例属性 :定义类中的成员属性对齐填充 : 因为HotSpot虚拟机的主动内存管理系统要求对象的起始地址必须是8字节的整数倍,也就是对象的大小必须是8字节的整数倍二、根本类型占用的字节和位数根本类型字节位数byte1 byte8 bitchar2 byte16 bitint4 byte32 bitshort2 byte16 bitlong8 byte64 bitdouble8 byte64 bitfloat4 byte32 bit三、new 一个对象占用多少字节public class Demo1 { public static void main(String[] args) { LockObject lockObject = new LockObject(); System.out.println(ClassLayout.parseInstance(lockObject).toPrintable()); } static class LockObject{ int a = 4; long b = 1; boolean c = false; }} 依据下面根本类型占用的字节和位数可计算出: 在开启了指针压缩的状况下: 对象头 12个字节实例数据 int  a=4 4个字节,long b=1 8个字节,boolean c=false 1个字节对齐补充 7个字节。总共32个字节四、对象内存中offset作用offset:绝对于类对象所占内存区域起始地位的偏移 ...

April 16, 2022 · 1 min · jiezi

关于jvm:各大框架都在使用的Unsafe类到底有多神奇

前言简直每个应用 Java开发的工具、软件基础设施、高性能开发库都在底层应用了sun.misc.Unsafe,比方Netty、Cassandra、Hadoop、Kafka等。 Unsafe类在晋升Java运行效率,加强Java语言底层操作能力方面起了很大的作用。但Unsafe类在sun.misc包下,不属于Java规范。 很早之前,在浏览并发编程相干类的源码时,看到Unsafe类,产生了一个纳闷:既然是并发编程中用到的类,为什么命名为Unsafe呢? 深刻理解之后才晓得,这里的Unsafe并不是说线程平安与否,而是指:该类对于一般的程序员来说是”危险“的,个别利用开发者不会也不应该用到此类。 因为Unsafe类性能过于弱小,提供了一些能够绕开JVM的更底层性能。它让Java领有了像C语言的指针一样操作内存空间的能力,可能晋升效率,但也带来了指针的问题。官网并不倡议应用,也没提供文档反对,甚至打算在高版本中去掉该类。 但对于开发者来说,理解该类提供的性能更有助于咱们学习CAS、并发编程等相干的常识,还是十分有必要学习和理解的。 Unsafe的结构Unsafe类是"final"的,不容许继承,且构造函数是private,应用了单例模式来通过一个静态方法getUnsafe()来获取。 private Unsafe() { } @CallerSensitive public static Unsafe getUnsafe() { Class var0 = Reflection.getCallerClass(); if (!VM.isSystemDomainLoader(var0.getClassLoader())) { throw new SecurityException("Unsafe"); } else { return theUnsafe; } }在getUnsafe办法中对单例模式中的对象创立做了限度,如果是一般的调用会抛出一个SecurityException异样。只有由主类加载器加载的类能力调用这个办法。 那么,如何取得Unsafe类的对象呢?通常采纳反射机制: public static Unsafe getUnsafe() throws IllegalAccessException { Field unsafeField = Unsafe.class.getDeclaredFields()[0]; unsafeField.setAccessible(true); return (Unsafe) unsafeField.get(null);}当取得Unsafe对象之后,就能够”随心所欲“了。上面就来看看,通过Unsafe办法,咱们能够做些什么。 Unsafe的次要性能可先从依据下图从整体上理解一下Unsafe提供的性能: 上面筛选重要的性能进行解说。 一、内存治理Unsafe的内存治理性能次要包含:一般读写、volatile读写、有序写入、间接操作内存等分配内存与开释内存的性能。 一般读写Unsafe能够读写一个类的属性,即使这个属性是公有的,也能够对这个属性进行读写。 // 获取内存地址指向的整数public native int getInt(Object var1, long var2);// 将整数写入指定内存地址public native void putInt(Object var1, long var2, int var4);getInt用于从对象的指定偏移地址处读取一个int。putInt用于在对象指定偏移地址处写入一个int。其余原始类型也提供有对应的办法。 ...

March 28, 2022 · 2 min · jiezi

关于jvm:JVM搭建自定义加载器拓展性的实施

一、背景名单管理体系是手机上各个模块将需要管控的使用装备到文件中,而后下发到手机上进行使用管控的体系,比如各个使用的耗电量管控;各个模块的管控使用文件思考到平安问题,有本人的不同的加密办法,依照以往的经历,咱们能够使用模板办法+工厂形式来根据模块的类型来获取到不同的加密办法。代码类层次结构暗示如下: 使用工厂形式和模板办法形式,在有新的加密办法时,咱们能够通过增加新的handler来满足"对修改敞开,对扩大敞开"的准则,然而这种办法不可避免的需要修改代码和需要从新发版别和上线。那么有没有更好的办法能够去解决这个问题,这儿便是咱们今日要要点讲的主题。 二、类加载的机会一个类型从被加载到虚拟机内存中开始,到卸载出内存停止,它的整个生命周期将会经历`加载 (Loading)、验证(Verification)、筹备(Preparation)、解析(Resolution)、初始化 (Initialization)、使用(Using)和卸载(Unloading) 尽管classloader的加载过程有凌乱的7步,但事实上除了加载之外的四步,其它都是由JVM虚拟机操控的,咱们除了习惯它的规范进行开发外,能够干预的空间并不多。而加载则是咱们操控classloader完结特地目标最重要的办法了。也是接下来咱们介绍的要点了。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; }}loadClass加载办法流程: ...

March 25, 2022 · 2 min · jiezi

关于JVM:java内存区域与内存溢出异常

(一)Java虚拟机运行时数据区Java虚拟机运行时数据区2.jpg程序计数器(Program Counter Register) 程序计数器是一块较小的内存空间,它能够看作是以后线程所执行的字节码的行号指示器。在虚拟机的概念模型里,字节码解释器工作时就是通过扭转这个计数器的值来选取下一条须要执行的字节码指令,分支、循环、跳转、异样解决、线程复原等根底性能都须要依赖这个计数器来实现。因为Java虚拟机的多线程是通过线程轮流切换并调配处理器执行工夫的形式来实现的,在任何一个确定的时刻,一个处理器(对于多核处理器来说是一个内核)都只会执行一条线程中的指令。因而,为了线程切换后能复原到正确的执行地位,每条线程都须要有一个独立的程序计数器,各条线程之间计数器互不影响,独立存储,咱们称这类内存区域为“线程公有”的内存。如果线程正在执行的是一个Java办法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是Native办法,这个计数器值则为空(Undefined)。此内存区域是惟一一个在Java虚拟机标准中没有规定任何OutOfMemoryError状况的区域。Java虚拟机栈(Java Virtual Machine Stacks) Java虚拟机栈也是线程公有的,它的生命周期与线程雷同。虚拟机栈形容的是Java办法执行的内存模型:每个办法在执行的同时都会创立一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动静链接、办法进口等信息。每一个办法从调用直至执行实现的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。局部变量表寄存了编译期可知的各种根本数据类型(boolean、byte、char、short、int、float、long、double)、对象援用(reference类型,它不等同于对象自身,可能是一个指向对象起始地址的援用指针,也可能是指向一个代表对象的句柄或其余与此对象相干的地位)和returnAddress类型(指向了一条字节码指令的地址)。在Java虚拟机标准中,对这个区域规定了两种异样情况:如果线程申请的栈深度大于虚拟机所容许的深度,将抛出StackOverflowError异样;如果虚拟机栈能够动静扩大(以后大部分的Java虚拟机都可动静扩大,只不过Java虚拟机标准中也容许固定长度的虚拟机栈),如果扩大时无奈申请到足够的内存,就会抛出OutOfMemoryError异样。本地办法栈(Native Method Stack) 与虚拟机栈所施展的作用是十分类似的,它们之间的区别不过是虚拟机栈为虚拟机执行Java办法(也就是字节码)服务,而本地办法栈则为虚拟机应用到的Native办法服务。与虚拟机栈一样,本地办法栈区域也会抛出StackOverflowError和OutOfMemoryError异样。Java堆(Java Heap) Java堆是Java虚拟机所治理的内存中最大的一块。Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创立。此内存区域的惟一目标就是寄存对象实例,简直所有的对象实例都在这里分配内存。Java堆是垃圾收集器治理的次要区域,因而很多时候也被称做“GC堆”(Garbage Collected Heap,幸好国内没翻译成“垃圾堆”)。从内存回收的角度来看,因为当初收集器根本都采纳分代收集算法,所以Java堆中还能够细分为:新生代和老年代;再粗疏一点的有Eden空间、From Survivor空间、To Survivor空间等。从内存调配的角度来看,线程共享的Java堆中可能划分出多个线程公有的调配缓冲区(Thread Local Allocation Buffer,TLAB)。不过无论如何划分,都与寄存内容无关,无论哪个区域,存储的都依然是对象实例,进一步划分的目标是为了更好地回收内存,或者更快地分配内存。依据Java虚拟机标准的规定,Java堆能够处于物理上不间断的内存空间中,只有逻辑上是间断的即可,就像咱们的磁盘空间一样。在实现时,既能够实现成固定大小的,也能够是可扩大的,不过以后支流的虚拟机都是依照可扩大来实现的(通过-Xmx和-Xms管制)。如果在堆中没有内存实现实例调配,并且堆也无奈再扩大时,将会抛出OutOfMemoryError异样。办法区(Method Area) 办法区与Java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息(l类名、拜访修饰符)、常量、动态变量、即时编译器编译后的代码等数据。尽管Java虚拟机标准把办法区形容为堆的一个逻辑局部,然而它却有一个别名叫做Non-Heap(非堆),目标应该是与Java堆辨别开来【有时被叫做永恒代】。依据Java虚拟机标准的规定,当办法区无奈满足内存调配需要时,将抛出OutOfMemoryError异样。运行时常量池(Runtime Constant Pool) 运行时常量池(Runtime Constant Pool)是办法区的一部分。Class文件中除了有类的版本、字段、办法、接口等形容信息外,还有一项信息是常量池(Constant Pool Table),用于寄存编译期生成的各种字面量和符号援用,这部分内容将在类加载后进入办法区的运行时常量池中寄存。间接内存(Direct Memory) 间接内存(Direct Memory)并不是虚拟机运行时数据区的一部分,也不是Java虚拟机标准中定义的内存区域。然而这部分内存也被频繁地应用,而且也可能导致OutOfMemoryError异样呈现。在JDK 1.4中新退出了NIO(New Input/Output)类,引入了一种基于通道(Channel)与缓冲区(Buffer)的I/O形式,它能够应用Native函数库间接调配堆外内存,而后通过一个存储在Java堆中的DirectByteBuffer对象作为这块内存的援用进行操作。这样能在一些场景中显著进步性能,因为防止了在Java堆和Native堆中来回复制数据。显然,本机间接内存的调配不会受到Java堆大小的限度,然而,既然是内存,必定还是会受到本机总内存(包含RAM以及SWAP区或者分页文件)大小以及处理器寻址空间的限度。服务器管理员在配置虚拟机参数时,会依据理论内存设置-Xmx等参数信息,但常常疏忽间接内存,使得各个内存区域总和大于物理内存限度(包含物理的和操作系统级的限度),从而导致动静扩大时呈现OutOfMemoryError异样。(二)对象的创立 内存调配:对象所需内存的大小在类加载实现后便可齐全确定,为对象调配空间的工作等同于把一块确定大小的内存从Java堆中划分进去(指针碰撞、闲暇列表)。分配内存时须要思考线程安全性,就批改指针所指向的地位而言,通常有两种解决方案:1 对象所需内存的大小在类加载实现后便可齐全确定(如何确定将在2.3.2节中介绍),为对象调配空间的工作等同于把一块确定大小的内存从Java堆中划分进去。2 是把内存调配的动作依照线程划分在不同的空间之中进行,即每个线程在Java堆中事后调配一小块内存,称为本地线程调配缓冲(Thread Local Allocation Buffer,TLAB)。哪个线程要分配内存,就在哪个线程的TLAB上调配,只有TLAB用完并调配新的TLAB时,才须要同步锁定。内存空间初始化(不包含对象头):内存调配实现后,虚拟机须要将调配到的内存空间都初始化为零值。丰盛对象头等信息:虚拟机要对对象进行必要的设置,例如这个对象是哪个类的实例、如何能力找到类的元数据信息、对象的哈希码、对象的GC分代年龄等信息。对象初始化:执行new指令之后会接着执行<init>办法,把对象依照程序员的志愿进行初始化,这样一个真正可用的对象才算齐全产生进去。(三)对象的内存布局: 在HotSpot虚拟机中,对象在内存中存储的布局能够分为3块区域:对象头(Header)、实例数据(Instance Data)和对齐填充(Padding)。HotSpot虚拟机的对象头包含两局部信息,第一局部用于存储对象本身的运行时数据,如哈希码(HashCode)、GC分代年龄、锁状态标记、线程持有的锁、偏差线程ID、偏差工夫戳等,这部分数据的长度在32位和64位的虚拟机(未开启压缩指针)中别离为32bit和64bit,官网称它为“Mark Word”。对象头的另外一部分是类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。并不是所有的虚拟机实现都必须在对象数据上保留类型指针,换句话说,查找对象的元数据信息并不一定要通过对象自身。实例数据局部是对象真正存储的无效信息,也是在程序代码中所定义的各种类型的字段内容。无论是从父类继承下来的,还是在子类中定义的,都须要记录起来。对齐填充并不是必然存在的,也没有特地的含意,它仅仅起着占位符的作用。对齐填充并不是必然存在的,也没有特地的含意,它仅仅起着占位符的作用。(四)对象的拜访定位 建设对象是为了应用对象,咱们的Java程序须要通过栈上的reference数据来操作堆上的具体对象。因为reference类型在Java虚拟机标准中只规定了一个指向对象的援用,并没有定义这个援用应该通过何种形式去定位、拜访堆中的对象的具体位置,所以对象拜访形式也是取决于虚拟机实现而定的。目前支流的拜访形式有应用句柄和间接指针两种。通过句柄拜访:如果应用句柄拜访的话,那么Java堆中将会划分出一块内存来作为句柄池,reference中存储的就是对象的句柄地址,而句柄中蕴含了对象实例数据与类型数据各自的具体地址信息。对象拜访方(句柄)式.jpg 间接指针拜访:需思考如何搁置拜访类型数据的相干信息,Java堆对象的布局中reference中存储的间接就是对象地址。对象拜访形式(指針).jpg

January 12, 2022 · 1 min · jiezi

关于jvm:不停机GC算法

论文作者 Cliff Click, Gil Tene, Michael WolfAzul Systems, Inc.1600 Plymouth StreetMountain View, CA 94043 概论1. 简介2. 相干工作3. 硬件反对3.1 背景3.2 操作系统层反对3.3 硬件读屏障4. 不停机GC算法不停机GC算法分为三个次要的阶段:标记、重定位和重映射;每个阶段都是全并行和并发。

January 12, 2022 · 1 min · jiezi

关于jvm:Jdk8垃圾回收处理流程大家伙看我画得对不对

要发车了,没工夫解释,须要解释请留言~

December 31, 2021 · 1 min · jiezi

关于jvm:记录一次线上系统OOM问题的解决过程

前段时间跳槽了,刚入职没3、4个月,我所在的小组次要负责DSP零碎,我主攻DSP中的dist-orders服务 先说一下DSP是干嘛的,项目组所在的公司次要是从事医药行业的,在淘宝、天猫、京东......很多渠道都有自家的店铺,他们本人还有一套ERP,次要是进销存那一套,DSP就是负责连贯ERP和各个渠道之间传递信息的,什么订单啦、库存啦...... 前几周,leader收到线上某某某个机子系统OOM的邮件,leader就顺手丢给我,让我去没事的时候去查查 那我间接登堡垒机去服务器上看看,各种乌七八糟命令就开始用,什么top、jstack、jinfo、jstat、jmap...后果发现一个都不行,不是命令不行,是我没权限查看...... 不说我只不晓得该申请什么样子的权限,就算是晓得了,运维也曾经重启完了,那还查个屁 最终心愿只能放在堆转储文件上,堡垒机是能够下载服务器上的文件,奈何文件太大,6G多,基本下不下来,寻求运维组的共事帮忙,各种发邮件、拉群、最终在我的软磨硬泡下,终于是给到了我 废话不多说,间接上jdk自带的jvisualvm软件进行剖析,(jvisualvm在你装置jdk的bin目录下) 交给jvisualvm进行解析剖析,我电脑是16G内存,剖析6G的堆转储文件,差点没给我电脑送走...... 这里除了导致OOM线程 和 零碎属性之外 没什么太重要的信息点开 显示零碎属性 发现JVM也没设置什么参数(最大堆、最小堆、GC一些参数等等)咱们来看 类 发现char[]和String占了大头,双击char[],进去看看emmm,不晓得怎么回事有的中央是乱码,而且临时也没看到有用的信息 点过去点过来,看了良久,最终决定换个软件 用IBM heapanalyzer试试(下载地址:https://www.ibm.com/support/pages/node/1109955?mhsrc=ibmsearch_a&mhq=heapanalyzer) 是个jar包,间接java -jar -Xmx15g xxx.jar启动(这里的-Xmx多少内存本人定,如果剖析过程中OOM就是你给的内存太少),或者你能够轻易写个启动的脚本 将堆转储文件载入进来,最终就是这个样子了 他这里有个 疑似泄露的点,是DisOrderItemBO对象,占了2.8G内存,约58.74% 再往后下看ArrayList,大概猜想就是ArrayList装载了太多的DisOrderItemBO 让他以树结构模式展现: 没跑了,就是ArrayList汇合内DisOrderItemBO元素太多,看解析是有1936459个DisOrderItemBO的实例,总共占用了2G多内存 看似失去后果了,实则还是不晓得从哪下手,因为DisOrderItemBO用到的中央太多了,基本不晓得是哪个中央引发的 于是,我又又又换了一个软件 MemoryAnalyzer(下载地址:https://www.eclipse.org/downloads/download.php?file=/mat/1.12.0/rcp/MemoryAnalyzer-1.12.0.20210602-win32.win32.x86_64.zip) 加载进来就是这个样子了 这里也有一个 疑似内存透露的点,点击去查看,列出了具体的信息: 首先是哪个线程呈现的问题,其次是哪些对象导致的内存增长且回收不掉 发现和上一个软件剖析的后果差不多,都是ArrayList汇合内DisOrderItemBO元素太多 当初是已知DisOrderItemBO对象产生的太多(在一个线程内),且回收不掉,ArrayList间接就摈弃掉了,因为他自身没问题,只是承载了太多的DisOrderItemBO对象,当初我只须要晓得是哪个线程,刚好这个软件能够看(或者前两个也能看,我没找到吧) http-nio-7210-exec-9线程,我没记错的话,应该是Tomcat工作线程池的线程吧,当初又能够确定一件事,就是导致OOM的必定不是零碎中的异步工作或者是什么JOB等等,其实这里曾经给出了答案,怪就怪在我懒,没往下持续看 线程信息外面给出了我的Service绝对应的类的全包名以及对应的controller的全包名 依据信息间接找到代码,略微捋了一下代码逻辑,在本地启动试试,我去线上日志摸了一个申请报文,用postman申请一下,试了几次怎么玩都不会呈现很多DisOrderItemBO对象 而后我就想,如果参数为空(本来代码就不会对必填参数校验),我就试了一下,控制台打出sql之后,我拿着去线上DB中执行这个sql 去掉不必要的查问列,以及排序,只看个数(一条数据对应一个DisOrderItemBO示例),同理如果查出来了后面剖析的1936459个示例,那么就对上了 为了谨严,我在where条件多了一个创立工夫,工夫截止到这个堆转储文件产生的前10分钟(产生OOM时产生的堆转储文件会很慢)后果查问进去了1936374个条数据,尽管没对上,然而误差也没有很多,当初看来,大差不差就是这里除了问题了 尽管找到了,然而疑难点是,这个接口是查问订单详情,在页面上操作不应该呈现没有必填参数(订单主体ID)的状况呀,这个疑难我始终没想通...... 如果过了几周半个月的,我没有更新后续持续排查的文章,就阐明曾经把这个问题完满解决了

December 28, 2021 · 1 min · jiezi

关于jvm:jvm运行时数据区

jvm运行时数据区总览java虚拟机在运行时会将它所治理的内存分成若干个区域,这些区域有各自的用处、创立和销毁工夫,有的是随着虚拟机过程启动而创立,有的是随着用户线程的启动和完结而创立和销毁,依照虚拟机标准,虚拟机内存被划分为以下区域 程序计数器定义Program computer Register 程序计数器,其作用为 记住下一条指令地址程序执行过程中,呈现分支、循环、跳转、线程复原须要通过计数器获取下一条指令地址去执行特点 线程公有 每个线程都有本人独立的程序计数器,因为多线程是通过线程轮流切换实现,同一时刻永远只有一个线程在cpu或内核上执行,一个线程执行到一半轮到下一个线程执行,须要通过计数器保留指令执行的地址,复原后就从计数器保留的地址开始继续执行 占用内存很小,不存在内存溢出危险案例上面通过一个例子看看程序计数器怎么起作用的 public class PCTest { public static void main(String[] args) { for (int i = 0; i < 2; i++) { if(i == 0){ System.out.println("hello"); }else { System.out.println("world"); } } }}应用javap -v PCTest进行反编译,失去以下片段,左侧红框外面是字节码指令地址,而篮框示意这条字节码指令会跳转到指定地址去执行。PC的作用就是记录这些指令的地址。 虚拟机栈定义每个线程执行时须要的内存空间被称为栈办法调用时会创立一个栈帧,并将其入栈,办法调用结束后会将其栈帧出栈栈帧外面蕴含了局部变量表、操作数栈、动静链接、返回地址等信息每个栈只有一个流动栈帧即位于栈顶的那个栈帧,对应着正在执行的办法栈帧局部变量表一组变量值的存储空间,能够了解为一个数组,数组中每个地位用于存储一个局部变量,或者办法参数、this变量(实例办法才有)。在编译为class文件时,局部变量表的最大长度曾经确定,存在code属性的max_locals附加属性中。 变量槽每个变量槽都能寄存一个boolean、 byte、char、short、int、float、reference或returnAddress类型的数据如果是double 或long类型的数据,要用间断的两个变量槽reference类型有两个作用 能够间接或间接查到对象在堆中的地址能够间接或间接查到对象所属数据类型的在办法区中的类型信息 变量槽的调配 如果是实例办法,第一个变量槽用this先将参数散布到变量槽,再为局部变量调配变量槽变量槽复用 办法体内定义的变量,其作用域不肯定是整个办法,一个变量作用域范畴以外定义的变量,能够复用其变量槽操作数栈是一个先入后出的栈最大深度在编译实现后曾经确定了,寄存在code属性的max_stacks数据项中double和long占用两个栈容量,其余数据类型占用一个栈容量能够用来传递参数给调用的办法能够用来进行算术运算动静链接每个栈帧都蕴含了一个援用,指向以后办法所属类型在运行时常量池中的地址,用来反对办法代码的动静链接。在class文件中,一个办法通过符号援用来调用其余办法或拜访字段。动静链接将这些办法的符号援用转换为办法的间接援用,必要时会加载类信息以解析尚未解析的符号援用,并将变量的拜访转换为与这些变量运行时地位相干的存储构造中的适当偏移量。返回地址办法退出有两种,失常退出和异样退出,失常退出可能有返回值,异样退出肯定不会有返回值,然而无论失常或异样退出,都必须返回到办法最后被调用的地位失常退出是通过栈帧中保留的主调办法的PC计数器的值作为返回地址异样退出时返回地址通过异样处理表来确定办法退出的流程可能如下 以后栈帧销毁,复原上一层栈帧的操作数栈和局部变量表,将以后栈帧的返回值压入调用者栈帧的操作数栈,调整PC计数器的值指向办法调用后一条指令的地位线程、栈、栈帧的关系<img src="https://gitee.com/thomasChant/drawing-bed/raw/master/%E7%BA%BF%E7%A8%8B%E3%80%81%E6%A0%88%E3%80%81%E6%A0%88%E5%B8%A7.jpg" alt="线程、栈、栈帧" style="zoom:25%;" /> 栈帧演示应用idea编写测试类,并在methodC()办法打上断点,而后以debug模式启动程序 public class StackFrameTest { public static void main(String[] args) { methodA(); } private static void methodA() { System.out.println("A"); methodB(); } private static void methodB() { System.out.println("B"); methodC(); } private static void methodC() { System.out.println("C"); }}当程序运行到methodC()的时候,查看控制台,发现呈现了4个栈帧 ...

December 19, 2021 · 3 min · jiezi

关于jvm:JVM启动参数简述

JVM启动参数的格局JVM的启动参数大略有1000多个,能够分成上面几种格局: 以-结尾:规范参数。所有JVM都要实现这些参数,且向后兼容。如:-server。以-D结尾:设置零碎属性。例如:-Dfile.encoding=UTF-8。以-X结尾:非标准参数。根本都是传递给JVM应用的,默认JVM实现这些参数的性能,但并不保障所有JVM都满足,且不保障向后兼容。例如:-Xmx4g、-Xms2g。能够应用java -X命令来查看以后JVM反对的非标准参数。以-XX结尾:非稳固参数。专门用于管制JVM的行为,跟具体的JVM实现无关,随时可能会在下个版本勾销。 -XX:+-Flags模式:+-是对布尔值进行开关。例如:-XX:+UseG1GC。-XX:key=value模式:指定具体某个选项的值。例如:-XX:NewRatio=2。JVM启动参数的类型依照JVM启动参数的特点和作用,能够划分为上面几类: 零碎属性参数运行模式参数堆内存设置参数GC设置参数剖析诊断参数JavaAgent参数1、零碎属性参数通过-D结尾的JVM启动参数来配置零碎属性,例如: -Dfile.encoding=UTF-8-Duser.timezone=GMT+08-Dmaven.test.skip=true-Dio.netty.eventLoopThreads=8-Da=A100通过JVM启动参数来配置零碎属性的规范格局为:-Dargname=argvalue。多个参数之间用空格隔开,如果参数值两头有空格,则用引号括起来。 零碎参数能够分为两种类型: Java 默认。此类参数由 JVM 虚拟机自动识别并失效,例如,-Dfile.encoding=UTF-8 用于指定文件编码格局。用户自定义。例如,-Da=A100,程序中能够读取该参数值,执行相干逻辑。零碎属性参数是零碎级全局变量,在程序中任何地位都能够拜访到。能够通过上面的形式,在程序中获取配置的零碎属性: String a=System.getProperty("a");也能够通过System.setProperty办法在程序中配置零碎属性。例如:System.setProperty("a","A100"); 2、运行模式参数JVM运行模式: -server:设置 JVM 应用 server 模式,特点是启动速度比较慢,但运行时性能和内存管理效率很高,实用于生产环境。64 位JDK 下默认启用该模式,而疏忽 -client 参数。-client :JDK1.7 之前在32位的 x86 机器上的默认值是 -client 选项。设置 JVM 应用 client 模式,特点是启动速度比拟快,但运行时性能和内存管理效率不高,通常用于客户端应用程序或者 PC 利用开发和调试。此外,咱们晓得 JVM 加载字节码后,能够解释执行,也能够编译成本地代码再执行,所以能够配置 JVM 对字节码的解决模式: -Xint:解释模式(interpreted mode)。会强制 JVM 解释执行所有的字节码,这当然会升高运行速度,通常低10倍或更多。-Xcomp:编译模式。-Xcomp 参数与-Xint 正好相同,JVM 在第一次应用时会把所有的字节码编译成本地代码,从而带来最大水平的优化。(理论应用时要留神预热)-Xmixed:混合模式,将解释模式和编译模式进行混合应用,这是 JVM 的默认模式,也是举荐模式。 咱们应用 java -version 能够看到 mixed mode 等信息。3、堆内存设置参数上面只列出了几个罕用的堆内存设置参数: -Xmx<size>:指定最大堆内存, 如 -Xmx4g。只是限度堆内存,不包含栈内存,也不包含堆外应用的内存。如果一个4G的机器只部署一个Java程序,那设置-Xmx为3G比拟适合。-Xms<size>:指定堆内存空间的初始大小。 如 -Xms4g。 而且指定的内存大小,并不是操作系统理论调配的初始值,而是GC先布局好,用到才调配。 专用服务器上须要放弃 –Xms 和 –Xmx 统一,否则利用刚启动可能就有好几个 FullGC。当两者配置不统一时,堆内存扩容可能会导致性能抖动。-XX:MaxMetaspaceSize=size:Java8 默认为-1,即不限度 Meta 空间,个别不容许设置该选项。4、GC相干-XX:+UseSerialGC:应用串行垃圾回收器(serial + serial old)-XX:+UseParallelGC:应用并行垃圾回收器(parallel savenge + parallel old)-XX:+UseConcMarkSweepGC:应用ParNew收集器(新生代)+ CMS收集器(老年代)的GC组合-XX:+UseG1GC:应用 G1 垃圾回收器-XX:+UnlockExperimentalVMOptions -XX:+UseZGC :JDK11上要先解锁能力应用ZGC。-XX:+UnlockExperimentalVMOptions -XX:+UseShenandoahGC:JDK12上要先解锁能力应用ShenandoahGC。5、剖析诊断参数-XX:+-HeapDumpOnOutOfMemoryError :当 OutOfMemoryError 产生,即内存溢出时,主动 Dump 堆内存。示例:java -XX:+HeapDumpOnOutOfMemoryError -Xmx256m JavaApp。-XX:HeapDumpPath:与 HeapDumpOnOutOfMemoryError 搭配应用,指定内存溢出时 Dump 文件的目录。如果没有指定则默认为启动 Java 程序的工作目录。示例:java -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/usr/local/ JavaApp。-XX:OnError:产生致命谬误时(fatal error)执行的脚本。例如, 写一个脚本来记录出错工夫, 执行一些命令,或者 curl 一下某个在线报警的 url。-XX:OnOutOfMemoryError:抛出 OutOfMemoryError 谬误时执行的脚本。-XX:ErrorFile=filename:致命谬误的日志文件名,绝对路径或者相对路径。开启近程调试:须要多个参数,上面示例示意裸露8888为近程debug端口:java -Djavax.net.debug=ssl -Xdebug -Xnoagent -Djava.compiler=NONE -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=8888 -jar springboot-1.0.jar。6、JavaAgent参数JavaAgent是JVM外面的一项黑科技,它的原理是额定提供一个Agent库,配置在咱们的命令行启动参数外面 ,JVM启动了当前,在加载所有的类文件之前,会先加载Agent库,能够用Agent内置的一些逻辑,对咱们加载的所有类文件进行一个预处理,能够对它们进行加强或者转换。那么就能够在不改变原先的jar包和class文件的状况下,对原有的所有的类,在运行期加载的形式做一个加强。所以这是一种非侵入式的形式,来实现对现有程序性能的加强。 ...

December 14, 2021 · 1 min · jiezi

关于jvm:JVM自动内存管理2垃圾收集器与内存分配策略

December 12, 2021 · 0 min · jiezi

关于jvm:JVM1自动内存管理

November 28, 2021 · 0 min · jiezi

关于jvm:深入理解Java虚拟机-Java运行时数据区域

文章目录 1. 运行时数据区域 1.1 程序计数器 1.2 Java虚拟机栈 1.3 本地办法栈 1.4 Java堆 1.5 办法区 1.6 运行时常量池 2. 间接内存本文参考于《深刻了解Java虚拟机》运行时数据区域Java虚拟机在执行Java程序的过程中会把它所治理的内存划分为若干个不同的数据区域。其包含:程序计数器、Java虚拟机栈、本地办法栈、Java堆和办法区。 在这里插入图片形容1.1 程序计数器 (1)、什么是程序计数器? 程序计数器(Program Counter Register) 是一块较小的内存空间,它能够看作是以后线程所执行的字节码的行号指示器。在物理层面上是由寄存器实现的。它用于存储下一条所要执行的 JVM 指令的执行地址。 (2)、为什么须要程序计数器? 在Java虚拟机的概念模型里,字节码解释器工作时就是通过扭转这个计数器的值来选取下一条须要执行的字节码指令,它是程序控制流的指示器,分支、循环、跳转、异样解决、线程复原等根底性能都须要依赖这个计数器来实现。它的核心作用就是:用于存储下一条所要执行的 JVM 指令的执行地址。由执行引擎执行下一条JVM指令。 图示阐明 在这里插入图片形容 (3)、程序计数器的相干特点 它是一块很小的内存空间简直能够忽略不计,也是运行速度最快的存储区域。在JVM标准中,每个线程都有它本人的程序计数器,是线程公有的,生命周期与线程生命周期保持一致。如果线程正在执行的是一个Java办法,这个计数器记录的是正在执行的虚拟机字节码指令的地 址;如果正在执行的是本地(Native)办法,这个计数器值则应为空(Undefined)。此内存区域是惟一一个在《Java虚拟机标准》中没有规定任何OutOfMemoryError状况的区域。(4)、应用程序计数器存储字节码指令地址有什么用?为什么应用程序计数器记录以后线程的执行地址呢? 因为CPU须要不停的切换各个线程,这时候切换回来当前,就得晓得接着从哪儿开始继续执行。JVM的字节码解释器就须要通过改变程序计数器的值来明确下一条应该执行什么样的字节码指令。 (5)、程序计数器为什么是线程公有的? 为了线程切换后能复原到正确的执行地位,每条线程都须要有一个独立的程序计数器,各条线程之间计数器互不影响,独立存储,咱们称这类内存区域为 “线程公有”的内存。 (6)、Java代码的执行过程 首先Java代码会被转换为JVM指令(二进制字节码,进而Java能够实现跨平台运行)而后解释器会对JVM指令进行转换,从而转换为机器码给CPU执行。与此同时,程序计数器的中的值指向下一条须要执行的JVM指令的地址。学海无涯,一次分享的知识点是无限的的,然而学习是有限的,我这有一份Java中高级外围常识全面解析,内含业内大佬笔记,如有须要,戳此处收费支付。 1.2 Java虚拟机栈 (1)、什么是Java虚拟机栈? 与程序计数器一样,Java虚拟机栈(Java Virtual Machine Stack)也是线程公有的,它的生命周期与线程雷同(即随着线程的创立而创立,随着线程的沦亡而沦亡)。虚拟机栈形容的是Java办法执行的线程内存模型:每个办法被执行的时候,Java虚拟机都会同步创立一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动静连贯、办法进口等信息。每一个办法被调用直至执行结束的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。 (2)、为什么须要虚拟机栈? 每个办法被执行的时候,Java虚拟机都会同步创立一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动静连贯、办法进口等信息。每一个办法被调用直至执行结束的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。 (3)、虚拟机栈由什么组成? 虚拟机栈由一个又一个的栈帧组成,然而每个线程只有一个流动栈帧(即以后正在执行的办法),如果一个办法须要执行,则相应的栈帧须要入栈;如果一个办法执行完结,则相应的栈帧须要出栈。 在这里插入图片形容 一个栈帧蕴含以下几局部: 局部变量表:局部变量表寄存了编译期可知的各种Java虚拟机根本数据类型(boolean、byte、char、short、int、float、long、double)、对象援用(reference类型,它并不等同于对象自身,可能是一个指向对象起始地址的援用指针,也可能是指向一个代表对象的句柄或者其余与此对象相干的地位)和returnAddress类型(指向了一条字节码指令的地址)。这些数据类型在局部变量表中的存储空间以局部变量槽(Slot)来示意,其中64位长度的long和double类型的数据会占用两个变量槽,其余的数据类型只占用一个。局部变量表所需的内存空间在编译期间实现调配,当进入一个办法时,这个办法须要在栈帧中调配多大的局部变量空间是齐全确定的,在办法运行期间不会扭转局部变量表的小。请读者留神,这里说的“大小”是指变量槽的数量,虚拟机真正应用多大的内存空间(譬如依照1个变量槽占用32个比特、64个比特,或者更多)来实现一个变量槽。操作数栈:也能够称之为表达式栈(Expression Stack),在办法执行过程中,依据字节码指令,往栈中写入数据或提取数据,即入栈(push)和 出栈(pop)。某些字节码指令将值压入操作数栈,其余的字节码指令将操作数取出栈,应用它们后再把后果压入栈,比方:执行复制、替换、求和等操作。操作数栈,次要用于保留计算过程的两头后果,同时作为计算过程中变量长期的存储空间。在这里插入图片形容 动静链接办法进口等信息(4)、该区域常呈现的两种异样情况 StackOverflowError异样:如果Java虚拟机栈的容量不能够动静拓展,线程申请的栈深度大于虚拟机所容许的深度,将抛出StackOverflowError异样。OutOfMemoryError异样:如果Java虚拟机栈的容量能够动静拓展,当栈扩大时无奈申请到足够的内存会抛出OutOfMemoryError异样(5)、Java办法的完结形式 return语句抛出异样1.3 本地办法栈 (1)、什么是本地办法栈? Java虚拟机栈为虚拟机执行Java办法(也就是字节码)服务,而本地办法栈则是为虚拟机应用到的本地(Native)办法服务的。 补充概念 本地办法:当Java虚拟机须要和操作系统底层进行交互的时候须要调用C或者C++办法,而所调用的C或者C++办法就是本地办法。 在这里插入图片形容 ...

November 12, 2021 · 1 min · jiezi

关于JVM:JVM垃圾收集器和垃圾收集算法详细介绍

JVM垃圾收集器Serial收集器Serial收集器是最根本,倒退最悠久的收集器,在JDK1.3.1之前是虚拟机新生代垃圾回收的惟一抉择。这个收集器是一个单线程的。它的单线程的意义并不仅仅阐明它只会应用一个CPU或者一条收集线程去实现收集工作,最重要的是,它进行垃圾收集时,其余工作线程会暂停,直到收集完结。这项工作由虚拟机在后盾主动发动和执行的,在用户不可见的状况下将所有工作线程全副停掉,这对于很多应用程序来说是不可容忍的。咱们能够构想一下,咱们的计算机在运行1个小时就要进行5分钟的时候,这是什么状况?对于这种设计,虚拟机设计人员示意的也是十分冤屈,因为不可能边收集,这边还要一直的产生垃圾对象,这样是清理不完的。所以从1.3始终到当初,虚拟机的开发团队始终为缩小因垃圾回收而产生的线程进展所致力着,所呈现的虚拟机越来越优良,但直到现在,仍然没有齐全打消。 讲到这里,貌似Serial收集器曾经是"食之无味弃之可惜"了,但实际上,它仍然是虚拟机在Client模式下,新生代默认的垃圾收集器。它有绝对于其余垃圾收集器的劣势,比方因为没有线程之间切换的开销,分心做垃圾收集天然可能播种最高的线程利用效率。在用户桌面利用背景下,个别调配给虚拟机的内存不会太大,收集几十兆或者一两百兆的新生代对象,进展工夫齐全能够管制在几十毫秒到一百毫秒之间,这个是能够承受的,只有不是频繁产生。因而,Serial收集器在Client模式下,对于新生代来说仍然是一个很好的抉择。 ParNew收集器ParNew收集器其实就是Serial收集器的多线程版本,除了应用多线程进行垃圾回收之外,其余可控参数,收集算法,进行工作线程,对象分配原则,回收策略等与Serial收集器完全一致。 除了多线程实现垃圾收集之外,其余没有什么太多翻新之处,然而它的确Server模式下的新生代的首选的虚拟机收集器。其中一个重要的起因就是除了Serial收集器外,只有它能与CMS配合应用。在JDK1.5期间,HotSpot推出了一款在强交互利用划时代的收集器CMS,这款收集器是HotSpot第一款真正意义上的并发收集器,第一次实现了垃圾回收与工作线程同时工作的可能性,换而言之,你能够边净化,边收集。 不过CMS作为老年代的收集器,却无奈与1.4中公布的最新的新生代垃圾收集器配合应用,反之只能应用Serial或者Parnew中的一个。ParNew收集器能够应用-XX:+UseParNewGC强行指定它,或者应用-XX:+UseConcMarkSweepGC选项后的默认新生代收集器。 ParNew收集器在单CPU环境下相对不会有比Serial收集器更好的成果,甚至优于存在线程交互开销,该收集器在通过超线程技术实现的两个CPU的环境下都不能保障百分之百超过Serial收集器。当然,随着CPU数量的减少,对于GC时零碎的无效资源利用还是很有益处的。在CPU十分多的状况下,能够应用-XX:ParallelGCThreads来限度垃圾回收线程的数量。 Parallel Scavenge收集器Parallel Scavenge收集器是一个新生代收集器,采纳复制算法,又是并行的多线程垃圾收集器。它的关注点与其它收集器的关注点不一样,CMS等收集器的关注点在于缩短垃圾回收时用户线程进行的工夫,而Parallel Scavenge收集器则是达到一个可管制的吞吐量,所谓吞吐量就是CPU运行用户线程的工夫与CPU运行总工夫的比值,即 吞吐量 = (用户线程工作工夫)/(用户线程工作工夫 + 垃圾回收工夫),比方虚拟机总共运行100分钟,垃圾收集耗费1分钟,则吞吐量为99%。进展工夫越短越适宜与用户交互的程序,良好的响应速度能进步用户体验,然而高吞吐量则能够高效率的利用CPU的工夫,尽快实现程序的运算工作,次要适宜在后盾运算而不须要太多交互的程序。 有两个参数管制吞吐量,别离为最大垃圾收集工夫: -XX:MaxGCPauseMills, 间接设置吞吐量的大小: -XX:GCTimeRatio -XX:+UseAdaptiveSizePolicy 自适应策略也是Parallel Scavenge收集器区别去Parnew收集器的重要一点 Serial Old收集器Serial Old收集器是Serial收集器的老年代版本,它同样是一个单线程收集器,应用标记-整顿算法,这个收集器的次要目标也是在与给Client模式下应用。如果在Server模式下,还有两种用处,一种是在jdk5以前的版本中配合Parallel Scavenge收集器应用,另一种用处作为CMS的备用计划,在并发收集产生Concurrent Mode Failure时应用。 Parallel Old收集器Parallel Old收集器是Parallel Scavenge收集器的老年代版本,应用多线程和标记-整顿算法,这个收集器在jdk6中才开始应用的,在此之前Parallel Scavenge收集器始终处于比拟难堪的阶段,起因是,如果新生代采纳了Parallel Scavenge收集器,那么老年代除了Serial Old之外,别无选择,因为老年代Serial在服务端的连累,使得应用了Parallel Scavenge收集器也未必能达到吞吐量最大化的成果,因为单线程的老年代无奈充分利用服务器多CPU的解决能力,在老年代很大而且硬件比拟高级的环境中,这种组合的吞吐量甚至不如Parallel Scavenge收集器 + CMS。 直到Parallel Old收集器呈现后,"吞吐量优先收集器"终于有了货真价实的组合,在重视吞吐量优先和CPU资源敏感的场合,能够采纳Parallel Scavenge收集器 + Parallel Old收集器。 CMS收集器CMS收集器是一种以获取最短进展工夫为指标的收集器。从名字(Concurrent Mark Sweep)上就能够看出,采纳的标记-革除算法,它的过程分为4个步骤: 只有初始标记和从新标记须要暂停用户线程。 (1)初始标记 --- 仅仅关联GC Roots能间接关联到的对象,速度很快; (2)并发标记 --- 进行GC Roots Tracing的过程; (3)从新标记 --- 为了修改并发标记期间,因用户程序运作而导致标记产生变动的那一部分对象的标记记录; (4)并发革除 因为整个过程中耗时最长的并发标记和并发革除过程收集器都能与用户线程一起工作,所以总的来说,CMS的内存回收过程与用户线程一起并发执行的 CMS收集器的三大毛病: (1)CMS收集器对CPU资源十分敏感 (2)无奈解决浮动垃圾 (3)因为基于标记革除算法,所以会有大量的垃圾碎片产生 -XX:+UseCMSCompactAtFullCollection G1收集器首先,G1的设计准则就是简略可行的性能调优 ...

October 25, 2021 · 1 min · jiezi

关于jvm:动图图解GC算法-让垃圾回收动起来

原创:码农参上(微信公众号ID:CODER_SANJYOU),欢送分享,转载请保留出处。提到Java中的垃圾回收,我置信很多小伙伴和我一样,第一反馈就是面试必问了,你要是没背过点GC算法、收集器什么的常识,出门都不敢说本人背过八股文。说起来还真是有点难堪,工作中理论用到这方面常识的场景真是不多,并且这货色学起来也很干燥,然而奈何面试官就是爱问,咱们能有什么方法呢? 既然曾经卷成了这样,不学也没有方法,Hydra就义了周末工夫,给大家画了几张动图,心愿通过这几张图,可能帮忙大家对垃圾收集算法有个更好的了解。废话不多说,首先还是从根底问题开始,看看怎么判断一个对象是否应该被回收。 判断对象存活垃圾回收的基本目标是利用一些算法进行内存的治理,从而无效的利用内存空间,在进行垃圾回收前,须要判断对象的存活状况,在jvm中有两种判断对象的存活算法,上面别离进行介绍。 1、援用计数算法在对象中增加一个援用计数器,每当有一个中央援用它时计数器就加 1,当援用生效时计数器减 1。当计数器为0的时候,示意以后对象能够被回收。 这种办法的原理很简略,判断起来也很高效,然而存在两个问题: 堆中对象每一次被援用和援用革除时,都须要进行计数器的加减法操作,会带来性能损耗当两个对象互相援用时,计数器永远不会0。也就是说,即便这两个对象不再被程序应用,依然没有方法被回收,通过上面的例子看一下循环援用时的计数问题:public void reference(){ A a = new A(); B b = new B(); a.instance = b; b.instance = a; }援用计数的变动过程如下图所示: 能够看到,在办法执行实现后,栈中的援用被开释,然而留下了两个对象在堆内存中循环援用,导致了两个实例最初的援用计数都不为0,最终这两个对象的内存将始终得不到开释,也正是因为这一缺点,使援用计数算法并没有被理论利用在gc过程中。 2、可达性剖析算法可达性剖析算法是jvm默认应用的寻找垃圾的算法,须要留神的是,尽管说的是寻找垃圾,但实际上可达性剖析算法寻找的是依然存活的对象。至于这样设计的理由,是因为如果间接寻找没有被援用的垃圾对象,实现起来绝对简单、耗时也会比拟长,反过来标记存活的对象会更加省时。 可达性剖析算法的基本思路就是,以一系列被称为GC Roots的对象作为起始点,从这些节点开始向下搜寻,搜寻所走过的门路称为援用链,当一个对象到GC Roots没有任何援用链相连时,证实该对象不再存活,能够作为垃圾被回收。 在java中,可作为GC Roots的对象有以下几种: 在虚拟机栈(栈帧的本地变量表)中援用的对象在办法区中动态属性援用的对象在办法区中常量援用的对象在本地办法栈中JNI(native办法)援用的对象jvm外部的援用,如根本数据类型对应的Class对象、一些常驻异样对象等,及零碎类加载器被同步锁synchronized持有的对象援用反映jvm外部状况的 JMXBean、JVMTI 中注册的回调本地代码缓存等此外还有一些临时性的GC Roots,这是因为垃圾收集大多采纳分代收集和部分回收,思考到跨代或跨区域援用的对象时,就须要将这部分关联的对象也增加到GC Roots中以确保准确性其中比拟重要、同时提到的比拟多的还是后面4种,其余的简略理解一下即可。在理解了jvm是如何寻找垃圾对象之后,咱们来看一看不同的垃圾收集算法的执行过程是怎么的。 垃圾收集算法1、标记-革除算法标记革除算法是一种十分根底的垃圾收集算法,当堆中的无效内存空间耗尽时,会触发STW(stop the world),而后分标记和革除两阶段来进行垃圾收集工作: 标记:从GC Roots的节点开始进行扫描,对所有存活的对象进行标记,将其记录为可达对象革除:对整个堆内存空间进行扫描,如果发现某个对象未被标记为可达对象,那么将其回收通过上面的图,简略的看一下两阶段的执行过程: 然而这种算法会带来几个问题: 在进行GC时会产生STW,进行整个应用程序,造成用户体验较差标记和革除两个阶段的效率都比拟低,标记阶段须要从根汇合进行扫描,革除阶段须要对堆内所有的对象进行遍历仅对非存活的对象进行解决,革除之后会产生大量不间断的内存碎片。导致之后程序在运行时须要调配较大的对象时,无奈找到足够的间断内存,会再触发一次新的垃圾收集动作此外,jvm并不是真正的把垃圾对象进行了遍历,把外部的数据都删除了,而是把垃圾对象的首地址和尾地址进行了保留,等到再次分配内存时,间接去地址列表中调配,通过这一措施进步了一些标记革除算法的效率。 2、复制算法复制算法次要被利用于新生代,它将内存分为大小雷同的两块,每次只应用其中的一块。在任意工夫点,所有动态分配的对象都只能调配在其中一个内存空间,而另外一个内存空间则是闲暇的。复制算法能够分为两步: 当其中一块内存的无效内存空间耗尽后,jvm会进行利用程序运行,开启复制算法的gc线程,将还存活的对象复制到另一块闲暇的内存空间。复制后的对象会严格依照内存地址顺次排列,同时gc线程会更新存活对象的内存援用地址,指向新的内存地址在复制实现后,再把应用过的空间一次性清理掉,这样就实现了应用的内存空间和闲暇内存空间的对调,使每次的内存回收都是对内存空间的一半进行回收通过上面的图来看一下复制算法的执行过程: 复制算法的的长处是补救了标记革除算法中,会呈现内存碎片的毛病,然而它也同样存在一些问题: 只应用了一半的内存,所以内存的利用率较低,造成了节约如果对象的存活率很高,那么须要将很多对象复制一遍,并且更新它们的利用地址,这一过程破费的工夫会十分的长从下面的毛病能够看出,如果须要应用复制算法,那么有一个前提就是要求对象的存活率要比拟低才能够,因而,复制算法更多的被用于对象“朝生暮死”产生更多的新生代中。 3、标记-整顿算法标记整顿算法和标记革除算法十分的相似,次要被利用于老年代中。可分为以下两步: 标记:和标记革除算法一样,先进行对象的标记,通过GC Roots节点扫描存活对象进行标记整顿:将所有存活对象往一端闲暇空间挪动,依照内存地址顺次排序,并更新对应援用的指针,而后清理末端内存地址以外的全副内存空间标记整顿算法的执行过程如下图所示: 能够看到,标记整顿算法对后面的两种算法进行了改良,肯定水平上补救了它们的毛病: 绝对于标记革除算法,补救了呈现内存空间碎片的毛病绝对于复制算法,补救了节约一半内存空间的毛病然而同样,标记整顿算法也有它的毛病,一方面它要标记所有存活对象,另一方面还增加了对象的挪动操作以及更新援用地址的操作,因而标记整顿算法具备更高的应用老本。 4、分代收集算法实际上,java中的垃圾回收器并不是只应用的一种垃圾收集算法,以后大多采纳的都是分代收集算法。jvm个别依据对象存活周期的不同,将内存分为几块,个别是把堆内存分为新生代和老年代,再依据各个年代的特点抉择最佳的垃圾收集算法。次要思维如下: 新生代中,每次收集都会有大量对象死去,所以能够抉择复制算法,只须要复制大量对象以及更改援用,就能够实现垃圾收集老年代中,对象存活率比拟高,应用复制算法不能很好的进步性能和效率。另外,没有额定的空间对它进行调配担保,因而抉择标记革除或标记整顿算法进行垃圾收集通过图来简略看一下各种算法的次要利用区域: 至于为什么在某一区域抉择某种算法,还是和三种算法的特点非亲非故的,再从3个维度进行一下比照: 执行效率:从算法的工夫复杂度来看,复制算法最优,标记革除次之,标记整顿最低内存利用率:标记整顿算法和标记革除算法较高,复制算法最差内存参差水平:复制算法和标记整顿算法较参差,标记革除算法最差只管具备很多差别,然而除了都须要进行标记外,还有一个相同点,就是在gc线程开始工作时,都须要STW暂停所有工作线程。 总结本文中,咱们先介绍了垃圾收集的根本问题,什么样的对象能够作为垃圾被回收?jvm中通过可达性剖析算法解决了这一关键问题,并在它的根底上衍生出了多种罕用的垃圾收集算法,不同算法具备各自的优缺点,依据其特点被利用于各个年代。 尽管这篇文章唠唠叨叨了这么多,不过这些都还是根底的常识,如果想要彻底的把握jvm中的垃圾收集,后续还有垃圾收集器、内存调配等很多的常识须要了解,不过咱们明天就介绍到这里啦,心愿通过这一篇图解,可能帮忙大家更好的了解垃圾收集算法。 最初,提前祝大家国庆小长假欢快,咱们下篇见~ 作者简介,码农参上(CODER_SANJYOU),一个酷爱分享的公众号,乏味、深刻、间接,与你聊聊技术。集体微信DrHydra9,欢送增加好友,进一步交换。

September 27, 2021 · 1 min · jiezi

关于JVM:JVM内存模型你看这一篇就够了

摘要:JVM是一种用于计算设施的标准,是一个虚构进去的计算机,通过在理论的计算机上仿真模仿各种计算机性能来实现的。本文分享自华为云社区《[[云驻共创]JVM内存模型的探知之旅](https://bbs.huaweicloud.com/b...)》,作者:多米诺的古牌。 1. JVM介绍1.1 什么是JVM?JVM是Java Virtual Machine(Java虚拟机)的简称,是一种用于计算设施的标准,是一个虚构进去的计算机,通过在理论的计算机上仿真模仿各种计算机性能来实现的。 1.2 JVM的长处1.2.1 一次编写,到处运行。JVM能够让java程序,一次编写,导出运行。让底层代码和运行环境分来到,编写好一份代码后,不必再次批改内容,只用通过装置不同的JVM环境主动进行转换即可运行,在各种零碎中无缝连贯。 1.2.2 主动内存治理,垃圾回收机制。在Java诞生之时,C和C++称霸天下,然而这两种语言中没有内存管理机制,是通过手动操作来进行的治理,十分麻烦和繁琐。 此时Java应运而生,为了解决内存治理这个方面,专门设计了垃圾回收机制,来主动进行内存的治理。极大的优化了操作,让程序员们不必正在噼里啪啦在码代的陆地中漫游时,还要操心内存会不会溢出这些“影响我方输入”的问题,登时取得了成吨的好评。 1.2.3 数组下标越界查看在Java诞生之时,还有个让过后C和C++大佬头疼的问题是,数组下标越界是没有查看机制的,这还了得,又是一个影响“我方暴力输入”的罪魁祸首,因而JVM持续抱着暖男的思维,又来了个爱的抱抱。 JVM又一次看见了大佬们的懊恼,果决提供了数组下标越界的主动查看机制,在检测到数组下标呈现越界后,会在运行时主动抛出“java.lang.ArrayIndexOutOfBoundsException”这个异样,在过后可是打动了很多业界大佬(我猜的)。 1.2.4 多态JVM还有一个多态性能,是通过雷同接口,不同的实例进行实现,实现不同的业务操作,比方:定义了一个动物接口(外面有一个吃的办法),咱们就能够通过这个动物发明小猫(吃鱼),再发明一个狗狗(吃肉),再发明一个小助手(吃零食,O(∩_∩)O哈哈~)。 认真想想,对咱们有啥影响呢,那益处老多了,比方: (1)打消类型之间的耦合关系; (2)可替换性; (3)可扩充性; (4)接口性; (5)灵活性; (6)简化性; 1.3 JVM、JRE、JDK之间的关系1.3.1 JVM的简介JVM是Java Virtual Machine的简称,是Java虚拟机,是一种模仿进去的虚构计算机,它通过在不同的计算机环境当中模仿实现计算性能来实现的。 引入Java虚拟机后,Java语言在不同平台上运行时就不须要从新编译。在其中,Java虚拟机屏蔽了与具体平台的相干信息,使得Java源程序在编译实现之后即可在不同的平台运行,达到“一次编译,到处运行”的目标,Java语言重要的特点之一跨平台,也即与平台的无关性,其关键点就是JVM。 1.3.2 JRE的简介JRE是Java Runtime Environment的简称,是Java运行环境,是让操作系统运行Java应用程序的环境,其外部蕴含JVM,也就是说JRE只负责对曾经存在的Java源程序进行运行的操作,它不蕴含开发工具JDK,对JDK外部的编译器、调试器和其它工具均不蕴含。 1.3.3 JDK的简介JDK是Java Development Kit的简称,是Java开发工具包,是整个Java程序开发的外围。其次要蕴含了JRE、Java的零碎类库以及对Java程序进行编译以及运行的工具,例如:javac.exe和java.exe命令工具等。 1.4 JVM的常见实现Oracle(Hotspot、Jrockit)、BEA(LiquidVM)、IBM(J9)、taobaoVM(淘宝专用,对Hotspot进行了深度定制)、zing(垃圾回收机制十分快,达到1毫秒左右)。 1.5 JVM的内存结构图 当Java程序编译实现为.class文件==》类加载器(ClassLoader)==》将字节码文件加载进JVM中; 1.5.1办法区、堆办法区中保留的次要是类的信息(类的属性、成员变量、构造函数等)、堆(创立的对象)。 1.5.2虚拟机栈、程序计数器、本地办法栈堆中的对象调用办法时,办法会运行在虚拟机栈、程序计数器、本地办法栈中。 1.5.3执行引擎执行办法中代码时,代码通过执行引擎执行中的“解释器”执行;办法中常常调用的代码,即热点代码,通过“即时编译器”执行,其执行速度十分快。 1.5.4 GC(垃圾回收机制)GC是针对堆内存中没有援用的对象进行回收,能够手动也能够主动。 1.5.5本地办法接口因为JVM不能间接调用操作系统的性能,只能通过本地办法接口来调用操作系统的性能。 2. JVM内存构造-程序计数器2.1 程序计数器的定义Program Counter Register即程序计数器(寄存器),用于记录下一条Jvm指令的执行地址。 2.2 操作步骤javap次要用于操作JVM,javap -c 是对java代码进行反汇编操作。下图为通过先编译demo.java后,再执行javap -c demo的输入后果: 其中第一列为二进制字节码,即JVM指令,第二列为java源代码。第一列中的序号为JVM指令的执行地址。 JVM会通过程序计数器记录下一条须要执行的JVM指令的地址(比方第一行的0),而后交给解释器解析为机器码,最初交给cpu(只能辨认机器码),实现一行的执行。 想要执行下一行,持续让JVM的程序计数器记录下一条地址(比方第二行的3),再交给解释器解析后给cpu,以此类推执行完结。 2.3 特点2.3.1 线程公有的2.3.2 不会存在内存溢出3. JVM内存构造-虚拟机栈3.1 定义虚拟机栈是每个线程运行所须要的内存空间,每个栈中由多个栈帧组成,每个线程中只能有一个流动栈帧(对应以后正在执行的办法),所有栈帧都遵循后进先出,先进后出的准则。 ...

September 16, 2021 · 1 min · jiezi

关于JVM:JVM内存结构

Java运行时数据区 JVM的运行时数据区次要有办法区,堆,虚拟机栈,程序计数器,本地办法栈这几局部,其中办法区和堆是所有线程共有,虚拟机栈,本地办法栈和程序计数器每个线程都有一个。 1、程序计数器指的是一块内存区域,底层是bcp,字节码指针,用来寄存以后线程执行字节码的行号。 如果线程正在执行的是一个Java办法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是本地(Native)办法,这个计数器值则应为空(Undefined)。 因为JVM多线程是通过线程轮流切换,调配处理器执行工夫的形式实现的。也就是说在任意一个确定的时刻,一个处理器只会执行一个线程的一条指令。因而,为了线程切换的后能复原到正确的执行地位,每条线程都须要有一个独立的程序计数器,各条线程之间计数器互不影响,独立存储。所以是线程公有。而如果程序计数器没有OOM,因为一旦它OOM了,程序就不晓得该执行哪个线程的办法,JVM就会解体。 在JVM中相似这样一个循环: while( not end ) { 取PC中的地位,找到对应地位的指令; 执行该指令; PC ++; } 2、虚拟机栈讲述的是Java办法执行的线程内存模型:每个办法被执行的时候,Java虚拟机都会同步创立一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动静连贯、办法进口等信息。每一个办法被调用直至执行结束的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。 Java虚拟机以办法作为最根本的执行单元,“栈帧”(Stack Frame)则是用于反对虚拟机进行办法调用和办法执行背地的数据结构,它也是虚拟机运行时数据区中的虚拟机栈(Virtual Machine Stack)的栈元素。栈帧存储了办法的局部变量表、操作数栈、动静连贯和办法返回地址等信息。每一个办法从调用开始至执行完结的过程,都对应着一个栈帧在虚拟机栈外面从入栈到出栈的过程。 然而有一点须要留神,一个栈帧须要调配多少内存,并不会受到程序运行期变量数据的影响,而仅仅取决于程序源码和具体的虚拟机实现的栈内存布局模式。 相当于cpu外面的寄存器,用于寄存办法参数和办法外部定义的局部变量。利用办法的Code属性的max_locals确定最大容量。局部变量表所需的内存空间是在编译期间确定的,在办法运行期间不会扭转局部变量表的大小。 在Java虚拟机标准中,对这部分区域规定了两种异样: 1、当一个线程的栈深度大于虚拟机所容许的深度的时候,将会抛出StackOverflowError异样; 2、如果当创立一个新的线程时无奈申请到足够的内存,则会抛出OutOfMemeryError异样。 3、本地办法栈和虚拟机栈相似,然而区别是虚拟机栈为虚拟机执行Java办法(也就是字节码)服务,而本地办法栈则为虚拟机应用到的本地办法服务。 一个Native Method就是一个Java调用非Java代码的接囗。该办法的实现由非Java语言实现,为C或者C++实现的,和虚拟机栈相似,只不过用于治理非Java办法的。 无奈调优和治理,所以个别不论它,晓得有这么一个货色就行 4、Java堆堆(heap)是虚拟机中最大的一块内存区域了,被所有线程共享,在虚拟机启动时创立。它的目标便是寄存对象实例。 堆是垃圾收集器治理的次要区域,因而 很多时候也被成为‘GC’堆(Garbage Collected Heap)。 从垃圾回收的角度来讲,当初的收集器包含HotSpot都采纳分代收集算法,所以堆又能够分为:新生代(Young)和老年代(Tenured),再粗疏一点,新生代又可分为Eden、From Survivor空间和To Survivor空间。 从内存调配的角度来讲,又能够分为若干个线程公有的调配缓冲区(Thread Local Allocation Buffer ,TLAB)。 当堆空间有余切无奈扩大,会抛出OutOfMemoryError异样。 5、办法区它用于存储已被虚拟机加载的类型信息,常量,动态变量,即时编译器编译后的代码缓存等数据。 办法区中有一个重要的概念叫运行时常量池,它用来寄存编译器生成的各种字面量与符号援用,它们将在类加载当前放入办法区的运行时常量池中。JVM虚拟机标准没有很严格的规定,但个别除了保留Class文件中形容的符号援用外,还会把符号援用翻译进去的间接援用也存储在外面。运行时常量池有动态性,并非预置入Class文件中常量池的内容能力进入办法区运行时常量池,运行期间也能够将新的常量放入池中,这种个性被开发人员利用得比拟多的便是String类的intern()办法。 JVM虚拟机标准并没有规定如何实现办法区。在hotspot中,1.8以前,用永恒代来实现的办法区。然而这种计划导致Java利用更容易在遇到内存溢出问题,而且极少数办法(String::intern())会因为永恒代的起因导致不同虚拟机下有不同的体现。1.8当前,改用本地内存中实现的元空间(metaspace)来代替。而常量池放在JVM堆中了。 6、间接内存间接内存:间接内存,归操作系统管,不归JVM管。1.4当前新退出NIO类,引入了一种基于通道和缓存区的IO形式。它通过native函数库操作堆外内存,而后通过一个存储在Java堆的DirectByteBuffer对象作为这块内存的援用进行操作。这样做次要能够提高效率,防止在Java堆和Native堆中来回复制数据。

August 26, 2021 · 1 min · jiezi

关于jvm:深入理解jvm-编译优化上

前言编译优化的内容还是不少的,当然次要的内容集中在后端的编译下面,为了管制篇幅的长度所以这里抉择拆分为高低两局部解说,咱们平时写的代码和理论运行时候的代码成果是齐全不一样的,理解编译优化的细节是有必要的。 概述理解javac的根本编译过程以及根本的解决细节理解根本的前端优化伎俩:语法糖和泛型的实现理解前后端编译的内容以及局部后端编译的内容。Javac的编译过程javac的工程代码并不属于java se api的一部分,同时因为jdk9的版本之后模块化被独自分离出来了,书中应用了jdk9的版本来解说对于javac的编译过程。 筹备工作包所在的地位(jdk9):JDK_SRC_HOM E/src/jdk.comp iler/share/classes/com/sun/tools/javac 如果须要搭建一个javac的工程只有新建一个工程并且把上面门路的内容复制到工程的上面即可。 J D K SR C H O M E / l a n gt o o l s / s r c / s h a r e / c l a s s e s / c o m / s u n / *拷贝实现之后,一个反对javac命令的工程就搭建好了 javac的编译步骤对于javac的编译步骤根本如下,须要留神的是这里蕴含了jdk5版本中的注解处理器的内容: 筹备:初始化插入式注解处理器解析和填充符号表过程 词法剖析填充符号表插入式注解处理器处理过程: 插入式注解处理器的执行阶段剖析与字节码生成(语法分析是IDE罕用局部) 标注查看(数据分析,常量折叠优化)数据流和数据分析(上下文语义剖析查看)解语法糖(由desagrc 办法触发)字节码生成上面是书中对于整个编译过程的一张图表演示,能够看到程序不是固定的,而是会存在更换程序的状况: 前端优化注解处理器注解处理器的步骤是在jdk5当中新增的内容,在Javac源码中,插入式注解处理器的初始化过程是在initPorcessAnnotations()办法中实现的,而它 的执行过程则是在processAnnotations()办法中实现。这个办法会判断是否还有新的注解处理器须要执 行 , 如 果 有 的 话 , 通 过 c o m . s u n . t o o l s . j a v a c . p r o c e s s i n g. J a v a c P r o c e s s i n g- E n v i r o n m e n t 类 的 d o P r o c e s s i n g( ) 方 法 来生成一个新的JavaComp iler对象,对编译的后续步骤进行解决。 ...

August 24, 2021 · 2 min · jiezi

关于jvm:Java开发者需要明白的最小JVM知识点

先说点废话咱们好多java开发者,代码编写了很多年,然而要么始终是在做CRUD的活儿,要么就是不停的学习各种框架怎么应用,对于java最最根底的JVM局部,除了在最开始学习java的时候有过一些隐约的印象。 晓得JVM是java编译后可能跨平台运行的根底,也晓得能够应用-Xms和-Xmx配置堆内存大小,然而对于什么是堆内存,jvm就只有堆内存吗?jvm外部各个区域的构造是怎么样的,为什么要这么设计?代码在jvm外部是怎么执行的,并没有一个清晰的概念。 对于一般开发者来说,如果始终是在编写业务代码的,仿佛不理解这些也并不影响,可能也确实没啥影响。 不过我想,但但凡对技术有点谋求的,还是须要去理解一下jvm的内部结构的。包含咱们去面试一些java岗位的时候,jvm都是必问的。如果面试的时候一家公司连jvm都不问,那基本上能够必定这家公司的java技术团队是挺拉跨的。 对于jvm的学习,其实和java源码、框架原理的学习有点相似。 很多时候咱们会有一种感觉,不往下深刻学习,也不影响把货色做进去。 然而真的如此吗?大家能够尝试一下深刻学习一门技术,而后再回过头去看看先前对该常识的利用,必定会有不一样的领会。 JDK、JRE与JVM 这是一张Java世界中比拟有名的图了, 位于最底层的,是各类操作系统,往上一层,就是咱们的JVM了,这里大家可能会有个疑难,JVM怎么还辨别Client和Server? 这其实是JVM的两种运行模式,Client模式启动速度较快,Server模式启动较慢;然而启动进入稳定期长期运行之后Server模式的程序运行速度比Client要快很多。这是因为Server模式启动的JVM采纳的是重量级的虚拟机,对程序采纳了更多的优化;而Client模式启动的JVM采纳的是轻量级的虚拟机。所以Server启动慢,但稳固后速度比Client远远要快。 要想查看以后jvm是以何种模式运行的,咱们的jvm默认都是以server模式运行的 mac@MACs-iMac-2  ~  java -versionjava version "11.0.10" 2021-01-19 LTSJava(TM) SE Runtime Environment 18.9 (build 11.0.10+8-LTS-162)Java HotSpot(TM) 64-Bit Server VM 18.9 (build 11.0.10+8-LTS-162, mixed mode)JVM和其余java程序运行所需的根底类库,组成了JRE,即 Java Runtime Environment JRE联合开发应用的工具包,则造成了整个JDK JDK加上其余所有基于Java开发的各种类库、框架,就造成了咱们的Java生态 当然,这里只是从语言层面上比拟不那么谨严的这么一说,真正的Java生态,要简单的多。 根本堆栈构造 JVM在启动后,会将内存分为两大类,一类是跟着线程存在而存在的内存空间,包含程序计数器、虚拟机、本地办法栈一类是所有线程共享的区域,包含堆、办法区。 第一类空间,存储的是以后正在执行的线程的局部变量、办法参数,会随着线程的终止而开释堆空间,存储new进去的对象是JVM中占用空间最大的一个区域,办法区,存储的是类信息、常量、动态变量等等 而咱们对JVM的调优,次要就是设置各项存储空间的大小、比例,以及前面会讲到的各项GC参数 聊聊线程隔离区其实对于jvm调优来说,线程隔离区不是重点,须要配置的参数也不多,不过因为内容不多,所以还是简略的来介绍一下。 线程隔离去所有的内存空间占用,都会随着线程的完结而开释 程序计数器存储的是 以后线程所执行的字节码的行号指示器。字节码解释器通过线程的程序计数器的值,晓得以后执行到哪一行,下一步执行哪一步。不过不同于咱们的java代码,jvm层面的一行和java代码的一行往往不是一个概念,如下代码 package com.zhangln;/** * @author sherry * @date 2021年08月11日 8:00 下午 */public class HelloWorld { public static void main(String[] args) { int a = 1; int b = 2; System.out.println(a + b); }}当我把它编译后,再应用javap命令进行反编译 ...

August 24, 2021 · 1 min · jiezi

关于jvm:深入理解JVM-动态类型语言

前言上一节讲述了栈桢和分派的细节,这一节咱们来讲讲自java语言诞生新减少的新语言个性:动静类型语言反对,这一节将会依据动静语言的个性以及相干的介绍同时讲述jvm一个重要的指令:invoke dynamic指令。然而须要留神的是:invokedy namic指令面向 的次要服务对象并非Java语言,而是其余Java虚拟机之上的其余动静类型语言 概述介绍什么是动静类型语言,以及java为什么是动态语言的解说。介绍invokeDynamic指令在理论案例中的使用介绍java实现动静语言调用的一些曲线救国的伎俩。动静类型语言什么是动静类型语言动静类型语言的要害特色是它的类型查看的主体过程是在运行期而不是编译期。而java就是典型的动态类型语言,须要在编码的过程中确定的,动态的语言也意味着所有的类型在编译器必须确定。 为什么java是动态类型?这里牵扯到一个问题就是为什么java是动态类型呢?咱们能够看一下invokeVitual命令,这个命令依据如下的内容,确定一个属性的全类名,以及类型,在符号援用的阶段能够看到根本的内容: invokevirtual #4; //Method java/io/PrintStream.println:(Ljava/lang/String;)V这些符号援用在翻译为间接援用是须要确切的类型的,所以在晚期的java天生不足动静语言的反对。 动静语言类型反对java在jdk7之后退出了动静语言反对,对于退出动静语言类型的反对外围是应用invoke dynamic命令, 这种形式应用了相似曲线救国的形式,也是为兼容思考不得不做的一种斗争,比方最常见的类型数组,在java中咱们必须声明确数组寄存的类型,而jdk引入了invokeDynamic这个指令之后,就能够实现对于一个办法参数的动静调用。 动静类型语言是能够让对象的类型能够在运行时候再确定,比方JS和Python的var。invokedynamic指令上面来说下invoke这个指令是如何实现动静类型语言的,在java中是无奈把一个函数作为参数传递的,更多的形式应用相似实现接口的形式进行解决,而新的指令在某种程度上是应用相似MethodHandle的形式进行解决的,MethodHandle是对通过字节码的办法指令调用的模仿,但和反射不同的是反射是基于Java语言服务的,而MethodHandler则是服务于所有虚拟机上的一种语言。 每一处含有invokedynamic指令的地位都被称作“动静调用点(Dynamically-Computed Call Site)”, 这条指令的第一个参数不再是代表办法符号援用的CONSTANT_Methodref_info常量,而是变为JDK 7 时新退出的CONSTANT_InvokeDynamic_info常量,从这个新常量中能够失去3项信息:疏导办法 (Bootstrap Method,该办法寄存在新增的BootstrapMethods属性中)、办法类型(MethodType)和 名称。 为了更好的了解这个命令,上面咱们来看下理论运行过程当中的利用,比方在jdk8中引入的lambada表达式和默认办法就是通过invokedynamic命令实现的,然而应用jdk8的实现看起来比拟难以了解,上面来看一下书中给的一段案例代码: Constant pool:#121 = NameAndType #33:#30 // testMethod:(Ljava/lang/String;)V #123 = InvokeDynamic #0:#121 // #0:testMethod:(Ljava/lang/String;)Vpublic static void main(java.lang.String[]) throws java.lang.Throwable; Code:stack=2, locals=1, args_size=10: ldc #23 // String abc2: invokedynamic #123, 0 // InvokeDynamic #0:testMethod: (Ljava/lang/String;)V 7: nop8: returnpublic static java.lang.invoke.CallSite BootstrapMethod(java.lang.invoke.Method Handles$Lookup, java.lang.Strin Code: stack=6, locals=3, args_size=30: new3: dup4: aload_05: ldc7: aload_18: aload_29: invokevirtual #6512: invokespecial #71 15: areturn#63#1// class java/lang/invoke/ConstantCallSite// class org/fenixsoft/InvokeDynamicTest// Method java/lang/invoke/MethodHandles$ Lookup.findStatic:(Ljava/lang/Cl // Method java/lang/invoke/ConstantCallSite. "<init>":(Ljava/lang/invoke/M 从下面的办法调用能够看到,应用的是invokeDynamic的调用指令以及参数为第123项的常量,比方如下的内容: ...

August 23, 2021 · 1 min · jiezi

关于jvm:深入理解jvm-类加载过程

深刻了解jvm - 类加载过程前言 在最早的文章中,咱们尽管探讨过了类加载器的过程,然而并没有讲述外部的细节,本文将会依据类加载器的过程,具体说一下整个类加载的过程中每一个步骤都干什么事件。 类加载的过程如下:加载,验证,筹备,初始化,解析,应用,卸载。重点须要关注的步骤是后面的五个步骤,这些细节算是八股文的内容,所以这篇文章以简略的总结和演绎为主。 概述 本篇次要讲述类加载的加载过程,在类加载的过程当中蕴含了前五个步骤和具体的细节。 类加载的过程 下们拆分这五个步骤,讲讲每一个步骤都做了哪些事件: 加载 第一步是加载,加载做的事件就是什么时候jvm须要去找到.class这个对象,咱们都晓得.class对象如果办法区中存在的话,java是不会去加载第二次的,那么类什么时候会进行初始化呢? 咱们最容易想到的就是new的时候,所以能够必定在new的时候会作为触发条件。接着咱们有时候应用public static String mind = "xxx"这种常量的时候,有时候会构建常量类并且间接援用,这时候必定也是须要先把对应的类加载过去的时候才能够应用的,最初既然咱们应用其余类的动态字段会触发,那么应用其余类的静态方法必定也是会触发类加载条件的。下面这三个条件,是咱们最容易想到的三个,上面会略微简单一点点点触发加载动作的条件。 除了new之外,咱们还晓得一种形式是通过java的反射机制,其实就是拿到.class文件对应的类加载器去生成一个类,反射工具就是来简化这一个动作的,所以这里能够猜到,如果反射须要加载的类还不在办法,那必定也要先把要加载的类加载进来才行。 咱们从继承和实现两个角度去思考什么时候会加载,从继承的角度看,如果父类没有被加载,那么父类也是要被加载进来的,至于为什么必须应用父类,这个问题类结构器能够作为解答,咱们都晓得在结构器的办法会执行一条super()的隐式办法,至于为什么要执行super()则是因为所有的类的父类都是Object。也是因为jvm的类加载器的设计所决定,双亲委派机制决定了所有的子类加载前须要加载父类。(留神,仅仅是加载,是否须要初始化下文会提到) 最初咱们再来看下因为jdk版本带来的改良。首先是jdk7动静语言的反对,所有波及new或者应用动态属性指令的类都会触发加载。而jdk8因为引入了接口的default办法(默认办法)让接口也能够实现“抽象类”的事件,所以如果有子类实现了领有默认办法的接口,也是须要进行加载的。 上面咱们总结下面对于加载的“初始化”条件: New、动态字段援用、静态方法援用继承的父类,如果应用的是父类定义的字段或者办法时候会加载父类,然而不会加载子类。然而如果是然而如果是调用子类的,父类肯定会被加载。反射机制生成的类须要加载(否则无奈进行反射)。jdk7动静语言波及new和static的相干指令jdk8实现了带有默认办法的接口的类。最初,看一下书中给的一段加载“初始化”的代码,后果有点出其不意哦: package org.fenixsoft.classloading;/*** 被动应用类字段演示一:* 通过子类援用父类的动态字段,不会导致子类初始化 **/public class SuperClass { static { System.out.println("SuperClass init!"); } public static int value = 123; }public class SubClass extends SuperClass { static { System.out.println("SubClass init!"); } }/*** 非被动应用类字段演示 **/public class NotInitialization { public static void main(String[] args) { System.out.println(SubClass.value); } }/*运行后果: SuperClass init! */ 上面再看下如果只调用子类的静态方法会产生什么事件: ...

August 19, 2021 · 1 min · jiezi

关于jvm:深入理解JVM-类文件结构

深刻了解JVM - 类文件构造前言 JVM的类文件构造根本都会要记忆的内容,我置信你也记不住,当然我也是记不住的,所以这里只会列出大抵的类文件构造,咱们须要大抵理解类文件构造是怎么一回事就行了,具体到那个位存哪个内容,内容的确太多了,感兴趣能够间接去读书中对应的第6章 类文件构造这一个章节的内容。 类文件构造集体认为须要留神的点就是这几点:大抵的类文件构造,局部Jdk的个性如何通过改变class文件构造实现,比方泛型,主动拆装箱,动静代理,lambada语法等。 概述: 其实次要内容就是介绍CLASS的文件构造。 理解JVM的类文件根本构造。理解常量池的内容理解重点内容属性表汇合思维导图 上面是思维导图的地址:https://www.mubucm.com/doc/1-... 什么是Class类文件? .class文件是由.java通过Javac的命令编译而来的,也是JVM实现跨平台的要害,同时Class类文件实际上的内容是蕴含字节码指令的二进制文件,而字节码指令简略了解是jvm对于汇编指令的进一步封装,甚至有一些书籍拿字节码的指令来讲局部操作系统的底层逻辑实现,留神不要被洗脑了,JVM的字节码指令只能被JVM辨认,放到别的平台就是一堆乱码,如果带歪了倡议看CSAPP这本书洗回来。 既然是由内部的.java文件翻译并且加载到虚拟机上,那么class文件的构造毫无疑问须要严格的规定,避免代码毁坏jvm失常执行,事实上《JVM虚拟机标准》规定了Class文件的整个构造,对于每一位都有严格的要求,古代的JDK尽管对于这个标准有了不少的改变,然而整体来看最根底的构造还是依照最初始公布的那一套执行,所以根本不须要放心过期的问题。 任意一个class文件对应一个类或者接口定义,class文件构造也能够看做是一种标准,只有其余语言也能恪守class文件的标准,意味着齐全能够通过编写其余语言的程序转化为class文件最终翻译到JVM中。 class文件构造 Class文件是一组以8个字节为根底单位的二进制流,各个数据我的项目严格依照程序紧凑地排列在文件之中,两头没有增加任何分隔符,这使得整个Class文件中存储的内容简直全副是程序运行的必要数据,没有空隙存在。当遇到须要占用8个字节以上空间的数据项时,则会依照高位在前 [2] 的形式宰割成若干个8个字节进行存储。 Class文件格式采纳一种相似于C语言构造体的伪构造来存储数据,这种伪构造中只有两种数据类型:“无符号数”和“表”。 无符号数:代表了根本的数据类型,比方u1、u2、u3等,数字代表了字节,比方1个字节,2个字节,3个字节,无符号数能够形容数字,索引援用或者通过UTF-8的编码为字符串存储 比方\u304这种字符串 表:表是由多个无符号数或者其余表作为数据项形成的复合数据类型,习惯性以“_info”结尾。 最初能够整个class文件构造看出如下的一张表。 class文件构造详解 理解了class文件的大抵构造,上面来聊聊class文件的具体组成了。 魔数0xCAFEBABE 在class文件的构造中,每个Class文件的头4个字节被称为魔数(Magic Number),它惟一的作用是标记这个文件是一个class文件,除此之外没有其余作用,至于为什么叫做咖啡宝贝是因为它象征着驰名咖啡品牌Peet’s Coffee并且深受欢送的Baristas咖啡。 次主版本号 为什么叫做次主版本号?是因为接着魔数的前面的位数第5、6个字节被称为次版本号,第7、8个字节是主版本号。Java的版本号是从45开始的,JDK 1.1之后的每个JDK大版本公布主版本号向上加1(JDK 1.0~1.1应用了45.0~45.3的版本号)。高版本反对向下兼容,然而低版本不反对向上兼容,哪怕代码截然不同。《Java虚拟机标准》 例如,JDK 1.1能反对版本号为45.0~45.65535的Class文件,无奈执行版本号为46.0以上的Class文件,而JDK 1.2则能反对45.0~46.65535的Class文件。目前最新的JDK版本为13,可生成的Class文件主版本号最大值为57.0。 上面是书中给出的具体案例: 上面是一个JDK版本号对应参考图: 从JDK 9开始,Javac编译器不再反对应用-source参数编译版本号小于1.5的源码 起因:JDK9的模块化,以及扩大类加载器的改变。 常量池 留神:常量池的入口须要搁置一个U2(16进制的F)类型的数据,用于记录常量池的容器计量值 主次版本号之后就是常量池的内容了,能够间接看做是class文件的资源仓库,同时是class文件构造关联最多的数据局部,也是最大的数据项之一。光靠这一篇文章必定是无奈讲完的,同样即便讲完了也记不住,所以这一部分咱们只须要把握寄存的内容即可。 寄存内容 常量池中次要寄存两大类常量:字面量(Literal)和符号援用(Symbolic References)。 字面量:比方文本字符串,final常量 符号援用则寄存如下内容: 模块导出或者凋谢包类与接口全限定名称字段与描述符办法句柄和类型动静调用点和动静常量常量表 常量池的每一个常量都是一个表,最后只有11种构造,起初扩大出4种和动静语言相干的常量,为了模块化:退出CONSTANT_Module_info和CONSTANT_Package_info两个常量,最终就是11+4+2 = 17种常量,所以到JDK16版本为止有17种常量。批准,记是记不住的,咱们也不须要记住: 下面的表重点关注:CONSTANT_Dynamic_info和CONSTANT_InvokeDynamic_info 上面咱们挑重点看一下这些常量对于JDK的影响。 ...

August 19, 2021 · 1 min · jiezi

关于jvm:JVM性能提升50聊一聊背后的秘密武器Alibaba-Dragonwell

简介: 你要晓得的对于Alibaba Dragonwell一些重要优化措施。 往年四月五日,阿里云凋谢了新一代ECS实例的邀测[1],Alibaba Dragonwell也在新ECS上进行了极致的优化。相比于之前的dragonwell_11.0.8.3版本,行将公布的dragonwell_11.0.11.6在SPECjbb2015[2] composite模式测试中,零碎吞吐量max-jOPS晋升55%,响应工夫束缚下的零碎吞吐量critical-jOPS晋升602%。 如下图所示,图中数据做了归一化解决,以11.0.8.3_GA的critical-jOPS为1个基准单位。测试环境:阿里云80核,256g内存ECS实例,操作系统为Alinux3 [3]。 Alibaba Dragonwell 过来的十几年中,Java在阿里巴巴外部迅猛发展。阿里内应用Java语言编写的利用越来越多,数万的Java开发者每年产出超过十亿行Java代码,这些代码都运行在阿里巴巴外部的OpenJDK定制版AJDK上。 Alibaba Dragonwell是AJDK的开源版[4](github链接见文章开端),应用和OpenJDK一样的License,并永恒收费。Alibaba Dragonwell有8和11两个版本,于2019年开源,过后仅反对x86-64架构,在2020年扩大到AArch64平台。 Alibaba Dragonwell联合阿里在线电商、金融、物流等各个业务场景做了大量粗疏优化,增加了协程/多租户/Jwarmup等诸多自研个性,并且在阿里云超大规模的服务器集群上禁受了长时间大规模的验证。 调优计划与工具因为SPECjbb2015动辄就须要两个小时能力失去一次残缺的跑分分数,为了压迫性能调优单位工夫内咱们所能取得的信息量和性能试验的效率,咱们开发了自动测试平台和性能剖析工具来辅助SPECjbb2015性能调优,并且这套办法能够用在将来更多相似的性能调优案例中。 自动测试平台能够主动发动测试,并且在测试过程中调用基于perf的性能剖析工具来采集CPU微架构数据以及零碎热点数据,从而收集到每次试验过程中的要害性能数据,并将数据存档以可视化界面的模式展示,不便将来回顾和剖析。 同时为了防止SPECjbb2015单次试验耗时长影响效率,跑性能试验时咱们采纳了SPECjbb2015非凡的PRESET模式。该模式下能够指定压力指定工夫来启动性能测试,不仅不便调优零碎进行性能采集,还能够察看在肯定压力下SPECjbb2015的零碎热点和微架构数据状况。 咱们通过该套调优零碎获取到了Alibaba Dragonwell和其余JDK在跑SPECjbb2015时的热点和微架构数据,并且发现了诸多优化机会,如在GC热点和暂停工夫上有较为显著的问题,从而深刻到相干代码,并以性能数据为线索解决了相干的性能问题,具体的技术细节将在下文中向大家一一道来。 GC暂停工夫优化这项优化源于一个出乎意料的发现,在SPECjbb2015中GC暂停工夫居然超过了总运行工夫的20%,并且稳固复现。 通过上一大节中提到的调优零碎,定位到出问题的是一个GC工作队列相干函数,并且明确的指向了原子Compare and Swap(CAS)相干代码。 新ECS采纳的CPU架构中CAS次要有如下的两种实现形式: 应用带load-aquire和store-release语义的指令对的实现形式LSE指令集中的CAS专项指令 少数JVM在GC中应用第一种办法,然而第二种在高抵触的状况下性能更加杰出,因而Dragonwell扭转了编译形式,应用LSE指令集实现CAS,无效的缩小了暂停工夫。下图展现了优化成果,咱们采集了SPECjbb2015运行在不同核数上的GC数据,并采纳吞吐量作为掂量GC性能的指标。 吞吐量 = (运行工夫 – Stop-The-World工夫)/运行工夫 * 100% 咱们能够看到优化前的CAS形式会造成吞吐量随应用的核数减少而激烈降落,在80核的状况下甚至有余80%,而应用LSE CAS后吞吐量稳固在99%以上。 对这个优化的另外两点补充阐明: 此改变只针对JVM外部的CAS实现,不包含JIT(Just-In-Time)生成的代码。JIT会动静查看硬件个性,在反对LSE指令集的零碎上会优先应用LSE指令集。除了应用LSE CAS外,扭转GC队列算法缩小CAS也能够达到缩小暂停工夫的成果,OpenJDK社区在新版本中采纳了这种办法。不过两种方法并不抵触,Alibaba Dragonwell同时采纳了两种优化,达到了最优成果。疾速序列化Alibaba Dragonwell在保障兼容性根底上对java原生序列化进行了优化,通过缓存大幅提高了性能。通过剖析发现, 原生序列化瓶颈大多在于大量的class 查找,如在反序列化时须要获取对端类定义的元信息等。引入了一层通过类全限定名和类加载器映射到java类对象的缓存,缩小了大量Class.forName的调用。 具体做法:在反序列化时获取到类描述符,再依据类描述符查找信息时将会受限从classCache中查找,命中则立刻返回,如果没有找到以后classloader和类全限定名惟一指定的类对象,将会走默认的类查找流程并且将后果缓存。同时, 在反序列化时会大量调用latestUserDefinedLoader 来查找首个用户定义的类加载器,因为此过程较重(波及一次JNI调用和爬栈)也进行了缓存。 指令交融指令交融是指将多个指令应用效率更高的一条或者几条指令进行替换从而进步性能。 Dragonwell对内存屏障/内存读写/比拟跳转等多个场景做了优化,因为篇幅限度而且此类优化原理较为相似,在此仅举一例,三条指令交融成一条,如下图所示。 下面介绍了Alibaba Dragonwell外部的一些优化,上面咱们换一个角度,从参数调优方面介绍对SPECjbb2015的优化。 大内存零碎开启压缩指针SPECjbb2015是一个内存敏感型的测试,压缩指针对SPECjbb2015分数的晋升非常明显。不过默认状况下应用压缩指针最大只能用32g内存,这对80核的零碎来说切实是太小了。其实通过适当的参数组合,咱们齐全能够在更大的内存中应用压缩指针。 首先咱们理解下压缩指针的基本原理。如上图所示,因为Java对象有明确的对齐要求,因而对象的地址必然由数个0结尾,0的个数由对齐位数决定。省略java对象地址结尾的数个0可解决内存而且不会失落无效地址信息,须要拜访对象时能够通过补0取得残缺的地址。 由此可知,咱们能够通过调整Java对象对齐位数管制压缩指针失效的最大内存。默认状况下Java为8字节对齐(3bit),加上压缩指针自身的的32bit,最多只能示意32g内存。但如果调整为32字节对齐,那么有37bit能够应用,也就是128g,这对于80核来说基本上够用了。 分层编译调优分层编译是JVM最根底的机制之一,个别状况下对它改变比拟少,不过在SPECjbb2015的场景下,在分层编译上仍有调优空间。首先介绍下分层编译。JVM在运行的时候动静的将字节码编译成机器码执行,JVM(hotspot)外部编译引擎次要有三个: 解释器:无编译开销,但解释执行效率很低。C1编译器:编译开销较低,生成代码品质个别。C2编译器:生成代码品质很高,但编译开销很高。这三个编译引擎相互配合,执行次数较少的代码由解释器和C1负责,C2只编译热点代码,从而让Java能够达到峰值性能与编译开销的均衡,使利用运行更加平滑。 不过分层编译也有本人的毛病,一个较为显著的问题是它会增大生成代码的总量。下图展现SPECjbb2015运行时C1/C2编译办法数目。 图中Level1-3均为C1编译,依据收集运行信息的力度不同分为了三个等级,Level4为C2编译。咱们能够看到C1编译了70%的办法,因而敞开分层编译,仅保留C2编译器能够缩小生成代码,从而肯定水平上进步高速缓存和叶表命中率。 对于SPECjbb2015来说,因为分数只取决于最初几分钟的峰值解决能力,后面大略两个小时的申请俯冲阶段都能够视作预热,因而启动期的编译开销并不要害。咱们能够敞开分层编译来缩小生成代码,进步高速缓存和列表命中率。最终在测试中发现敞开分层编译生成代码总量由29M升高到9M,有显著缩小。 本文总结了Alibaba Dragonwell的一些重要优化措施,请留神阿里承诺会继续的优化Dragonwell性能,同时更严密地和OpenJDK等开源社区合作,奉献更多的定制化个性,促成Java技术的继续倒退。 援用 [1] https://www.aliyun.com/daily-...[2] SPECjbb2015是一款模仿电商利用的权威基准测试程序,蕴含了购买下单、折扣优惠、库存计算、客户数据存储与剖析等典型电商利用行为:https://www.spec.org/jbb2015/[3]Alinux3:Alibaba Cloud Linux是阿里云推出的Linux发行版,它为云上应用程序环境提供Linux社区的最新加强性能,在提供云上最佳用户体验的同时,也针对阿里云基础设施做了深度的优化https://help.aliyun.com/docum...[4] Alibaba Dragonwell是阿里巴巴开源JDK:https://github.com/alibaba/dr... ...

August 17, 2021 · 1 min · jiezi

关于JVM:面试知识点学习4反射机制类加载时的内存布局

4 反射机制、类加载时的内存布局4.1 学习材料——B站韩顺平【韩顺平讲Java】Java反射专题 -反射 反射机制 类加载 reflection Class 类构造 等_哔哩哔哩_bilibili 4.2 动态加载/动静加载动态加载:编译时加载相干的类,如果没有则报错,依赖性强动静加载:运行时加载须要的类,如果运行时不必该类,即便不存在该类也不报错,升高了依赖性。 反射是动静加载,编译时不会报错,只有当代码执行到相应的代码时才会报错 4.3 类加载机会(只有反射是动静加载) 4.4 类加载过程 4.4.1 加载将字节码从不同的数据源(可能是class文件,也可能是jar包,甚至是网络)转化为二进制字节流加载到内存中(办法区中),并生成一个代表该类的java.lang.Class对象。 4.4.2 连贯—验证 4.4.3 连贯—筹备==动态变量==调配内存(办法区中调配),默认初始化(不论给什么值,先依照对应的数据类型的默认值初始化,如0、0L、null、false等); 筹备阶段代码示例:(n2在初始化时赋值为20) 4.4.4 连贯—解析虚拟机把常量池中的符号援用替换成间接援用; 假如A类援用B类,编译时应用的符号来援用,比方1援用2;解析后有内存地址,变成了A的地址援用B的地址。 4.4.5 初始化显式(指定)初始化,程序员可管制; 代码示例: 剖析: 1.加载B类,生成B的Class对象;2.链接:num=0;3.初始化:顺次收集类中所有动态变量的赋值动作和动态代码块中的语句: clinit(){ System.out.println(“B动态代码块被执行”); num=300; num=100;}进行合并:num=100补充: 如果main函数是这样的: public static void main(String[] args) { System.out.println(B.num);}此时间接应用类的动态属性,则不执行结构器办法,也会有类的加载,输入为: B动态代码块被执行100 如果main函数是这样的: public static void main(String[] args) { new B(); System.out.println(B.num);}输入为: B() 结构器被执行B动态代码块被执行100

August 7, 2021 · 1 min · jiezi

关于JVM:面试知识点学习3双亲委派模型

3 双亲委派模型3.1 三层类加载器启动类加载器拓展类加载器应用程序类加载器(如果应用程序中没有自定义过本人的类加载器,个别状况下这个就是程序中默认的类加载器)。3.2 双亲委派模型图 JDK 9之前的Java利用都是由这三品种加载器互相配合来实现加载的,如果用户认为有必要,还能够退出自定义的类加载器来进行拓展。 组合:就是在一个类中援用另一个类作为成员 3.3 工作过程及益处 3.4 双亲委派模型的源码

August 7, 2021 · 1 min · jiezi

关于jvm:类的加载过程面试必问

类的加载过程loading 加载 通过双亲委派机制进行加载。次要出于平安的思考。父加载器不是加载器的加载器,也不是父类加载的加载器。 linking 链接verification 验证preparation 筹备 动态变量赋默认值,private static int test =10; 在这个阶段只是 test赋默认值0,而不是10。resolution 解析 将类、办法、属性等符号援用解析为间接援用。常量池中的各种符号援用解析为指针,偏移量等内存地址的间接援用。initalizing 初始化  private static int test =10在这一步才会被赋值成test=10类的加载过程public class T002_ClassLoaderLevel {    public static void main(String[] args) {      // 由顶层类 Bootstrap加载      System.out.println(String.class.getClassLoader());      System.out.println(sun.net.spi.nameservice.dns.DNSNameService.class.getClassLoader());      System.out.println(T002_ClassLoaderLevel.class.getClassLoader());      System.out.println(sun.net.spi.nameservice.dns.DNSNameService.class.getClassLoader().getClass().getClassLoader());      System.out.println(T002_ClassLoaderLevel.class.getClassLoader().getClass().getClassLoader());      System.out.println(new T006_MSBClassLoader().getParent());      System.out.println(ClassLoader.getSystemClassLoader());    }}每个类被加载后都会产生一个对应的class对象,咱们能够通过class对象去获取。输入后果为: nullsun.misc.Launcher$ExtClassLoader@5e2de80csun.misc.Launcher$AppClassLoader@18b4aac2nullnullsun.misc.Launcher$AppClassLoader@18b4aac2sun.misc.Launcher$AppClassLoader@18b4aac2咱们先看了一下String的ClassLoad,String是属于外围类库,应用顶层类加载器Bootstrap加载,所以打印为null,sun.net.spi.nameservice.dns.DNSNameService的ClassLoad是ExtClassLoader,而后咱们通过System.out.println(sun.net.spi.nameservice.dns.DNSNameService.class.getClassLoader().getClass().getClassLoader());获取DNSNameService的加载器的加载器也就是ExtClassLoader的在加载器打印也为null,也就是说ExtClassLoader的加载器是Bootrap。各种加载器的层级关系如下图 一个类被加载到内存中的过程是先依照右边的箭头方向以此判断是否曾经被加载,如果到了Bootstrap发现没有被加载,则会依照有测箭头方向顺次向下执行加载操作,如果咱们有个自定义加载器的类第一次被加载,过程为:去查看CustomClassLoad是否曾经加载=否>查看AppClassLoad=否>查看ExtensionClassLoad=否>查看Bootstrap=否>Bootstrap尝试加载,不是外围类库=>ExtensionClassLoad加载=没有找到>AppClassLoad加载=没有找到>CustomClassLoad加载胜利加载或者抛出异样ClassNotFoundException,咱们通常说 Bootstrap是所以加载器的父类,custom ClassLoad是底层加载器,是不是意味着下面的图就是依照Bootstrap=>Extension=>APP=>Custom ClassLoad的继承关系过去的呢?答案是否定的,下面的关系并不是依照继承的关系来的。他们是依照下图的形式继承的。比方CustomClassLoad的父加载器是AppClassLoad,是CustomClassLoad的成员变量中保留了AppClassLoad。 问:为什么要应用双亲委派机制?次要是为了平安。如果所有的加载操作都应用一个类加载,我就能够本人自定义一个外围类库比方String去笼罩jdk提供的String,咱们当初应用双亲委派机制,每次加载一个类都会先向查看是否曾经被加载如果曾经被加载间接返回。同时下层曾经加载实现上层就不须要加载了。 上面通过一个小程序查看一下不同的加载器别离加载了哪些货色。 public class T003_ClassLoaderScope {    public static void main(String[] args) {        String pathBoot = System.getProperty("sun.boot.class.path");        System.out.println(pathBoot.replaceAll(";", System.lineSeparator()));        System.out.println("--------------------");        String pathExt = System.getProperty("java.ext.dirs");        System.out.println(pathExt.replaceAll(";", System.lineSeparator()));        System.out.println("--------------------");        String pathApp = System.getProperty("java.class.path");        System.out.println(pathApp.replaceAll(";", System.lineSeparator()));    }}输入后果为: /Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/jre/lib/resources.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/jre/lib/rt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/jre/lib/sunrsasign.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/jre/lib/jsse.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/jre/lib/jce.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/jre/lib/charsets.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/jre/lib/jfr.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/jre/classes--------------------/Users/yanghongxing/Library/Java/Extensions:/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/jre/lib/ext:/Library/Java/Extensions:/Network/Library/Java/Extensions:/System/Library/Java/Extensions:/usr/lib/java--------------------/Users/yanghongxing/Library/Application Support/Code/User/workspaceStorage/7172d33d4189eaa64607305e947a5428/redhat.java/jdt_ws/src_28ac311/bin如何实现一个自定义类加载器首先搞清楚一件事,如果咱们要加载一个类怎么写?咱们间接调用AppClassLoad的loadClass()办法即可 public class Test {    public static void main(String[] args) throws ClassNotFoundException {  //执行加载操作  Class clazz = Test.class.getClassLoader().loadClass("com.yhx.test.Test1");                System.out.println(clazz.getName());    }}上面咱们看一下加载过程的源码: 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;        }  .....    protected Class<?> findClass(String name) throws ClassNotFoundException {        throw new ClassNotFoundException(name);    }      }先开始通过findLoadedClass(name)查看是否曾经加载过,返回null则没有被加载,调用父类的findLoadedClass(name)查看办法返回,这一部分就相当于咱们之前的双亲委派的加载示意图的右边的箭头,查看是否被加载,如果全部都是未加载,开始执行示意图的右半局部,   long t1 = System.nanoTime(); c = findClass(name);调用findClass这个办法,然而这个办法就间接抛出异样。(所以咱们再实现自定义加载器只有继承ClassLoader重写其中的findClass办法即可)抛出异样会被子类捕捉,子类就会反复执行find操作。 public class Test extends ClassLoader {    public static int seed = 0B10110110;    @Override    protected Class<?> findClass(String name) throws ClassNotFoundException {        File f = new File("/Users/yanghongxing/Downloads", name.replace('.', '/').concat(".testclass"));        try {            FileInputStream fis = new FileInputStream(f);            ByteArrayOutputStream baos = new ByteArrayOutputStream();            int b = 0;            while ((b=fis.read()) !=0) {                baos.write(b ^ seed);            }            byte[] bytes = baos.toByteArray();            baos.close();            fis.close();            // 这个办法能够把二进制流转换成class对象            return defineClass(name, bytes, 0, bytes.length);        } catch (Exception e) {            e.printStackTrace();        }        return super.findClass(name); //throws ClassNotFoundException    }  }编译器解释器  - bytecode inpetreter将字节码解释为操作系统能辨认的机器码。即时编译器 JTT -Just In Time Compiler,建字节码编译成机器码。 Java默认应用混合模式,起始阶段应用解释执行,运行时采纳热点代码检测,屡次被调用的办法(办法计数器:检测执行频率)屡次调用的循环(循环计数器:检测循环调用的频率)应用参数 -Xmixed 混合模式 -Xint 解释模式 -Xcopm 编译模式 面试题上面的代码执行后果是怎么样的?public class Test {    public static void main(String[] args) {        System.out.println(T.count);    }}class T {  public static T t = new T(); // null    public static int count = 2; //0    //private int m = 8;    private T() {        count ++;    }}后果为2. 咱们剖析一下类T的加载过程,类在在加载时,加载给1.加载,2.链接,2.1校验,2.2筹备,给动态成员变量赋初始值T t =null,int count=0。2.3解析,将类、办法、属性等符号援用解析为间接援用,T t指向new T()并执行t的构造方法,此时执行 count++,count=1。3初始化,count=2。输入2。 思考:上面的代码执行后果如何?public class Test {    public static void main(String[] args) {        System.out.println(T.count);    }}class T {   public static int count = 2;   public static T t = new T();        //private int m = 8;    private T() {        count ++;    }}

August 3, 2021 · 1 min · jiezi

关于jvm:JVM琐碎知识

1. 线程平安点 safe point线程在safe point上时,能够平安得被其余JVM线程所操作和观测,不在的则不行;线程平安点: 办法返回之前调用某个办法之后抛出异样的地位循环的开端

August 3, 2021 · 1 min · jiezi

关于jvm:深入理解JVM-阶段总结与回顾一

深刻了解JVM - 阶段总结与回顾(一)前言 这篇文章不写新内容,而是回顾之前的文章内容。 为什么开设专栏 开设这个专栏的目标毫无疑问是给集体的成长做一个记录和归档,因为这段时间下来发现学货色肯定要零碎并且有目标循序渐进的学才有更快的成长,JVM的内容和细节是学不完的,所以要分明学这个货色的作用是什么很要害,集体学这个货色无非就是为了面试以及理解底层原理,同时本着书到用时方恨少的准则编写专栏。 系列文章阶段总结: 专栏地址:深刻了解JVM虚拟机 第一篇:深刻了解JVM虚拟机 - JVM的初步理解 系列的开篇,在第一篇专栏咱们须要理解JVM到底是个什么货色,他对于JAVA开发者而言的重要意义,同时咱们编写的代码是如何通过JVM运行并且实现咱们想要的成果的,重点在于JAVA加载到JVM的工作流程。 这里也讲述了JVM的双亲委派模型和Tomcat的双亲委派模型是如何突破他的,类加载器也是JVM的一个核心内容。 第二篇:深刻了解JVM虚拟机 - 虚拟机的倒退历史 根本就是《深刻了解JVM虚拟机》的笔记了,当然很多博客也介绍了。 第三篇:深刻了解JVM - 分代的基本概念 讲述了JVM的传统分代模型的特点,以及新生代和老年代的划分,重点是新生代如何进入老年代,须要的条件以及相干的参数设置也是面试高频点。最初介绍了JVM的调优参数。 第四篇:深刻了解JVM虚拟机 - jvm的对象调配策略 这篇文章集体认为值得关注的点是理论的JVM测试后果和书外面不一样!同时能够发现新版本当中的Parnew会在间断调配大对象的时候让对象间接进入老年代,真的很奇怪!还是倡议学这些货色肯定本人尝试,花点工夫换来的收益是你动向不到的! 第五篇:深刻了解JVM - 垃圾回收算法 和题目一样,介绍JVM的垃圾回收算法以及新生代老年代是如何使用算法进行回收的,须要留神的是这些垃圾算法没有好坏之分,所有看理论垃圾收集器开发者的设计理念。比方CMS还是典型的标记-革除,然而到了G1就是标记整顿了。 第六篇:深刻了解JVM - CMS收集器 讲述CMS收集器的手机细节,以及CMS的好搭档ParNew的一些解决细节,CMS+ParNew置信还是少数公司的首选垃圾收集器。毕竟不是谁都有那么宏大的用户量或者大我的项目这种。 第七篇:深刻了解JVM - 实战老年代优化 无需多介绍,内容和题目相似。 第八篇:深刻了解JVM - G1收集器 性能和应用看似都非常简略,然而内含的原理十分复杂,次要也是解说G1收集器的一些性能和细节点。 同时是否须要钻研原理这就看集体需要了,当然多懂点总是坏事。 第九篇:深刻了解JVM - G1调优简述 感觉齐全是水了一篇,能够不看,因为集体目前没遇到须要用上G1去调优的场景(=-=) 相干探讨回收的说法我混同了? Minor gc: 中文翻译是主要GC,主要这两个字很容易混同,然而少数状况是新生代回收 ...

July 24, 2021 · 1 min · jiezi

关于jvm:深入理解JVM-解读GC日志

深刻了解JVM - 解读GC日志前言 这次的文章会依据实战来介绍如何看JVM的日志,看JVM日志说难也难,说容易也容易,更多的是须要工夫去一直的尝试进行总结。 另外,因为代码的理论运行成果在不同的机器是不一样的!这篇文章应用的是jdk1.8.0_221 的版本,具体的系统配置查看: 概述: 次要内容还是以解说如何浏览日志,同时不同的机器运行的后果不同,文章更多的是介绍如何解读参数: 参数配置案例 配置介绍: 新生代5M最大新生代内存5M初始化堆内存大小10M最大堆内存大小10M新生代eden区域大小,或者说survior配比:8 代表 8:1:1应用ParNew+CMS收集器实际操作:不执行任何代码测试:参数配置: -verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8 -XX:+UseSerialGC 参数的含意: 这里给堆调配了20M的内存空间,新生代调配10M,同时打印GC信息,新生代的分配比例为8:1:1,最初应用serrial收集器。留神是serrial收集器。 代码配置 先从最简略的办法开始,咱们运行一个没有任何代码的Main办法 public class MinorGcTest { public static void main(String[] args) { }}留神上面的内容是运行一个空Main办法的日志内容。 Heap def new generation total 9216K, used 3977K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000) eden space 8192K, 48% used [0x00000000fec00000, 0x00000000fefe27f0, 0x00000000ff400000) from space 1024K, 0% used [0x00000000ff400000, 0x00000000ff400000, 0x00000000ff500000) to space 1024K, 0% used [0x00000000ff500000, 0x00000000ff500000, 0x00000000ff600000) tenured generation total 10240K, used 0K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000) the space 10240K, 0% used [0x00000000ff600000, 0x00000000ff600000, 0x00000000ff600200, 0x0000000100000000) Metaspace used 3241K, capacity 4496K, committed 4864K, reserved 1056768K class space used 349K, capacity 388K, committed 512K, reserved 1048576K解释: ...

July 24, 2021 · 3 min · jiezi

关于jvm:深入理解JVM-G1调优简述

深刻了解JVM - G1调优简述前言 G1收集器是一个不太好调优的收集器,因为他不能像固定分代的收集器那样能够本人想划分多少就划分多少,更多的调配动作是由收集器动作,因为region是一块块的,同时主动增长也是由G1管制,如同的确不太好调优。 这篇文章更多的是提供调优的一个大抵方向,更多的内容须要后续介绍JVM的工具进行解说。 案例实战 这次应用一个在线的教育平台作为案例解释G1是如何进行优化的。 在线教育平台的压力来自于哪里?首先孩子白天须要上学同时家长也须要下班,所以白天的访问量不会很大,同时次要的业务也不在在线教育平台解决。然而一旦到了早晨,机器的压力就上来了,同时孩子也会在线进行听课上课,这时候用户量会暴增,会有上万人同时在线听课。这时候能够发现在线教育平台的压力在于直播,而直播的流量顶峰在于课堂的互动环节,为什么是互动环节呢,因为孩子不喜爱干燥的课堂,为了带动课堂气氛,课堂中的游戏肯定是沉闷氛围的要害,也是零碎压力的外围,这时零碎须要记录各种数据,比方流动时长,得分,积分处分等等,同时也会呈现大量的对象调配,为了保障直播的晦涩,零碎要求非常低的提早响应工夫。 所以最终的论断是:在线教育平台大略在直播的互动环节压力会倍增,零碎要求非常低的提早响应工夫。 通过了下面的状况剖析,咱们假如单台机器每秒大略有600个申请,假如每一个申请占用10KB,则是6000KB的大小占用也就是最终6M左右的内存占用。同时部署在一个4外围8G的零碎下面。 同样的,这种案例也只是模仿和假如,具体情况受到各种因素的限度,切勿过于深究细节。如何剖析零碎传统的分代概念 咱们用传统分代的概念部署一下这个零碎,依据每一秒的申请为6M的对象大小,同时依据零碎4外围8的配置,那么给JVM的内存大略是4G,咱们晓得直播的互动环节产生的积分,处分,计算等对象根本都是朝生夕死的小对象,所以咱们不太须要给老年代过大的空间,所以进来办法区和虚拟机线程栈的内存,咱们给大概会给新生代3G和老年代1G左右的内容。 如果每秒产生6M的对象,那么一分钟就是300多M,依照默认的新生代配比8:1:1则Eden区域大略为2.4G的大小,survior区域为两个300M的大小空间。一分钟300M,那么基本上8分钟左右新生代就会满,此时假如有300M左右对象存活进入Survior区域,这时候Survior区域尽管能够装的下,然而因为超过了50%的配比,最终还是有约150M的对象进入老年代。 这时候再推算,老年代每8分钟进入150M的对象,大概40分钟左右就会整个零碎进展一次,这个进展工夫还是乐观预计,因为零碎不可能只运作这一块的内容,单单是推算直播互动这一块就会产生这样的成果,可想而知加上整个零碎的其余模块,实际上5、6分钟可能会进展一次!!!这样必定是不行的,试想下你玩游戏隔几分钟就停一下,对于小孩子来说忽然卡一下导致丢分最初问题不佳孩子又哭又闹,家长这时候不必想必定会大量的投诉,被骂也不远了...... 应用G1收集器 咱们接着应用G1的收集器进行替换,零碎部署在一个4外围8G的零碎下面,假如机器在JVM上调配4G给堆,新生代默认初始化比例为5%,最大占比为60%,JAVA线程堆栈为1M,则大概开启几百个线程须要200,300M的空间,而办法区占用256M够用。 在上一节在文章中介绍过,能够把G1的工作机制设想成开盘子,然而放到零碎上就很头疼了,G1什么时候回来收垃圾是咱们无奈预测的!!这里就须要通过一些辅助伎俩,同时这部分的监控操作须要工具和日志进行解读,所以将会放到后续的专门一篇文章进行解读。 如何计算Region的占用和大小: 依照4096M/2048 每个region是2M,如果依照新生代初始为5%,则依据参数5%新生代的大小为100个Region左右,4G的内存机器后果能够得出新生代初始200M左右的内存占用大小 至关重要的参数: -XX:MaxGCPauseMills 参数:默认值为200,代表了200MS,示意最大的进展工夫为200MS。 如果应用G1收集器,这个参数间接影响了整个JVM零碎的性能,如果这个数值过大,会导致垃圾收集的工夫过长而导致前台卡顿,也容易导致新生代来不及触发垃圾回收就满了,或者导致老年代内存无奈及时的回收。 多久会触发新生代回收操作 依据之前的阐明,新生代最大能够应用60%的空间,同时也阐明了新生代应用复制算法,依据8:1:1的规定,大略达到新生代的80%左右就会触发垃圾的回收操作?这种做法显然不合乎G1基于全堆以及混合回收的操作。所以不能用固定大小的回收思路思考g1的回收操作。 正确做法: G1会依据200MS的要求,定时去断定以后的新生代是否能够合乎200MS的收集操作要求,粗心就是当新生代的垃圾回收须要耗时200MS的时候,就会触发新生代的回收。 这里也能够间接依照之前的了解餐厅的服务员定时过去开盘子的操作了解新生代多久进行一次回收操作。 如何优化: 下面的探讨几点之后,这就头疼了,这要这么优化?这时咱们须要用上一些压测的工具以及GC日志和内存剖析工具来思考了,然而也不要让GC挺多进展工夫预设值太大了导致GC进展工夫太长,应该给个正当的值。 Mixed gc如何优化? 既然新生代的优化都曾经很麻烦了,更不用说老年代回收了。而老年代的回收自身也没有了Old GC,取而代之的是Mixed GC,所以须要小心看待,咱们从根本上还是须要避免让对象进入到老年代一直扩大导致mixed gc,这更加须要关注工夫进展模型这个参数。 最终剖析 新生代: 因为是复制算法,所以须要从根本上解决的话仍然须要管制新生代的存活对象进入survior的大小,同时管制在50%以内。尽量避免GC之后对象间接进入老年代。另外60%的新生代空间通常也不必怎么调整,除非业务对象频繁创立新生代会产生大量对象才须要思考。新生代有一个参数是存活对象大于85%的时候不须要进行拷贝,这个值如果设置小一点可能会进步回收效率,然而有可能造成大量的长寿对象进入老年代的危险。 老年代: 依照G1的最初一个步骤,垃圾回收和零碎回交替8次,同时在回到5%的region的时候进行收集,这个参数其实能够适当调大一些:G1HeapWastePercent45%的老年代占用触发垃圾回收的机制,这个参数也不须要大改,因为JDK设置这个参数必定是通过了很多测试和考量之后的后果。总结 这一篇更多的是提供优化思路,JVM调优没有万金油的解决方案,特地是G1收集器这种算法细节十分复杂的收集器,调优须要更多的精力和工夫测试调优成果。 写在最初 下一篇章会做一个整个系列到目前为止的大节,温故而知新,人的遗忘曲线更加须要咱们重复的坚固常识和内容。

July 23, 2021 · 1 min · jiezi

关于jvm:深入理解JVM-CMS收集器

深刻了解JVM - CMS收集器前言 上一节咱们解说分代和垃圾回收算法,这一节咱们来解说老年代重要的垃圾收集器:cms收集器。这一节的内容同样比拟多。 这一节次要围绕着非常罕用的CMS垃圾收集器进行解说。 前文回顾 上一篇文章咱们解说分代的基础理论,同时解说了新生代和老年代各自的算法复制算法和标记整顿算法,之后咱们总结了新生代进入老年代的条件,在最初咱们介绍的援用类型,同时进行了练习的发问和相干的解答。 概述讲述cms收集器之前,简略理解他的黄金搭档ParNew解说cms收集器的参数,以及外围的运行步骤局部。解说CMS收集器运行过程的一些细节以及CMS的参数的意义。整顿小局部常见的JVM问题黄金搭档ParNew 作为最罕用的新生代垃圾收集器ParNew,他和cms收集器的搭配在Jdk1.9之前是jdk官网举荐的配置,也是目前最常被用到的收集器组合。 ParNew收集器自身是Serial收集器的多线程版本。而Serial 收集器和Serial Old收集器因为过于古老这里不再进行介绍,然而并不是说他们曾经退出了历史舞台,文章前面的内容将会提到Serial收集器的关键作用。 最初,须要留神ParNew是除了Serial之外惟一能够和cms配合的垃圾收集器 特点:和Serrial只是单线程和多线程区别除了Serrial之外惟一能够和cms配合的垃圾收集器问题解答:多线程回收器和单线程回收器那个好? 通常状况下,如果是服务端通常更加倡议应用多线程收集器,而客户端则更加偏向应用单线程的收集器。因为如果是单核的机器应用多线程会带来额定的“上下文切换”的操作,性能不会晋升反而会降落。同时客户端少数状况下对于多线程的要求并不是很高,所以客户端更加举荐应用单线程。 Serial 和 ParNew那个 回收器要好? 和下面的问题一样,要依据应用的机器是多核还是单核来决定。当然少数状况下会应用多线程,因为古代处理器的多线程技术曾经非常成熟。 剖析: 解答下面的问题首先咱们要弄清楚什么是客户端模式,什么是服务端模式,客户端模式中,-client 代表了客户端的所需参数,而 -server 则是服务器须要的运行参数。 服务端模式:通常实用于多外围的环境,比方对于多线程垃圾回收具备高效利用的Parnew。 客户端模式:如果是单核性能较差的机器适宜应用,因为客户端模式通常运行单核,适宜Serial收集器,因为他是单线程的,没有线程切换的开销 CMS回收器 jdk9之前老年代最常应用的垃圾回收器,次要应用标记-革除算法(不齐全应用标记革除算法)。为了保障运行的效率,cms会采纳用户线程以及垃圾收集线程并发执行的形式进行解决,也是首款反对用户线程和垃圾回收线程并发的垃圾收集器。 之前的文章探讨过,标记革除算法会产生大量的内存碎片,为什么还要应用标记-革除算法呢? 其实cms会依据一个零碎参数断定多少次垃圾回收之后执行整顿动作,而这个动作须要停下以后所有的用户线程,并且开启单线程Serial收集器对于老年代的内存碎片进行整顿,而这里的整顿就是应用的标记-整顿。 然而通常状况下cms应用的还是标记-革除的动作。 CMS收集器特点:不能独自应用,须要和其余收集器配合,并且只能和Serrial、ParNew这两个收集器配合为了保障运行的效率,cms会采纳用户线程以及垃圾收集线程并发执行的形式进行解决。也是首款反对用户线程和垃圾回收线程并发的垃圾回收器基于标记-革除的算法。侧重于最短进展工夫的一款垃圾收集器CMS主要参数:-XX:ParallelGCThreads:限度垃圾回收线程的数量,默认状况下线程数量为(cpu外围总数+ 3) / 4,比方8个核的线程为2个垃圾收集线程+UseCms-CompactAtFullCollection(jdk9开启废除):开启之后,运行每次FullGc之后内存碎片并且进行整顿的操作,而内存整理须要进行用户线程。会减少整个stop the world的工夫-XX:CMSFullGCsBefore-Compaction(jdk9开启废除):留神这个参数失效的前提是+UseCms-CompactAtFullCollection这个参数开启,用于管制多少次FullGc之后进行内存整理,默认是0次,示意每次都进行内存碎片的整顿操作。-XX:CmsInitiatingOccupancyFranction:用于限度老年代内存占用超过多少占比之后开启垃圾回收的动作。jdk5为68%,jdk6之后为92%。CMS的运行步骤(重点) cms的四个回收步骤比拟好了解,次要为四个步骤: 初始标记:这个过程非常疾速,须要 stop the world,遍历所有的对象并且标记初始的gc root并发标记:这个过程能够和用户线程一起并发实现,所以对于零碎过程的影响较小,次要的工作为在零碎线程运行的时候通过gc root对于对象进行根节点枚举的操作,标记对象是否存活,留神这里的标记也是较为迅速和简略的,因为下一步还须要从新标记从新标记:须要 stop the world,这个阶段会持续实现上一个阶段的动作,对于上一个步骤标记的对象进行二次遍历,从新标记是否存活。并发清理:和用户线程一起并发,负责将没有Gc root援用的垃圾对象进行回收。 从下面的步骤形容能够看到,cms的垃圾收集器曾经有了很大的提高,能够实现并发的标记和并发的整顿阶段做到和用户线程并发执行(然而比拟吃系统资源),不烦扰用户线程的对象调配操作,然而须要留神初始标记和从新标记阶段仍然须要进展。 初始标记 初始标记阶段:须要暂停用户线程, 开启垃圾收集线程, 然而仅仅是收集以后老年代的GC ROOT对象,整个运行过程的速度十分快,用户简直感知不到。 这里须要留神的是哪些对象会作为GC ROOT,而哪些则不会,比方实例变量不是GC ROOT的对象,同时在根节点枚举当中如果发现没有被援用也会标记为垃圾对象。 ...

July 15, 2021 · 1 min · jiezi

关于jvm:JVM面试必问G1垃圾回收器

摘要:G1垃圾回收器是一款次要面向服务端利用的垃圾收集器。本文分享自华为云社区《JVM面试高频考点:由浅入深带你理解G1垃圾回收器!!!》,原文作者:Code皮皮虾 。 G1垃圾回收器介绍G1垃圾回收器是一款次要面向服务端利用的垃圾收集器。作为垃圾回收器技术发展史上里程碑的成绩,G1垃圾回收器不同于以往的垃圾回收器,首先是思维上的转变,如下图: G1对于Java堆的划分下面的图,小伙伴们第一次看可能不咋明确,因为各位还不理解G1,看看上面的话,应该就差不多了。 G1垃圾回收器对于Java堆区域的划分不同于以往咱们对Java对区域划分的认知 以往对于Java堆区域的划分为:新生代和老年代,新生代又划分为 Eden区和 Survivor区,Survivor区又分为 from区和 to区。 然而当初,G1不再保持固定大小以及固定数量的分代区域划分,而是把间断的Java堆空间划分为多个大小相等的独立区域(Region),每个Region都能够成为 Eden空间、Survivor空间、老年代空间。 这种思维上的转变和设计,使得G1能够面向堆内存任何局部来组成回收集来进行回收,衡量标准不再是它属于哪个分代,而是哪块内存寄存的垃圾最多,回收收益最大,这就是G1收集器的 Mixed GC模式,即混合GC模式。 Region还有一类非凡的 Humongous 区域,专门用来存储大对象。G1认为只有大小超过了一个Region容量一半的对象即可断定为大对象。如果是那些超过了整个Region容量的超大对象,将会放在间断 N 个 Humongous Region区域。 Region的取值范畴为 1M ~ 32M Region的默认个数为 2048个 -XX:G1HeapRegionSize = N G1这么做看起来是由一种面目一新的感觉,但仔细的小伙伴可能曾经发现,如果 Region之间存在跨区援用对象,那这些对象如何解决? 不论是G1还是其余分代收集器,JVM都是应用 记忆集(Remembered Set) 来防止全局扫描。每个Region都有一个对应的记忆集。每次Reference类型数据写操作时,都会产生一个 写屏障(Write Barrier)临时去终止操作而后查看将要写入的援用 指向的对象是否和该Reference类型数据在不同的 Region(其余收集器:查看老年代对象是否援用了新生代对象)如果不同,通过 卡表(Card Table)把相干援用信息记录到援用指向对象的所在Region对应的记忆集(Remembered Set) 中当进行垃圾收集时,在GC Roots枚举范畴加上记忆集;就能够保障不进行全局扫描了。G1的记忆集能够了解为一个哈希表,Key就是别的Region的起始地址,Value就是卡表的索引号汇合。 因为G1将Java堆划分为一个个Region的缘故,而Region数量相比于传统分代数量显著多得多,所以G1相比于传统的垃圾回收器来说,须要耗费相当于Java堆容量 10%~ 20%的额定空间来维持收集器的工作。 G1 垃圾回收器工作流程初始标记(Initial Marking):这阶段仅仅只是标记GC Roots能间接关联到的对象并批改TAMS(Next Top at Mark Start)的值,让下一阶段用户程序并发运行时,能在正确的可用的Region中创立新对象,这阶段须要进展线程,然而耗时很短。而且是借用进行Minor GC的时候同步实现的,所以G1收集器在这个阶段理论并没有额定的进展。并发标记(Concurrent Marking):从GC Roots开始对堆的对象进行可达性剖析,递归扫描整个堆里的对象图,找出存活的对象,这阶段耗时较长,然而能够与用户程序并发执行。当对象图扫描实现当前,还要重新处理SATB记录下的在并发时有援用变动的对象。最终标记(Final Marking):对用户线程做另一个短暂的暂停,用于解决并发阶段完结后仍遗留下来的最初那大量的 SATB 记录。筛选回收(Live Data Counting and Evacuation):负责更新 Region 的统计数据,对各个 Region 的回收价值和老本进行排序,依据用户所冀望的进展工夫来制订回收打算。能够自由选择多个Region来形成会收集,而后把回收的那一部分Region中的存活对象==复制==到空的Region中,在对那些Region进行清空。除了并发标记外,其余过程都要 STW ...

July 15, 2021 · 1 min · jiezi

关于jvm:JVM面试高频考点由浅入深带你了解G1垃圾回收器

G1垃圾回收器介绍G1垃圾回收器是一款次要面向服务端利用的垃圾收集器。作为垃圾回收器技术发展史上里程碑的成绩,G1垃圾回收器不同于以往的垃圾回收器,首先是思维上的转变,如下图: G1对于Java堆的划分 下面的图,小伙伴们第一次看可能不咋明确,因为各位还不理解G1,看看上面的话,应该就差不多了。 G1垃圾回收器对于Java堆区域的划分不同于以往咱们对Java对区域划分的认知 以往对于Java堆区域的划分为:新生代和老年代,新生代又划分为 Eden区和 Survivor区,Survivor区又分为 from区和 to区。 然而当初,G1不再保持固定大小以及固定数量的分代区域划分,而是把间断的Java堆空间划分为多个大小相等的独立区域(Region),每个Region都能够成为 Eden空间、Survivor空间、老年代空间。 这种思维上的转变和设计,使得G1能够面向堆内存任何局部来组成回收集来进行回收,衡量标准不再是它属于哪个分代,而是哪块内存寄存的垃圾最多,回收收益最大,这就是G1收集器的 Mixed GC模式,即混合GC模式。 Region还有一类非凡的 Humongous 区域,专门用来存储大对象。G1认为只有大小超过了一个Region容量一半的对象即可断定为大对象。如果是那些超过了整个Region容量的超大对象,将会放在间断 N 个 Humongous Region区域。 Region的取值范畴为 1M ~ 32M Region的默认个数为 2048个 -XX:G1HeapRegionSize = N G1这么做看起来是由一种面目一新的感觉,但仔细的小伙伴可能曾经发现,如果 Region之间存在跨区援用对象,那这些对象如何解决? 不论是G1还是其余分代收集器,JVM都是应用 记忆集(Remembered Set) 来防止全局扫描。每个Region都有一个对应的记忆集。每次Reference类型数据写操作时,都会产生一个 写屏障(Write Barrier)临时去终止操作而后查看将要写入的援用 指向的对象是否和该Reference类型数据在不同的 Region(其余收集器:查看老年代对象是否援用了新生代对象)如果不同,通过 卡表(Card Table)把相干援用信息记录到援用指向对象的所在Region对应的记忆集(Remembered Set) 中当进行垃圾收集时,在GC Roots枚举范畴加上记忆集;就能够保障不进行全局扫描了。G1的记忆集能够了解为一个哈希表,Key就是别的Region的起始地址,Value就是卡表的索引号汇合。 因为G1将Java堆划分为一个个Region的缘故,而Region数量相比于传统分代数量显著多得多,所以G1相比于传统的垃圾回收器来说,须要耗费相当于Java堆容量 10%~ 20%的额定空间来维持收集器的工作。 G1 垃圾回收器工作流程初始标记(Initial Marking):这阶段仅仅只是标记GC Roots能间接关联到的对象并批改TAMS(Next Top at Mark Start)的值,让下一阶段用户程序并发运行时,能在正确的可用的Region中创立新对象,这阶段须要进展线程,然而耗时很短。而且是借用进行Minor GC的时候同步实现的,所以G1收集器在这个阶段理论并没有额定的进展。并发标记(Concurrent Marking):从GC Roots开始对堆的对象进行可达性剖析,递归扫描整个堆里的对象图,找出存活的对象,这阶段耗时较长,然而能够与用户程序并发执行。当对象图扫描实现当前,还要重新处理SATB记录下的在并发时有援用变动的对象。最终标记(Final Marking):对用户线程做另一个短暂的暂停,用于解决并发阶段完结后仍遗留下来的最初那大量的 SATB 记录。筛选回收(Live Data Counting and Evacuation):负责更新 Region 的统计数据,对各个 Region 的回收价值和老本进行排序,依据用户所冀望的进展工夫来制订回收打算。能够自由选择多个Region来形成会收集,而后把回收的那一部分Region中的存活对象==复制==到空的Region中,在对那些Region进行清空。除了并发标记外,其余过程都要 STWG1和CMS的区别G1从整体上来看是 标记-整顿 算法,但从部分(两个Region之间)是复制算法。而CMS是 标记-革除算法 所以说,G1不会产生内存碎片,而CMS会产生内存碎片CMS应用了 写后屏障来保护卡表,而G1不仅应用了写后屏障来保护卡表,还是用了 写前屏障来跟踪并发时的指针变动状况(为了实现原始快照)。CMS对Java堆内存应用的是传统的 新生代和老年代划分办法,而G1应用的全新的划分办法。CMS收集器只收集老年代,能够配合新生代的Serial和ParNew收集器一起应用。G1收集器收集范畴是老年代和新生代。不须要联合其余收集器应用CMS应用 增量更新解决并发标记下呈现的谬误标记问题,而G1应用原始快照解决最初我是 Code皮皮虾,一个酷爱分享常识的 皮皮虾爱好者,将来的日子里会不断更新出对大家无益的博文,期待大家的关注!!! ...

July 12, 2021 · 1 min · jiezi

关于jvm:每日三道面试题通往自由的道路4

茫茫人海千千万万,感激这一秒你看到这里。心愿我的面试题系列能对你的有所帮忙!共勉! 愿你在将来的日子,放弃酷爱,奔赴山海! 每日三道面试题,成就更好自我昨天既然你有讲到字符串常量池是吧,那这样吧1. 你能够讲下JVM的运行时数据区或者说内存构造吗?咱们能够分为线程公有和线程共享的两种状况 线程公有:程序计数器,本地办法栈,虚拟机栈 线程共享:堆和办法区 程序计数器:它占用了很小的一块内存空间,记录的是咱们以后线程的一个执行的行数。因为线程它可能一直的切换,如何保障到以后线程时,它执行到哪里呢,就是靠程序计数器来实现的。该内 存区域是惟一一个 Java 虚拟机标准没有规定任何 OOM 状况的区域。虚拟机栈:当jvm执行办法时,会在此区域创立栈帧入栈,它存储办法的各种信息比方局部变量表,操作数栈,动静连贯,办法放回地址这些信息。本地办法栈:它也虚拟机栈相似,然而它次要为native办法服务,例如java须要应用c语言的接口服务时。堆: 也叫 Java 堆或者是 GC 堆,它是一个线程共享的内存区域,也是 JVM 中占用内存最大的一块区域,简直所有对象都贮存在这里分配内存,也是垃圾回收期次要的治理区域。办法区:存储一些被虚拟机加载的类信息,常量,动态变量,编译器编译后的代码等数据。 不错不错,JVM都有理解,那再问你一点吧。2. 类加载过程零碎加载Class类型文件的次要步骤有加载-->连贯--> 初始化,连贯又能够分为验证-->筹备-->解析 加载:依据类的全限定名来获取类的二进制字节流,在内存中生成一个代表该类的Class对象验证:次要验证查看class文件的正确性,比方文件格式,元数据,字节码,符号援用的验证。筹备:次要就是为类变量分配内存并设置类变量初始的一个阶段。解析:虚拟机将常量池内的符号援用替换成间接援用的一个过程。初始化:它是类加载的最初一步,就是真正执行类中定义的Java程序代码的过程。能够,那问你最初一道:3. 而其中类加载器是什么,那有哪些呢?对于任意一个类,都须要由加载它的类加载器和这个类自身一起确立在 JVM 中的唯一性,每一个类加载器,都有一个独立的类名称空间。而类加载器就是依据指定全限定名称将 class 文件加载到 JVM 内存,而后再转化为 class 对象。 次要有一下四品种加载器: 启动类加载器(BootstrapClassLoader)用来加载java外围类库,无奈被java程序间接援用。扩大类加载器(ExtensionClassLoader):它用来加载 Java 的扩大库。Java 虚拟机的实现会提供一个扩大库目录。该类加载器在此目录外面查找并加载 Java 类。应用程序类加载器(ApplicationClassLoader):它依据 Java 利用的类门路(CLASSPATH)来加载 Java 类。一般来说,Java 利用的类都是由它来实现加载的。能够通过ClassLoader.getSystemClassLoader()来获取它。个别状况,如果咱们没有自定义类加载器默认就是用这个加载器。用户自定义类加载器,咱们能够自行去通过继承 java.lang.ClassLoader类的形式实现。 而对于一个类加载的过程中,如果一个类加载器收到类加载的申请的时候,它首先不会本人去加载这个类,而是把这个申请委派给本人的父类加载器去实现,始终到顶层的启动类加载器时,只有当父加载无奈实现这一加载申请时,就会往下一层一层的尝试去加载类。这种模式就是双亲委派模式,这中模式的益处能够使类有了档次划分,也保障平安。 小伙子不错嘛!明天就到这里,期待你今天的到来,心愿能让我持续放弃惊喜!注: 如果文章有任何谬误和倡议,请各位大佬纵情留言!如果这篇文章对你也有所帮忙,心愿可恶亲切的您给个三连关注下,非常感谢啦!也能够微信搜寻太子爷哪吒公众号私聊我,感激各位大佬!

June 29, 2021 · 1 min · jiezi

关于jvm:搞懂Java的Class类

搞懂Java的大Classclass Student{ String name; int age; public void learn(){ System.out.println("正在学习"); } public int getAge(){ return age; } public void hello(String message){ System.out.println(message); }}class Book{ private float price; private String author;}所以,不论是下面的Student,Book类,还是Java语言中的自带类比方String类 所有的类,都有上面的一些共性: 所有的类,都有一个类名:比方下面的 Student, Book, String类,再加上包名,如果包名是 com.test , 那么这些类的名字就是com.test.Student , com.test.Book, com.test.String所有的类,都有0个或者多个字段,比方下面的 name,age,price,author所有的类,都有0个或者多个办法,比方下面的learn()所有的类,都有修饰符,比方public,private,protected等所有的类,都有0个或者多个静态方法等等等Class到底是什么?通过下面的剖析咱们晓得,所有的类都有一些公共个性,那么咱们就定义一个类,来形容这些公共个性,咱们暂且把这个类起个类名,叫 Info 其实咱们这里的Info类,就是java中的Class,java中的Class其实就是个一般的类而已,和其它的类没什么不一样的。不信看看Class定义 Class就是一个一般的类,这个类形容的是所有的类的公共个性Class类能干什么?既然下面咱们晓得了,所有的类都是有公共个性的,咱们定义了一个叫Class的类来形容这些公共个性 那么是不是所有的类的办法,所有的类的字段,是不是也有公共的个性?答案是的 是不是有点迷糊? 不要怕,很简略,举个例子就晓得了。还是下面的Student,Book类, 所有的类的字段,都有名字。比方Book类中的price字段,它的名字是不是"price"所有的类的字段,都有类型。比方Book类中的price字段,它的类型是 float所有的类的字段,都有修饰符。比方Book类中的price字段,它的修饰符是 private所以这些类的字段公共的个性,咱们也能够定义一个类来形容,这个类就是Field咱们看一下JDK中对于Field的定义 是不是和Class类一样,Field类也是一个一般的类 同理,类中所有的办法,是不是也有公共的个性?答案:是的 所有的类的办法,都有办法名比方Student类中的getAge()办法,它的办法名是 "getAge"所有的类的办法,都有返回值类型比方Student类中的getAge()办法,它的返回值是 int类型所有的类的办法,都有参数。(无参数也是算是非凡的参数吧)比方Student类中的hello(String message)办法,有一个参数,参数的类型是值是 String类型所有类的办法,都有润饰符号。比方Student类中的getAge()办法,它润饰符号是 public所以这些类的办法的公共的个性,咱们也能够定义一个类来形容,这个类就是Method咱们看下JDK中的定义 由下面能够,那么Class类就是通过Field和Method来形容类中的字段和办法咱们来看一下JDK中Class类中,对于Field和Method的成员 这些数组就是保留类中的字段或者办法的。 Class如何应用?咱们先看一下JDK中 Class类的构造函数 就这一个构造函数,还是公有的,所以,咱们不能间接new一个对象了 ...

June 25, 2021 · 1 min · jiezi

关于JVM:JVM垃圾收集

垃圾收集算法标记-革除算法该算法次要有两个阶段:标记,革除。首先标记出所有须要回收的对象,标记实现后,回收(革除)所有被标记的对象;或标记所有不须要要回收的对象,而后回收所有未被标记的对象。毛病: 执行效率不稳固,如果Java堆中蕴含大量须要革除的对象,这是必须要进行大量的标记和革除动作,导致两个过程的执行效率随着对象的数量蒸架而升高。内存碎片化标记-复制算法将可用内存依照容量划分为两个大小统一的内存空间,每次仅应用其中一块,当一块内存用完后,将存活的对象挪动到另一块内存中,而后清理该内存。毛病: 如果有大量对象存活,在复制时会产生大量的开销可用内存放大一半,空间节约重大。标记-整顿算法首先标记存活的对象,而后依照肯定形式用存活的对象笼罩“死亡”对象所占用的内存空间以达到整顿的目标。 各个垃圾收集器的关系

June 14, 2021 · 1 min · jiezi

关于jvm:JVisualVM的使用教程

一、前言JVisualVM是一个Java虚拟机的监控工具,要是须要对JVM的性能进行监控能够应用这个工具哦 应用这个工具,你就能够监控到java虚拟机的gc过程了 那么,这么弱小的工具怎么下载呢? 在JDK1.6后的版本是自带这个工具,它就在你的jdk的bin目录上 如果是默认装置的JDK,个别就在C盘,Program Files的java目录,就会看到你的jdk版本,点进去之后关上bin这个文件夹,就能够看到这个软件了 二、启动JVisualVM启动办法: 1.进入jdk装置目录的bin目录,双击关上这个程序 2.菜单键+R,输出cmd进入命令行模式,输出命令jvisualvm 启动程序。注:要是应用命令行启动的软件,命令框可不能敞开哦,敞开了的话JVisualVM也会被敞开,切记切记!!! 启动程序之后进入这个界面,这个就是JVisualVM的应用界面了 三、装置插件应用之前,咱们须要装置一个插件,来更好的来察看虚拟机的性能,点击上方的工具-插件 在可用插件那里抉择下载,装置一个VIsual GC的插件 个别会报错,因为默认的链接曾经给转移了,须要在设置那里把默认的链接更改 点击设置,编辑,把URL更改一下 那URL填什么呢?先确定一下本人的jdk版本号,而后用以下链接去查看URL 确认版本号,能够菜单键+R,执行cmd,输出java -version来查看本人的版本号 比方我的是201 那就在这个网站 :https://visualvm.github.io/pl...找到本人版本号的地址,复制URL到设置那里 比方我的是JDK8的201,所以应该是131-291之间,所以我就复制上面那行蓝色的URL到设置的定制器中 而后就能够下载想要的插件啦 而后重启一下即可看到有visual GC这个选项了 四、应用那我先执行一个不进行的程序 后果是不停的输入1,这是个死循环,咱们再回去看看GC 第一次察看 几秒钟后察看 我把这个程序进行掉之后,最初进行察看,右边的test这个java程序就不见了,左边的GC也就停了下来 那当初就开始剖析一下这几个过程,就看最初关掉之后的那个状态,能够看到GC time是指产生了多少次的GC,图中就是产生了233次GC,就花了276.256ms的工夫,而下一行的Eden区,也是产生了223次GC,破费的工夫也是276.256ms,很显然,产生的GC都是在Eden区,Old老年代区产生了0次GC,破费0s。 这只是个一般的死循环,工作量并不大,所以占用不了多少内存空间,基本就不会产生多少次GC,也基本不须要老年代区GC 而左边的进度图,就是阐明内存应用的状况,当图中的色块达到顶端的时候,就是内存满的时候,这时候就须要进行一次GC,把内存占用推送到下一个区,满一次清理一次就GC一次 除了能够检测java虚拟机的垃圾回收,还能够监督一下该java程序CPU的占用状况,线程等等。 当咱们开发其余程序的时候,可能须要对程序做优化,就要联合这些指标来进行剖析,确定优化的办法。比如说CPU使用率始终只有很低,那就阐明CPU能够利用的内存比拟大,能够适当升高其余的耗费,晋升CPU损耗来晋升效率等等 当然,除了我提及的各种性能监控和GC过程的查看,还有其余的性能能够应用,比方性能危险,察看各过程的运行状况等等 最初,可能会有小伙伴会问,如果执行的程序不是死循环,而是一个一般的输入呢。如果执行的是那种执行结束就会进行的,比方我这里把循环条件去掉,使程序只执行一次后进行 点击运行之后返回JVisualVM来查看后果,但JVisualVM并没有显示这个java过程,或者是一闪而过。这是因为JVisualVM只能实时监控到执行中的java程序,因为改变过的程序执行了输入之后就完结了,所以JVisualVM基本没方法去监控程序的状态,后面进行之后还能看到,是因为你曾经在进行之前关上了这个程序的监控,在点了进行之后,显示的是进行之前最初一刻的状态 明天的分享就到此结束了,感觉本人又向前迈了一小步,喜爱本文章的小伙伴能够点个赞或者留个评论反对一下哦

June 10, 2021 · 1 min · jiezi

关于JVM:☕Java技术指南深入分析Class类文件的结构上篇

【前提概要】Java源码文件通过编译(Compile)后生产Class字节码文件。JVM时通过字节码来执行。对于深刻开掘和透彻了解Java技术体系,除了意识学习Java API相干的技术之外,最应该优先学习的技术class字节码文件的构造体系。接下来就让咱们深刻开掘学习class字节码吧。【跨平台性】各种不同平台的虚拟机与所有平台都对立应用的程序存储格局——字节码(ByteCode)是形成平台无关性的基石,也是语言无关性的根底。Java 虚拟机不和包含 Java 在内的任何语言绑定,它只与“Class文件”这种特定的二进制文件格式所关联,Class文件中蕴含了Java虚拟机指令集和符号表以及若干其余辅助信息。【Class文件】任何一个 Class 文件都对应着惟一一个类或接口的定义信息,但反过来说,Class 文件实际上它并不一定以磁盘文件的模式存在。Class文件是一组以 8 位字节为根底单位的二进制流。【Class文件构造】Class文件格式采纳相似C语言构造体的伪构造来存储数据,这种伪构造中只有两种数据类型:“无符号数” 和 “表”。【无符号概念】【无符号数】属于根本的数据类型,以u1、u2、u4、u8来别离代表1个字节、2个字节、4个字节、8个字节的无符号数,无符号数能够用来形容数字、索引援用、数量值或者依照UTF-8编码形成字符串值。【表的概念】【数据表】是由多个无符号数或其余表作为数据表形成的复合数据类型,为了与无符号数以及其余构造进行辨别,所有表的命名都习惯性的以“_info”结尾。例如,field_info、method_info、attribute_info等。整个Class文件实质上就是一张表。理解Class文件的构造对理解虚拟机执行引擎有重要作用。【构造严谨性】Class的构造不像XML等描述语言,因为它没有任何分隔符号,所以在其中的数据项,无论是程序还是数量,都是被严格限定的,哪个字节代表什么含意,长度是多少,先后顺序如何,都不容许扭转。 【魔术头和版本号】 【魔术头】魔数头是每个Class文件的头4个字节称为魔数(Magic Number),它的惟一作用是确定这个文件是否为一个能被虚拟机承受的 Class 文件。应用魔数而不是扩展名来进行辨认次要是基于平安方面的思考,因为文件扩展名能够随便地改变。文件格式的制定者能够自在地抉择魔数值,只有这个魔数值还没有被宽泛采纳过同时又不会引起混同即可。所有的由Java编译器编译而成的class文件的前4个字节都是“0xCAFEBABE” (谐音咖啡宝贝)。当JVM在尝试加载某个文件到内存中来的时候,会首先判断此class文件有没有JVM认为能够承受的“签名”,即JVM会首先读取文件的前4个字节,判断该4个字节是否是“0xCAFEBABE”,如果是,则JVM会认为能够将此文件当作class文件来加载并应用。不同版本的Java虚拟机实现反对的版本号也不同,高版本号的 Java 虚拟机实现能够反对低版本号的 Class 文件,反之则不成立。【主/次版本】紧接着魔数的 4 个字节存储的是Class文件的版本号: 第5和第6个字节是次版本号(MinorVersion)。第7和第8个字节是主版本号(Major Version)。Java的版本号是从45(十进制)开始的,JDK1.1之后的每个JDK大版本公布主版本号向上加1高版本的JDK能向下兼容以前版本的Class文件,但不能运行当前版本的Class文件,即便文件格式并未产生任何变动,虚拟机也必须拒绝执行超过其版本号的Class文件。例如:JDK1.0主版本号为45,JDK1.1为46,顺次类推到JDK8的版本号为52,16进制为0x33。【版本解析流程】一个JVM实例只能反对特定范畴内的主版本号(Mi至Mj)和0至特定范畴内 (0至m)的副版本号。假如一个Class文件的格局版本号为V, 仅当Mi.0≤v≤Mj.m成立时,Class文件才能够被此Java虚拟机反对。JVM在加载class文件的时候,会读取出主版本号,而后比拟这个class文件的主版本号和JVM自身的版本号,如果JVM自身的版本号<class文件的版本号,JVM会认为加载不了这个class文件。会抛出咱们常常见到" java.lang.UnsupportedClassVersionError: Bad version number in .class file "Error谬误;反之,JVM会认为能够加载此class文件,持续加载此class文件。【常量池元数据】常量池元数据次要蕴含了常量池数据表和常量池计数器。 【常量池计数器】常量池是由一组CP构造体(cp_info)数据项组成的,而数据表的大小则由常量池计数器指定。常量池计数器constant_pool_count的值是constant_pool表中的成员数+1(永远执行下一个待调配的索引数据值)。常量池中常量的数量是不固定的,所以在常量池的入口须要搁置一项u2类型的数据,代表常量池容量计数值(constant_pool_count)。constant_pool表的索引值只有在大于0且小于constant_pool_count时才会被认为是无效的。 注意事项:常量池计数器默认从1开始而不是从0开始,与Java中语言习惯不一样的是,这个容量计数是从1而不是0开始的 当constant_pool_count = 1时,常量池中的cp_info个数为0;当constant_pool_count为n时,常 量池中的cp_info个数为n-1。起因: 在指定class文件标准的时候,将索引#0项常量空进去是有非凡思考的,这样当:某些数据在特定的状况下想表白“不援用任何一个常量池项”的意思时,就能够将其援用的常量的索引值设置为#0来示意。【常量池数据表】常量池数据表次要寄存两大类常量:字面量和符号援用。 常量池中次要寄存两大类常量:字面量(Literal)和符号援用(Symbolic References)。 字面量比拟靠近于 Java 语言层面的常量概念,如文本字符串、申明为 final 的常量值、根本变量等。符号援用则属于编译原理方面的概念,包含了上面三类常量:类和接口的全限定名(Fully Qualified Name)、字段的名称和描述符(Descriptor)、办法的名称和描述符、以及一些扩大援用信息。 常量池的结构图: cp_info { u1 tag; u1 info[]; } JVM是依据tag的值来确定常量池项cp_ino的类型字面量的。 【拜访标记】 在常量池完结之后,紧接着2个字节代表拜访标记(access_flag),这个标记用来辨认这个Class是类还是接口;是否定义为public类型;是否定义为abstract类型;如果是类的话,是否被申明为final等等。 【类索引、父类索引与接口索引汇合】 Class文件由this_class、super_class和interfaces这三项数据来确定该类的继承关系。类索引用来确定类的全名限定,父类索引用来确定该类的父类的全名限定,接口索引汇合用来形容该类实现了哪些接口。【类索引】类索引,this_class的值必须是对constant_pool表中我的项目的一个无效索引值。constant_pool表 在这个索引处的项必须为CONSTANT_Class_info 类型常量,示意这个 Class文件所定义的类或接口。【父类索引】父类索引,对于类来说,super_class的值必须为0或者是对constant_pool 表中我的项目的一个无效索引值。如果它的值不为0,constant_pool在索引处的项必须CONSTANT_Class_info类型常量,示意这个Class文件所定义的类的间接父类。以后类的间接父类,以及它所有间接父类的 access_flag 中都不能带有ACC_FINAL标记。对于接口来说,它的Class文件的super_class项的值必须是对constant_pool表中我的项目的一个无效索引值。constant_pool表在这个索引处的项必须为代表java.lang.Object的 CONSTANT_Class_info类型常量。如果Class文件的super_class的值为 0,那这个Class文件只可能是定义的是 java.lang.Object类,只有它是惟一没有父类的类。【接口元数据】接口计数器,interfaces_count的值示意以后类或接口的【间接父接口数量】。接口数据表,interfaces[]数组中的每个成员的值必须是一个对constant_pool表中我的项目的一个无效索引值, 它的长度为 interfaces_count。每个成员interfaces[i] 必须为CONSTANT_Class_info类型常量,其中 【0 ≤ i <interfaces_count】。在interfaces[]数组中,成员所示意的接口程序和对应的源代码中给定的接口程序(从左至右)一样,即interfaces[0]对应的是源代码中最右边的接口。【字段表汇合】 ...

June 8, 2021 · 1 min · jiezi

关于JVM:深入理解JVM虚拟机-虚拟机的发展历史

深刻了解JVM虚拟机 - 虚拟机的倒退历史 内容根本来自《深刻了解JVM虚拟机》。算是对于倒退历史的一点集体总结。 概述:JVM的倒退历史以及历史进程Hotspot为什么能够称霸武林Hotspot和JRocket 合并,后果喜忧参半jvm面临的挑战以及将来的倒退前瞻思维导图: 虚拟机倒退历史classic VM - 第一台正式商用JAVA虚拟机 于1996年1月23日Sun公布jdk1.0诞生,是JAVA真正意义上第一台JVM虚拟机 特点:只反对纯解释器运行条件编译智能用外挂(Sun wjit)。解释器和编译器不能配合工作。外部工作原理非常简略意义:jdk1.2之前惟一指定虚拟机jdk1.2 存在hotspot和exact vm 混合的状况媲美hotspot的虚拟机:Exact Vm特点:精确的内存治理(能够晓得那一块内存的准确数据类型)。摈弃基于句柄的对象查找形式热点探测,两级即时编译,编译和解释混合意义:因为更优良的HotSpot虚拟机呈现,没有被正式商用,存在工夫非常短暂jdk1.2时,sun提供了此虚拟机配合classic应用武林霸主:hotspot Vm特点:具备exact vm虚拟机的所有个性反对热点代码摸索准确的内存治理高频代码的规范即便编译和栈上替换(重要)意义:HotRocket:jdk8的Hotspot和JRocket进行合并实际效果并不好,JRocket的很多个性没有施展进去。手机端虚拟机:Embeded vm 专门为了挪动智能手机设计的一款jvm,然而最终失败。被Andriod间接取代。 天下第二:JRocket 和 IBM J9VMJRocket:特点:2008年JRockit随着BEA被Oracle收买,现已不再 持续倒退,永远停留在R28版本,这是JDK 6版JRockit的代号。JRockit外部不蕴含解释器实现,全副代 码都靠即时编译器编译后执行专门为服务器硬件和服务端利用场景高度优化的虚拟机意义:在JDK1.8当中oracle整合JRockit到HotSpot虚拟机上,然而因为两者的个性差别较大,只整合了局部个性,后果并不是非常现实作为一款优良的JVM实现已经当先JVM前列同时随同着优良的组件Java Mission Control故障解决套件诞生。IBM J9VM特点:原名叫做:IT4J,因为名字不好记J9更为宽泛认知由k8扩大而来,名字来源于一个8bit的谬误问题号称是世界上最快的Java虚拟机(官网定义)在商用虚拟机的畛域极具影响力意义:2017年左右,IBM公布了开源J9 VM,命名为openJ9,交给Eclipse基金会治理,也称为Ecilpse openJ9须要非凡平台运行:Bea liquid / Azulejo VM (专用虚拟机)Bea liquid:特点:自身实现一个专门操作系统。运行在自家Hypervisor零碎上因为JRocket的开发而终止。意义:随着JRockit虚拟机终止开发,Liquid VM我的项目也进行了。Azule VM:特点:对于HotSpot进行大量的改良,运行与Azul System专有零碎下面的Java虚拟机提供微小的内存范畴的进展工夫和垃圾收集工夫:pic收集器c4收集器意义:最终产线投入到Zing VM虚拟机 低提早疾速预热易于监控挑战:Apache Harmony / google android dalvik vmApache Harmony:特点:对于HotSpot进行大量的改良,运行与Azul System专有零碎下面的Java虚拟机提供微小的内存范畴的进展工夫和垃圾收集工夫:pic收集器c4收集器意义:已经因为提交TCK和SUN矛盾而愤然退出JCP组织因为Open Jdk 的呈现悄悄退出市场。然而外部许多的代码排汇进ibm open jdk7的实现google android dalvik vm特点:andriod 4之前是支流的虚拟机平台,5之后被反对提前编译ART虚拟机代替已经十分强力的一个虚拟机不能间接运行class,然而和JAVA有着很亲密的关系。 DEX文件能够通过Class文件进行转化意义:andriod 5之后被反对提前编译ART虚拟机代替其余JVM虚拟机 这里介绍一些书中没有提到的非重点的JVM虚拟机 Micronsoft JVM:已经作为Window平台性能最好的虚拟机。被Sun公司进行侵权制裁之后,微软转而与JAVA为敌,开发后续的.net语言反抗JAVA生态。KVM:强调轻量,简略,高度可移植。运行速度比较慢。在IOS和Android呈现之前受到欢送JAVA Card VM:JAVA虚拟机的一个子集,负责Applet解释和执行Squarewk VM:由Sun公司开发,运行于Sun SPot,也已经用于java card。是一款JAVA比重非常高的虚拟机。JavaInJava:Sun公司在97 - 98年开发一款试验性质的虚拟机,必须运行在一个宿主的JVM下面。价值在于自证元循环,具备肯定的钻研价值Maxine VM: 和javainjava十分类似,简直全副以JVM作为运行环境,Graal编辑器让他有了更进一步的倒退,同时Graal也是作为graal编辑器的良好辅助虚拟机Jikes RVM: ibm开发的专门钻研JAVA虚拟机技术的我的项目。也是一个元循环虚拟机IKVM.NET:基于.NET 框架的java虚拟机,借助MONO失去肯定的跨平台能力

June 7, 2021 · 1 min · jiezi

关于jvm:一个-println-竟然比-volatile-还好使

先点赞再看,养成好习惯前两天一个小伙伴忽然找我求助,说筹备换个坑,最近在零碎学习多线程常识,但遇到了一个刷新认知的问题…… 小伙伴:Effective JAVA 里的并发章节里,有一段对于可见性的形容。上面这段代码会呈现死循环,这个我能了解,JMM 内存模型嘛,JMM 不保障 stopRequested 的批改能被及时的观测到。static boolean stopRequested = false;public static void main(String[] args) throws InterruptedException { Thread backgroundThread = new Thread(() -> { int i = 0; while (!stopRequested) { i++; } }) ; backgroundThread.start(); TimeUnit.MICROSECONDS.sleep(10); stopRequested = true ;}但奇怪的是在我加了一行打印之后,就不会呈现死循环了!难道我一行 println 能比 volatile 还好使啊?这俩也没关系啊static boolean stopRequested = false;public static void main(String[] args) throws InterruptedException { Thread backgroundThread = new Thread(() -> { int i = 0; while (!stopRequested) { // 加上一行打印,循环就能退出了! System.out.println(i++); } }) ; backgroundThread.start(); TimeUnit.MICROSECONDS.sleep(10); stopRequested = true ;}我:小伙子八股文背的挺熟啊,JMM 张口就来。 ...

June 1, 2021 · 3 min · jiezi

关于JVM:线上系统是如何设置JVM内存大小

摘要咱们在我的项目开发中,个别首先须要在我的项目上线前对我的项目的JVM内存大小须要进行设置。所以咱们个别的思路是:线上业务零碎流程梳理->每天的数据访问量->高峰期的QPS->依据QPS计算出计算机机器配置(1、须要多少台机器 2、每台机器的内存多大 3、每台机器的JVM参数设置)->零碎裁减10-20倍数预估(个别咱们的零碎都会比预估的大10-20倍); 其中每台机器的JVM内存参数设置是要害:须要咱们依据并发、每一次申请的数据大小、以及每一次申请的解决时长来计算机器每次会进入多大数据量到Java堆内存的新生代触发Minor GC/Young GC。 内容目前依据所在我的项目的业务背景做一个大略的业务流程整顿,而后依据机器的配置剖析设置JVM的内存参数。 线上零碎业务流程形象首先是外围业务流程简化、形象以及梳理。目前我的项目上是做智能外呼零碎,外围流程就是第三方零碎/利用零碎前端导入数据入库,而后会创立相应的工作规定,外呼平台依据相应的工作规定进行呼叫,因为呼叫规定比较复杂,外呼平台发动呼叫的时候,须要调用利用系统核心两个接口:1、外呼重呼轮呼接口:次要是针对1人多号码状况下,针对于未接通或者接通的号码做是否须要从新呼叫这个号码,如果不在重呼,是否须要发动轮呼。2、号码防火墙接口:此接口次要是平台发动呼叫时候的号码校验,一个号码是否在白名单、黑名单、以及每天是否呼叫限度,以及一些号码变换(依据业务号码获取实在号码,被叫号码解密) 而后平台呼叫结束之后,会将呼叫后果入库存储,而后利用零碎的定时调度会每次查问出100条数据接口返回给第三方零碎。简化的业务流程图如下: 零碎QPS零碎呼叫后果量高峰期大略20万,号码接通率均匀50%,未呼通的号码均匀呼叫3次,一个可能多个号码,咱们依照1人一号码算,呼叫后果传输依据咱们三方零碎的个数,有不同的定时传送工作。目前对接的三方零碎有4个。所以咱们预估每天的进入智能外呼零碎的数据量有:总数据量=20万(外呼平台-号码防火墙接口)+10万(接入数据)+15万(重呼轮呼接口)+20万(后果回传)+5万(其余)=70万,每天呼叫工夫大略为8个小时;因为外呼零碎呼叫是主动后盾触发,不存在80%的数据量在20%的工夫涌入。所以QPS为:700000/8/60/60=25; JVM参数预估.每秒有多少次申请 每秒25个申请 每次申请耗时 20万(外呼平台-号码防火墙接口--均匀1秒)+10万(接入数据-8秒)+15万(重呼轮呼接口-均匀耗时3秒)+20万(后果回传-200毫秒)+5万(其余)=70万 号码防火墙接口:均匀1秒 接入数据耗时:咱们大略算8秒. 重呼:咱们大略算3秒.. 后果回传:均匀200毫秒 共计每次申请耗时大略:(20+10x8+15x3+20x0.2+5)/70=2.17秒。 每个申请大略须要多大的内存空间?每个申请数据均匀来说有20多个字段,大略500b 每秒发动的申请对内存的占用?每秒有25个申请,每个申请大小是500b,所以每秒发动的申请对内存的占用为:25x500b=12.2kb 对残缺的零碎内存占用须要进行预估. 咱们目前只是对系统外呼时候的接入数据量进行评估,实在外呼零碎运行,必定会每秒创立大量其余对象,更有可能在某一个时刻数据过去的多时候触发更大的数据并发,联合此咱们能够大略估算整个零碎每秒钟大略有多少内存空间,咱们再次根底之上将计算结果扩充10~20倍。也就是说,每秒钟在java堆内存中创立的为:120kb-250kb之间,下一秒持续触发,一次新生代垃圾将会越来越多,而后会触发Minor GC回收掉垃圾。 零碎的JVM堆内存应该怎么设置?其实个别上线业务零碎,常见的机器配置是2核4G,或者是4核8G;依照2核4G来计算: 4G内存调配JVM虚拟机内存为2G;JVM虚拟机运行时候除了Java堆内存、Java虚拟机栈、程序计数器、办法区;咱们调配给Java对内存的大小个别就是占一半多,也就是1G多,Java堆有新生代跟老年代,依照java对象大都是"朝存夕亡",咱们的新生代跟老年代大概是8:2也就是新生代是800M;每秒零碎有250kb的新生代对象进入;那么800x1024/250/60=55min;也就是55分钟左右会触发一个Minor GC,所以曾经足够。 并发扩充10倍零碎的JVM堆内存应该怎么设置?如果零碎并发减少10倍,也就是每秒250个申请的话,那么每秒将会有:1200kb-2500kb数据进入java堆内存,此时就算是2M;800M的java堆内存将会在:800/2/60=7分钟,大略7分钟左右就会触发一次Minor GC,频繁触发会影响零碎系能, 如果采纳4外围8G的话,咱们4G的内存调配给JVM虚拟机,而后Java堆内存是2G,新生代占用:1.6G,那么1.6x1024/2/60=13分钟,此时也会造成Minor GC频繁,所以咱们能够让JVM虚拟机为5G,而后java堆内存为3G,新生代为:2.4G,那么:2.4x1024/2/60=21分钟,大略21分钟触发一次Minor GC还算对付。 所以参数设置: -Xms3G -Xmx3G -Xmn2.4G

May 30, 2021 · 1 min · jiezi

关于JVM:一段代码告诉你JVM的工作原理类加载到运行及JVM各个运行时数据区

前言学习技术特地是工作中常常使用不到的技术兴许会让很多人感觉烦闷干燥,实践如果不联合实际终究会成为过眼云烟。技术没有捷径,须要考究办法地继续学习。既然是原理,他必定是一个动作化地一系列行为总和。行为的背地必定有主体或者实体以及引入的背景。所以集体感觉在面试中,如果面试官问一些原理性货色,最好的形式还是why what how;why-用于解答为什么须要这个货色,what-阐明这个货色是什么以及其特点 how-次要是联合what行为化的形容其是如何实现特点的。 摘要本节是集体学习总结,次要是联合简略的案例demo代码以图文并貌的模式总结下本人学习的JVM工作原理。从为什么引入JVM的java代码如何运行?到JVM运行时候须要用到什么主体-类加载器、字节码执行引擎、JVM的各个运行时候数据区以及JVM在怎么运行咱们代码的,类加载器加载的数据会加载存储到哪些数据区,字节码执行引擎的时候是如何联合咱们的JVM运行数据去来工作的?以此来加深本人的常识体系。 内容1、案例引入咱们为什么须要JVM.咱们工作中写的代码是如何运行起来呢?咱们的.java后缀的代码(源代码)通过打包编译后成为字节码.class(jar/war包),而后启动一个tomcat或者应用java -jar xxx.jar即可开启一个JVM过程来运行咱们的java代码。下面就是咱们为什么须要JVM的起因。 2、JVM工作原理运行时候须要哪些实体.咱们从工作中写的代码如何运行起来能够晓得?JVM运行起来时候是从编译后的.class文件进行加载,而后必定是加载到内存中,所以这外面就有JVM的运行数据区,运行的代码文件不可能永无止境在加载到内存,必定会存在内存满的时候,所以就存在垃圾,而后就牵涉到垃圾回收。所以JVM运行时候次要从类加载->在JVM的内存区域运行代码->而后运行的代码进行垃圾回收。 2.1 Java类加载机制类加载必定是加载java中的.class文件,然而什么时候才会加载一个类?以及类加载到应用开释的过程?类加载有哪些类加载器以及类加载的时候类加载的原理。 JVM在什么状况下会加载一个类?当咱们通过java -jar命令运行一个JVM过程,此时JVM会先加载咱们main办法所在的类,而后通过字节码引擎执行执行main办法,当须要某一个类的时候就会加载某个类。 类从加载到应用经验的过程 当须要应用到一个类的时候,会加载这个类,这个类从加载到应用的过程如下:加载->校验->筹备->解析->初始化->应用->卸载。加载:类加载器加载须要应用到的类(按需加载)。校验:校验加载的类是否合乎JVM标准,不符合规范的报错,不再进行其余步骤。筹备:给加载进来的类变量调配空间,并设定默认值。援用类型为null,根本类型为对应值的原始值,比方int类型的为0等解析:将符号援用替换为间接援用初始化:先初始化类相干的代码比方动态成员变量,动态代码块,而后初始化实例数据,初始化一个类时候,发现父类还没初始化,先初始化父类。应用:应用对象。卸载:垃圾回收。 咱们看下上面的代码执行步骤: ReplicaManager: /** * @Author: yexinming * @Description: 正本管理器 * @Date: 2021/5/29 9:24 上午 */public class ReplicaManager extends AbstractReplicaManager{ public static int flushInterval = Configuration.getInt("replica.flush.interval"); public static Map<String,Replica> replicas; private Integer count; private int cnt; public ReplicaManager(){ super(4); System.out.println("==ReplicaManager constructor 对象成员变量:count 之前=="+this.count); System.out.println("==ReplicaManager constructor 对象成员变量:cnt 之前=="+this.cnt); System.out.println("==ReplicaManager constructor 结构器办法执行=="); this.count = 5; System.out.println("==ReplicaManager constructor 对象成员变量:count 之后=="+this.count); } static { System.out.println("==ReplicaManager static 类成员变量:flushInterval=="+ReplicaManager.flushInterval); System.out.println("==ReplicaManager static 成员变量:replicas=="+ReplicaManager.replicas); System.out.println("==ReplicaManager static 动态代码块执行=="); loadReplicaFromDisk(); } public static void loadReplicaFromDisk(){ System.out.println("==ReplicaManager loadReplicaFromDisk =从本地加载正本"); ReplicaManager.replicas = new HashMap<String, Replica>(); } public void load(){ System.out.println("==ReplicaManager load =ReplicaManager加载"); } public static void main(String[] args) { ReplicaManager manager = new ReplicaManager(); manager.load(); }}AbstractReplicaManager: ...

May 30, 2021 · 1 min · jiezi

关于jvm:掌握好这些Java内存模型知识你才算一个合格的程序员

Java内存模型简略介绍一下Java内存模型Java内存模型即Java Memory Model,简称JMM。JMM定义了Java 虚拟机(JVM)在计算机内存(RAM)中的工作形式。JVM是整个计算机虚构模型,所以JMM是隶属于JVM的。 Java内存模型是共享内存的并发模型,线程之间次要通过读-写共享变量(堆内存中的实例域,动态域和数组元素)来实现隐式通信。Java 内存模型(JMM)管制 Java 线程之间的通信,决定一个线程对共享变量的写入何时对另一个线程可见。 JVM主内存与工作内存Java 内存模型的次要指标是定义程序中各个变量的拜访规定,即在虚拟机中将变量(线程共享的变量)存储到内存和从内存中取出变量这样底层细节。 Java内存模型中规定了所有的变量都存储在主内存中,每条线程还有本人的工作内存,线程对变量的所有操作都必须在工作内存中进行,而不能间接读写主内存中的变量。这里的工作内存是 JMM 的一个抽象概念,也叫本地内存,其存储了该线程以读 / 写共享变量的正本。 就像每个处理器内核领有公有的高速缓存,JMM 中每个线程领有公有的本地内存。不同线程之间无奈间接拜访对方工作内存中的变量,线程间的通信个别有两种形式进行,一是通过消息传递,二是共享内存。Java 线程间的通信采纳的是共享内存形式,线程、主内存和工作内存的交互关系如下图所示: 线程A和线程B通信要通过两个步骤: 线程A把本地内存A中更新过的共享变量刷新到主内存中线程B到主内存中去读取线程A之前已更新过的共享变量这里所讲的主内存、工作内存与 Java 内存区域中的 Java 堆、栈、办法区等并不是同一个档次的内存划分,这两者基本上是没有关系的,如果两者肯定要勉强对应起来,那从变量、主内存、工作内存的定义来看,主内存次要对应于Java堆中的对象实例数据局部,而工作内存则对应于虚拟机栈中的局部区域。 JMM数据原子操作read(读取):从主内存读取数据load(载入):将主内存读取到的数据写入工作内存use(应用):从工作内存读取数据来计算assign(赋值):将计算好的值从新赋值到工作内存中store(存储):将工作内存数据写入主内存write(写入):将store过来的变量值赋值给主内存中的变量lock(锁定):将主内存变量加锁,标识为线程独占状态unlock(解锁):将主内存变量解锁,解锁后其余线程能够锁定该变量 public class VolatileVisibilityTest { private static volatile boolean initFlag = false; public static void main(String[] args) throws InterruptedException { new Thread(new Runnable() { @Override public void run() { System.out.println("waiting data..."); while (!initFlag) { } System.out.println("====================success"); } }).start(); Thread.sleep(2000); new Thread(new Runnable() { @Override public void run() { prepareDate(); } }).start(); } public static void prepareDate() { System.out.println("preparing data..."); initFlag = true; System.out.println("prepare end..."); }} ...

May 5, 2021 · 1 min · jiezi

关于jvm:JVM学习笔记八类加载

1 起源起源:《Java虚拟机 JVM故障诊断与性能优化》——葛一鸣章节:第十章本文是第十章的一些笔记整顿。 2 概述本文次要讲述了类加载器以及类加载的具体流程。 3 类加载流程类加载的流程能够简略分为三步: 加载连贯初始化而其中的连贯又能够细分为三步: 验证筹备解析上面会别离对各个流程进行介绍。 3.1 类加载条件在理解类接在流程之前,先来看一下触发类加载的条件。 JVM不会无条件加载类,只有在一个类或接口在首次应用的时候,必须进行初始化。这里的应用是指被动应用,被动应用包含如下状况: 创立一个类的实例的时候:比方应用new创立,或者应用反射、克隆、反序列化调用类的静态方法的时候:比方应用invokestatic指令应用类或接口的动态字段:比方应用getstatic/putstatic指令应用java.lang.reflect中的反射类办法时初始化子类时,要求先初始化父类含有main()办法的类除了以上状况外,其余状况属于被动应用,不会引起类的初始化。 比方上面的例子: public class Main { public static void main(String[] args){ System.out.println(Child.v); }}class Parent{ static{ System.out.println("Parent init"); } public static int v = 100;}class Child extends Parent{ static { System.out.println("Child init"); }}输入如下: Parent init100而加上类加载参数-XX:+TraceClassLoading后,能够看到Child的确被加载了: [0.068s][info ][class,load] com.company.Main[0.069s][info ][class,load] com.company.Parent[0.069s][info ][class,load] com.company.ChildParent init100然而并没有进行初始化。另外一个例子是对于final的,代码如下: public class Main { public static void main(String[] args){ System.out.println(Test.STR); }}class Test{ static{ System.out.println("Test init"); } public static final String STR = "Hello";}输入如下: ...

May 4, 2021 · 2 min · jiezi

关于jvm:JVM学习笔记七Class文件结构

1 起源起源:《Java虚拟机 JVM故障诊断与性能优化》——葛一鸣章节:第九章本文是第九章的一些笔记整顿。 2 概述本文次要介绍了Class文件的次要组成,包含魔数、版本号、常量池、拜访标记等。 3 Class文件概览依据JVM标准,一个Class文件能够十分谨严地形容为: ClassFile{ u4 magic; u2 minor_version; u2 major_version; u2 constant_pool_count; cp_info constant_pool[constant_pool_count-1]; u2 access_flags; u2 this_class; u2 super_class; u2 interfaces_count; u2 interfaces[interfaces_count]; u2 fields_count; field_info fields[fields_count]; u2 methods_count; method_info methods[methods_count]; u2 attributes_count; attribute_info attributes[attributes_count];}上面会按程序具体介绍外面的各个字段。 4 魔数魔数(Magic Number)作为Class的标记,用来通知JVM这是一个Class文件,魔数是一个4字节的无符号整数,固定为0xCAFEBABE。如果一个Class文件不以0xCAFEBABE结尾,那么会抛出如下谬误: Linux下能够间接应用vim关上class文件进行查看,比方须要关上一个Test.class文件,能够输出如下命令: vim -b Test.class:%!xxd切换到十六进制后就能够看到魔数了: 5 版本魔数前面紧跟着Class的小版本和大版本号,这示意以后Class文件是由哪个版本的编译期产生的。小版本和大版本后都是占用两个字节,比方下图: 0000是小版本号0037是大版本号,十进制为55,也就是对应JDK 11版本的编译期6 常量池在版本号前面,紧跟着就是常量池的数量以及若干个常量池表项: 其中每一个常量池表项都具备标签属性: 对应关系举例如下: tag为3:类型为CONSTANT_Integertag为4:类型为CONSTANT_Float等等,比方CONSTANT_Integer构造如下: CONSTANT_Integer_info { u1 tag; u4 bytes;}一个tag加上一个四字节的无符号整数。其余类型大部分相似,篇幅限度,具体请看JVM标准。 7 拜访标记拜访标记应用两个字节示意,用于表明该类的访问信息,比方public/abstract等,对应关系如下: ...

May 2, 2021 · 2 min · jiezi

关于jvm:JVM学习笔记六锁优化与CAS

1 起源起源:《Java虚拟机 JVM故障诊断与性能优化》——葛一鸣章节:第八章本文是第八章的一些笔记整顿。 2 概述本文次要讲述了JVM在运行层面和代码层面的锁优化策略,最初介绍了实现无锁的其中一种办法CAS。 3 对象头JVM中每个对象都有一个对象头,用于保留对象的零碎信息,64bit JVM的对象头构造如下图所示: 其中: Mark Word由64bit组成,一个性能数据区,能够寄存对象的哈希、对象年龄、锁的指针等信息KClass Word在没有开启指针压缩的状况下,64bit组成,然而64bit JVM会默认开启指针压缩(+UseCompressedOops),所以会压缩到32bit另外,从图中能够看到,不同的锁对应于不同的Mark Word: 无锁:25bit空+31bit哈希值+1bit空+4bit分代年龄+1bit是否偏差锁+2bit锁标记偏差锁:54bit持有偏差锁的线程ID+2bit偏差工夫戳+1bit空+4bit分代年龄+1bit是否偏差锁+2bit锁标记轻量锁:62bit栈中锁记录指针+2bit锁标记分量锁:62bit重量级锁指针+2bit锁标记JVM如何辨别锁次要看两个字段:biased_lock与lock,对应关系如下: biased_lock=0 lock=00:轻量级锁biased_lock=0 lock=01:无锁biased_lock=0 lock=10:重量级锁biased_lock=0 lock=11:GC标记biased_lock=1 lock=01:偏差锁4 锁的运行时优化很多时候JVM都会对线程竞争的操作在JVM层面进行优化,尽可能解决竞争问题,也会试图打消不必要的竞争,实现的办法包含: 偏差锁轻量级锁重量级锁自旋锁锁打消4.1 偏差锁(JDK15默认敞开)4.1.1 简介偏差锁是JDK 1.6提出的一种锁优化形式,核心思想是,如果线程没有竞争,则勾销曾经获得锁的线程同步操作,也就是说,某个线程获取到锁后,锁就会进入偏差模式,当线程再次申请该锁时,无需再次进行相干的同步操作,从而节俭操作工夫。而在此期间如果有其余线程进行了锁申请,则锁退出偏差模式。 开启偏差锁的参数是-XX:+UseBiasedLocking,处于偏差锁时,Mark Word会记录取得锁的线程(54bit),通过该信息能够判断以后线程是否持有偏差锁。 留神JDK15后默认敞开了偏差锁以及禁用了相干选项,能够参考JDK-8231264。 4.1.2 加锁流程偏差锁的加锁过程如下: 第一步:拜访Mark Word中的biased_lock是否设置为1,lock是否设置为01,确认为可偏差状态,如果biased_lock为0,则是无锁状态,间接通过CAS操作竞争锁,如果失败,执行第四步第二步:如果为可偏差状态,测试线程ID是否指向以后线程,如果是,达到第五步,否则达到第三步第三步:如果线程ID没有指向以后线程,通过CAS操作竞争锁,如果胜利,将Mark Word中的线程ID设置为以后线程ID,而后执行第五步,如果失败,执行第四步第四步:如果CAS获取偏差锁失败,示意有竞争,开始锁撤销第五步:执行同步代码4.1.3 例子上面是一个简略的例子: public class Main { private static List<Integer> list = new Vector<>(); public static void main(String[] args){ long start = System.nanoTime(); for (int i = 0; i < 1_0000_0000; i++) { list.add(i); } long end = System.nanoTime(); System.out.println(end-start); }}Vector的add是一个synchronized办法,应用如下参数测试: ...

April 30, 2021 · 2 min · jiezi