美文网首页Android开发经验谈Android开发Android技术知识
浅谈 JVM 虚拟机及其内存模型为何要分成新生代和老年代

浅谈 JVM 虚拟机及其内存模型为何要分成新生代和老年代

作者: 程序老秃子 | 来源:发表于2022-06-24 16:35 被阅读0次

    先想想一些问题

    我们开发人员编写的Java代码是怎么让电脑认识的

    首先先了解电脑是二进制的系统,他只认识 01010101

    比如我们经常要编写 HelloWord.java 电脑是怎么认识运行的 HelloWord.java是我们程序员编写的,我们人可以认识,但是电脑不认识

    Java文件编译的过程
    因此就需要编译:

    程序员编写的.java文件

    由javac编译成字节码文件.class:(为什么编译成class文件,因为JVM只认识.class文件)

    在由JVM编译成电脑认识的文件 (对于电脑系统来说 文件代表一切)

    (这是一个大概的观念 抽象画的概念)

    为什么说java是跨平台语言

    这个跨平台是中间语言(JVM)实现的跨平台

    java有JVM从软件层面屏蔽了底层硬件、指令层面的细节让他兼容各种系统

    难道 C 和 C++ 不能跨平台吗?

    其实也可以

    C和C++需要在编译器层面去兼容不同操作系统的不同层面,写过C和C++的就知道不同操作系统的有些代码是不一样

    JVM中的新生代,老年代和永久代

    共享内存区划分

    • 1.共享内存区 = 持久代 + 堆(注;jdk1.8及以上jvm废弃了持久代)
    • 2.持久带代= 方法区 + 其他
    • 3.Java堆 = 老年代 + 新生代
    • 4.新生代 = Eden(伊甸区) + S1(幸存1) + S2(幸存2)

    新生代、老年代、永久代

    一般把java堆分为新生代、老年代,这样就可以根据各个年代的特点采用最适当的收集算法

    • 新生代中,每次垃圾收集时都发现大批对象死去,只有少量对象存活,便采用了复制算法,只需要付出少量存活对象的复制成本就可以完成收集
    • 而老年代中因为对象存活率高、没有额外空间对它进行分配担保,就必须采用“标记-清理”或者“标记-整理”算法

    新生代又分为Eden和Survivor(From Space与To Space)两个区;加上老年代就这三个区

    • 数据会首先分配到Eden区当中(当然也有特殊情况,如果是大对象那么会直接放入到老年代(大对象是指需要大量连续内存空间的java对象)
    • 当Eden没有足够空间的时候就会触发jvm发起一次Minor GC;如果对象经过一次Minor-GC还存活,并且又能被Survivor空间接受,那么将被移动到Survivor空间当中;并将其年龄设为1,对象在Survivor每熬过一次Minor GC,年龄就加1,当年龄达到一定的程度(默认为15)时,就会被晋升到老年代中了,当然晋升老年代的年龄是可以设置的

    JVM的方法区

    也被称为永久代; 在这里都是放着一些被虚拟机加载的类信息,静态变量,常量等数据。这个区中的东西比老年代和新生代更不容易回收

    其实新生代和老年代就是针对于对象做分区存储,更便于回收等等

    为什么分年老代和新生代

    新生代:

    年轻代主要存放新创建的对象,内存大小相对会比较小,垃圾回收会比较频繁。年轻代分成1个Eden Space和2个Suvivor Space(from 和to)

    老年代:

    年老代主要存放JVM认为生命周期比较长的对象(经过几次的新生代的垃圾回收后仍然存在),内存大小相对会比较大,垃圾回收也相对没有那么频繁

    为什么要分为Eden和Survivor?为什么要设置两个Survivor区?

    如果没有Survivor,Eden区每进行一次Minor GC,存活的对象就会被送到老年代。老年代很快被 填满,触发Major GC.老年代的内存空间远大于新生代,进行一次Full GC消耗的时间比Minor GC 长得多,所以需要分为Eden和Survivor

    Survivor的存在意义,就是减少被送到老年代的对象,进而减少Full GC的发生,Survivor的预筛选保证,只有经历16次Minor GC还能在新生代中存活的对象,才会被送到老年代

    设置两个Survivor区最大的好处就是解决了碎片化,刚刚新建的对象在Eden中,经历一次Minor GC,Eden中的存活对象就会被移动到第一块survivor space S0,Eden被清空;等Eden区再满 了,就再触发一次Minor GC,Eden和S0中的存活对象又会被复制送入第二块survivor space S1(这个过程非常重要,因为这种复制算法保证了S1中来自S0和Eden两部分的存活对象占用连续 的内存空间,避免了碎片化的发生)

    Minor GC ,Full GC 触发条件

    Minor GC触发条件:

    • 当Eden区满时,触发Minor GC

    Full GC触发条件:

    • (1)调用System.gc时,系统建议执行Full GC,但是不必然执行
    • (2)老年代空间不足
    • (3)方法区空间不足
    • (4)通过Minor GC后进入老年代的平均大小大于老年代的可用内存
    • (5)由Eden区、From Space区向To Space区复制时,对象大小大于To Space可用内存,则把该对象转存到老年代,且老年代的可用内存小于该对象大小

    直接内存(Direct Memory)

    直接内存不是虚拟机运行时数据区的一部分,也不是java虚拟机规范中定义的内存区域; 但是既然是内存,肯定还是受本机总内存(包括RAM以及SWAP区或者分页文件)大小以及处理器寻址空间的限制

    在JDK1.4 中新加入了NIO(New Input/Output)类,引入了一种基于通道(Channel)与缓冲区(Buffer)的I/O 方式,它可以使用native 函数库直接分配堆外内存,然后通脱一个存储在Java堆中的DirectByteBuffer 对象作为这块内存的引用进行操作;这样能在一些场景中显著提高性能,因为避免了在Java堆和Native(本地)堆中来回复制数据

    直接内存与堆内存的区别

    直接内存申请空间耗费很高的性能,堆内存申请空间耗费比较低

    直接内存的IO读写的性能要优于堆内存,在多次读写操作的情况相差非常明显

    代码示例:(报错修改time 值)

    package com.lijie;
    
    import java.nio.ByteBuffer;
    
    /**
     * 直接内存 与 堆内存的比较
     */
    public class ByteBufferCompare {
    
        public static void main(String[] args) {
            allocateCompare();   //分配比较
            operateCompare();    //读写比较
        }
    
        /**
         * 直接内存 和 堆内存的 分配空间比较
         */
        public static void allocateCompare() {
            int time = 10000000;    //操作次数
            long st = System.currentTimeMillis();
            for (int i = 0; i < time; i++) {
    
                ByteBuffer buffer = ByteBuffer.allocate(2);      //非直接内存分配申请
            }
            long et = System.currentTimeMillis();
            System.out.println("在进行" + time + "次分配操作时,堆内存:分配耗时:" + (et - st) + "ms");
            long st_heap = System.currentTimeMillis();
            for (int i = 0; i < time; i++) {
                ByteBuffer buffer = ByteBuffer.allocateDirect(2); //直接内存分配申请
            }
            long et_direct = System.currentTimeMillis();
            System.out.println("在进行" + time + "次分配操作时,直接内存:分配耗时:" + (et_direct - st_heap) + "ms");
        }
    
        /**
         * 直接内存 和 堆内存的 读写性能比较
         */
        public static void operateCompare() {
            //如果报错修改这里,把数字改小一点
            int time = 1000000000;
            ByteBuffer buffer = ByteBuffer.allocate(2 * time);
            long st = System.currentTimeMillis();
            for (int i = 0; i < time; i++) {
                buffer.putChar('a');
            }
            buffer.flip();
            for (int i = 0; i < time; i++) {
                buffer.getChar();
            }
            long et = System.currentTimeMillis();
            System.out.println("在进行" + time + "次读写操作时,堆内存:读写耗时:" + (et - st) + "ms");
            ByteBuffer buffer_d = ByteBuffer.allocateDirect(2 * time);
            long st_direct = System.currentTimeMillis();
            for (int i = 0; i < time; i++) {
                buffer_d.putChar('a');
            }
            buffer_d.flip();
            for (int i = 0; i < time; i++) {
                buffer_d.getChar();
            }
            long et_direct = System.currentTimeMillis();
            System.out.println("在进行" + time + "次读写操作时,直接内存:读写耗时:" + (et_direct - st_direct) + "ms");
        }
    }
    
    

    测试结果:

    在进行10000000次分配操作时,堆内存:分配耗时:98ms
    在进行10000000次分配操作时,直接内存:分配耗时:8895ms
    在进行1000000000次读写操作时,堆内存:读写耗时:5666ms
    在进行1000000000次读写操作时,直接内存:读写耗时:884ms
    

    JVM字节码执行引擎; 虚拟机核心的组件就是执行引擎,它负责执行虚拟机的字节码,一般户先进行编译成机器码后执行

    “虚拟机”是一个相对于“物理机”的概念; 虚拟机的字节码是不能直接在物理机上运行的,需要JVM字节码执行引擎编译成机器码后才可在物理机上执行

    有需要完整代码的同学 可以 点击 此处 即可 免费获取

    现在点击还可以获得更多《Android 学习笔记+源码解析+面试视频》

    垃圾收集系统

    程序在运行过程中,会产生大量的内存垃圾(一些没有引用指向的内存对象都属于内存垃圾,因为这些对象已经无法访问,程序用不了它们了,对程序而言它们已经死亡),为了确保程序运行时的性能,java虚拟机在程序运行的过程中不断地进行自动的垃圾回收(GC)

    垃圾收集系统是Java的核心,也是不可少的,Java有一套自己进行垃圾清理的机制,开发人员无需手工清理

    为什么要学习JVM

    为什么要学习Jvm?学习Jvm可以干什么?

    首先先想: 为什么Java可以霸占企业级开发那么多年?

    因为: 内存管理

    我们在java开发中何时考虑过内存管理 不像c和c++还要考虑什么时候释放资源 我们java只需要考虑业务实现就行了

    那就有些人可能又会要说了,Jvm都做完了这些操作,为什么我们还要学习,学习个屁啊

    假如:内存出现问题了,出现了内存溢出 ,内存泄漏问题怎么办

    这就好像一个人一样,我一般情况吃什么从来不用考虑进入了身体那一个部位,可是总有一天,假如吃了不该吃的也是要进医院的

    今天关于 JVM 的问题就说到这里

    有需要完整代码的同学 可以 点击 此处 即可 免费获取

    现在点击还可以获得更多《Android 学习笔记+源码解析+面试视频》

    技术是无止境的,你需要对自己提交的每一行代码、使用的每一个工具负责,不断挖掘其底层原理,才能使自己的技术升华到更高的层面

    Android 架构师之路还很漫长,与君共勉

    PS:有问题欢迎指正,可以在评论区留下你的建议和感受;

    欢迎大家点赞评论,觉得内容可以的话,可以转发分享一下

    相关文章

      网友评论

        本文标题:浅谈 JVM 虚拟机及其内存模型为何要分成新生代和老年代

        本文链接:https://www.haomeiwen.com/subject/vszvvrtx.html