Javaのバッファソースコードの詳細な分析
ネイティブ環境: Linux 4.4.0-21-generic #37-Ubuntu SMP Mon Apr 18 18:33:37 UTC 2016 x86_64 x86_64 x86_64 GNU/Linux
Linux 4.4.0-21-generic #37-Ubuntu SMP Mon Apr 18 18:33:37 UTC 2016 x86_64 x86_64 x86_64 GNU/Linux
Buffer
Buffer的类图如下:
除了Boolean,其他基本数据类型都有对应的Buffer,但是只有ByteBuffer才能和Channel交互。只有ByteBuffer才能产生Direct的buffer,其他数据类型的Buffer只能产生Heap类型的Buffer。ByteBuffer可以产生其他数据类型的视图Buffer,如果ByteBuffer本身是Direct的,则产生的各视图Buffer也是Direct的。
Direct和Heap类型Buffer的本质
首选说说JVM是怎么进行IO操作的。
JVM在需要通过操作系统调用完成IO操作,比如可以通过read系统调用完成文件的读取。read的原型是:ssize_t read(int fd,void *buf,size_t nbytes)
,和其他的IO系统调用类似,一般需要缓冲区作为其中一个参数,该缓冲区要求是连续的。
Buffer分为Direct和Heap两类,下面分别说明这两类buffer。
Heap
Heap类型的Buffer存在于JVM的堆上,这部分内存的回收与整理和普通的对象一样。Heap类型的Buffer对象都包含一个对应基本数据类型的数组属性(比如:final **[] hb),数组才是Heap类型Buffer的底层缓冲区。
但是Heap类型的Buffer不能作为缓冲区参数直接进行系统调用,主要因为下面两个原因。
JVM在GC时可能会移动缓冲区(复制-整理),缓冲区的地址不固定。
系统调用时,缓冲区需要是连续的,但是数组可能不是连续的(JVM的实现没要求连续)。
所以使用Heap类型的Buffer进行IO时,JVM需要产生一个临时Direct类型的Buffer,然后进行数据复制,再使用临时Direct的Buffer作为参数进行操作系统调用。这造成很低的效率,主要是因为两个原因:
需要把数据从Heap类型的Buffer里面复制到临时创建的Direct的Buffer里面。
可能产生大量的Buffer对象,从而提高GC的频率。所以在IO操作时,可以通过重复利用Buffer进行优化。
Direct
Direct类型的buffer,不存在于堆上,而是JVM通过malloc直接分配的一段连续的内存,这部分内存成为直接内存,JVM进行IO系统调用时使用的是直接内存作为缓冲区。-XX:MaxDirectMemorySize
,通过这个配置可以设置允许分配的最大直接内存的大小(MappedByteBuffer分配的内存不受此配置影响)。
直接内存的回收和堆内存的回收不同,如果直接内存使用不当,很容易造成OutOfMemoryError。JAVA没有提供显示的方法去主动释放直接内存,sun.misc.Unsafe类可以进行直接的底层内存操作,通过该类可以主动释放和管理直接内存。同理,也应该重复利用直接内存以提高效率。
MappedByteBuffer和DirectByteBuffer之间的关系
This is a little bit backwards: By rights MappedByteBuffer should be a subclass of DirectByteBuffer, but to keep the spec clear and simple, and for optimization purposes, it's easier to do it the other way around.This works because DirectByteBuffer is a package-private class.(本段话摘自MappedByteBuffer的源码)
实际上,MappedByteBuffer属于映射buffer(自己看看虚拟内存),但是DirectByteBuffer只是说明该部分内存是JVM在直接内存区分配的连续缓冲区,并不一是映射的。也就是说MappedByteBuffer应该是DirectByteBuffer的子类,但是为了方便和优化,把MappedByteBuffer作为了DirectByteBuffer的父类。另外,虽然MappedByteBuffer在逻辑上应该是DirectByteBuffer的子类,而且MappedByteBuffer的内存的GC和直接内存的GC类似(和堆GC不同),但是分配的MappedByteBuffer的大小不受-XX:MaxDirectMemorySize参数影响。
MappedByteBuffer封装的是内存映射文件操作,也就是只能进行文件IO操作。MappedByteBuffer是根据mmap产生的映射缓冲区,这部分缓冲区被映射到对应的文件页上,属于直接内存在用户态,通过MappedByteBuffer可以直接操作映射缓冲区,而这部分缓冲区又被映射到文件页上,操作系统通过对应内存页的调入和调出完成文件的写入和写出。
MappedByteBuffer
通过FileChannel.map(MapMode mode,long position, long size)
得到MappedByteBuffer,下面结合源码说明MappedByteBuffer的产生过程。
FileChannel.map
バッファ
Javaのバッファソースコードの詳細な分析は以下の通りです:
ダイレクト バッファーとヒープ タイプ バッファーの本質
🎜 最初の選択肢は、JVM が IO 操作を実行する方法について説明することです。 🎜🎜JVM は、オペレーティング システム コールを通じて IO 操作を完了する必要があります。たとえば、読み取りシステム コールを通じてファイルの読み取りを完了できます。 read のプロトタイプは次のとおりです:ssize_t read(int fd, void *buf, size_t nbytes)
は、他の IO システム コールと同様に、通常、パラメーターの 1 つとしてバッファーを必要とし、バッファーは次のようになります。連続的であること。 🎜🎜バッファは、ダイレクトとヒープの 2 つのカテゴリに分類されます。これらの 2 つのタイプのバッファについては、以下で説明します。 🎜ヒープ
🎜 ヒープ タイプ バッファーは JVM ヒープ上に存在します。メモリのこの部分のリサイクルと並べ替えは、通常のオブジェクトと同じです。ヒープ タイプのバッファ オブジェクトにはすべて、基本データ タイプ (たとえば、final **[] hb) に対応する配列属性が含まれており、その配列はヒープ タイプ バッファの基礎となるバッファです。 🎜ただし、主に次の 2 つの理由により、ヒープ タイプの Buffer を直接システム コールのバッファ パラメータとして使用することはできません。 🎜- 🎜 JVM は GC 中にバッファを移動 (コピー整理) する可能性があり、バッファのアドレスは固定されていません。 🎜
- 🎜 システムコールを行うとき、バッファーは連続的である必要がありますが、配列は連続的でなくても構いません (JVM 実装では連続性は必要ありません)。 🎜
- 🎜 データをヒープ タイプのバッファから一時的に作成されたダイレクト バッファにコピーする必要がある。 🎜
- 🎜 大量の Buffer オブジェクトが生成されるため、GC の頻度が増加する可能性があります。したがってIO 操作中に、バッファを再利用することで最適化できます。 🎜
Direct
🎜 ダイレクト タイプのバッファはヒープ上に存在しませんが、JVM によって malloc を通じて直接割り当てられる連続メモリです。メモリのこの部分です。ダイレクト メモリになると、JVM は IO システム コールを行うときにバッファとしてダイレクト メモリを使用します。 🎜-XX:MaxDirectMemorySize
では、この構成を通じて、割り当て可能な最大直接メモリ サイズを設定できます (MappedByteBuffer によって割り当てられたメモリは、この構成の影響を受けません)。 🎜 ダイレクト メモリのリサイクルは、ヒープ メモリのリサイクルとは異なります。ダイレクト メモリが不適切に使用されると、OutOfMemoryError が発生しやすくなります。 JAVA には、直接メモリをアクティブに解放するための明示的なメソッドが用意されていません。sun.misc.Unsafe クラスは、基礎となる直接メモリ操作を実行でき、このクラスを通じてダイレクト メモリをアクティブに解放および管理できます。同様にダイレクトメモリも効率を高めるために再利用する必要があります。 🎜MappedByteBuffer と DirectByteBuffer の関係
🎜これは少し逆です。権利上、MappedByteBuffer は DirectByteBuffer のサブクラスである必要がありますが、仕様を明確かつ単純に保つためです。最適化の目的では、DirectByteBuffer がパッケージ プライベート クラスであるため、これが機能します。(この段落は MappedByteBuffer のソース コードから引用しています)🎜🎜In実際、MappedByteBuffer はマップされたバッファ (仮想メモリを自分で見てください) ですが、DirectByteBuffer は、メモリのこの部分が JVM によってダイレクト メモリ領域に割り当てられた連続バッファであることを示すだけであり、必ずしもマップされる必要はありません。つまり、MappedByteBuffer は DirectByteBuffer のサブクラスである必要がありますが、利便性と最適化のために、MappedByteBuffer は DirectByteBuffer の親クラスとして使用されます。さらに、MappedByteBuffer は論理的に DirectByteBuffer のサブクラスである必要があり、MappedByteBuffer メモリの GC はダイレクト メモリの GC と似ていますが (ヒープ GC とは異なります)、割り当てられた MappedByteBuffer のサイズは -XX:MaxDirectMemorySize の影響を受けません。パラメータ。 🎜MappedByteBuffer はメモリ マップされたファイル操作をカプセル化します。つまり、ファイル IO 操作のみを実行できます。 MappedByteBuffer は、mmap に基づいて生成されたマッピング バッファです。バッファのこの部分は、対応するファイル ページにマッピングされ、ユーザー モードでは直接メモリに属し、MappedByteBuffer を通じて直接操作できます。ファイル ページでは、オペレーティング システムは、対応するメモリ ページを呼び出したり呼び出したりすることによって、ファイルの書き込みと書き込みを完了します。 🎜
MappedByteBuffer
🎜FileChannel.map(MapMode mode,longposition,long size)
でMappedByteBufferを取得します。MappedByteBufferの生成処理をソースコードを用いて説明します。 🎜🎜FileChannel.map
ソース コード: 🎜🎜public MappedByteBuffer map(MapMode mode, long position, long size)throws IOException {ensureOpen();if (position < 0L)throw new IllegalArgumentException("Negative position");if (size < 0L)throw new IllegalArgumentException("Negative size");if (position + size < 0)throw new IllegalArgumentException("Position + size overflow");//最大2Gif (size > Integer.MAX_VALUE)throw new IllegalArgumentException("Size exceeds Integer.MAX_VALUE");int imode = -1;if (mode == MapMode.READ_ONLY) imode = MAP_RO;else if (mode == MapMode.READ_WRITE) imode = MAP_RW;else if (mode == MapMode.PRIVATE) imode = MAP_PV;assert (imode >= 0);if ((mode != MapMode.READ_ONLY) && !writable)throw new NonWritableChannelException();if (!readable)throw new NonReadableChannelException();long addr = -1;int ti = -1;try {begin(); ti = threads.add();if (!isOpen())return null;//size()返回实际的文件大小//如果实际文件大小不符合,则增大文件的大小,文件的大小被改变,文件增大的部分默认设置为0。if (size() < position + size) { // Extend file sizeif (!writable) {throw new IOException("Channel not open for writing " +"- cannot extend file to required size"); }int rv;do { //增大文件的大小rv = nd.truncate(fd, position + size); } while ((rv == IOStatus.INTERRUPTED) && isOpen()); }//如果要求映射的文件大小为0,则不调用操作系统的mmap调用,只是生成一个空间容量为0的DirectByteBuffer//并返回if (size == 0) { addr = 0;// a valid file descriptor is not requiredFileDescriptor dummy = new FileDescriptor();if ((!writable) || (imode == MAP_RO))return Util.newMappedByteBufferR(0, 0, dummy, null);elsereturn Util.newMappedByteBuffer(0, 0, dummy, null); }//allocationGranularity的大小在我的系统上是4K//页对齐,pagePosition为第多少页int pagePosition = (int)(position % allocationGranularity);//从页的最开始映射long mapPosition = position - pagePosition;//因为从页的最开始映射,增大映射空间long mapSize = size + pagePosition;try {// If no exception was thrown from map0, the address is valid//native方法,源代码在openjdk/jdk/src/solaris/native/sun/nio/ch/FileChannelImpl.c,//参见下面的说明addr = map0(imode, mapPosition, mapSize); } catch (OutOfMemoryError x) {// An OutOfMemoryError may indicate that we've exhausted memory// so force gc and re-attempt mapSystem.gc();try { Thread.sleep(100); } catch (InterruptedException y) { Thread.currentThread().interrupt(); }try { addr = map0(imode, mapPosition, mapSize); } catch (OutOfMemoryError y) {// After a second OOME, failthrow new IOException("Map failed", y); } }// On Windows, and potentially other platforms, we need an open// file descriptor for some mapping operations.FileDescriptor mfd;try { mfd = nd.duplicateForMapping(fd); } catch (IOException ioe) {unmap0(addr, mapSize);throw ioe; }assert (IOStatus.checkAll(addr));assert (addr % allocationGranularity == 0);int isize = (int)size; Unmapper um = new Unmapper(addr, mapSize, isize, mfd);if ((!writable) || (imode == MAP_RO)) {return Util.newMappedByteBufferR(isize, addr + pagePosition, mfd, um); } else {return Util.newMappedByteBuffer(isize, addr + pagePosition, mfd, um); } } finally { threads.remove(ti);end(IOStatus.checkAll(addr)); } }
map0
的源码实现:
JNIEXPORT jlong JNICALL Java_sun_nio_ch_FileChannelImpl_map0(JNIEnv *env, jobject this, jint prot, jlong off, jlong len) {void *mapAddress = 0; jobject fdo = (*env)->GetObjectField(env, this, chan_fd);//linux系统调用是通过整型的文件id引用文件的,这里得到文件idjint fd = fdval(env, fdo);int protections = 0;int flags = 0;if (prot == sun_nio_ch_FileChannelImpl_MAP_RO) { protections = PROT_READ; flags = MAP_SHARED; } else if (prot == sun_nio_ch_FileChannelImpl_MAP_RW) { protections = PROT_WRITE | PROT_READ; flags = MAP_SHARED; } else if (prot == sun_nio_ch_FileChannelImpl_MAP_PV) { protections = PROT_WRITE | PROT_READ; flags = MAP_PRIVATE; }//这里就是操作系统调用了,mmap64是宏定义,实际最后调用的是mmapmapAddress = mmap64(0, /* Let OS decide location */len, /* Number of bytes to map */protections, /* File permissions */flags, /* Changes are shared */fd, /* File descriptor of mapped file */off); /* Offset into file */if (mapAddress == MAP_FAILED) {if (errno == ENOMEM) {//如果没有映射成功,直接抛出OutOfMemoryErrorJNU_ThrowOutOfMemoryError(env, "Map failed");return IOS_THROWN; }return handle(env, -1, "Map failed"); }return ((jlong) (unsigned long) mapAddress); }
虽然FileChannel.map()
的zise参数是long,但是size的大小最大为Integer.MAX_VALUE,也就是最大只能映射最大2G大小的空间。实际上操作系统提供的MMAP可以分配更大的空间,但是JAVA限制在2G,ByteBuffer等Buffer也最大只能分配2G大小的缓冲区。
MappedByteBuffer是通过mmap产生得到的缓冲区,这部分缓冲区是由操作系统直接创建和管理的,最后JVM通过unmmap让操作系统直接释放这部分内存。
Haep****Buffer
下面以ByteBuffer为例,说明Heap类型Buffer的细节。
该类型的Buffer可以通过下面方式产生:
ByteBuffer.allocate(int capacity)
ByteBuffer.wrap(byte[] array)
使用传入的数组作为底层缓冲区,变更数组会影响缓冲区,变更缓冲区也会影响数组。ByteBuffer.wrap(byte[] array,int offset, int length)
使用传入的数组的一部分作为底层缓冲区,变更数组的对应部分会影响缓冲区,变更缓冲区也会影响数组。
DirectByteBuffer
DirectByteBuffer只能通过ByteBuffer.allocateDirect(int capacity)
产生。ByteBuffer.allocateDirect()
源码如下:
public static ByteBuffer allocateDirect(int capacity) {return new DirectByteBuffer(capacity); }
DirectByteBuffer()
源码如下:
DirectByteBuffer(int cap) { // package-private super(-1, 0, cap, cap); //直接内存是否要页对齐,我本机测试的不用 boolean pa = VM.isDirectMemoryPageAligned(); //页的大小,本机测试的是4K int ps = Bits.pageSize(); //如果页对齐,则size的大小是ps+cap,ps是一页,cap也是从新的一页开始,也就是页对齐了 long size = Math.max(1L, (long)cap + (pa ? ps : 0)); //JVM维护所有直接内存的大小,如果已分配的直接内存加上本次要分配的大小超过允许分配的直接内存的最大值会 //引起GC,否则允许分配并把已分配的直接内存总量加上本次分配的大小。如果GC之后,还是超过所允许的最大值, //则throw new OutOfMemoryError("Direct buffer memory"); Bits.reserveMemory(size, cap); long base = 0; try { //是吧,unsafe可以直接操作底层内存 base = unsafe.allocateMemory(size); } catch (OutOfMemoryError x) {、 //没有分配成功,把刚刚加上的已分配的直接内存的大小减去。 Bits.unreserveMemory(size, cap); throw x; } unsafe.setMemory(base, size, (byte) 0); if (pa && (base % ps != 0)) { // Round up to page boundary address = base + ps - (base & (ps - 1)); } else { address = base; } cleaner = Cleaner.create(this, new Deallocator(base, size, cap)); att = null; }
unsafe.allocateMemory()
的源码在openjdk/src/openjdk/hotspot/src/share/vm/prims/unsafe.cpp中。具体的源码如下:
UNSAFE_ENTRY(jlong, Unsafe_AllocateMemory(JNIEnv *env, jobject unsafe, jlong size)) UnsafeWrapper("Unsafe_AllocateMemory"); size_t sz = (size_t)size; if (sz != (julong)size || size < 0) { THROW_0(vmSymbols::java_lang_IllegalArgumentException()); } if (sz == 0) {return 0; } sz = round_to(sz, HeapWordSize); //最后调用的是 u_char* ptr = (u_char*)::malloc(size + space_before + space_after),也就是malloc。 void* x = os::malloc(sz, mtInternal); if (x == NULL) { THROW_0(vmSymbols::java_lang_OutOfMemoryError()); } //Copy::fill_to_words((HeapWord*)x, sz / HeapWordSize); return addr_to_java(x); UNSAFE_END
JVM通过malloc分配得到连续的缓冲区,这部分缓冲区可以直接作为缓冲区参数进行操作系统调用。
以上がJavaのバッファソースコードの詳細な分析の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

ホットAIツール

Undresser.AI Undress
リアルなヌード写真を作成する AI 搭載アプリ

AI Clothes Remover
写真から衣服を削除するオンライン AI ツール。

Undress AI Tool
脱衣画像を無料で

Clothoff.io
AI衣類リムーバー

AI Hentai Generator
AIヘンタイを無料で生成します。

人気の記事

ホットツール

メモ帳++7.3.1
使いやすく無料のコードエディター

SublimeText3 中国語版
中国語版、とても使いやすい

ゼンドスタジオ 13.0.1
強力な PHP 統合開発環境

ドリームウィーバー CS6
ビジュアル Web 開発ツール

SublimeText3 Mac版
神レベルのコード編集ソフト(SublimeText3)

ホットトピック











Python 言語は高級プログラミング言語として、学習が容易で読み書きも容易であり、ソフトウェア開発の分野で広く使用されています。ただし、Python のオープン ソースの性質により、ソース コードには他の人が簡単にアクセスできるため、ソフトウェアのソース コードの保護にいくつかの課題が生じます。したがって、実際のアプリケーションでは、Python ソース コードを保護し、そのセキュリティを確保するために何らかの方法を講じる必要があることがよくあります。ソフトウェア ソース コードの保護では、Python のさまざまなアプリケーション プラクティスから選択できます。以下は一般的なものです

IDEA で Tomcat ソース コードを表示する手順: 1. Tomcat ソース コードをダウンロードする; 2. Tomcat ソース コードを IDEA にインポートする; 3. Tomcat ソース コードを表示する; 4. Tomcat の動作原理を理解する; 5. 注意事項; 6. 継続的な学習と更新する; 7. ツールとプラグインを使用する; 8. コミュニティに参加して貢献する。詳細な紹介: 1. Tomcat ソース コードをダウンロードします。ソース コード パッケージは、Apache Tomcat の公式 Web サイトからダウンロードできます。通常、これらのソース コード パッケージは ZIP または TAR 形式などです。

徹底した分析: Go 言語のパフォーマンスは何ですか?はじめに: 今日のソフトウェア開発の世界では、パフォーマンスが重要な要素です。開発者にとって、優れたパフォーマンスを備えたプログラミング言語を選択すると、ソフトウェア アプリケーションの効率と品質を向上させることができます。最新のプログラミング言語として、Go 言語は多くの開発者によって高性能言語であると考えられています。この記事では、Go 言語のパフォーマンス特性を詳しく掘り下げ、特定のコード例を通じて分析します。 1. 同時実行機能: 同時実行に基づいたプログラミング言語として、Go 言語は優れた同時実行機能を備えています。

PHPコードのソースコードを解釈・実行せずにブラウザ上に表示するにはどうすればよいでしょうか? PHP は、動的 Web ページの開発に一般的に使用されるサーバー側スクリプト言語です。サーバー上で PHP ファイルが要求されると、サーバーはそのファイル内の PHP コードを解釈して実行し、最終的な HTML コンテンツを表示のためにブラウザーに送信します。ただし、PHP ファイルのソース コードを実行するのではなく、ブラウザーに直接表示したい場合があります。この記事では、PHPコードのソースコードを解釈・実行せずにブラウザ上に表示する方法を紹介します。 PHPでは、次のように使用できます

Vue ではソースコードを表示できます Vue でソースコードを表示する方法は、 1. 「git clone https://github.com/vuejs/vue.git」で Vue を入手する; 2. 「npm i」で依存関係をインストールする; 3. 「 npm i -g rollup」を使用してロールアップをインストールします; 4. 開発スクリプトを変更します; 5. ソース コードをデバッグします。

ブラウザの開発者ツールを使用して、Web サイトのソース コードを表示できます。Google Chrome ブラウザの場合: 1. Chrome ブラウザを開き、ソース コードを表示する Web サイトにアクセスします。2. Web 上の任意の場所を右クリックします。ページに移動して「検査」を選択するか、ショートカット キー Ctrl + Shift + I を押して開発者ツールを開きます; 3. 開発者ツールの上部メニュー バーで、「要素」タブを選択します; 4. HTML と CSS コードを確認するだけですウェブサイトの。

PHP ソース コード エラー: インデックス エラーの問題を解決するには、特定のコード サンプルが必要ですインターネットの急速な発展に伴い、開発者は Web サイトやアプリケーションを作成するときにさまざまな問題に遭遇することがよくあります。中でも PHP は人気のあるサーバーサイド スクリプト言語であり、そのソース コード エラーは開発者がよく遭遇する問題の 1 つです。 Web サイトのインデックス ページを開こうとすると、「InternalServerError」、「Unde」などのさまざまなエラー メッセージが表示されることがあります。

Golang フレームワークのソース コードを理解することで、開発者は言語の本質を習得し、フレームワークの機能を拡張できます。まず、ソース コードを入手して、そのディレクトリ構造を理解します。次に、コードを読み、実行フローをトレースし、依存関係を理解します。実際の例では、この知識を適用する方法、つまりカスタム ミドルウェアの作成とルーティング システムの拡張方法を示します。ベスト プラクティスには、段階的に学習すること、無意識のコピー&ペーストを避けること、ツールを利用すること、オンライン リソースを参照することが含まれます。
