JVM 内存分析

一、JVM 与操作系统的关系
1、JVM 把 .class 文件翻译成操作系统能识别的 2 进制数据进行执行。
2、JVM跨平台性:假设我们写一个HelloWord.java程序,它即可以在Linux平台上运行,又可以在Windows平台运行,这个主要得益于 JDK的版本有Linux 和 Windows。
3、JVM跨语言:Kotlin 可以运行在JVM上面,还有groovy 语言等都是运行在JVM上。
在这里插入图片描述
二、运行时数据区
在这里插入图片描述

1、运行时数据区:Java虚拟机在执行程序的过程中,会把它所管理的内存分为如干个不同的数据区域。有程序计数器、虚拟机栈、本地方法栈、Java堆、方法区、直接内存。
2、程序计数器:程序计数器指向当前线程正在执行的字节码指令的地址,大致认为记录当前线程执行字节码的行号,如下图所示

public class Person {
    public int work(){ // work 整体运行打包成一个栈帧
        int x = 1;// x 为一个局部变量
        int y = 2;
        int z = (x + y) * 10;
        return z;
    }
    public static void main(String[] args) {
        Person person = new Person();// person 一个引用,new Person() 对象
        person.work();
    }
}

在这里插入图片描述
为什么需要程序计数器,就是因为CPU执行操作方式是时间片轮转机制,CPU切出时需要记录当前线程执行的位置。
程序计数器是JVM 中唯一不会OOM 区域,因为它就是记录一个字节码的地址值,int 类型肯定可以存放的下。
3、虚拟机栈:存储当前线程运行方法所需要的数据,是线程私有的,多个线程就有多个虚拟机栈。
虚拟机栈是由栈帧组成,一个方法就是一个栈帧
栈帧里面包含:局部变量表、操作数栈、动态链接、完成出口。
虚拟机栈的内存默认是 1 M,大小配置参数是 -Xss
A、局部变量表:只能存储 8 基础类型+和引用
B、操作数栈:存放方法的执行操作,如下图所示
在这里插入图片描述
C、动态链接:Java语言有多态,分为静态分派 和 动态分派
Person king = new Man();
king.wc();
king = new Woman();
king.wc();
上面代码写在一个方法中,在编译的时候,无法确定上男厕所还是女厕所,在代码执行的时候,栈帧里面有一个动态链接来确定是上那个厕所。(把运行时期的符号引用变成具体引用)
D、完成出口(返回地址):出栈的时候,会带着返回值返回,返回到调用的地方。
E、本地方法栈:存放native 方法的,目前Hotspot 直接把本地方法栈 和 虚拟机栈合二为一
F、方法区
方法区用来存放类信息,也就是class,在所有的程序运行时,首先会通过ClassLoader 把 class 加载到方法区中。
常量、静态变量
G、堆:几乎所有的对象实例(有些对象存放在栈中,逃逸分析技术后面会讲),数组
方法区和堆都是线程共享为什么不放在同一块区域?也就是不用划分的这么细。因为:堆中间存放的是对象和数组,这个区域是要不停的回收的,而方法区存放的是类、常量、静态变量他们要回收的难度是非常大的,这是一种动静分类的思想,有利于垃圾回收器进行回收。
堆的大小参数设置:-Xmx 堆区域中可被分配的最大上限,-Xms 堆区域中初始内存分配的大小
H、直接内存:不是虚拟机运行时数据区的一部分,也不是java虚拟 机规范中定义的内存区域。(默认与堆内存最大值一样),所以也会出现OOM
三、JAVA可视化工具查看内存
HSDB位于\Java\jdk1.8.0_212\lib里面,接下来启动HSDB
java -cp .\sa-jdi.jar sun.jvm.hotspot.HSDB
jps 查看当前有哪些Java进程在运行
栈中地址如下图所示,选中main,然后点击菜单的 Stack Memory在这里插入图片描述
堆中的地址如下图, 可以看出堆中划分为 Eden、from、to、老年代。具体打开方式是 tools–> heap parameteras
在这里插入图片描述
总结:栈内存归属于单个线程,每个线程都会有一个栈内存,对外部线程不可见。堆内存中的对象对所有线程可见。栈内存要远远小于堆内存,栈的深度是有限的有可能发生StackOverFlowError问题。
四、代码演示
栈也会OutOfMemory,假设有 1000 个线程同时跑(虚拟机栈占用 1 M内存),但是机器只有 500 M ,此时就会抛出 OutOfMemory的异常。总之就是去申请的时候,发现机器没有了。
堆溢出:

/**
 * VM Args:-Xms30m -Xmx30m -XX:+PrintGCDetails 此参数是打印垃圾回收
 * 堆内存溢出(直接溢出)
 */
public class HeapOom {
   public static void main(String[] args)
   {
       byte[] bytes = new byte[35*1000*1000];  //35m的数组(堆)
   }
}
/**
 *  VM Args:-Xms30m -Xmx30m -XX:+PrintGC    堆的大小30M
 * 造成一个堆内存溢出(分析下JVM的分代收集)
 */
public class HeapOom2 {
   public static void main(String[] args) {
       //GC ROOTS
       LinkedList<Object> list = new LinkedList<>();
       int i = 0;
       while (true){
           i++;
           if (i % 10000 == 0) System.out.println("打印日志 : " + i);
           list.add(new Object());
       }
   }
}

直接内存溢出案例

/**
 * VM Args:-XX:MaxDirectMemorySize=100m
 * 堆外内存(直接内存溢出)
 */
public class DirectOom {
    public static void main(String[] args) {
        //直接分配128M的直接内存(100M)
        ByteBuffer bb = ByteBuffer.allocateDirect(128*1024*1204);
    }
}

JVM优化技术,方法内联:实际就是在编译的时候,已经确定了这个值,就可以把这个方法内的值,移到上面去,避免多一次调用(把目标方法原封不动,复制到调用方法中去)实例代码如下:

/**
 * 方法内联
 */
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;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值