网络知识 娱乐 浅谈 JVM 虚拟机及其内存模型为何要分成新生代和老年代

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

先想想一些问题

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

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

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

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

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

程序员编写的.java文件

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

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

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

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

测试结果:

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

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

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

有需要完整代码的同学 可以 私信 发送 “底层源码” 即可 免费获取

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

垃圾收集系统

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

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

为什么要学习JVM

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

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

因为: 内存管理

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

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

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

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

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

有需要完整代码的同学 可以 私信 发送 “底层源码” 即可 免费获取

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

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

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

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

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