为内存密集型应用程序转义JVM堆

如果您曾经分配过大的Java堆,您就会知道在某个时候(通常从大约4 GiB开始),您将开始遇到垃圾回收暂停的问题。

我不会详细介绍为什么在JVM中会出现暂停,但是总之,当JVM进行完整的收集并且您有很大的堆时,就会发生暂停。 随着堆的增加,这些集合可能会变得更长。

解决此问题的最简单方法是调整JVM垃圾回收参数,以匹配特定应用程序的内存分配和释放行为。 这有点晦涩,需要仔细测量,但是可能有很大的堆,同时又避免了大多数旧式垃圾回收。 如果您想了解有关垃圾收集调优的更多信息,请查阅JVM GC调优指南 。 如果您总体上对GC真的很感兴趣,那么这是一本很棒的书: The Garbage Collection Handbook 。

有一些JVM实现可以保证比Sun VM(例如Zing JVM)要少得多的暂停时间,但是通常会增加系统的其他成本,例如增加内存使用量和单线程性能。 易于配置和低gc保证仍然非常吸引人。 出于本文的目的,我将使用内存缓存或Java存储示例,这主要是因为我在过去使用这些技术中的一部分时已经构建了一对。

我们假设我们有一个基本的缓存接口定义,如下所示:

import java.io.Externalizable;public interface Cache<K extends Externalizable, V extends Externalizable> {public void put(K key, V value);public V get(K key);
}

对于这个简单的示例,我们要求键和值是可外部化的,而不是像此IRL那样。

我们将展示如何使用此缓存的不同实现,以不同的方式将数据存储在内存中。 实现此缓存的最简单方法是使用Java集合:

import java.io.Externalizable;
import java.util.HashMap;
import java.util.Map;public class CollectionCache<K extends Externalizable, V extends Externalizable> implements Cache<K, V> {private final Map<K, V> backingMap = new HashMap<K, V>();public void put(K key, V value) {backingMap.put(key, value);}public V get(K key) {return backingMap.get(key);}
}

该实现是直接的。 但是,随着地图大小的增加,我们将分配大量对象(并取消分配),我们使用的是盒装原语,它占用了更多的内存空间,因此原语和地图需要不时调整大小。 当然,我们可以简单地通过使用基于基元的映射来改进此实现。 它会使用较少的内存和对象,但仍会占用堆中的空间并可能对堆进行分区,如果由于其他原因我们执行完整的GC,则会导致更长的暂停时间。

让我们看看不使用堆来存储相似数据的其他方法:

  • 使用一个单独的过程来存储数据 。 可能是通过套接字或Unix套接字连接的Redis或Memcached实例。 实施起来相当简单。
  • 使用内存映射文件将数据卸载到磁盘 。 操作系统是您的朋友,并且会做很多繁重的工作来预测接下来从文件中读取的内容以及与文件的接口,就像是一大堆数据一样。
  • 使用本机代码并通过JNI或JNA访问它 。 通过JNI,您将获得更好的性能,并通过JNA易于使用。 需要您编写本机代码。
  • 使用 NIO包中直接分配的缓冲区
  • 使用Sun特定的Unsafe类可以直接从Java代码访问内存。

我将重点介绍本文仅使用Java的解决方案,直接分配的缓冲区和Unsafe类。

直接分配的缓冲区

在Java NIO中开发高性能网络应用程序时,直接分配缓冲区非常有用,并且广泛使用。 通过在堆外直接分配数据,在许多情况下,您可以编写软件,使这些数据实际上永远不会碰到堆。

创建新的直接分配缓冲区非常简单:

int numBytes = 1000;
ByteBuffer buffer = ByteBuffer.allocateDirect(numBytes);

创建新缓冲区后,可以用几种不同的方式来操作缓冲区。 如果您从未使用过Java NIO缓冲区,那么绝对值得一看,因为它们确实很棒。

除了填充,清空和标记缓冲区中不同点的方法外,您还可以选择在缓冲区上使用不同的视图而不是ByteBuffer –例如, buffer.asLongBuffer()为您提供了在ByteBuffer上的视图,您可以在该视图上buffer.asLongBuffer()操作元素。

那么如何在我们的Cache示例中使用它们? 有很多种方法,最直接的方法是将值记录的序列化/外部化形式存储在一个大数组中,以及指向该数组中记录的偏移量和大小的键映射。

