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;
            }
        }
    }
}

  

原文地址:https://www.cnblogs.com/nyatom/p/10770182.html

时间: 2025-01-05 23:16:19

Java进程通信之映像文件共享内存的相关文章

进程通信(二)&mdash;&mdash; 信号量&amp;内存共享

内存共享是进程间常用的通信方式,可以实现两个完全独立的进程通信. 在访问共享内存时,同时需要信号量进行访问控制. 使用ipcs -m命令可以查看系统共享内存,ipce -m + key 可以删除指定的共享内存. 对共享内存操作时,使用信号量对共享内存进行保护,类似与线程中的互斥锁.都可以看做是通过PV操作实现临界资源保护. P:对信号量标记位-1,获取信号量,如果标记位为0,表示有其他进程占用资源,无法获取. V:对信号量标记位+1,释放信号量,将资源释放,给其他进程使用. 信号量和内存共享需要

Windows进程通信 -- 共享内存

享内存的方式原理就是将一份物理内存映射到不同进程各自的虚拟地址空间上,这样每个进程都可以读取同一份数据,从而实现进程通信.因为是通过内存操作实现通信,因此是一种最高效的数据交换方法. 共享内存在 Windows 中是用 FileMapping 实现的,从具体的实现方法上看主要通过以下几步来实现: 1.调用 CreateFileMapping 创建一个内存文件映射对象: HANDLE CreateFileMapping( HANDLE hFile, // handle to file to map

Windows进程通信 -- 共享内存(1)

共享内存的方式原理就是将一份物理内存映射到不同进程各自的虚拟地址空间上,这样每个进程都可以读取同一份数据,从而实现进程通信.因为是通过内存操作实现通信,因此是一种最高效的数据交换方法. 共享内存在 Windows 中是用 FileMapping 实现的,从具体的实现方法上看主要通过以下几步来实现: 1.调用 CreateFileMapping 创建一个内存文件映射对象: HANDLE CreateFileMapping( HANDLE hFile, // handle to file to ma

android92 aidl远程进程通信

05项目RemoteService.java package com.itheima.remoteservice; //05项目 import com.itheima.remoteservice.PublicBusiness.Stub; import android.app.Service; import android.content.Intent; import android.os.Binder; import android.os.IBinder; public class Remote

Java进程内存泄漏判断及解决方法

内存泄漏种类Java使用的内存种类包含三种,这三种类型的内存都可能发生内存泄漏.? 堆内存泄漏,如果JVM 不能在java 堆中获得更多内存来分配更多java 对象,将会抛出java堆内存不足(java OOM) 错误.如果java 堆充满了活动对象,并且JVM 无法再扩展java 堆,那么它将不能分配更多java 对象.更多情况是程序设计有问题,生成的对象占用过多的堆内存造成堆内存泄漏.? 本地内存泄漏, 如果JVM 无法获得更多本地内存,它将抛出本地OOM错误.当进程用到的内存到达操作系统的

进程通信之内存共享篇

进程通信之_ 内存共享 概念:共享内存是被多个进程共享的一部分物理内存.共享内存是进程间的共享数最快的方法,一个进程向共享内存区域写入数据,共享这个内存区域的所有进程就可以写入数据,所有进程就可以立刻看到其中的内容. 实现步骤;1.创建共享内存,使用shmget函数2.映射共享内存,将这段创建的共享内存映射到具体的进程空间去,使用shmat函数 创建:int shmget (key_t key,int size,int shmflg)key 标识内存功效的键值0/ipc_private.成功返回

进程通信之共享内存

共享内存 共享内存就是允许两个不相关的进程访问同一个逻辑内存.共享内存是在两个正在运行的进程之间共享和传递数据的一种非常有效的方式.不同进程之间共享的内存通常安排为同一段物理内存.进程可以将同一段共享内存连接到它们自己的地址空间中,所有进程都可以访问共享内存中的地址,就好像它们是由用C语言函数malloc分配的内存一样.而如果某个进程向共享内存写入数据,所做的改动将立即影响到可以访问同一段共享内存的任何其他进程. 共享内存并未提供同步机制,也就是说,在第一个进程结束对共享内存的写操作之前,并无自

死磕内存篇2 --JAVA进程是怎么突然挂掉的

JVM内存不足导致进程死掉. Native memory allocation (mmap) failed to map 一台服务器上部署很多JAVA进程已经是微服务的常态,但也有些坑. 背景,测试服务器上的一些JAVA进程突然挂掉,查看call back的日志发现如下: # There is insufficient memory for the Java Runtime Environment to continue. # Native memory allocation (mmap) fa

Java 父子进程通信

由父进程创建子进程,收发消息 package org.tango.process.parent; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.tango.process.signal.Signal; import java.io.*; import java.lang.reflect.Field; import java.util.Map; impo