先想想一些问题
我们开发人员编写的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:有问题欢迎指正,可以在评论区留下你的建议和感受;
欢迎大家点赞评论,觉得内容可以的话,可以转发分享一下
网友评论