1.jvm和io和idea都是Java知识体系的一部分。
2.idea是编写.java文件格式的工具,编写完成后可以通过build编译为.class文件格式,out包或着target包中的java类都变成了.class形式。
3.jvm的入场时机。jvm的作用是加载生成的.class文件及其他配置文件进内存,每个Java程序执行时(运行main方法)都会启动一个jvm实例(可以在idea中配置当前程序的jvm执行参数)。
4.程序进内存的过程一般遵循段页式调度,程序分段,内存分页,按需加载。
首先,实存被等分成页。在段页式虚拟存储器中,把程序按逻辑结构分段以后,再把每段按照实存的页的大小分页,程序按页进行调入和调出操作,但它又可按段实现共享和保护(如果需要让出内存空间时需要同时考虑段和页的使用间隔或频率)。因此,它可以兼有页式和段式系统的优点。它的缺点是在地址映像过程中需要多次查表,虚拟地址转换成物理地址是通过一个段表和一组页表来进行定位的。段表中的每个表目对应一个段,每个表目有一个指向该段的页表的起始地址(页号)及该段的控制保护信页表指明该段各页在主存中的位置以及是否已装入、已修改等标志。
虚存空间的用户程序按照虚地址编程并存放在辅存中。程序运行时,由地址变换机构依据当时分配给该程序的实地址空间把程序的一部分调入实存。每次访存时,首先判断该虚地址所对应的部分是否在实存中:如果是,则进行地址转换并用实地址访问主存;否则,按照某种算法将辅存中的部分程序调度进内存,再按同样的方法访问主存。
5.jvm是Java进程运行时所需的内存空间,那么jvm执行的代码存放在哪里?内存是怎么怎么分配的?
jvm加载运行.class文件后,会在jvm的方法区存放类的信息,常量,运行时常量池,被即时编译的代码,并在jvm堆中产生一个Class类对象对该类进行访问。所以在方法区中存放着很多被加载多的类的信息及其代码指令(尚未被访问的类信息暂时不存在,因为端页式调度,按需加载),程序计数器记录当前线程的执行位置,堆中存放产生根据类信息生成的类对象以及字符串常量池和静态变量。栈中存放当前线程的Java方法执行信息,本地方法栈存放的是c方法的执行信息。在程序的执行过程难免涉及对io的访问,例如程序执行到通过数据库获取学生列表的代码时,本来是一个空的list,但是经过ORM框架映射,就在jvm堆中生成了一个list,当中包含了很多的学生对象,所以java程序就是一个通过io设备获得想要的数据存入堆中获取的过程或者创建数据存入io设备的过程。
6.如何进行io
首先明确io设备分为
- 在Linux中,设备类型可以分为:字符设备、块设备和网络设备。
字符设备
提供连续的数据流,应用程序可以顺序读取,通常不支持随机存取。相反,此类设备支持按字节/字符来读写数据。举例来说,键盘、串口、调制解调器都是典型的字符设备。
块设备
应用程序可以随机访问设备数据,程序可自行确定读取数据的位置。硬盘、软盘、CD-ROM驱动器和闪存都是典型的块设备,应用程序可以寻址磁盘上的任何位置,并由此读取数据。此外,数据的读写只能以块(通常是512B)的倍数进行。与字符设备不同,块设备并不支持基于字符的寻址。
网卡属于网络设备 一般也是顺序读取(可参考我的另一篇网络io相关文章)
这里只说明一下块设备的存取方式
相关类:
FileInputStream(读取文件,可用于块设备和网络设备)
FileOutputStream(写入到磁盘文件)
以上两种可以通过流的方式直接对文件进行操作,当jvm中有read或write 指令时,需要经过系统调用切换到内核态,执行内核方法读取或写入到有内核进行控制的pageCache中,由内核定时更新到io块设备。
RandomAccessFile(可读可写且任意位置,只针对块设备)
此类比较特殊,可以更方便的对io块设备进行操作,它可以获取当前指向磁盘文件的指针位置,
1 long getFilePointer():返回文件记录指针的当前位置 2 void seek(long pos):将文件记录指针定位到pos位置
可以通过seek将指针移动到任意位置,对文件进行操作。
以上三个类都可以通过getChannel方法获得一个与io块设备交互的通道,这种方式使用了双向的可读可写的buffer,当jvm中有read或write 指令时,需要经过系统调用切换到内核态,执行内核方法读取或写入到有内核进行控制的pageCache中,由内核定时更新到io块设备。
以上三个类还可以通过channel.map直接获取文件的映射,通过put方法可以不经过系统调用内核方法直接写入pageCache中,减少了用户态和内核态的切换
总结一下写入到磁盘的几种方法:
1.直接使用outputStream,write系统调用写
2.outputstream.getchannel()和byteBuffer.allolcatejvm堆内分配字节数组配合使用,使用write系统调用
3.使用outputstream.getchannel()和byteBuffer.allolcateDirect()jvm堆外分配配合使用,使用write系统调用
4.使用channel.map()直接建立io映射,使用map.put()方法写,不经过系统调用
MappedByteBuffer map = rafchannel.map(FileChannel.MapMode.READ_WRITE, 0, 4096);
map.put("@@@".getBytes());
上图解释:两种buffer字节数组分配方式,allocateDirect方式,效率更高一些,因为jvm堆内分配还是要先经过复制到堆外,再从堆外使用write方法系统调用。
netty使用的是堆外分配
Kafka使用的是mmap
为了不丢数据,一般会进行主从复制,(通过网络io进行,包括同步和异步两种方式)
堆外:指的就是linux操作系统的内存,只不过Linux内存再细分也包括堆栈这些,jvm是伪操作系统
效率来说,堆内<堆外<mmap
个人理解总结:如有错误,感谢指正