JVM基础知识介绍

为什么要JVM调优

  • 代码是运行在jvm中,而部署环境多样,每种环境采用默认配置可能运行的效率较差
  • 高并发的情况下,默认配置不足以支撑

JVM调优的目标

JVM调优目标:使用较小的内存占用来获得较高的吞吐量或者较低的延迟。

  • 延迟:由于垃圾收集而引起的程序停顿时间
  • 吞吐量:用户程序运行时间占用户程序和垃圾收集占用总时间的比值

JVM的组成

运行时数据区、类加载子系统、本地方法库、执行引擎

类加载子系统

类加载机制:当需要使用某个类时,虚拟机将会加载它的”.class”文件,并创建对应的class对象,将class文件加载到虚拟机的内存

类的加载过程

类加载到虚拟机过程:加载 验证 准备 解析 初始化 ----------------加载后:使用 卸载

  • 加载:将类的class文件读入内存中,并为之创建一个Class对象【Class类型】

​ 任何类被使用时,都会在堆内存中建立一个该类型的Class对象

​ 类的加载方式分为隐式加载和显示加载两种

​ 隐式加载:使用new关键词创建对象

​ 显示加载:直接调用Class.forName()方法

  • 验证:验证类是否符合java的语言规范,与其他类的兼容性(如final修饰的类不能被继承)

  • 准备:为类的静态成员变量分配内存空间,并设置默认初始值为0或null(如定义static int i=123,此时只会被赋值为0,123在接下来的初始阶段才会被赋值,但如果static final int i=123,则准备阶段i会被赋值为123)

  • 解析:把类中的符号引用变为直接引用。

    符号引用:方法名,对象名

    直接引用:内存地址

  • 初始化:静态代码块的执行、静态成员变量的初始化、普通成员变量的初始化

    当初始化一个类的时候发现其父类还没有初始化,需要先对父类进行加载

类加载器
  • 类加载器有四种:前三种必须有

    ①启动类加载器(BootStrap ClassLoader):加载jdk安装目录下的核心类

    ②扩展类加载器(Extension ClassLoader):加载jdk安装目录下的扩展类

    ③应用程序类加载器(Application ClassLoader):加载classpath下的类,如第三方jar包的类、自定义的类

    ④自定义加载器(User ClassLoader)

  • 不同类加载器加载同一个字节码文件,等到的类不同

  • 双亲委派机制:如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器去执行,如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终将到达顶层的启动类加载器,如果父类加载器可以完成类加载任务,就成功返回,倘若父类加载器无法完成此加载任务,子加载器才会尝试自己去加载

  • 双亲委派机制的优势:

    • 避免重复加载造成的混乱,同名的类父类加载成功,子类就不需要加载了
    • 安全,防止java的核心内库被修改。比如在calsspath下自定义一个java.lang.Object类,通过双亲委派机制最终会委托启动类加载器加载,而启动类加载器会在自己的加载路径下发现这个同名的类,就不会去加载classpath下的object类,直接返回自己加载路径下的object类

运行时数据区

运行时数据区组成:方法区、堆、虚拟机栈、本地方法栈、程序计数器

线程共享的数据区:方法区、堆

线程隔离的数据区:虚拟机栈、本地方法栈、程序计数器

程序计数器
  • 程序计数器:用于确定指令的执行顺序
  • 每个线程都有自己独立的程序计数器,程序计数器占用内存空间很小,但为了保证每个线程执行的顺序,JVM中唯一不会发生内存溢出的区域

OOM:out of memory 内存溢出

虚拟机栈

虚拟机栈中放栈帧

虚拟机会为每一个线程创建一个虚拟机栈,每个栈又存放了若干栈帧,每个方法执行都会创建一个栈帧,每个栈帧中存储了局部变量、操作数栈、方法返回地址、动态链接等。

虚拟机栈常见的两个错误:

  • StackOverFlowError:
  • OOM:OutofMemoryError
