一 、jvm 内存模型
JVM模型图.jpg作用:JVM不仅承担了Java字节码的分析(JIT compiler)和执行(Runtime),同时也内置了自动内存分配管理机制。
1 、 堆(Heap)
堆是 JVM 内存中最大的一块内存空间,该内存被所有线程共享,几乎所有对象和数组都被分配到了堆内存中
2、程序计数器(Program Counter Register)
程序计数器是一块很小的内存空间,主要用来记录各个线程执行的字节码的地址,例如,分支、循环、跳转、异常、线程恢复等都依赖于计数器。
3、方法区(Method Area)
方法区主要是用来存放已被虚拟机加载的类相关信息,
包括类信息、运行时常量池、字符串常量池。
类信息又包括了类的版本、字段、方法、接口和父类等信息。
方法区与堆空间类似,也是一个共享内存区,所以方法区是线程共享的。
假如两个线程都试图访问方法区中的同一个类信息,而这个类还没有装入 JVM,那么此时就只允许一个线程去加载它,另一个线程必须等待。
4、虚拟机栈(VM stack)
线程私有的内存空间,和线程一起创建,
当创建一个线程时,虚拟机栈中申请一个线程栈,用来保存方法的局部变量、操作数栈、动态链接方法和返回地址等信息,并参与方法的调用和返回。
每一个方法的调用都伴随着栈帧的入栈操作,方法的返回则是栈帧的出栈操作。
5、本地方法栈(Native Method Stack)
Java 虚拟机栈用于管理 Java 函数的调用,而本地方法栈则用于管理本地方法的调用。
本地方法由 C 语言实现的。
二 、JVM的运行原理
1 、JVM 向操作系统申请内存,通过配置参数或者默认配置参数向操作系统申请内存空间,
根据内存大小找到具体的内存分配表,然后把内存段的起始地址和终止地址分配给 JVM,接下来 JVM 就进行内部分配。
2 、JVM 获得内存空间后,会根据配置参数分配堆、栈以及方法区的内存大小。
3 、class文件加载、验证、准备以及解析,其中准备阶段会为类的静态变量分配内存,初始化为系统的初始值
4 、初始化阶段,JVM首先会执行构造器方法,编译器会在.java文件被编译成.class 文件时,
收集所有类的初始化代码,包括静态变量赋值语句、静态代码块、静态方法,收集在一起成为方法。
5 、 执行方法、堆内存中会创建一个对象,对象引用存放在栈中。
三 、 类编译加载执行过程
类编译加载执行过程.jpg1、类编译
.java文件变成.class文件,编译后的字节码文件主要包括常量池和方法表集合这两部分
2 、类加载
不同的实现类由不同的类加载器加载,
JDK 中的本地方法类一般由根加载器(Bootstrp loader)加载进来,
JDK 中内部实现的扩展类一般由扩展加载器(ExtClassLoader )实现加载,
而程序中的类文件则由系统加载器(AppClassLoader )实现加载
在类加载后,class 类文件中的常量池信息以及其它数据会被保存到 JVM 内存的方法区中
3 、类连接
验证:验证类符合 Java 规范和 JVM 规范。
准备:为类的静态变量分配内存,初始化为系统的初始值。
解析:将符号引用转为直接引用的过程(JVM 可以直接获取的内存地址或指针)。
4 、类初始化
JVM首先将执行构造器方法,
编译器会在将 .java 文件编译成 .class 文件时,
收集所有类初始化代码,包括静态变量赋值语句、静态代码块、静态方法,收集在一起成为构造方法
JVM 会保证构造方法的线程安全,保证同一时间只有一个线程执行。
5 、即时编译
在字节码转换为机器码的过程中,虚拟机中还存在着一道编译,那就是即时编译。
即时编译器(JIT)会把运行比较频繁的代码编译成与本地平台相关的机器码,
并进行各层次的优化,然后保存到内存中。
6、编译优化技术
1、方法内联
把目标方法的代码复制到发起调用的方法之中,避免发生真实的方法调用,减少时间和空间的消耗。
2 、逃逸分析
是判断一个对象是否被外部方法引用或外部线程访问的分析技术,编译器会根据逃逸分析的结果对代码进行优化。
栈上分配:
如果发现一个对象只在方法中使用,就会将对象分配在栈上;相比于在堆中分配内存,垃圾回收机制回收消耗性能与时间更少。
锁消除:
在局部方法中创建的对象只能被当前线程访问,无法被其它线程访问,
这个变量的读写肯定不会有竞争,这个时候 JIT 编译会对这个对象的方法锁进行锁消除。
标量替换:
逃逸分析证明一个对象不会被外部访问,将对象拆分后,可以分配对象的成员变量在栈或寄存器上,原本的对象就无需分配内存空间了
四、 垃圾回收机制
1、回收发生在哪里?
VM 的内存区域中,程序计数器、虚拟机栈和本地方法栈这 3 个区域是线程私有的,随着线程的创建而创建,销毁而销毁,因此这三个区域的内存分配和回收都具有确定性。
那么垃圾回收的重点就是堆和方法区中的内存,
堆中的回收主要是对象的回收,
方法区的回收主要是废弃常量和无用的类的回收。
2、对象在什么时候可以被回收?
一般一个对象不再被引用,就代表该对象可以被回收。
引用计数算法:当对象的引用计数器的值为 0 时,就说明该对象不再被引用,可以被回收了。
可达性分析算法:当一个对象到 GC Roots 没有任何引用链相连时,就证明此对象是不可用的(HotSpot 虚拟机采用的就是这种算法)。
3、 如何回收这些对象?
自动性:当 JVM 处于空闲循环时,垃圾收集器线程会自动检查每一块分配出去的内存空间,然后自动回收每一块空闲的内存块。
不可预期性: 垃圾回收线程在 JVM 中是自动执行的,Java 程序无法强制执行。只能通过System.gc方法建议回收。
4、GC算法
GC算法.jpg
5、GC性能衡量指标
吞吐量:这里的吞吐量是指应用程序所花费的时间和系统总运行时间的比值。
停顿时间:指垃圾收集器正在运行时,应用程序的暂停时间。
垃圾回收频率:适当地增大堆内存空间,保证正常的垃圾回收频率。
6、GC调优策略
1、 降低 新生代 GC( Minor GC) 频率
通常在虚拟机中,复制对象的成本要远高于扫描成本。
2、 降低 Full GC 的频率
减少创建大对象,增大堆内存空间。
7、选择合适的 GC 回收器
响应速度较快:CMS(Concurrent Mark Sweep)回收器和 G1 回收器
吞吐量高:Parallel Scavenge 回收器
在 JDK1.8 环境下,默认使用的是 Parallel Scavenge(年轻代)+Serial Old(老年代)垃圾收集器
堆外内存减少内存拷贝:
Socket 堆外内存:
使用ByteBuffer.allocateDirect()得到一个DirectByteBuffer对象,初始化堆外内存大小;
里面会创建Cleaner对象,绑定当前this.DirectByteBuffer的回收,通过put,get传递进去Byte数组,或者序列化对象;
Cleaner对象实现一个虚引用(当内存被回收时,会受到一个系统通知)当Full GC的时候,如果DirectByteBuffer标记为垃圾被回收,则Cleaner会收到通知调用clean()方法,回收堆外内存DirectByteBuffer。
内容源于极客时间app -《java性能调优实战》,侵删。
网友评论