最简单的JVM内存结构图

JVM内存结构图


image.png

大家好,好几天没有更新了,今天的内容有点多,们详细介绍下JVM内部结构图,还是和之前一样,案例先行,方便大家理解记忆。

/**
 * @author :jiaolian
 * @date :Created in 2021-03-10 21:28
 * @description:helloworld测试jvm内存区域
 * @modified By:
 * 公众号:叫练
 */
public class HelloWorldTest {
    public static void main(String[] args) {
        //新建HelloWorldTest对象;
        HelloWorldTest helloWorldTest = new HelloWorldTest();
        //新建2个线程调用sayHello
        for (int i=0; i<2; i++) {
            new Thread(()->helloWorldTest.sayHello("world")).start();
        }
    }
    /**
     * 对某人说hello
     * @param who
     */
    public void sayHello(String who) {
        System.out.println(Thread.currentThread().getName()+"hello!"+who);
    }
} 

如上代码:在主线程中for循环新建2个线程调用sayHello,最后两个线程分别对世界问好!这段代码比较好理解,就不贴输出结果了。们编写并运行了这段代码,们主要看看这段代码在JVM中是怎么运作的。

首先,们编写一个HelloWorldTest.Java文件,经过javac编译 会转化成字节码HelloWorldTest.class,为什么要转化成字节码呢?因为Java虚拟机能识别!最后由类加载子系统ClassLoader将字节码装载到内存。每块内存各有自己的作用,最后由执行引擎来执行字节码。下面们重点介绍下各块内存发挥的作用!

image.png

方法区


方法区主要装一些静态信息,比如:类元数据,常量池,方法信息,类变量等。如上代码HelloWorldTest.class是类元数据,sayHello,main都是方法信息等都是放在方法区存储的。方法区中还需要注意两点:

  1. 如果方法区太大,超过设置,会报OutOfMemoryError:PermGen space错误。gclib工具可以动态生成类测试该错误。
  2. 在JDK1.7以前,方法区叫永久代,而 1.8之后叫元空间。原因是JDK1.8为了释放管理压力,把运行时常量池交给堆去管理。


堆中主要存放实例对象。你可以这么理解,只要看到用关键字new 的对象,数据都放在堆中。如上代码HelloWorldTest helloWorldTest = new HelloWorldTest();helloWorldTest是HelloWorldTest对象的引用,指向new出来的HelloWorldTest对象实例,helloWorldTest引用是放在栈中的,也叫局部变量方法内 申明的对象类型或普通类型),们简单画图来表示下堆,栈,方法区关系。当JVM执行了HelloWorldTest helloWorldTest = new HelloWorldTest();这句话,JVM内存结构看起来是这样的。如果指向对象引用消失,对象会被GC回收。

image.png

在堆内存中,内存需要划分成两块区域,新生代老年代。如下图所示。

  1. 新生代:在堆内存中,新生代又分为三块,eden(伊甸园创建新生命,对应new对象),from,to,这三块内存区域都属于新生代,默认比例是8:1:1,每次new对象都会先存储到eden中,如果eden区域内存满了,会触发monitor gc回收该区域,还未回收的对象会放入from或者to,from,to内存其中一块是空的,方便对象在内存中整理标记,每GC一次,from,to两块空间对象每移动一次,还未回收的对象年纪也会增加1,到达一定年纪(默认是15岁),就会进入老年代了。
  2. 老年代:当老年代满了,会触发Full GC回收,如果系统太大,Full GC都回收不了,程序会出现类似java.lang.OutOfMemoryError: Java heap space,们可以通过配置JVM参数:如 -Xmx32m设置最大堆内存为32M。

对堆分块原因是方便JVM自动处理垃圾回收堆内存是GC回收的主要区域

image.png


