JVM
- 1 字节码篇
- 1.1 JVM概述
- 1.1.1 Java语言及Java生态圈
- 1.1.2 JVM架构与知识脉络图
- 1.2 字节码文件概述
- 1.2.1字节码文件—跨平台
- 1.2.2什么是字节码指令?
- 1.3 Class文件结构细节
- 1.4 官方反解析工具—javap
- 1.5 字节码指令集与解析
- 1.5.1 字节码与数据类型
- 1.5.2 指令分类
- 2 类的加载篇
- 2.1 类的加载过程(生命周期)
- 2.1.1 说说类加载分几步?
- 2.1.2 Loading阶段
- 2.1.3 Linking阶段:
- 2.1.4 Initialization阶段
- 2.1.5 类的使用
- 2.1.6 类的卸载
- 2.2 类的加载器
- 2.2.1 作用
- 2.2.2 类的加载是唯一的吗?
- 2.2.3 类加载机制的基本特征
- 2.3类的加载器分类
- 2.3.1 子父类加载器的关系
- 2.3.2 具体类的加载器介绍
- 2.3.3 用户自定义类加载器
- 2.4 ClassLoader源码分析
- 2.5 相关机制
- 2.5.1 双亲委派机制
- 2.5.2 沙箱安全机制
- 3 运行时内存篇
- 3.1 JVM内存布局
- 3.2 程序计数器
- 3.3 虚拟机栈
- 3.3.1 虚拟机栈概述
- 3.3.2 栈的单位—栈帧
- 3.3.3 栈的内部结构
- 3.4 本地方法栈
- 3.5 堆
- 3.5.1 堆的内部结构
- 3.5.2 如何设置堆内存大小
- 3.5.2 对象分配
- 3.5.3 MinorGC、MajorGC、FullGC
- 3.5.4 OOM如何解决
- 3.5.5 快速分配策略:TLAB
- 3.5.6 方法区
- 3.6 永久代与元空间
- 3.7 StringTable
- 4 对象布局篇
- 4.1 对象的实例化
- 4.2 对象的内存布局
- 4.3 对象的访问定位
- 5 执行引擎篇
- 5.1 执行引擎的作用
- 5.2 代码编译和执行的过程
- 5.3 JIT编译器
- 5.4 HotSpot VM执行方式
- 6 垃圾回收篇
- 6.1 概述
- 6.2 垃圾回收算法
- 6.2.1 垃圾判别阶段算法(垃圾标记阶段)
- 6.2.2 垃圾清除阶段算法
- 6.3 相关概念
- 6.3.1 System.gc()和finalize()方法详解
- 6.3.2 STW
- 6.3.3 垃圾回收的并行与并发
- 6.3.4 五种引用
- 6.4 垃圾回收器
- 6.4.1 GC分类
- 6.4.2 GC评估指标
- 6.4.3 垃圾回收器有哪些?
- 6.4.4 GC使用场景
- 7 性能监控篇
- 7.1 JVM监控及诊断工具—命令行
- 7.2 JVM监控及诊断工具—GUI
- 7.3 JVM参数
- 7.3.1 JVM类型选项参数
- 7.3.2 常用的JVM参数选项
- 8 性能调优篇
- 8.1 概述篇
- 8.2 OMM案例
- 8.2.1 堆溢出
- 8.2.2 元空间溢出
- 8.2.3 GC overhead limit exceeded
- 8.2.4 线程溢出
- 8.3 性能优化案例
- 8.3 性能优化案例
Oracle JDK与Open JDK的关系:OpenJDK完全开源免费,版本更新速度快,而Oracle JDK绝大部分功能开源免费,版本更新速度较慢
JDK JRE JVM之间的关系:
说说你认识的JVM
JVM的生命周期
重点说一下hotspot?
JVM架构图
最上层:类加载器系统,分为引导类加载器,扩展类加载器,系统类加载器,把javac编译器将编译好的字节码class文件,通过java 类装载器执行机制,把对象或class文件存放在 jvm划分内存区域。装载->链接(验证,准备,解析)->初始化
中间层:Runtime Data Area
最下层:执行引擎
JVM知识脉络
类加载->内存结构与分配<-(执行引擎)——>GC——>性能监控与调优
class文件里是什么?
字节码是一种二进制的类文件,它的内容是JVM的指令,而不像C、C++经由编译器直接生成机器码
介绍一下生成的class文件的编译器?(前端编译器)
哪些类型对应有Class的对象?class、interface、[]:数组、enum:枚举、annotation:注解@interface、primitive type:基本数据类型、void
Java虚拟机的指令由一个字节长度的、代表着某种特定操作含义的操作码(opcode)以及跟随其后的零至多个代表此操作所需参数的操作数(operand)所构成。虚拟机中许多指令并不包含操作数,只有一个操作码
魔数,Class文件版本,常量池,访问标识,类索引,父类索引,接口索引集合,字段表集合,方法表集合,属性表集合
魔数:class文件的标志
常量池:存放所有常量,可以理解为Class文件之中的资源仓库,它是Class文件结构中与其他项目关联最多的数据类型(后面的很多数据类型都会指向此处),也是占用Class文件空间最大的数据项目之一,常量池表项中,用于存放编译时期生成的各种字面量和符号引用
访问标识:在常量池后,紧跟着访问标记。该标记使用两个字节表示,用于识别一些类或者接口层次的访问信息,包括:这个 Class 是类还是接口;是否定义为 public 类型;是否定义为 abstract 类型;如果是类的话,是否被声明为 final 等
类索引,父类索引,接口索引集合
字段表集合:用于描述接口或类中声明的变量
方法表集合:用于表示当前类或接口中某个方法的完整描述
属性表集合:指的是class文件所携带的辅助信息
javap是jdk自带的反解析工具。它的作用就是根据class字节码文件,反解析出当前类对应的code区(字节码指令)、局部变量表、异常表和代码行偏移量映射表、常量池等信息
在Java虚拟机的指令集中,大多数的指令都包含了其操作所对应的数据类型信息。例如,iload指令用于从局部变量表中加载int型的数据到操作数栈中,而fload指令加载的则是float类型的数据
加载与存储指令、算术指令、类型转换指令、对象的创建与访问指令、方法调用与返回指令、操作数栈管理指令、控制转移指令、异常处理指令、同步控制指令
加载与存储指令:加载和存储指令用于将数据从栈帧的局部变量表和操作数栈之间来回传递。
算数指令:算术指令用于对两个操作数栈上的值进行某种特定运算,并把结果重新压入操作数栈
类型转换指令:类型转换指令可以将两种不同的数值类型进行相互转换
创建和访问指令:
创建指令:创建类实例的指令—new,创建数组的指令—newarray
字段访问指令:getstatic、putstatic、getfield、putfield
数组操作指令:iaload、iastore
类型检查指令
方法调用与返回指令:
操作数栈管理指令:pop,dup
控制转移指令
异常处理指令
同步控制指令
加载—>链接(验证、准备、解析)—>初始化
将Java类的字节码文件加载到机器内存中,并在内存中构建出Java类的原型——类模板对象。
为类的静态变量赋予正确的初始值。(显式初始化)
开发人员可以在程序中访问和调用它的静态类成员信息(比如:静态字段、静态方法),或者使用new关键字为其创建对象实例。
如果程序运行过程中,将上图左侧三个引用变量都置为null,此时Sample对象结束生命周期,MyClassLoader对象结束生命周期,代表Sample类的Class对象也结束生命周期,Sample类在方法区内的二进制数据被卸载
ClassLoader负责通过各种方式将Class信息的二进制数据流读入JVM内部,转换为一个与目标类对应的java.lang.Class对象实例(只负责loading过程)
对于任意一个类,都需要由加载它的类加载器和这个类本身一同确认其在Java虚拟机中的唯一性。每一个类加载器,都拥有一个独立的类名称空间:比较两个类是否相等,只有在这两个类是由同一个类加载器加载的前提下才有意义。否则,即使这两个类源自同一个Class文件,被同一个虚拟机加载,只要加载他们的类加载器不同,那这两个类就必定不相等
双亲委派模型。但不是所有类加载都遵守这个模型,有的时候,启动类加载器所加载的类型,是可能要加载用户代码的,比如JDK内部的ServiceProvider/ServiceLoader机制,用户可以在标准API框架上,提供自己的实现,JDK也需要提供些默认的参考实现。例如,Java 中JNDI、JDBC、文件系统、Cipher等很多方面,都是利用的这种机制,这种情况就不会用双亲委派模型去加载,而是利用所谓的上下文加载器。
可见性。子类加载器可以访问父加载器加载的类型,但是反过来是不允许的。不然,因为缺少必要的隔离,我们就没有办法利用类加载器去实现容器的逻辑。
单一性。由于父加载器的类型对于子加载器是可见的,所以父加载器中加载过的类型,就不会在子加载器中重复加载。但是注意,类加载器“邻居”间,同一类型仍然可以被加载多次,因为互相并不可见。(邻居间具有隔离性)
在程序中最常见的类加载器结构:
protected Class<?> loadClass(String name, boolean resolve) //resolve:true-加载class的同时进行解析操作。
throws ClassNotFoundException{
synchronized (getClassLoadingLock(name)) { //同步操作,保证只能加载一次。
//首先,在缓存中判断是否已经加载同名的类。
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
//获取当前类加载器的父类加载器。
if (parent != null) {
//如果存在父类加载器,则调用父类加载器进行类的加载
c = parent.loadClass(name, false);
} else { //parent为null:父类加载器是引导类加载器
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) { //当前类的加载器的父类加载器未加载此类 or 当前类的加载器未加载此类
// 调用当前ClassLoader的findClass()
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;
}
}
定义:如果一个类加载器在接到加载类的请求时,它首先不会自己尝试去加载这个类,而是把这个请求任务委托给父类加载器去完成,依次递归,如果父类加载器可以完成类加载任务,就成功返回。只有父类加载器无法完成此加载任务时,才自己去加载。
本质:规定了类加载的顺序是:引导类加载器先加载,若加载不到,由扩展类加载器加载,若还加载不到,才会由系统类加载器或自定义的类加载器进行加载。
破坏双亲委派机制:
双亲委派机制出现在JDK1.2之后,为了兼容该版本之前的用户已有的自定义类加载器代码,无法再以技术手段避免loadClass()被子类覆盖的可能性,只能在JDK1.2之后的java.lang.ClassLoader中添加一个新的protected方法findClass(),并引导用户编写的类加载逻辑时尽可能去重写这个方法,而不是在loadClass()中编写代码。
线程上下文类加载器:父类加载器去请求子类加载器完成类加载的行为,默认上下文加载器就是应用类加载器,这样以上下文加载器为中介,使得启动类加载器中的代码也可以访问应用类加载器中的类。
是由于用户对程序动态性的追求而导致的。如:代码热替换(Hot Swap)、模块热部署(Hot Deployment)等
Tomcat 如何实现自己独特的类加载机制?
Tomcat的类加载机制是违反了双亲委托原则的,对于一些未加载的非基础类,各个web应用自己的类加载器(WebAppClassLoader)会优先查看自己的仓库加载,加载不到时再交给commonClassLoader走双亲委托。
从图中的委派关系中可以看出:
违背双亲委派原因:
双亲委派模型要求除了顶层的启动类加载器之外,其余的类加载器都应当由委派自己的父类加载器加载。很显然,tomcat 不是这样实现,tomcat 为了实现隔离性,没有遵守这个约定,每个webappClassLoader加载自己的目录下的class文件,不会传递给父类加载器。
沙箱机制就是将Java代码限定在虚拟机(JVM)特定的运行范围中,并且严格限制代码对本地系统资源访问。通过这样的措施来保证对代码的有限隔离,防止对本地系统造成破坏。
基本特征:
作用:
Java 中堆和栈有什么区别?
如何设置栈的大小?-Xss size (即:-XX:ThreadStackSize)
一般默认为512k-1024k,取决于操作系统。栈的大小直接决定了函数调用的最大可达深度。
方法和栈桢之间存在怎样的关系?
局部变量表:定义为一个数字数组,主要用于存储方法参数和定义在方法体内的局部变量,这些数据类型包括各类基本数据类型(8种)、对象引用(reference),以及returnAddress类型。
局部变量表不存在线程安全问题,因为虚拟机栈是线程私有的
Slot槽位是可以重复利用的
静态变量与局部变量的对比:静态变量在准备阶段和初始化阶段能够被两次初始化,而局部变量必须手动赋值,否则会报错
局部变量表中的变量是重要的垃圾回收根节点,只要被局部变量表中直接或间接引用的对象都不会被回收。
操作数栈:
动态链接:
方法返回地址
一个JVM实例只存在一个堆内存,堆也是Java内存管理的核心区域。
Java 堆区在JVM启动的时候即被创建,其空间大小也就确定了。是JVM管理的最大一块内存空间。
堆内存的大小是可以调节的。
《Java虚拟机规范》规定,堆可以处于物理上不连续的内存空间中,但在逻辑上它应该被视为连续的。
堆,是GC ( Garbage Collection,垃圾收集器)执行垃圾回收的重点区域。
在方法结束后,堆中的对象不会马上被移除,仅仅在垃圾收集的时候才会被移除。
现代垃圾收集器大部分都基于分代收集理论设计,堆空间细分为:
新生代和老年代:
通常会将 -Xms 和 -Xmx两个参数配置相同的值
,其目的是为了能够在java垃圾回收机制清理完堆区后不需要重新分隔计算堆区的大小,从而提高性能
设置新生代与老年代的比例:
设置Eden、幸存者比例:
参数设置小结
:
-Xms -Xmx:初始内存 (默认为物理内存的1/64;最大内存(默认为物理内存的1/4)
-Xmn:设置新生代的大小。(初始值及最大值)
-XX:NewRatio:默认-XX:NewRatio=2,表示新生代占1,老年代占2,新生代占整个堆的1/3
-XX:SurvivorRatio:Eden空间和另外两个Survivor空间缺省所占的比例是8:1
-XX:MaxTenuringThreshold:设置新生代垃圾的最大年龄。超过此值,仍未被回收的话,则进入老年代,默认值为15
-XX:+PrintGCDetails:输出详细的GC处理日志
-XX:HandlePromotionFailure:在发生Minor GC之前,虚拟机会检查老年代最大可用的连续空间是否大于新生代所有对象的总空间(空间分配担保策略
)
如果大于,则此次Minor GC是安全的
如果小于,则虚拟机会查看-XX:HandlePromotionFailure设置值是否允许担保失败。
如果HandlePromotionFailure=true,那么会继续检查老年代最大可用连续空间是否大于历次晋升到老年代的对象的平均大小,如果大于,则尝试进行一次Minor GC,但这次Minor GC依然是有风险的;如果小于或者HandlePromotionFailure=false,则改为进行一次Full GC。
对象分配过程:
内存分配策略:
如果对象在Eden 出生并经过第一次MinorGC 后仍然存活,并且能被Survivor 容纳的话,将被移动到Survivor 空间中,并将对象年龄设为1 。对象在Survivor 区中每熬过一次MinorGC , 年龄就增加1岁,当它的年龄增加到一定程度(默认为15 岁,其实每个JVM、每个GC都有所不同)时,就会被晋升到老年代中。
内存分配原则:
针对不同年龄段的对象分配原则如下所示:
概述:
年轻代GC(Minor GC
)触发机制:
STW
,暂停其它用户的线程,等垃圾回收结束,用户线程才恢复运行。老年代GC(Major GC/Full GC
)触发机制:
Full GC
触发机制:
调用System.gc()时,系统建议执行Full GC,但是不必然执行
老年代空间不足
方法区空间不足
通过Minor GC后进入老年代的平均大小大于老年代的可用内存
由Eden区、survivor space0(From Space)区向survivor space1(To Space)区复制时,对象大小大于To Space可用内存,则把该对象转存到老年代,且老年代的可用内存小于该对象大
为什么需要快速分配策略?
什么是TLAB?
JVM为每个线程分配了一个私有缓存区域,它包含在Eden空间内。
用于存储已被虚拟机加载的类型信息、域信息、方法信息、常量、静态变量、即时编译器编译后的代码缓存等。
常量池有什么?
HotSpot中永久代的变化:
元空间1.8使用的是本地内存,不再是占用JVM的内存
静态变量存放在堆中
方法区存在GC吗?回收的都是什么?
方法区的垃圾收集主要回收两部分内容:常量池中废弃的常量和不再使用的类型。
常量池中废弃的常量
不再使用的类型:类被卸载
String的实例化方式:
String intern()
方法:是当前的字符对象(通过new出来的对象)可以使用intern方法从常量池中获取,如果常量池中不存在该字符串,那么就新建一个这样的字符串放到常量池中。
public class StringTest4 {
public static void main(String[] args) {
String s = new String("1");
s.intern();
String s2 = "1";
System.out.println(s == s2);//
String s3 = new String("1") + new String("1");
s3.intern();
String s4 = "11";
System.out.println(s3 == s4);//
}
}
JDK1.6:false;false
JDK1.7:false;true(字符串常量池从方法区搬到了堆中)
创建对象的方法:
对象的创建过程:
对象头
实例数据
padding对齐填充
执行引擎(Execution Engine)的任务就是将字节码指令解释/编译为对应平台上的本地机器指令才可以。
javac.exe
的执行:Java代码编译是由Java源码编译器来完成解释器的工作机制:
解释器真正意义上所承担的角色就是一个运行时“翻译者”,将字节码文件中的内容“翻译”为对应平台的本地机器指令执行。从这个角度说,java是解释语言。
当一条字节码指令被解释执行完成后,接着再根据PC寄存器中记录的下一条需要被执行的字节码指令执行解释操作。
为什么说Java是半编译半解释型语言?
Java代码的执行分类:
现在JVM在执行Java代码的时候,通常都会将解释执行与编译执行二者结合起来进行。
JIT(Just In Time Compiler)编译器:就是虚拟机将源代码直接编译成和本地机器平台相关的机器语言
为什么还保留解释器执行方式?
尽管JRockit VM中程序的执行性能会非常高效,但程序在启动时必然需要花费更长的时间来进行编译。对于服务端应用来说,启动时间并非是关注重点,但对于那些看中启动时间的应用场景而言,或许就需要采用解释器与即时编译器并存的架构来换取一个平衡点。在此模式下,当Java虚拟器启动时,解释器可以首先发挥作用,而不必等待即时编译器全部编译完成后再执行,这样可以省去许多不必要的编译时间。随着时间的推移,编译器发挥作用,把越来越多的代码编译成本地代码,获得更高的执行效率。
解释器与JIT编译器并存的架构:
-Xint
:完全采用解释器模式执行程序;-Xcomp
:完全采用即时编译器模式执行程序。如果即时编译出现问题,解释器会介入执行。-Xmixed
:采用解释器+即时编译器的混合模式共同执行程序。(默认情况)两个编译器:Client Compile
r和Server Compiler
-client
:指定Java虚拟机运行在Client模式下,并使用C1编译器;
-server
:指定Java虚拟机运行在Server模式下,并使用C2编译器。
什么是垃圾?
垃圾是指在运行程序中没有任何指针指向的对象,这个对象就是需要被回收的垃圾。
Java中垃圾回收的重点区域是?
垃圾回收器可以对年轻代回收,也可以对老年代回收,甚至是全堆和方法区的回收。
从次数上讲
引用计数算法
:对每个对象保存一个整型的引用计数器属性,用于记录对象被引用的情况。
可达性分析算法
标记清除算法
:产生内存碎片
复制算法
:将活着的内存空间分为两块,每次只使用其中一块,在垃圾回收时将正在使用的内存中的存活对象复制到未被使用的内存块中,之后清除正在使用的内存块中的所有对象,交换两个内存的角色,最后完成垃圾回收
标记压缩算法:从根节点开始标记所有被引用对象,然后将所有的存活对象压缩到内存的一端,按顺序排放,最后清理边界外所有的空间
分代收集算法:不同的对象的生命周期是不一样的。因此,不同生命周期的对象可以采取不同的收集方式,以便提高回收效率。
增量收集算法:如果一次性将所有的垃圾进行处理,需要造成系统长时间的停顿,那么就可以让垃圾收集线程和应用程序线程交替执行。每次,垃圾收集线程只收集一小片区域的内存空间,接着切换到应用程序线程。依次反复,直到垃圾收集完成。
分区算法:G1 GC使用的算法 将整个堆空间划分成连续的不同小区间。
6.3.1 内存溢出和内存泄漏
内存溢出:没有空闲内存,并且垃圾收集器也无法提供更多内存。
内存泄漏:只有对象不会再被程序用到了,但是GC又不能回收他们的情况,才叫内存泄漏。但实际情况很多时候一些不太好的实践(或疏忽)会导致对象的生命周期变得很长甚至导致OOM,也可以叫做宽泛意义上的“内存泄漏”。
Stop-the-World :简称STW,指的是GC事件发生过程中,会产生应用程序的停顿。停顿产生时整个应用程序线程都会被暂停,没有任何响应,有点像卡死的感觉,这个停顿称为STW。
安全点与安全区域:
串行vs并行:
并发式与独占式:
压缩式与非压缩式:
年轻代与老年代:工作的内存区间分,又可分为年轻代垃圾回收器和老年代垃圾回收器。
最为常见的组合:
Serial GC(新生代)与Serial Old GC(老年代)
ParNew GC(新生代)与CMS GC(老年代)
ParNew收集器则是Serial收集器的多线程版本
CMS(低延时):是HotSpot虚拟机中第一款真正意义上的并发收集器,它第一次实现了让垃圾收集线程与用户线程同时工作。
Parallel Scanvenge GC与 Parallel Old GC(吞吐量优先)
G1 GC:目标是在延迟可控的情况下获得尽可能高的吞吐量,担当起“全功能收集器”的重任与期望
为什么叫G1(Garbage First)
针对配备多核CPU及大容量内存的机器,以极高概率满足GC停顿时间的同时,还兼具高吞吐量的性能特征。
特点:
并行与并发:
分代收集:
空间整合:
可预测的停顿时间模型:能够设置最大的停顿时间
如何查看默认GC
类型一:标准参数 以-开头
类型二:-x
非标准化参数
JVM的JIT编译模式相关的选项
设置栈堆大小
类型三 -xx
非标准化参数:用于开发和调试JVM
打印设置的XX选项及值:例如-XX:+PrintFlagsFinal,表示打印出XX选项在运行程序时生效的值
堆、栈、方法区等内存大小设置:
栈 -Xss128k等价于-XX:ThreadStackSize=128k
堆
设置初始堆大小:-Xms3550m等价于-XX:InitialHeapSize,设置JVM初始堆内存为3550M
设置最大堆大小:-Xmx3550m等价于-XX:MaxHeapSize,设置JVM最大堆内存为3550M
设置年轻代:年轻代初始大小-Xmn1g等价于-XX:NewSize=1024m -XX:MaxNewSize=1024m
-XX:SurvivorRatio=8
-XX:+UseAdaptiveSizePolicy
-XX:NewRatio=4
-XX:PretenureSizeThreadshold=1024 设置让大于此阈值的对象直接分配在老年代,单位为字节
-XX:MaxTenuringThreshold=15 新生代每次MinorGC后,还存活的对象年龄+1,当对象的年龄大于设置的这个值时就进入老年代
永久代
元空间
/**
* 模拟线上环境OOM
*/
@RequestMapping("/add")
public void addObject(){
ArrayList<People> people = new ArrayList<>();
while (true){
people.add(new People());
}
}
@Data
public class People {
private String name;
private Integer age;
private String job;
private String sex;
}
-XX:+PrintGCDetails -XX:MetaspaceSize=60m -XX:MaxMetaspaceSize=60m -Xss512K -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=heap/heapdumpMeta.hprof -XX:SurvivorRatio=8 -XX:+TraceClassLoading -XX:+TraceClassUnloading -XX:+PrintGCDateStamps -Xms60M -Xmx60M -Xloggc:log/gc-oomMeta.log
元空间数据类型:用于存储已被虚拟机加载的类信息、常量、即时编译器编译后的代码等数据
报错信息:java.lang.OutOfMemoryError: Metaspace
-XX:+PrintGCDetails -XX:MetaspaceSize=60m -XX:MaxMetaspaceSize=60m -Xss512K -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=heap/heapdumpMeta.hprof -XX:SurvivorRatio=8 -XX:+TraceClassLoading -XX:+TraceClassUnloading -XX:+PrintGCDateStamps -Xms60M -Xmx60M -Xloggc:log/gc-oomMeta.log
/**
* 案例2:模拟元空间OOM溢出
*/
@RequestMapping("/metaSpaceOom")
public void metaSpaceOom(){
ClassLoadingMXBean classLoadingMXBean = ManagementFactory.getClassLoadingMXBean();
while (true){
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(People.class);
enhancer.setUseCache(false);
enhancer.setCallback((MethodInterceptor) (o, method, objects, methodProxy) -> {
System.out.println("我是加强类哦,输出print之前的加强方法");
return methodProxy.invokeSuper(o,objects);
});
People people = (People)enhancer.create();
people.print();
System.out.println(people.getClass());
System.out.println("totalClass:" + classLoadingMXBean.getTotalLoadedClassCount());
System.out.println("activeClass:" + classLoadingMXBean.getLoadedClassCount());
System.out.println("unloadedClass:" + classLoadingMXBean.getUnloadedClassCount());
}
}
public class People {
public void print(){
System.out.println("我是print本人");
}
}
原因:
解决办法
-XX:-UseGCOverheadLimit
禁用这个检查,其实这个参数解决不了内存问题,只是把错误的信息延后,最终出现 java.lang.OutOfMemoryError: Java heap space。性能优化案例1:调整堆大小提高服务的吞吐量
性能优化案例2:JVM优化之JIT优化
逃逸分析:通过逃逸分析,Java Hotspot编译器能够分析出一个新的对象的引用的使用范围,从而决定是否要将这个对象分配到堆上。
代码优化:
性能优化案例3:合理配置堆内存
性能优化案例3:CPU占用很高
性能优化案例4:调整垃圾回收器提高服务的吞吐量
.OutOfMemoryError : unable to create new native Thread
性能优化案例1:调整堆大小提高服务的吞吐量
性能优化案例2:JVM优化之JIT优化
逃逸分析:通过逃逸分析,Java Hotspot编译器能够分析出一个新的对象的引用的使用范围,从而决定是否要将这个对象分配到堆上。
代码优化:
性能优化案例3:合理配置堆内存
性能优化案例3:CPU占用很高
性能优化案例4:调整垃圾回收器提高服务的吞吐量