php网站建设模板,logo设计 公司 免费,wordpress访问私密帖子,网站暂时关闭 seo一、JVM模型
JVM内部体型划分 JVM的内部体系结构分为三部分#xff0c;分别是#xff1a;类加载器#xff08;ClassLoader#xff09;子系统、运行时数据区#xff08;内存#xff09;和执行引擎
1、类加载器
概念
每个JVM都有一个类加载器子系统#xff08;class l…
一、JVM模型
JVM内部体型划分 JVM的内部体系结构分为三部分分别是类加载器ClassLoader子系统、运行时数据区内存和执行引擎
1、类加载器
概念
每个JVM都有一个类加载器子系统class loader subsystemJVM的类加载器包括用户自定义类加载器、应用类加载器、拓展类加载器、启动类加载器 启动类加载器BootstrapClassLoader 非java语言实现 作用加载指定路径中jar里面的class文件 路径1java安装目录\jre\lib 路径2java安装目录\jre\classes 例如rt.jar 扩展类加载器ExtClassLoader java语言实现是ClassLoader类型的对象 作用加载指定路径中jar里面的class文件 只能是jar中存在的class 路径java安装目录\jre\lib\ext 例如ext中默认存在的jar或者用户放到ext目录下的jar包 应用类加载器 AppClassLoader java语言实现是ClassLoader类型的对象 作用加载指定路径中class文件或者jar里面的class文件 路径CLASSPATH中配置路径这个是用户自己配置的 例如.:bin:hello.jar
JVM的类加载机制 全盘负责 所谓全盘负责就是当一个类加载器负责加载某个Class时该Class所依赖和引用其他Class也将由该类加载器负责载入除非显示使用另外一个类加载器来载入 双亲委派 双亲委派机制如上图当一个Hello.class这样的文件要被加载时。不考虑我们自定义类加载器首先会在应用类加载器如果存在用户自定义类加载器则从用户自定义类加载器开始中检查是否加载过如果有就无需再加载。如果没有那么会拿到父加载器然后调用父加载器的loadClass方法。父类中同理也会先检查自己是否已经加载过如果没有再往上。这个类似递归的过程直到到达启动类加载器之前都是在检查是否加载过并不会选择自己去加载。直到启动类加载器已经没有父加载器了这时候开始考虑自己是否能加载了如果自己无法加载会下沉到子加载器去加载一直到最底层如果没有任何加载器能加载就会抛出ClassNotFoundException异常 双亲委派机制的优势 采用双亲委派模式的是好处是Java类随着它的类加载器一起具备了一种带有优先级的层次关系通过这种层级关可以避免类的重复加载当父亲已经加载了该类时就没有必要子ClassLoader再加载一次。其次是考虑到安全因素java核心api中定义类型不会被随意替换假设通过网络传递一个名为java.lang.Integer的类通过双亲委托模式传递到启动类加载器而启动类加载器在核心Java API发现这个名字的类发现该类已被加载并不会重新加载网络传递的过来的java.lang.Integer而直接返回已加载过的Integer.class这样便可以防止核心API库被随意篡改 缓存机制 缓存机制将会保证所有加载过的Class都会被缓存当程序中需要使用某个Class时类加载器先从缓存区中搜寻该Class只有当缓存区中不存在该Class对象时系统才会读取该类对应的二进制数据并将其转换成Class对象存入缓冲区中。这就是为很么修改了Class后必须重新启动JVM程序所做的修改才会生效的原因
作用 java中的类想要运行就必须把类对应的class文件加载到内存JVM中真正负责加载class文件内容的是类加载器 负责将Java字节码文件class文件加载进JVM内存运行时数据区
2、 执行引擎
概念
每一个Java虚拟机都有一个执行引擎execution engine
作用
负责执行被加载类中包含的指令
3、 运行时数据区 运行时数据区又可以分为两大部分
1、线程共享堆区、方法区
2、线程私有程序计数器、Java虚拟机栈、本地方法栈
3.1 程序计数器 概念 程序计数器就是记录当前线程执行程序的位置通过改变计数器的值来确定执行的下一条指令比如循环、分支、方法跳转、异常处理线程恢复都是依赖程序计数器来完成 Java虚拟机多线程是交替运行的。为了线程切换能恢复到正确的位置每条线程都需要一个独立的程序计数器所以它是线程私有的 如果线程正在执行的是一个Java方法这个计数器记录的是正在执行的虚拟机字节码指令的地址如果正在执行的是Native方法这个计数器值则为空Undefined。此内存区域是唯一一个在Java虚拟机中没有内存溢出情况的区域 作用 存储指向下一条指令的地址。由于Java虚拟机多线程是根据时间片交替运行的使用程序计数器可以使线程切换能恢复到正确的位置
3.2 Java虚拟机栈 概念 Java虚拟机栈是线程私有生命周期与线程相同。创建线程的时候就会创建一个java虚拟机栈 虚拟机执行java程序的时候每个方法都会创建一个栈帧栈帧存放在java虚拟机栈中通过压栈出栈的方式进行方法调用 栈帧又分为一下几个区域局部变量表、操作数栈、动态连接、方法返回地址等 平时我们所说的变量存在栈中这句话说的不太严谨应该说局部变量存放在java虚拟机栈的局部变量表中 Java的8中基本类型的局部变量的值存放在虚拟机栈的局部变量表中如果是引用型的变量则只存储对象的引用地址 作用 实现方法的调用
3.3 本地方法栈 本地方法native关键字修饰非java语言实现的方法。例如java调用C语言来操作某些硬件信息 本地方法栈为虚拟机使用到本地方法服务native。本地方法栈为线程私有功能和虚拟机栈非常类似。线程在调用本地方法时来存储本地方法的局部变量表本地方法的操作数栈等信息
3.4 堆区 堆是被所有线程共享的区域在虚拟机启动时创建。堆里面存放的都是对象的实例new 出来的对象都存在堆中
我们平常所说的垃圾回收主要回收的区域就是堆区
为了提升垃圾回收的性能又对堆进行了新生代和年老代比例1:2根据不同对象生命周期的不同存放在不同代中使用不同的垃圾回收算法进行回收效率更高
老年代的特点是每次垃圾收集时只有少量对象需要被回收而新生代的特点是每次垃圾回收时都有大量的对象需要被回收那么就可以根据不同代的特点采取最适合的收集算法。
目前大部分垃圾收集器对于新生代都采取复制算法因为新生代中每次垃圾回收都要回收大部分对象也就是说需要复制的操作次数较少但是实际中并不是按照11的比例来划分新生代的空间的一般来说是将新生代划分为一块较大的Eden空间和两块较小的Survivor空间8:1:1每次使用Eden空间和其中的一块Survivor空间当进行回收时将Eden和Survivor中还存活的对象复制到另一块Survivor空间中然后清理掉Eden和刚才使用过的Survivor空间
最新的创建的对象存放到Eden区随着项目的运行当对象占满Eden区就会触发GC执行引擎开启一个GC垃圾回收线程利用可达性分析算法标记非垃圾对象垃圾对象会被回收非垃圾对象移动到Survior区年龄1(经历过一次垃圾回收没有被收集年龄1)此时年经代使用的是复制算法进行垃圾回收。当对象的年龄达到15会从Survivor区移动到老年代
而由于老年代的特点是每次回收都只回收少量对象一般使用的是标记-整理算法压缩法
stw(stop the world)停止事件用户线程暂停专注垃圾回收当Eden区堆满会触发Minior gc或young gc当老年代堆满会触发Full gcyoung gc 和 full gc都会引发stw但是full gc引发的stw时间会比较长
3.5 方法区 方法区是被所有线程共享区域用于存放已被虚拟机加载的类信息、常量、静态变量等数据。被Java虚拟机描述为堆的一个逻辑部分。习惯是也叫它永久代permanment generation
永久代也会垃圾回收主要针对常量池回收类型卸载比如反射生成大量的临时使用的Class等信息。
常量池用于存放编译期生成的各种字节码和符号引用常量池具有一定的动态性里面可以存放编译期生成的常量运行期间的常量也可以添加进入常量池中比如string的intern()方法
当方法区满时无法在分配空间就会抛出内存溢出的异常OutOfMemoneyError
Java8中已经没有方法区了取而代之的是元空间Metaspace
4、 拓展理解
4.1 一个普通java程序的运行过程
javac 将java源代码编译成class文件java 命令将class文件加载进JVM类加载将class文件加载进方法区此时初始化静态成员方法以及执行静态方法先父后子main方法本就是静态方法创建main线程将main方法加载进java虚拟机栈执行main方法中的代码输出结果
4.2 各变量在内存中的位置 成员变量 作为对象的一部分存储在堆区中 局部变量 作为方法的一部分存放在栈区中。执行方法时方法压栈方法执行完毕方法弹栈局部变量也会消除 静态变量 方法区中
4.3 简单类执行过程
示例代码
class Student{private static country CN;private String name;private int age;public static void printlnCountry(){System.out.println(country);}public void showValue(){System.out.println(this.name);System.out.println(this.age);}public Student(String name, int age){this.String name;this.age age;}
}
public class Test{public static void main(String[] args){Student student new Student(张三20);student.showValue();Student.printlnCountry();}
}上述类的执行过程 首先Test类的字节码文件加载进方法区 Test静态方法区加载执行Test类的的静态变量、静态方法、main方法 main方法进栈 执行到 Student student new Student(“张三”20); Student.class被加载进方法区 Student静态方法区加载执行Student类的静态变量、静态方法 在堆区开辟一个Student对象的空间并赋予成员变量默认值name null;age 0; 返回一个内存地址 9988#。成员变量赋值、匿名代码块、构造方法如果存在则按此顺序执行 构造方法 Student(String name, int age)压栈this指向内存地址9988#执行方法弹栈 初始化堆区中的Student对象为其赋值name “张三”;age 20; 赋值操作将内存地址9988#指向student变量 showValue()方法压栈this指向内存地址9988#执行方法弹栈 Student类的静态方法区的printlnCountry()方法压栈执行方法弹栈 main方法弹栈 等待JVM GC回收Student对象
二、JVM垃圾回收 举例在食堂里吃饭吃完把餐盘端走清理的是 C 程序员吃完直接就走的是 Java 程序员。 相关概念 内存泄漏Memory Leak 使用完的对象不能及时回收 内存溢出Out of Memory 程序申请内存时没有足够的内存供程序使用内存泄漏的堆积会造成内存溢出
对象回收判断 引用计数法 每一个对象有一个引用计数属性新增一个引用时计数加1引用释放时计数减1计数为0的时候可以回收 可达性分析 从GcRoots开始向下搜索对象搜索所走过的路径被称为引用链当一个对象到GcRoots没有任何引用链相连时则证明此对象是可回收的 可以作为GcRoots的对象有 虚拟机栈中引用的对象本地方法栈中即一般说的native方法引用的对象方法区中静态属性引用的对象。方法区中常量引用的对象 可达性算法中的不可达对象并不是立即死亡的对象拥有一次自我拯救的机会对象被系统宣告死亡至少要经历两次标记过程第一次是经过可达性分析发现没有与GcRoots 相连接的引用链第二次是对象执行finalize()方法后收尾工作即可被回收 当对象变成GcRoot不可达时Gc会判断该对象是否覆盖了finalize方法若未覆盖则直接回收否则若对象未执行过finalize方法将其放入F-Queue队列由一低优先级线程执行该队列中对象的finalize方法
常见的垃圾回收算法 标记-清除算法(Mark-Sweep) 分为两个阶段标记阶段和清除阶段。标记阶段的任务是通过可达性分析标记出所有需要被回收的对象清除阶段就是回收被标记的对象所占用的空间 从图中可以很容易看出标记-清除算法实现起来比较容易但是有一个比较严重的问题就是容易产生内存碎片(就是导致可用内存不连续零零散散的)碎片太多可能会导致后续过程中需要为大对象分配空间时无法找到足够的空间而提前触发新的一次垃圾收集动作 复制算法 1969年Fenichel提出了一种称为“半区复制”Semispace Copying的垃圾收集算法它将可用内存按容量划分为大小相等的两块每次只使用其中的一块。当这一块的内存用完了就将还存活着的对象复制到另外一块上面然后再把已使用过的内存空间一次清理掉这样一来就不容易出现内存碎片的问题但是存在空间浪费的问题。具体过程如下图所示 标记-整理算法标记-整理-清除 为了解决Copying算法的缺陷充分利用内存空间提出了Mark-Compact算法。该算法标记阶段和Mark-Sweep一样在通过可达性分析标记出可以被回收的对象然后将存活对象都向一端移动然后清理掉端边界以外的内存效率不高。(在标记清除的基础上对可利用内存进行整理)具体过程如下图所示 分代收集算法 分代收集算法是目前大部分JVM的垃圾收集器采用的算法。它的核心思想是根据对象存活的生命周期将内存划分为若干个不同的区域。一般情况下将堆区划分为老年代Tenured Generation和新生代Young Generation老年代的特点是每次垃圾收集时只有少量对象需要被回收而新生代的特点是每次垃圾回收时都有大量的对象需要被回收那么就可以根据不同代的特点采取最适合的收集算法。目前主流的分代垃圾回收算法是新生代使用复制算法老年代使用标记-整理算法
典型的垃圾回收器 Serial/Serial Old收集器 最基本最古老的收集器它是一个单线程串行收集器并且在它进行垃圾收集时必须暂停所有用户线程。Serial收集器是针对新生代的收集器采用的是Copying算法Serial Old收集器是针对老年代的收集器采用的是Mark-Compact算法。它的优点是实现简单高效但是缺点是会给用户带来停顿 ParNew收集器 Serial收集器的多线程版本即多线程串行化收集器使用多个线程进行垃圾收集 Parallel Scavenge收集器 一个新生代的多线程收集器并行收集器它在回收期间不需要暂停其他用户线程其采用的是Copying算法该收集器与前两个收集器有所不同它主要是为了达到一个可控的吞吐量吞吐率代码运行时间/代码运行时间垃圾收集时间优先代码运行的时间更多 Parallel Old收集器 Parallel Scavenge收集器的老年代版本并行收集器使用多线程和Mark-Compact算法。 CMSConcurrent Mark Sweep收集器 一种以获取最短回收停顿时间为目标的收集器它是一种并发收集器采用的是Mark-Sweep算法 执行阶段 初始标记可达性分析标记会引发stw停止事件停止用户线程并发标记可以和用户线程并发执行重新标记重新标记会引发stw停止事件并发清除可以与用户线程并发执行不会引发stw停止事件 G1收集器 当今收集器技术发展最前沿的成果它是一款面向服务端应用的收集器它能充分利用多CPU、多核环境。因此它是一款并行与并发收集器并且它能建立可预测的停顿时间模型
三、JVM调优
何为性能调优
根据项目需求对JVM规划和预调优优化运行程序中JVM导致的卡顿解决JVM运行中的各种问题
JVM调优参数
-Xms 设置 JVM堆内存初始空间大小如-Xms2g
-Xmx 设置 JVM堆内存最大空间大小如-Xmx2g
-Xmn 设置 JVM新生代空间大小-Xmn1g
-XX:UseG1GC 使用G1垃圾回收器
-XX:SurvivorRatio8 设置survivor与eden的比例(1:8)
-XX:NewRatio4 设置新生代和老年代的比例(1:4)排查线上OOM 产生OOM的原因 一次性申请的对象太多 解决思路更改申请对象的数量 内存资源耗尽未释放 解决思路找到未释放的对象进行释放 本身内存资源不够 解决思路使用命令jmap -heap [pid] 查看堆内存信息 可以通过jpsJavaVirtualMachine Process Status JVM进程状态查看java进程id 示例 [rootiZ7xvjebghrlinlgmirqudZ ~]# jmap -heap 669
Attaching to process ID 669, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.74-b02using thread-local object allocation.
Mark Sweep Compact GCHeap Configuration:MinHeapFreeRatio 40 #最小堆使用比例MaxHeapFreeRatio 70 #最大堆可用比例MaxHeapSize 1073741824 (1024.0MB) #最大堆空间大小NewSize 89456640 (85.3125MB) #新生代分配大小MaxNewSize 357892096 (341.3125MB) #最大新生代可分配大小OldSize 178978816 (170.6875MB) #老年代分配大小NewRatio 2 #survivor区与eden区比例survivorSurvivorRatio 8 #survivor区与eden区比例edenMetaspaceSize 21807104 (20.796875MB) #元空间方法区大小CompressedClassSpaceSize 1073741824 (1024.0MB) MaxMetaspaceSize 17592186044415 MB #最大元空间大小G1HeapRegionSize 0 (0.0MB) #G1单个Region大小Heap Usage: #堆使用情况
New Generation (Eden 1 Survivor Space): #新生代capacity 80609280 (76.875MB) #总容量used 65509680 (62.47489929199219MB) #已使用free 15099600 (14.400100708007812MB) #剩余容量81.26816168064025% used #使用占比
Eden Space: #eden区capacity 71696384 (68.375MB)used 61261624 (58.42363739013672MB)free 10434760 (9.951362609863281MB)85.44590477533707% used
From Space: #from survivorcapacity 8912896 (8.5MB)used 4248056 (4.051261901855469MB)free 4664840 (4.448738098144531MB)47.661904727711395% used
To Space: #to survivorcapacity 8912896 (8.5MB)used 0 (0.0MB)free 8912896 (8.5MB)0.0% used
tenured generation: #老年代capacity 178978816 (170.6875MB)used 41792568 (39.85649871826172MB)free 137186248 (130.83100128173828MB)23.350566806744325% used30639 interned Strings occupying 3078128 bytes.另外可以通过命令jmap -histo:live [pid] | more 查看JVM运行时最耗费资源的对象 如何定位以及解决办法 系统已经发生OOM到导致宕机了 提前设置内存溢出记录 Dump 文件是 Java 进程的内存镜像其中主要包括 系统信息、虚拟机属性、完整的线程 Dump、所有类和对象的状态 等信息 java -XX:HeapDumpOnOutOfMemoryError -XX:HeapDumpPath/temp/xxx.hprof -jar xxx.jar#JVM运行时导出Dump
jmap -dump:file[文件路径] [pid]当内存溢出发生时可以根据生成的dump文件进行分析快速定位问题所在 导出dump文件JVM内存快照 jamp -dump:file[文件路径] [pid] 可能造成一次fullGC和一次STW 将导出的hhprof类型文件导入jvisualvmjava自带在jdk的bin目录下 结合jvisualvm进行调试 查看最多跟业务有关对象-找对GCRoot-查看线程栈。根据所得信息对代码进行优化 以上内容收集整理于网络如有疏漏和错误的地方欢迎指正~