Jvm原理
JDK
Java开发工具包
JRE
Java标准实现和java类库
JVM
Java虚拟机
Jvm组成
Java文件
.java
Class文件
.class
Javac编译
类加载子系统
classLoader
运行时数据区
直接内存区
直接内存(Direct Memory)并不是虚拟
机运行时数据区的一部分,也不是Java
虚拟机中定义的内存区域。但是这部分
内存也被频繁地使用,而且也可能导致
OutOfMemoryError 异常出现
直接内存是基于物理内存和Java虚拟机
内存的中间内存
1.Unsafe类
使用Java的Unsafe类,做一些内地内存
的操作。
2.Netty操作直接内存
(Direct Memory),底层会调用操作系
统的 malloc 函数。
3.JNI和JNA程序
有过不同语言间通信经历的一般都知
道,它允许Java代码和其他语言(尤其
C/C++)写的代码进行交互,只要遵守调
用约定即可。首先看下JNI调用C/C++的
过程,注意写程序时自下而上,调用时
自上而下。
直接内存主要是通过 DirectByteBuffer 申
请的内存,可以使用参数
“MaxDirectMemorySize”来限制它的大
小
方法区
行时常量池
类信息
静态变量
堆(heap)
对象
数组
虚拟机栈
每个方法在执行的同时都
会在Java 虚拟机栈中创建
一个栈帧(StackFrame)用
于存储局部变量表、操作
数栈、动态链接、方法出
口等信息;虚拟机栈是服务
Java方法的
本地方法栈
本地方法栈是为虚拟机调
用 Native 方法服务的
Native 关键字修饰的方法
是看不到的,Native 方法
的源码大部分都是 C和C++
的代码
程序计数器
程序计数器是一块较小的内存空间,它可以看作是:保存当前
线程所正在执行的字节码指令的地址,当前线程执行的行号指
示器,字节码解析器的工作是通过改变这个计数器的值,来选
取下一条需要执行的字节码指令,分支、循环、跳转、异常处
理、线程恢复等基础功能,都需要依赖这个计数器来完成
为什么要线程计数器?因为线程是不具备记忆功能
线程共享 线程私有线程共享 线程私有
执行引擎
即时编译器 垃圾回收器
Jvm的组成
两个子系统
两个组件
本地库接口 本地方法库
JVM包含两个子系统和两个组件: 两个子系统为Class loader(类装载)、Execution engine(执行引擎); 两个组件为
Runtime data area(运行时数据区)、Native Interface(本地接口)。
• Class loader(类装载):根据给定的全限定名类名(如:java.lang.Object)来装载class文件到Runtime data area中的
method area。
• Execution engine(执行引擎):执行classes中的指令。
• Native Interface(本地接口):与native libraries交互,是其它编程语言交互的接口。
• Runtime data area(运行时数据区域):这就是我们常说的JVM的内存。
流程 :首先通过编译器把 Java 代码转换成字节码,类加载器(ClassLoader)再把字节码加载到内存中,将其放在运
行时数据区(Runtime data area)的方法区内,而字节码文件只是 JVM 的一套指令集规范,并不能直接交给底层操作
系统去执行,因此需要特定的命令解析器执行引擎(Execution Engine),将字节码翻译成底层系统指令,再交由
CPU 去执行,而这个过程中需要调用其他语言的本地库接口(Native Interface)来实现整个程序的功能。
堆
Eden
幸存者区from
幸存者区to
老年区
新
生
区
元空间
虚拟机栈
.
.
.
A方法(栈帧)
局部变量表
动态链接
方法出口
操作数栈
由于Java虚拟机的多线程是通过线程轮流切换并分配处理器执
行时间的方式来实现的,一个处理器都只会执行一条线程中的
指令。因此,为了线程切换后能恢复到正确的执行位置,每条
线程都有一个独立的程序计数器,各个线程之间计数器互不影
响,独立存储。称之为“线程私有”的内存。程序计数器内存
区域是虚拟机中唯一没有规定OutOfMemoryError情况的区域。
1. Java虚拟机是线程私有的,它的生命周期和线程相同。
2. 虚拟机栈描述的是Java方法执行的内存模型: 每个方法在执行的同时 都
会创建一个栈帧(StackFrame)用于存储局部变量表、操作数栈、动态链
接、方法出口等信息。
解释:虚拟机栈中是有单位的,单位就是栈帧,一个方法一个栈帧。
一个栈帧中他又要存储,局部变量,操作数栈,动态链接,出口等。
解析栈帧:
1. 局部变量表:是用来存储我们临时8个基本数据类型、对象引用地址、
returnAddress类型。(returnAddress中保存的是return后要执行的字节码的指令
地址。)
2. 操作数栈:操作数栈就是用来操作的,例如代码中有个 i = 6*6,他在一开始
的时候就会进行操作,读取我们的代码,进行计算后再放入局部变量表中去
3. 动态链接:假如我方法中,有个 service.add()方法,要链接到别的方法中
去,这就是动态链接,存储链接的地方。
4. 出口:出口是什呢,出口正常的话就是return 不正常的话就是抛出异常落
为什么要使用直接内存
直接内存,其实就是不受 JVM 控制的内存。相比于堆内存有几个优
势:
• 减少垃圾回收,因为垃圾回收会STW
• 加快了复制速度。当我们进行IO时,会复制一份数据到堆外内
存再发送。直接使用直接内存省略了这一步。
• 可以实现进程数据共享,减少JVM间的对象复制,使得JVM的
分割部署更容易实现。
• 可以扩展内存。
直接内存的另一面
说白了就是缺点:
• 难以排查的OOM。
• 不适合存储复杂对象,一般来说简单对象比较适合。
新生区 ( Young ) = 1/3 的堆空间大小,老年区 ( Old ) = 2/3 的堆空间大小
Edem 和两个Survivor 区域比例是 = 8 : 1 : 1
但是JVM 每次只会使用 Eden 和其中的一块 Survivor 区域来为对象服务,所以无论什么时
候,总是有一块 Survivor 区域是空闲着的(即幸存者to区)。
类加载器本身是一个类,它的工作是
将class文件从硬盘读取到内存中。
类加载的五个阶段:加载、验证、准
备、解析、初始化
类加载器有四种:自定义类加载器、
系统类加载器、扩展类加载器、启动
类加载器
#常用的JVM调优参数
• -Xms:初始堆大小,JVM 启动的时候,给定堆空间大小。
• -Xmx:最大堆大小,JVM 运行过程中,如果初始堆空间不足的时候,最大可以扩展到多少。
• -Xmn:设置堆中年轻代大小。整个堆大小=年轻代大小+年老代大小+持久代大小。
• -XX:NewSize=n 设置年轻代初始化大小大小
• -XX:MaxNewSize=n 设置年轻代最大值
• -XX:NewRatio=n 设置年轻代和年老代的比值。如: -XX:NewRatio=3,表示年轻代与年老代比值为
1: 3,年轻代占整个年轻代+年老代和的 1/4 -XX:SurvivorRatio=n 年轻代中 Eden 区与两个 Survivor
区的比值。注意 Survivor 区有两个。8 表示两个Survivor :eden=2:8 ,即一个Survivor占年轻代的
1/10,默认就为8 -Xss:设置每个线程的堆栈大小。JDK5后每个线程 Java 栈大小为 1M,以前每个
线程堆栈大小为 256K。
• -XX:ThreadStackSize=n 线程堆栈大小
• -XX:PermSize=n 设置持久代初始值
• -XX:MaxPermSize=n 设置持久代大小
• -XX:MaxTenuringThreshold=n 设置年轻带垃圾对象最大年龄。如果设置为 0 的话,则年轻代对象不
经 过 Survivor 区,直接进入年老代。