栈溢出 stack overflow

方法的运行是在栈中进行的,每调用一次方法就会从栈顶往栈底压一层栈帧,调用结束就从栈顶弹出,当方法递归层次过深时,栈的空间就无法再存放栈帧,导致溢出

本地方法栈
  • 本地方法栈和虚拟机栈的区别:

    本地方法栈执行的是本地方法(native修饰的方法,非java语言实现),虚拟机栈执行的是java方法

方法区
  • JDK1.7以前,方法区主要用于存储虚拟机加载的类信息、静态变量、常量以及编译器编译后的代码。
  • JDK1.7及其以后,静态变量、字符串常量池在存放在堆内存,类信息、除字符串常量池以外的常量池、编译器编译后的代码还在方法区中
  • JDK1.7及其以前,方法区是堆内存的一部分,是一片连续的存储空间,当时为了和堆进行区分,方法区也叫:非堆、永久代
  • JDK1.8开始,类信息、静态变量、编译后的代码等存放到元空间,元空间直接占用的本地内存(不是java虚拟机占用的内存),方法区已经不存在
  • 去永久代的原因:
    1. 字符串常量放在永久代中,多了以后容易影响性能和内存溢出
    2. 永久代中GC回收效率低,容易造成堆内存泄漏
常量池

常量池分为静态常量池和运行时常量池。

  1. 静态常量池:*.class文件中的常量池,主要包括字面量常量和符号引用量。
  • 字面量:字符串常量、final修饰的常量
  • 符号引用量:类和接口的完全限定名、字段名称和描述符、方法名和描述符
  1. 运行时常量池:类加载完成后,将class文件中的常量池载入到内存中,并保存在方法区中,此外运行期间也可以放入新的常量
堆内存
  • 堆主要存放对象、数组,jDK1.7开始存放字符串常量和静态变量,是垃圾回收器的主要区域。
  • 堆内存和方法区被所有线程共享
  • 堆内存在虚拟机启动的时候创建
  • 堆内存占用JVM内存最多

JDK1.8以前,堆被分为:新生代、老年带、永久代(方法区),JDK1.8开始永久代(方法区)不存在了,被元空间替代,但元空间存放在本地内存,不占用虚拟机所占用的内存

  • 新生代又分为Eden区和两个Survivor区

    Eden主要存放new或instance方法创建出来的 的对象。

    当经历一次GC后,对象就会被存入其中一个survivor区。

    当再默认经历15次GC后,对象会被放入另外一个survivor区。

堆内存溢出OOM

新创建的对象最初存放在新生代,新生代满了后进行一次GC,如果Minor GC后仍然空间不足,就会把该对象和新生代中满足条件的对象就会被放入老年代,老年代空间不足时就会进行Full GC,如果之后的空间仍然不足以存放新对象,就会抛出OutofMemoryError错误。

常见原因:

  • 内存中加载数据过多,如一次性从数据库获取大量数据
  • 集合对对象引用过多,且使用完后没有清空集合
  • 循环过程中产生过多对象
  • 堆内存分配过小
堆内存泄漏

申请堆内存使用完后,无法释放该空间,导致无法再次使用该空间。一次内存泄漏可以忽略,多次就会出问题

C/C++容易出现,java有自动回收机制

元空间
  • 元空间占用的是本地内存,不占用虚拟机所占用的内存
  • 元空间存储:类信息、编译器编译后的代码、除字符串常量池以外的常量池
JDK1.8和JDK1.7最大区别

元空间取代了永久代(方法区),元空间位于本地内存,永久代位于虚拟机的堆内存

元空间存储

执行引擎

执行引擎包含即时编译器(JIT)和垃圾回收器(GC)

即时编译器JIT

执行特别频繁的代码,会被即时编译器翻译为与本地平台相关的机器码

垃圾回收器GC

Xms:JVM启动时的初始堆内存

Xmx:JVM运行时的最大堆内存

Xss:栈的大小

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值