栈内存空间相对于堆空间比较小,也属于线程私有,栈中主要是一堆栈帧,是先进后出的,理解起来栈帧对应就是一个方法,方法中包含局部变量,方法参数,还有方法出口,访问常量指针,和异常信息表,其中异常信息表和常量指针信息们在方法体中可能看不出来,但通过工具Jclasslib工具类在反编译class文件可以体现出来,异常信息表可以处理当程序执行报错,会跳转到具体哪行代码执行,JVM中就是通过异常表反馈的。们还是结合例子和图来详细分析下。当程序运行时,JVM中栈可能如下图呈现状态。

image.png

一个线程可能对应多个栈帧,栈帧都是从上往下压入,先进后出,如下图所示,在方法A中调用方法B,在方法B中调用C,在方法C中调用方法D,主线程对应栈帧的压栈情况,出栈顺序是D->C ->B ->A,最终程序结束。另外还需注意:操作数栈的意思是存储局部变量计算的中间结果,比如在方法A中定义int x = 1;在JVM中会将局部变量入操作数栈用来之后的计算。 栈也是有空间大小的,如果栈太大,超过栈深度,会类似报错,java.lang.OutOfMemoryError: Java stack space,最常见的例子就是递归了。你会写demo测试递归例子吗?

image.png

程序计数器


程序计数器也是线程独享的,多线程执行程序依赖于CPU分配时间片执行 ,画个简单的图,看看多线程怎么利用CPU时间片的。如下图,线程0和线程1分配cpu时间片交替执行程序,假设此时线程0先获取到了时间片,时间片用完后CPU会将时间片再分配给线程1,线程1执行完毕后,此时,时间片又回到线程0来执行,那么问题来了,线程0上次执行到哪儿了呢?具体是代码的多少行了呢,该行代码有没有执行完毕?此时程序计数器就发挥作用了,程序计数器 保存了 线程的执行现场,方便下次恢复运行。这也是为什么 程序计数器 是线程独享的原因。

image.png

本地方法栈


本地方法栈就不过多介绍了,和栈结构一样,是一块独立的区域,只是对应的是native方法。

直接内存


直接内存独立于JVM内存之外的内存,可以直接和NIO接口交互,NIO接口会频繁操作内存,如果放在JVM管理,无疑会增加JVM开销,所以单独将这块提出来,而且直接内存操作数据相比较JVM更快,显而易见提升了程序性能。

内存分配性能优化-逃逸分析


们之前说过,只要是看到关键字new,对象分配肯定在堆上,下面们来看一个案例。

/**
 * @author :jiaolian
 * @date :Created in 2021-03-10 16:10
 * @description:逃逸分析测试
 * @modified By:
 * 公众号:叫练
 */
public class EscapeTest {
    //private static Object object;
    public static void alloc() {
        //一个对象相当于16k大小,非逃逸对象
        //object = new Object();
        Object object = new Object();
    }
    public static void main(String[] args) throws InterruptedException {
        //亿次内存
        long begin=System.currentTimeMillis();
        for (int i=0; i<10000000; i++) {
            alloc();
        }
        long end=System.currentTimeMillis();
        System.out.println("time:"+(end-begin));
    }
}

如上代码,们在主函数里面通过for循环1亿次来new Object,一个object为16k,大致估算下有GB数据了,此时们手动配置JVM参数,-XX:+PrintGC -Xmx10M -XX:+DoEscapeAnalysis;设置打印GC信息,默认最大的堆内存是10M。

  1. -XX:+PrintGC。表示控制台打印GC信息。
  2. -Xmx10M。设置最大的堆内存为10M。
  3. -XX:+DoEscapeAnalysis 。 开启逃逸分析(默认开启)。

执行程序,打印结果如下图所示。一共进行了3次GC,你可能有疑问 10M堆内存需要容纳GB数据冲击,怎么也需要N次GC,为什么只有3次GC?如果设置-XX:- DoEscapeAnalysis关闭逃逸分析,GC可能会出现上千次。运行时间也从3毫秒增至1000毫秒以上。说明了非逃逸对象没有新建的堆上,而是建在栈上了。这样做的好处:从程序GC执行次数和执行时间上来看,程序运行效率提高了。

image.png

  • 原因分析:

