【32】JVM内存管理深度剖析

(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.打赏鼓励

感谢您的细心阅读,您的鼓励是我写作的不竭动力!!!

4.1微信打赏

在这里插入图片描述

4.2支付宝打赏

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
城市应急指挥系统是智慧城市建设的重要组成部分,旨在提高城市对突发事件的预防和处置能力。系统背景源于自然灾害和事故灾难频发,如汶川地震和日本大地震等,这些事件造成了巨大的人员伤亡和财产损失。随着城市化进程的加快,应急信息化建设面临信息资源分散、管理标准不统一等问题,需要通过统筹管理和技术创新来解决。 系统的设计思路是通过先进的技术手段,如物联网、射频识别、卫星定位等,构建一个具有强大信息感知和通信能力的网络和平台。这将促进不同部门和层次之间的信息共享、交流和整合,提高城市资源的利用效率,满足城市对各种信息的获取和使用需求。在“十二五”期间,应急信息化工作将依托这些技术,实现动态监控、风险管理、预警以及统一指挥调度。 应急指挥系统的建设目标是实现快速有效的应对各种突发事件,保障人民生命财产安全,减少社会危害和经济损失。系统将包括预测预警、模拟演练、辅助决策、态势分析等功能,以及应急值守、预案管理、GIS应用等基本应用。此外,还包括支撑平台的建设,如接警中心、视频会议、统一通信等基础设施。 系统的实施将涉及到应急网络建设、应急指挥、视频监控、卫星通信等多个方面。通过高度集成的系统,建立统一的信息接收和处理平台,实现多渠道接入和融合指挥调度。此外,还包括应急指挥中心基础平台建设、固定和移动应急指挥通信系统建设,以及应急队伍建设,确保能够迅速响应并有效处置各类突发事件。 项目的意义在于,它不仅是提升灾害监测预报水平和预警能力的重要科技支撑,也是实现预防和减轻重大灾害和事故损失的关键。通过实施城市应急指挥系统,可以加强社会管理和公共服务,构建和谐社会,为打造平安城市提供坚实的基础。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值