(1)一个人只要自己不放弃自己,整个世界也不会放弃你.
(2)天生我才必有大用
(3)不能忍受学习之苦就一定要忍受生活之苦,这是多么痛苦而深刻的领悟.
(4)做难事必有所得
(5)精神乃真正的刀锋
(6)战胜对手有两次,第一次在内心中.
(7)好好活就是做有意义的事情.
(8)亡羊补牢,为时未晚
(9)科技领域,没有捷径与投机取巧。
(10)有实力,一年365天都是应聘的旺季,没实力,天天都是应聘的淡季。
(11)基础不牢,地动天摇
(12)写博客初心:成长自己,辅助他人。当某一天离开人世,希望博客中的思想还能帮人指引方向.
(13)编写实属不易,若喜欢或者对你有帮助记得点赞+关注或者收藏哦~
JVM内存管理深度剖析
文章目录
1.前言
(1)快速掌握JVM内存管理
(2)如何通过栈帧分析字节码的运行过程
(3)内存可视化工具之HSDB大揭秘
(4)架构师角度认识内存溢出
2.从底层深入理解运行时数据区
2.1JVM与操作系统的关系
(1)它是识别字节码.class.jar等文件
(2)由JVM调用操作系统函数 Linux、Windows、MacOS
(3)跨平台性
- 不是说一个JDK就可以跨很多平台,而是说Oracle官网上可以下载JDK的不同版本
(4)跨语言
- Kotlin 可以运行是JVM上面
2.2Java SE体系架构
(1)JVM只是一个翻译.
(2)JRE提供了基础类库
(3)JDK提供了工具
2.3JVM整体
(1)JVM的运行过程
- 首先java文件经过javac编译之后,生成class文件,经过Java类加载器ClassLoader,将其加载到运行时数据区中,即JVM所管理的内存。
- JVM有一个执行引擎,用于执行运行时数据区中的各个操作解释执行。
- 重点,运行时数据区。
- 解释执行就是字节码翻译成机器码,解释一行字节码就翻译成一行机器码,然后执行,效率很低
- JIT 热点跟踪数据,就是一个方法,要执行1000次或更多次数,JVM认为这种解释执行没有意义,效率很低,所以会将代码直接编译成本地代码。这样的数据就称为热点数据,热点代码,热点方法。
(2)JVM是一个博大精深的东西
2.4运行时数据区域
(1)会将管理的内存划分成若干个数据区域
- 线程共享的数据区
包含方法区与堆.
方法区用于存放class的静态变量和常量。
堆:几乎所有的对象都在堆中分配内存。
- 线程隔离的数据区(线程私有数据区)
假设是三个线程,在我的JVM运行时数据区里面,它属于线程私有的,它就会有三份。这三份里面每一份包含些什么东西,包含虚拟机栈,本地方法栈,程序计数器。
2.4.1程序计数器
(1)程序计数器,在多线程环境中要去指向当前线程正在执行的字节码指令的地址。
(2)命令行查看字节码
javap
javap -c D:\xiangxuedemo\ref-jvm\bin\com\jvm\ex1\Person.class
- 每一个行字节码都有一个行号,它与程序计数器的概念是差不多的
- 字节码:code行号。它是针对work()方法体的偏移量。
- 大体可以认为这个行号==程序计数器记录的字节码的地址。
- 7偏移到了9,说明一个比较大的东西偏移的比较多。
(3)为什么在JVM里面需要程序计数器?
- 因为有一个时间片轮转机制,它是操作系统层的。
- 而JVM是一个软件,它的级别比较低,有可能这个线程在跑的时候,会切出时间片,或者把它挂起,或者它需要执行另外一个时间片,不执行当前方法,所以需要一个程序计数器来确保JVM的正常执行。无论是单线程还是多线程。
- 在多线程环境还要考虑线程切换。
(4)程序计数器:JVM内存区域中,是唯一不会OOM,OutOfMemory的程序。因为它是一块很小的区域,它只需要去记录我们的地址,类似于int这样一个数据类型大小的区域。
2.4.2虚拟机栈
存储当前线程运行方法所需要的数据,指令、返回地址。
(1)栈的数据结构:First in last out.先进后出,即后进先出。
(2)类似于一把枪的子弹夹。最先打出去的是后面压进去的。
(3)包含栈帧
- 局部变量表
用来存储局部变量的,在一个方法里面总有很多局部变量,局部变量表只能存储8大基础数据类型和引用。
- 操作数栈
是用来存放方法的执行操作。
-
动态连接
Java里面有多态:包含静态分派、动态分派。 -
完成出口
返回地址(正常返回)
异常返回(异常处理表)
(4)大小限制 -Xss
- 栈的大小是有限制的,即子弹夹的大小是有限制的,可以压入的子弹是不同的。
- 方法栈就是一个方法放进来,执行完,就出栈。
(5)字节码在执行的过程中一定是有一个程序计数器来记录的。
(6)而虚拟机栈
(7)每一个方法会封装成一个栈帧,即一颗子弹,在线程中执行。
(8)方法执行后的返回值还是会放入操作数栈的,作为某个方法的返回值。
(9)在Java中解释执行是基于栈的,基于栈帧执行主要是基于操作数栈。而C语言是基于寄存器的。
- 优点与缺点是什么?
基于寄存器的会快一些,是因为寄存器是基于硬件的,但是移值性差,每次执行程序都需要(make install)
而基于栈的解释执行,兼容性好,但是效率偏低。
2.5栈帧执行对内存区域的影响
2.6本地(native)方法运行的内存区域
2.6.1本地方法栈
(1)本地方法栈保存的是native方法的信息
(2)当一个JVM创建的线程调用native方法后,JVM不再为其在虚拟机栈中创建栈帧,JVM只是简单地动态链接并直接调用native方法。
(3)虚拟机规范无强制规定,各版本虚拟机自由实现
(4)HotSpot直接把本地方法栈和虚拟机栈合二为一。
本地方法是由C语言来实现的。
2.7线程共享的区域
即无论你启动多少个线程,都只会存放到一块地方,这个地方就是线程共享的区域。
2.7.1方法区
(1)类信息
(2)常量
(3)静态变量
(4)即时 编译期编译后的代码
- 首先要把一个字节码文件通过类加载器加载到运行时数据区中的方法区。
- 常量和静态变量也会放到方法区。
- 即时编译期编译后的代码也会放到方法区。
2.7.2Java堆
(1)对象实例(几乎所有)
(2)数组
方法区与Java堆中的东东都是线程共享的,为什么不用一份呢?为什么用两个区分?
- 堆中存放的是对象、数组。是需要频繁的回收的。
- 而方法区中的内容要回收的难度是相当大的。
- 这体现的是一种动静分离的一种思想。
2.7.3Java堆的大小参数设置
(1)-Xmx 堆区内存可被分配的最大上限
(2)-Xms 堆区内存初始内存分配的大小
2.8直接内存
也称堆外内存。
不是虚拟机运行时数据区的一部分,也不是Java虚拟机规范中定义的内存区域。
- 如果使用了NIO,这块区域会被频繁使用,在java堆内存可以用directByteBuffer对象直接引用并操作。
- 这块内存不受java堆大小限制,但受本机总内存的限制,可以通过MaxDirectMemorySize来设置(默认与堆内存最大值一样),所以也会出现OOM异常。
2.9从底层深入理解运行时数据区
(1)向操作系统去申请内存
- 它会给栈、堆、方法区设置好大小。
(2)类加载
- class字节码进入方法区
(3)常量、静态变量入方法区
(4)虚拟机栈
- 入线帧。
- 在虚拟机栈中,压入一个方法的栈帧。
(5)方法栈帧执行。
(6)不断的入栈出栈
2.9.1垃圾收集器
它主要是回收堆
2.10 HSDB
(1)cmd中进入cd D:\Program Files\Java\jdk1.8.0_202\lib
(2)执行如下命令
java -cp .\sa-jdi.jar sun.jvm.hotspot.HSDB
(3)打开HSDB工具
它可以监控JVM内存的运行时消耗情况。
(4)通过jps命令去查看
- 首先要保证JVM进程已经运行
- jps命令类似于linux中的ps命令升级版本。它是查看进程相关的信息。
- 找到进程编号后,在HSDB工具中执行File - Attach to HotSpot process…
就会出现Java线程
可以查看main方法所占用的内存信息,也就是虚拟机栈中的信息。
也可以查看方法区中的信息
2.11深入辨析堆和栈
2.11.1功能
-
以栈帧的方式存储方法调用的过程,并存储方法调用过程中基本数据类型的变量(int、short、long、byte、float、double、boolean、char等)以及对象的引用变量,其内存分配在栈上,变量出了作用域就会自动释放;
-
而堆内存用来存储Java中的对象。无论是成员变量,局部变量,还是类变量,它们指向的对象都存储在堆内存中;
堆内存中的对象会一直存在,除非JVM这个线程结束了,堆和方法区才会释放。
2.11.2线程独享还是共享
-
栈内存归属于单个线程,每个线程都会有一个栈内存,其存储的变量只能在其所属线程中可见,即栈内存可以理解成线程的私有内存。
-
堆内存中的对象对所有线程可见。堆内存中的对象可以被所有线程访问。
2.11.3空间大小
- 栈的内存要远远小于堆内存,栈的深度是有限制的,可能发生StackOverFlowError问题。
2.12内存溢出
2.12.1栈溢出
public class StackOverFlow {
public void king(){//一个栈帧--虚拟机栈运行
king();//无穷的递归
}
public static void main(String[] args)throws Throwable {
StackOverFlow javaStack = new StackOverFlow(); //new一个对象
javaStack.king();
}
}
- 会报如下异常
Exception in thread “main” java.lang.StackOverflowError
2.12.2堆溢出
- 先设置运行虚拟机内存
/**
* @author XiongJie
* @version appVer
* @Package com.gdc.javabase.jvm.ex1.oom
* @file
* @Description: 堆内存溢出(直接溢出)
* (1)VM Args:-Xms30m -Xmx30m -XX:+PrintGCDetails
* @date 2021-4-26 16:56
* @since appVer
*/
public class HeapOom {
public static void main(String[] args){
String[] strings = new String[35*1000*1000]; //35m的数组(堆)
}
}
- 直接报Exception in thread “main” java.lang.OutOfMemoryError: Java heap space
/**
* @author XiongJie
* @version appVer
* @Package com.gdc.javabase.jvm.ex1.oom
* @file
* @Description:堆内存溢出
* (1)VM Args:-Xms30m -Xmx30m -XX:+PrintGC 堆的大小30M
* (2)造成一个堆内存溢出(分析下JVM的分代收集)
* (3)GC调优---生产服务器推荐开启(默认是关闭的)
* @date 2021-4-26 16:49
* @since appVer
*/
public class HeapOom2 {
public static void main(String[] args){
//GC ROOTS
List<Object> list = new LinkedList<>(); // list 当前虚拟机栈(局部变量表)中引用的对象 是1,不是走2
int i =0;
while(true){
i++;
if(i%10000==0) System.out.println("i="+i);
list.add(new Object());
}
}
}
- 执行后抛出Exception in thread “main” java.lang.OutOfMemoryError: GC overhead limit exceeded异常
2.12.3方法区溢出
import java.lang.reflect.Method;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodProxy;
import net.sf.cglib.proxy.MethodInterceptor;
/**
* @author XiongJie
* @version appVer
* @Package com.gdc.javabase.jvm.ex1.oom
* @file
* @Description:方法区内存溢出
* (1)cglib动态生成
* (2)Enhancer中 setSuperClass和setCallback,
* 设置好了SuperClass后, 可以使用create制作代理对象了。
* (3)限制方法区的大小导致的内存溢出
* VM Args: -XX:MetaspaceSize=10M -XX:MaxMetaspaceSize=10M
* @date 2021-4-26 17:00
* @since appVer
*/
public class MethodAreaOutOfMemory {
public static void main(String[] args) {
while (true) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(MethodAreaOutOfMemory.TestObject.class);
enhancer.setUseCache(false);
enhancer.setCallback(new MethodInterceptor() {
public Object intercept(Object arg0, Method arg1, Object[] arg2, MethodProxy arg3) throws Throwable {
return arg3.invokeSuper(arg0, arg2);
}
});
enhancer.create();
}
}
public static class TestObject {
private double a = 34.53;
private Integer b = 9999999;
}
}
- 执行后报:Exception in thread “main” java.lang.OutOfMemoryError: Metaspace
2.12.4本机直接内存(堆外内存)溢出
package com.gdc.javabase.jvm.ex1.oom;
import java.nio.ByteBuffer;
/**
* @author XiongJie
* @version appVer
* @Package com.gdc.javabase.jvm.ex1.oom
* @file
* @Description:堆外内存(直接内存溢出)
* VM Args:-XX:MaxDirectMemorySize=100m
* @date 2021-4-26 17:08
* @since appVer
*/
public class DirectOom {
public static void main(String[] args) {
//直接分配128M的直接内存(100M)
ByteBuffer bb = ByteBuffer.allocateDirect(128*1024*1204);
}
}
- 直接报Exception in thread “main” java.lang.OutOfMemoryError: Direct buffer memory
2.12.5解决方案
内存溢出就是内存空间不够了。
(1)栈溢出就有可能发生了死循环
(2)堆溢出就有可能是堆中有很多对象,堆放不下这么多对象。
(3)方法区溢出就是因为需要加载很多的东西
(4)而堆外溢出就是空间不够或者是虚拟机限制引起的,这个时候就去排查我们的参数,排查代码。
2.13虚拟机的优化技术
2.13.1编译优化技术
(1)方法内联
/**
* @author XiongJie
* @version appVer
* @Package com.gdc.javabase.jvm.ex1
* @file
* @Description: 虚拟机优化之方法内联
* (1)如果参数是确定的,虚拟机需要进行一次入栈,和一次出栈,封装成max栈帧。
* (2)如果参数确定就可以不用写个方法去比较,直接写 1 > 2,就减少一次入栈的操作。因为
* 代码执行要比方法入栈执行快得多。
* @date 2021-4-26 17:23
* @since appVer
*/
public class MethodDeal {
public static void main(String[] args) {
// max(1,2);//调用max方法: 虚拟机栈 --入栈(max 栈帧)
boolean i1 = 1>2;
}
public static boolean max(int a,int b){//方法的执行入栈帧。
return a>b;
}
}
2.13.2栈的优化技术
(1)线帧之间数据共享
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zdgSiB0c-1619432189589)(15.png)]
/**
* @author XiongJie
* @version appVer
* @Package com.gdc.javabase.jvm.ex1
* @file
* @Description: 虚拟机优化之栈帧之间数据的共享
* @date 2021-4-26 17:32
* @since appVer
*/
public class JVMStack {
public int work(int x) throws Exception{
int z =(x+5)*10;//局部变量表有
Thread.sleep(Integer.MAX_VALUE);
return z;
}
public static void main(String[] args)throws Exception {
JVMStack jvmStack = new JVMStack();
jvmStack.work(10);//10 放入main栈帧操作数栈
}
}
3.问题
(1)常量池是在方法区还是在堆?
- JDK1.8 运行时的常量池(字符串部分放入堆)。静态(class)放入方法区。
(2)加载新的类,JVM会清空前一个类的内容吗?
方法区:类。类会在哪个时候卸载?回收。
- 类 所有的实例,都要回收掉。
- 加载的该类的classload已经被回收
- 该类,java.lang.class对象,没有任何地方被引用。无法通过反射访问该类的方法。
可被回收,JVM参数控制。
4.打赏鼓励
感谢您的细心阅读,您的鼓励是我写作的不竭动力!!!