观察们上述案例代码中alloc()方法,方法中Object object = new Object();object是一个局部变量,每次新建后到下一次循环再新建,上一次新建的对象就会出栈,object引用指向的对象就会失效,失效的对象就会被GC回收了。开启逃逸分析后,new Object()创建的对象就不在堆上分配空间了,而放到了栈上。这就是JVM通过逃逸分析对内存的优化。思考下,如果将private static Object object;注释放开,object还会是非逃逸对象吗? 注意:逃逸对象不能在栈上分配空间!

相信到这里你已经对逃逸分析应该有一个比较清晰的认识了。

总结


好了,写的有点累了,写的不全同时还有许多需要修正的地方,希望亲们加以指正和点评,喜欢的请点赞加关注哦。点关注,不迷路, 是【叫练公众号 ,微信号【jiaolian123abc】边叫边练。

image.png

[] ) [](top

原文创作:叫练

原文链接:https://www.cnblogs.com/jiaolian/p/14520483.html

文章列表

更多推荐

更多
  • 架构-Service Mesh 形态刍议 ” 们便开始锣鼓喧天地抬轿子迎进来。 基于这种认知愚人去年一年躲开了一些机会,但 "躲得了初一躲不过十五",今年刚进公司第一天,便被同事告知工作内容之一便是某系统的 Service Mesh 改造工作...... 考虑到饭碗,愚人还
    AI算法

  • 架构-sidecar 只能与同 Pod 内发? L,因为 MySQL 有些 bug,TiDB 将错就错照着实现了,而有些 bug 实在无法也去照着兼容实现,线上有社区同学质疑道,MySQL 也有 bug 吗? 且不论只要是软件都会有 bug,TiDB 为了兼容 MySQL,其 bu
    AI算法

  • 架构-微信架构 ...
    AI算法

  • 架构-流量控制中间件Sentinel笔记 本文主要分析阿里巴巴集团开源的流量控制中间件 Sentinel,其原生支持了 Java/Go/C++ 等多种语言,本文仅仅分析其 Go 语言实现。下文如无特殊说明,sentinel 指代 Sentinel-Go。1 基本概念 ...
    AI算法

  • 架构-RocksDB 笔记 进行调优。欲熟悉这些参数,必须对其背后的原理有所了解,本文主要整理一些 RocksDB 的 wiki 文档,以备自己参考之用。
    AI算法

  • 架构-信仰与存在 自身是一种信息体系,更是一个编排系统,编排其体系中的每个生物体。这个信息体产生于具有怀疑精神的、可对有效信息进行深度加工的人类先知,而后扩散,扩散范围俞广则力量俞强大。其生命力长久者则会成为宗教、民族意识以及国家认同,短暂则如流行文化,
    AI算法

  • 架构-人 - 机器 - 生命 rari即将出版新书《人类上帝:未来简史(Homo Deus: A Brief History of Tomorrow)》。在此书中,他提出新观点——未来计算机强大的算法,可能会让人类丢弃千百年来追求的自由意志,而把更多的事情交由机器决
    AI算法

  • 架构-一种基于Redis的多租户多粒度分布式内存文件系统 Redis采用了单进程架构,无法利用服务端多核的高性能,进而制约了其对服务器超大内存的使用能力。Redis能够使用的内存极限容量经验值为【8G, 24G】,超过这个容量上限其性能便急剧下降。 - 2 Scale out能力 基
    AI算法

  • 架构-分布式系统数据存储 为强一致(实时一致性)和弱一致(最终一致性),根据数据一致性要求的不同,读写流程也要做相应的改变。下面结合个人经验给出两种情况下的读写流程步骤。 一般的简单的分布式系统,缓存层可以使用redis集群,固化层则可以使用mysql或者mo
    AI算法

  • 架构-redis server 源码分析 redis启动流程: 1 加载配置;2 初始化redis master、slave以及sentinel的sri;3 注册事件事件serverCron。1.2.1 读取配置文件,...
    AI算法

  • 近期文章

    更多
    文章目录

      推荐作者

      更多