Java进程通信之映像文件共享内存
Java进程通信之映像文件共享内存
1. 共享内存 vs 进程通信
对UNIX系统来说,共享内存分为一般共享内存和映像文件共享内存两种.但windows实际上只有影像文件共享内存一种.
而说到进程通信,First当然是Socket通信,但缺点太明显.其一,浪费网络资源,其二,多余的code成本也绝非所愿.
综上,映像文件共享内存方式,成为了实际应用中推荐使用的进程通信手段.
2.优点
数据共享/动态配置/减少资源浪费
3.特点
- 可被多个进程打开访问
- 读写操作的进程在执行读写操作时,其他进程不能进行写操作
- 多个进程可以交替对某一共享内存执行写操作
- 一个进程执行了内存的写操作后,不影响其他进程对该内存的访问。同时其他进程对更新后的内存具有可见性
- 在进程执行写操作时,如果异常退出,对其他进程写操作禁止应自动解除
4.常见场景
1、独占的写操作,相应有独占的写操作等待队列。独占的写操作本身不会发生数据的一致性问题。
2、共享的写操作,相应有共享的写操作等待队列。共享的写操作则要注意防止发生数据的一致性问题。
3、独占的读操作,相应有共享的读操作等待队列。
4、共享的读操作,相应有共享的读操作等待队列。
5.开发要点
JDK 1.4新增的 MappedByteBuffer类 提供了实现共享内存的方法,该缓冲区实际上是一个磁盘文件的内存影像.二者的变化保持同步,即内存数据发生变化会立刻反映到磁盘文件中,进而有效的保证共享内存的实现.
FileChannel 是将共享内存和磁盘文件建立联系的文件通道类。FileChannel 类的加入是 JDK 为了统一对外设备(文件、网络接口等)的访问方法,并加强了多线程对同一文件进行存取的安全性。在这里用它来建立共享内存和磁盘文件间的一个通道。
RandomAccessFile 是 Java IO 体系中功能最丰富的文件内容访问类,它提供很多方法来操作文件,包括读写支持,与普通的IO流相比,它最大的特别之处就是支持任意访问的方式,程序可以直接跳到任意地方来读写数据。举例:向一个5G的文件中新增一行文字,"update by nya".可以直接使用 Java 中的流读取 txt 文本里所有的数据转成字符串后,随后拼接文字,再写回文本即可.而如果内存不充裕,则可以使用RandomAccessFile类来完成,可以实现零内存追加,这就是其支持任意位置读写类的强大之处.
6.代码实测:
此处读写进程,采用文件锁来保证数据读写安全.实操可用
import com.alibaba.fastjson.JSONObject; import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock; public class ShareMemory { int flen = 5242880; //开辟共享内存大小 50M
int fsize = 0; //文件的实际大小
String shareFileName; //共享内存文件名
String sharePath; //共享内存路径
MappedByteBuffer mapBuf = null; //定义共享内存缓冲区
FileChannel fc = null; //定义相应的文件通道
FileLock fl = null; //定义文件区域锁定的标记。
RandomAccessFile RAFile = null; //定义一个随机存取文件对象 /**
*
* @param sp 共享内存文件路径
* @param sf 共享内存文件名
*/
public ShareMemory(String sp, String sf) {
if (sp.length() != 0) {
FileUtil.createDir(sp);
this.sharePath = sp + File.separator;
} else {
this.sharePath = sp;
}
this.shareFileName = sf; try {
// 获得一个只读的随机存取文件对象 "rw" 打开以便读取和写入。如果该文件尚不存在,则尝试创建该文件。
RAFile = new RandomAccessFile(this.sharePath + this.shareFileName + ".sm", "rw");
//获取相应的文件通道
fc = RAFile.getChannel();
//获取实际文件的大小
fsize = (int) fc.size();
if (fsize < flen) {
byte bb[] = new byte[flen - fsize];
//创建字节缓冲区
ByteBuffer bf = ByteBuffer.wrap(bb);
bf.clear();
//设置此通道的文件位置。
fc.position(fsize);
//将字节序列从给定的缓冲区写入此通道。
fc.write(bf);
fc.force(false); fsize = flen;
}
//将此通道的文件区域直接映射到内存中。
mapBuf = fc.map(FileChannel.MapMode.READ_WRITE, 0, fsize);
} catch (IOException e) {
e.printStackTrace();
}
} /**
*
* @param ps 锁定区域开始的位置;必须为非负数
* @param len 锁定区域的大小;必须为非负数
* @param buff 写入的数据
* @return
*/
public synchronized int write(int ps, int len, byte[] buff) {
if (ps >= fsize || ps + len >= fsize) {
return 0;
}
try {
//获取此通道的文件给定区域上的锁定。
fl = fc.lock(ps, len, false);
if (fl != null) {
// 清除文件内容
// 清除文件内容,对MappedByteBuffer的操作就是对文件的操作
for (int i = ps ; i < (ps + len); i++) {
mapBuf.put(i,(byte)0);
}
mapBuf.position(ps);
ByteBuffer bf1 = ByteBuffer.wrap(buff);
mapBuf.put(bf1);
//释放此锁定。
fl.release();
return len;
}
} catch (Exception e) {
if (fl != null) {
try {
fl.release();
} catch (IOException e1) {
System.out.println(e1.toString());
}
}
return 0;
}
return 0;
} /**
*
* @param ps 锁定区域开始的位置;必须为非负数
* @param len 锁定区域的大小;必须为非负数
* @param buff 要取的数据
* @return
*/
public synchronized int read(int ps, int len, byte[] buff) {
if (ps >= fsize) {
return 0;
}
//定义文件区域锁定的标记。
try {
fl = fc.lock(ps, len, false);
if (fl != null) {
//System.out.println( "ps="+ps );
mapBuf.position(ps);
if (mapBuf.remaining() < len) {
len = mapBuf.remaining();
} if (len > 0) {
mapBuf.get(buff, 0, len);
} fl.release(); return len;
}
} catch (Exception e) {
if (fl != null) {
try {
fl.release();
} catch (IOException e1) {
System.out.println(e1.toString());
}
}
return 0;
}
return 0;
} /**
* 完成,关闭相关操作
*/
protected void finalize() throws Throwable {
if (fc != null) {
try {
fc.close();
} catch (IOException e) {
System.out.println(e.toString());
}
fc = null;
}
if (RAFile != null) {
try {
RAFile.close();
} catch (IOException e) {
System.out.println(e.toString());
}
RAFile = null;
}
mapBuf = null;
} /**
* 关闭共享内存操作
*/
public synchronized void closeSMFile() {
if (fc != null) {
try {
fc.close();
} catch (IOException e) {
System.out.println(e.toString());
}
fc = null;
}
if (RAFile != null) {
try {
RAFile.close();
} catch (IOException e) {
System.out.println(e.toString());
}
RAFile = null;
}
mapBuf = null;
} public static void main(String arsg[]) throws Exception{
try {
ShareMemory sm = new ShareMemory("/home/lab/test","22222");
JSONObject json = new JSONObject();
json.put("name","来兮子宁");
json.put("age",18);
String str = json.toJSONString(); // StringBuffer sb = new StringBuffer();
// for (int i = 0 ; i < 100000; i++) {
// sb.append("小米刚同学").append("\t");
// }
// System.out.println(sb.toString());
sm.write(40, 50000000, str.getBytes("UTF-8"));
// sm.write(40, 50000000, sb.toString().getBytes("UTF-8"));
byte[] b = new byte[50000000];
sm.read(40, 50000000, b);
String jsonStr = new String(b, "UTF-8").trim();
System.out.println(jsonStr);
JSONObject jsonObject = (JSONObject) JSONObject.parse(jsonStr);
String name = jsonObject.getString("name");
Integer age = jsonObject.getInteger("age");
System.out.println("name : " + name + " age : " + age);
} catch (Exception e) {
e.printStackTrace();
}
} }
6.参考资料
Java进程通信(共享内存) - https://chenhy.com/post/java_ipc/
Java实现共享内存操作 - https://huxu1986-163-com.iteye.com/blog/2163251
7.附文件操作工具类
import java.io.File;
import java.io.IOException; public class FileUtil { // 验证字符串是否为正确路径名的正则表达式
private static String matches = "[A-Za-z]:\\\\[^:?\"><*]*";
// 通过 sPath.matches(matches) 方法的返回值判断是否正确
// sPath 为路径字符串
boolean flag = false;
File file; /**
* 创建目录
* @param destDirName 需要创建目录的路径
* @return
*/
public static boolean createDir(String destDirName) {
File dir = new File(destDirName);
if (dir.exists()) {// 判断目录是否存在
return false;
}
if (!destDirName.endsWith(File.separator)) {// 结尾是否以"/"结束
destDirName = destDirName + File.separator;
}
if (dir.mkdirs()) {// 创建目标目录
System.out.println("创建目录成功!" + destDirName);
return true;
} else {
System.out.println("创建目录失败!");
return false;
}
} /**
* 根据路径删除指定的目录或文件,无论存在与否
* @param deletePath 指定的文件的目录
* @return
*/
public boolean DeleteFolder(String deletePath) {
flag = false;
if (deletePath.matches(matches)) {
file = new File(deletePath);
if (!file.exists()) {// 判断目录或文件是否存在
return flag; // 不存在返回 false
} else { if (file.isFile()) {// 判断是否为文件
return deleteFile(deletePath);// 为文件时调用删除文件方法
} else {
return deleteDirectory(deletePath);// 为目录时调用删除目录方法
}
}
} else {
System.out.println("要传入正确路径!");
return false;
}
} /**
* 删除单个文件
* @param filePath 文件路径
* @return
*/
public boolean deleteFile(String filePath) {
flag = false;
file = new File(filePath);
if (file.isFile() && file.exists()) {// 路径为文件且不为空则进行删除
file.delete();// 文件删除
flag = true;
}
return flag;
} /**
* 删除目录(文件夹)以及目录下的文件
* @param dirPath
* @return
*/
public boolean deleteDirectory(String dirPath) {
// 如果sPath不以文件分隔符结尾,自动添加文件分隔符
if (!dirPath.endsWith(File.separator)) {
dirPath = dirPath + File.separator;
}
File dirFile = new File(dirPath);
// 如果dir对应的文件不存在,或者不是一个目录,则退出
if (!dirFile.exists() || !dirFile.isDirectory()) {
return false;
}
flag = true;
File[] files = dirFile.listFiles();// 获得传入路径下的所有文件
for (int i = 0; i < files.length; i++) {// 循环遍历删除文件夹下的所有文件(包括子目录)
if (files[i].isFile()) {// 删除子文件
flag = deleteFile(files[i].getAbsolutePath());
System.out.println(files[i].getAbsolutePath() + " 删除成功");
if (!flag)
break;// 如果删除失败,则跳出
} else {// 运用递归,删除子目录
flag = deleteDirectory(files[i].getAbsolutePath());
if (!flag)
break;// 如果删除失败,则跳出
}
}
if (!flag)
return false;
if (dirFile.delete()) {// 删除当前目录
return true;
} else {
return false;
}
} /**
* 创建单个文件
* @param filePath 文件路径
* @return
*/
public static boolean createFile(String filePath) {
File file = new File(filePath);
if (file.exists()) {// 判断文件是否存在
System.out.println("目标文件已存在" + filePath);
return false;
}
if (filePath.endsWith(File.separator)) {// 判断文件是否为目录
System.out.println("目标文件不能为目录!");
return false;
}
if (!file.getParentFile().exists()) {// 判断目标文件所在的目录是否存在
// 如果目标文件所在的文件夹不存在,则创建父文件夹
System.out.println("目标文件所在目录不存在,准备创建它!");
if (!file.getParentFile().mkdirs()) {// 判断创建目录是否成功
System.out.println("创建目标文件所在的目录失败!");
return false;
}
}
try {
if (file.createNewFile()) {// 创建目标文件
System.out.println("创建文件成功:" + filePath);
return true;
} else {
System.out.println("创建文件失败!");
return false;
}
} catch (IOException e) {// 捕获异常
e.printStackTrace();
System.out.println("创建文件失败!" + e.getMessage());
return false;
}
} /**
* 创建临时文件
* @param prefix
* @param suffix
* @param dirName
* @return
*/
public static String createTempFile(String prefix, String suffix,
String dirName) {
File tempFile = null;
if (dirName == null) {// 目录如果为空
try {
tempFile = File.createTempFile(prefix, suffix);// 在默认文件夹下创建临时文件
return tempFile.getCanonicalPath();// 返回临时文件的路径
} catch (IOException e) {// 捕获异常
e.printStackTrace();
System.out.println("创建临时文件失败:" + e.getMessage());
return null;
}
} else {
// 指定目录存在
File dir = new File(dirName);// 创建目录
if (!dir.exists()) {
// 如果目录不存在则创建目录
if (FileUtil.createDir(dirName)) {
System.out.println("创建临时文件失败,不能创建临时文件所在的目录!");
return null;
}
}
try {
tempFile = File.createTempFile(prefix, suffix, dir);// 在指定目录下创建临时文件
return tempFile.getCanonicalPath();// 返回临时文件的路径
} catch (IOException e) {// 捕获异常
e.printStackTrace();
System.out.println("创建临时文件失败!" + e.getMessage());
return null;
}
}
}
}
Java进程通信之映像文件共享内存的更多相关文章
- Linux进程通信之System V共享内存
前面已经介绍过了POSIX共享内存区,System V共享内存区在概念上类似POSIX共享内存区,POSIX共享内存区的使用是调用shm_open创建共享内存区后调用mmap进行内存区的映射,而Sys ...
- Windows进程通信 -- 共享内存(1)
共享内存的方式原理就是将一份物理内存映射到不同进程各自的虚拟地址空间上,这样每个进程都可以读取同一份数据,从而实现进程通信.因为是通过内存操作实现通信,因此是一种最高效的数据交换方法. 共享内存在 W ...
- Windows进程通信 -- 共享内存
享内存的方式原理就是将一份物理内存映射到不同进程各自的虚拟地址空间上,这样每个进程都可以读取同一份数据,从而实现进程通信.因为是通过内存操作实现通信,因此是一种最高效的数据交换方法. 共享内存在 Wi ...
- android92 aidl远程进程通信
05项目RemoteService.java package com.itheima.remoteservice; //05项目 import com.itheima.remoteservice.Pu ...
- 故障重现, JAVA进程内存不够时突然挂掉模拟
背景,服务器上的一个JAVA服务进程突然挂掉,查看产生了崩溃日志,如下: # Set larger code cache with -XX:ReservedCodeCacheSize= # This ...
- 死磕内存篇 --- JAVA进程和linux内存间的大小关系
运行个JAVA 用sleep去hold住 package org.hjb.test; public class TestOnly { public static void main(String[] ...
- Linux 进程通信(共享内存区)
共享内存是由内核出于在多个进程间交换信息的目的而留出的一块内存区(段). 如果段的权限设置恰当,每个要访问该段内存的进程都可以把它映像到自己的私有地址空间中. 如果一个进程更新了段中的数据,其他进程也 ...
- 【翻译】Anatomy of a Program in Memory—剖析内存中的一个程序(进程的虚拟存储器映像布局详解)
[翻译]Anatomy of a Program in Memory—剖析内存中的一个程序(进程的虚拟存储器映像布局详解) . . .
- 通过JDK常用工具监控Java进程的内存占用情况
目录 1 JDK 工具的使用 2 查看 GC 日志信息 3 添加 JMS 远程监控 Tomcat是一款常用的Web容器, 它是运行在 JVM(Java Virtual Machine) 中的一个Jav ...
随机推荐
- Centos7+lnmp+zabbix4+分离mysql实验
一.简介 1.什么是zabbix zabbix是一个企业级的.开源的.分布式的监控套件. zabbix可以对网络和服务进行完整性,健康性的监控.zabbix利用灵活的告警机制,可以实验微信,短信和邮件 ...
- IOS多态在项目中的应用
今天我们讲述一个知识点(大家可能遗漏的) 多态是面试程序设计(OOP)一个重要特征,但在iOS中,可能比较少的人会留意这个特征,实际上在开发中我们可能已经不经意的使用了多态.比如说: 有一个table ...
- SVN安装和使用(简单版)
为什么使用SVN? 通常软件的开发需要团队协作开发,每个人负责一个方面,都做完后需要把每个人的代码整合在一起,而每个人的代码方面不同或版本不同就会拖延开发进度对开发项目造成麻烦,如果一个人需要另一个人 ...
- js或jquery实现点击某个按钮或元素显示div,点击页面其他任何地方隐藏div
点击某个元素显示div,点击页面其他任何地方隐藏div,可用javascript和jquery两种方法实现: 一:javascript实现方法技巧<script>//定义stopPropa ...
- jQuery里面的DOM操作(查找,创建,添加,删除节点)
一:创建元素节点(添加) 创建元素节点并且把节点作为元素的子节点添加到DOM树上 append(): 在元素下添加元素 用法:$("id").append("定义的节点& ...
- mysql命令行导入导出数据库
导出:1.在命令行里,进入mysql安装根目录下的bin目录下比如:D:\Program Files\MySQL\MySQL Server 5.0\bin输入 mysqldump -uroot -p ...
- Linux系统上安装MySQL(rpm)
1.准备工作 从MySQL官网上分别下载mysql服务器端于客户端包. 如: MySQL-server-5.5.15-1.linux2.6.x86_64.rpm和MySQL-client-5.5.15 ...
- Java 工厂模式(一)— 抽象工厂(Abstract Factory)模式
一.抽象工厂模式介绍: 1.什么是抽象工厂模式: 抽象工厂模式是所有形态的工厂模式中最为抽象和最具有一般性的一种形态,抽象工厂模式向客户端提供一个接口,使得客户端在不知道具体产品的情类型的情况下,创建 ...
- 【Java】itext根据模板生成pdf(包括图片和表格)
1.导入需要的jar包:itext-asian-5.2.0.jar itextpdf-5.5.11.jar. 2.新建word文档,创建模板,将文件另存为pdf,并用Adobe Acrobat DC打 ...
- Tomcat设置cmd窗口的title属性
说明:官网下载tomcat之后,双击bin目录下的startup.bat文件,即可运行tomcat:linux下面运行startup.sh. 但是如果测试服务器上面搭建了多个项目,则启动之后窗口一样, ...