可能看起来像这样(非常宽松的方法,缺少实现并假设记录大小固定):

import java.io.Externalizable;
import java.nio.ByteBuffer;
import java.util.HashMap;
import java.util.Map;public class DirectAllocatedCache<K extends Externalizable, V extends Externalizable> implements Cache<K,V> {private final ByteBuffer backingMap;private final Map<K, Integer> keyToOffset;private final int recordSize;public DirectAllocatedCache(int recordSize, int maxRecords) {this.recordSize = recordSize;this.backingMap = ByteBuffer.allocateDirect(recordSize * maxRecords);this.keyToOffset = new HashMap<K, Integer>();}public void put(K key, V value) {if(backingMap.position() + recordSize < backingMap.capacity()) {keyToOffset.put(key, backingMap.position());store(value);}   }public V get(K key) {int offset = keyToOffset.get(key);if(offset >= 0)return retrieve(offset);throw new KeyNotFoundException();}public V retrieve(int offset) {byte[] record = new byte[recordSize];int oldPosition = backingMap.position();backingMap.position(offset);backingMap.get(record);backingMap.position(oldPosition);//implementation left as an exercisereturn internalize(record);}public void store(V value) {byte[] record = externalize(value);backingMap.put(record);}
}

如您所见,此代码有许多限制:固定的记录大小,固定的支持映射大小,完成外部化的方式有限,难以删除和重用空间等。尽管其中某些方式可以通过巧妙的方法来克服以字节数组表示记录(也可以在直接分配的缓冲区中表示keyToOffset映射)或处理删除操作(我们可以实现自己的SLAB分配器),其他诸如调整支持映射大小的操作很难克服。 一个有趣的改进是将记录实现为记录和字段的偏移量,从而减少了我们仅按需复制和复制的数据量。

请注意,JVM对直接分配的缓冲区使用的内存量施加了限制。 您可以使用-XX:MaxDirectMemorySize选项进行调整。 查看ByteBuffer javadocs

不安全

直接从Java管理内存的另一种方法是使用隐藏的Unsafe类。 从技术上讲,我们不应该使用它,它是特定于实现的,因为它位于sun软件包中,但是提供的可能性是无限的。
Unsafe给我们带来的是直接从Java代码分配,取消分配和管理内存的能力。 我们还可以获取实际的指针,并将它们在本机代码和Java代码之间互换传递。

为了获得一个不安全的实例,我们需要走一些弯路:

private Unsafe getUnsafeBackingMap() {try {Field f = Unsafe.class.getDeclaredField('theUnsafe');f.setAccessible(true);return (Unsafe) f.get(null);} catch (Exception e) { }return null;
}

一旦有了不安全因素,我们可以将其应用于之前的Cache示例:

import java.io.Externalizable;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;import sun.misc.Unsafe;public class UnsafeCache<K extends Externalizable, V extends Externalizable> implements Cache<K, V> {private final int recordSize;private final Unsafe backingMap;private final Map<K, Integer> keyToOffset;private long address;private int capacity;private int currentOffset;public UnsafeCache(int recordSize, int maxRecords) {this.recordSize = recordSize;this.backingMap = getUnsafeBackingMap();this.capacity = recordSize * maxRecords;this.address = backingMap.allocateMemory(capacity);this.keyToOffset = new HashMap<K, Integer>();}public void put(K key, V value) {if(currentOffset + recordSize < capacity) {store(currentOffset, value);keyToOffset.put(key, currentOffset);currentOffset += recordSize;}}public V get(K key) {int offset = keyToOffset.get(key);if(offset >= 0)return retrieve(offset);throw new KeyNotFoundException();}public V retrieve(int offset) {byte[] record = new byte[recordSize];//Inefficientfor(int i=0; i<record.length; i++) {record[i] = backingMap.getByte(address + offset + i);}//implementation left as an exercisereturn internalize(record);}public void store(int offset, V value) {byte[] record = externalize(value);//Inefficientfor(int i=0; i<record.length; i++) {backingMap.putByte(address + offset + i, record[i]);}}private Unsafe getUnsafeBackingMap() {try {Field f = Unsafe.class.getDeclaredField('theUnsafe');f.setAccessible(true);return (Unsafe) f.get(null);} catch (Exception e) { }return null;}
}

有很多改进的空间,您需要手动执行许多操作,但是功能非常强大。 您还可以显式释放和重新分配以这种方式分配的内存,这使您可以以与C相同的方式编写一些代码。

查看javadocs中的Unsafe

结论

有许多种方法可以避免在Java中使用堆,并以此方式使用更多的内存。 您无需执行此操作,而且我个人看到运行20GiB-30GiB且已进行了适当调整的JVM,并且没有长时间的垃圾收集暂停,但这非常有趣。

如果要查看一些项目如何将其用于我在此处编写的基本(并且未经测试,几乎写在餐巾纸上)缓存代码,请查看EHCache的BigMemory或Apache Cassandra,它们也将Unsafe用于此类方法。

参考:在Java Advent Calendar博客上,我们的JCG合作伙伴 Ruben Badaro 从JVM堆转出了内存密集型应用程序 。

翻译自: https://www.javacodegeeks.com/2012/12/escaping-the-jvm-heap-for-memory-intensive-applications.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/369769.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

MySQL学习笔记1(增删查改)

创建表&#xff1a; /*创建数据库create database 数据库名; */ CREATE DATABASE mybase; /*使用数据库use 数据库名 */ USE mybase;/*创建数据表的格式create table 表名(列名1 数据类型 约束,列名2 数据类型 约束,列名3 数据类型 约束);创建用户表,用户编号,姓名,用户的地址将…

Angular4 中内置指令的基本用法

ngFor 作用&#xff1a;像 for 循环一样&#xff0c;可以重复的从数组中取值并显示出来。 // .tsthis.userInfo [张三, 李四, 王五];// .html<div class"ui list" *ngFor"let username of userInfo"><div class"item">{{username}}…

初入编程的新世界

准备跨入程序员的行列了&#xff0c; 今天开课第一天&#xff0c; 算起来之前学习的几天&#xff0c; 第一次真正的了解了网页制作包括什么&#xff0c; html&#xff08;结构&#xff09;&#xff0c;css&#xff08;页面美化 层叠样式表&#xff09;&#xff0c;JavaScri…

java中i+=2什么意思_三分钟看懂Java中i++与++i的性能差别以及循环中如何使用

在Java中&#xff0c;自增是一种非常常见的操作&#xff0c;在自增中&#xff0c;有两种写法&#xff0c;一种是前缀自增(i)&#xff0c;一种是后缀自增(i)。这里主要简单介绍两种自增的差别。一、含义差别前缀自增和后缀自增是不同的。前缀自增(i)是从内存中加载i&#xff0c;…

Java / JEE中的有效日志记录–映射的诊断上下文

当我和一位同事坐在一起解决一些应用程序问题时&#xff0c;一切都开始了&#xff0c;当时我注意到了一些有趣的事情。 他正在合并代码&#xff0c;我的眼睛吸引了此类“ org.apache.log4j.MDC”的注意。 这导致了以下发现&#xff1a; 什么是MDC&#xff1f; MDC代表“ 映射诊…

bzoj1049[HAOI2006]数字序列

1049: [HAOI2006]数字序列 Time Limit: 10 Sec Memory Limit: 162 MBSubmit: 1813 Solved: 789[Submit][Status][Discuss]Description 现在我们有一个长度为n的整数序列A。但是它太不好看了&#xff0c;于是我们希望把它变成一个单调严格上升的序列。但是不希望改变过多的数&…

具有Aspects的Java中的Mixin –用于Scala特性示例

Scala特征允许将新行为混合到一个类中。 考虑两个特征&#xff0c;可以向JPA实体添加审核和与版本相关的字段&#xff1a; package mvcsample.domainimport javax.persistence.Version import scala.reflect.BeanProperty import java.util.Datetrait Versionable {VersionBea…

前端:HTML

1,web服务的本质 import socketsk socket.socket()sk.bind(("127.0.0.1", 8080)) sk.listen(5)while True:conn, addr sk.accept()data conn.recv(8096)conn.send(b"HTTP/1.1 200 OK\r\n\r\n")conn.secd(b"<h1>Hello world!</h1>"…

动态规划:从新手到专家

作者&#xff1a;Hawstein出处&#xff1a;http://hawstein.com/posts/dp-novice-to-advanced.html前言 本文翻译自TopCoder上的一篇文章&#xff1a; Dynamic Programming: From novice to advanced &#xff0c;并非严格逐字逐句翻译&#xff0c;其中加入了自己的一些理解。水…

小程序 foreach_【第2106期】小程序依赖分析实践

前言这种可视化分析图还是很直观的&#xff0c;很有趣。今日早读文章由自然醒授权分享。正文从这开始~~用过 webpack 的同学肯定知道 webpack-bundle-analyzer &#xff0c;可以用来分析当前项目 js 文件的依赖关系。webpack-bundle-analyzer因为最近一直在做小程序业务&#x…

python----模块

collections---------------------------------------->扩展数据类型 re-------------------------------------------------->正则相关操作&#xff0c;正则 匹配字符串 time----------------------------------------------->时间相关 三种格式&#xff1a;时间戳&…

在MySQL数据库上使用Quartz Scheduler入门

这是一些简单的步骤&#xff0c;可帮助您使用Groovy在MySQL数据库上完全入门Quartz Scheduler。 以下脚本可让您使用外部文件快速尝试不同的Quartz配置设置。 第一步是使用表设置数据库。 假设您已经安装了MySQL&#xff0c;并且有权创建数据库和表。 bash> mysql -u root …

一招搞定css页面布局

如何做出漂亮的页面&#xff1a; 1、 多写页面&#xff0c;多改。 2、 多写页面&#xff0c;多改。 3、 多写页面&#xff0c;多改。 大致的思想步骤&#xff1a; 写页面的时候先规划好大致的分块&#xff0c;无论是用定位或者浮动&#xff0c;首先要确定要应用的场景&#xf…

mysql blob取值_MySQL 数据类型:

MySQL 数据类型&#xff1a;字符型数值型日期时间型内建类型字符型&#xff1a;CHAR, BINARY&#xff1a;定长数据类型&#xff1b;VARCHAR, VARBINARY&#xff1a;变长数据类型&#xff1b;需要结束符&#xff1b;TEXT&#xff1a;TINYTEXT, TEXT, MEDIUMTEXT, LONGTEXTBLOB: …

MySQL 中的三中循环 while loop repeat 的基本用法

-- MySQL中的三中循环 while 、 loop 、repeat 求 1-n 的和-- 第一种 while 循环 -- 求 1-n 的和 /* while循环语法&#xff1a; while 条件 DO循环体; end while; */ -- 实例&#xff1a; create procedure sum1(a int) begindeclare sum int default 0; -- default 是指…

css渲染(二) 文本

一、文本样式 首行缩进  text-indent 首行缩进是将段落的第一行缩进&#xff0c;这是常用的文本格式化效果。一般地&#xff0c;中文写作时开头空两格。[注意]该属性可以为负值&#xff1b;应用于: 块级元素(包括block和inline-block)  <div><p style"text-in…

RTKLIB的主要功能

RTKLIB是全球导航卫星系统GNSS(global navigation satellite system)的标准&精密定位开源程序包&#xff0c;RTKLIB由日本东京海洋大学&#xff08;Tokyo University of Marine Science and Technology&#xff09;的高须知二&#xff08;Tomoji Takasu&#xff09;开发。R…

28岁学python转行_28岁转行程序员,学Java还是Python?码农:想快点月薪过万就选它...

为什么要学Java&#xff1f;Python给人的印象简单是因为我们在用Python的时候&#xff0c;可以直接调用别人已经写好的代码接口就可以&#xff0c;相对于傻瓜模式&#xff0c;Java的许多处理都要原生很多&#xff0c;写的代码可能会多一些&#xff0c;但一旦完成封装&#xff0…

使用SSL和Spring Security保护Tomcat应用程序的安全

如果您看过我的上一个博客&#xff0c;您会知道我列出了Spring Security可以做的十件事 。 但是&#xff0c;在认真开始使用Spring Security之前&#xff0c;您真正要做的第一件事就是确保您的Web应用使用正确的传输协议&#xff0c;在这种情况下为HTTPS –毕竟&#xff0c;没有…

模块 hashlib模块

hashlib模块 提供摘要算法 主要做对比&#xff0c;比较两段代码是否完全一致 不管算法多么不同&#xff0c;摘要功能始终不变&#xff0c; 对同一个字符串进项同一算法摘要得到的值始终不变 MD5值的生成 import hashlib sha1 hashlib.md5() #一定加括号 sha1.update(bytes(a…