文字列定数プール、これを読み取るだけで十分です(1)

こんにちは、Ziyaです。彼は10年以上の技術的キャリアを持ち、技術初心者から技術ディレクター、JVMエキスパート、起業家精神に至るまで、困難を乗り越えてきました。アセンブリ、C言語、C ++、Windowsカーネル、Linuxカーネルなどのテクノロジースタック。私は特に仮想マシンの基本的な実装を研究するのが好きで、JVMについて詳細に研究しています。共有された記事はハードコアで、非常に難しいものです。


JVM、メモリプール、ガベージコレクションアルゴリズム、同期、スレッドプール、NIO、3色マーキングアルゴリズムを引き継ぎます...

今日は何について話しますか?文字列定数プール、つまり、Javaコードの文字列がJVMに格納される方法。

問題を分析するときは、非常に抽象的な問題を本質を追求する問題に抽出する必要があります。推論に関して言えば、一手で敵を倒す秘訣は、同じことで変化に対応することです。これらの2つのコードは、Java文字列を操作するための基盤です。Goの用語は「チェスアイ」と呼ばれ、Javaの世界では文字列の円を意味します。すべての変更はこれらの2つのコードから派生しています。

問題分析

物事を研究するとき、研究者の視点と設計者の視点の2つの視点があります。研究者の視点とは、学習の視点から始め、物事の軌跡を追求し、軌跡を深く掘り下げ、ソースコードを読んでデザイナーの考えを理解することです。設計者の視点は、私たちが1つのこと、それをどのように行うか、どのオプションが先にあるかを想像し、各オプションの長所と短所を分析し、最終的にトレードオフを行うことを意味します。

誰もが研究者の視点で問題を研究していることが多いと思いますが、今日は考え方を変えてデザイナーの視点で研究していきましょう。

本質的な質問に要約されます。JVMを作成する場合、文字列をどのように処理するのでしょうか。この問題は非常に単純です。ハッシュテーブル、つまりハッシュテーブルを使用してください。ここで、Javaの世界には2つのハッシュテーブルタイプ構造があることに注意してください。純粋なJavaで実装されるJavaのHashTableとHashMapと、純粋なC++で実装されるHotspotのハッシュテーブルです。今日話し合っているのは、ホットスポットソースコードのハッシュテーブルです。

多くの友人がC++を見るとき、彼らは恐れています...または考えます:これは私がお金を使わずに見ることができるものですか?これは私がこのレベルで見ることができるものですか?本当にわかりました...

ええと、恐れることはありません、今日はソースコードがありません!Ziyaだけが、AEによって描かれた主要な小さなアニメーションを長い間学んでおり、それを正確、迅速、正確に把握するのに役立ちます。さて、あなたがソースコードを見たいと思っても落胆しないでください、次の記事はあなたを満足させるために純粋なソースコードです。あなたがそれを見たい限り、私がわざと車を転がして、メッセージを残して、返信することは不可能ではありません:私は転覆を見たいです

ハッシュ表

記事の整合性を確保するために、ハッシュテーブルについて詳しく説明します。この作品があなたにとても馴染みがあると思うなら、それをもう一度読むこともお勧めします、それは報われる必要があります

まず、ハッシュテーブルがどのように機能するか、関連する名詞と現象を見てみましょう。後で詳しく説明します。

ハッシュテーブルの基本的な実装について話すと、2つの主流の方法があります:配列+単一リンクリスト、配列+赤黒木。3番目のタイプがあると主張することができます:配列+単一リンクリスト+赤黒木。あなたは突然たくさんの質問をするかもしれません:なぜあなたはこれをしたいのですか?異なる実装の違いは何ですか?心配しないでください、私はそれについて話します。

まず、配列+単一リンクリストがどのように機能するかを理解します。私たちの写真は、配列+単一リンクリストです。

ステップ1:文字列ziyaが到着すると、ハッシュアルゴリズムによって迎えられます。ハッシュアルゴリズムはハッシュアルゴリズムとも呼ばれます。それを実現する方法はたくさんあります。ここでは説明しません。興味のあるパートナーは、自分でBaiduを使用できます。達成される効果は、バケットインデックスを生成することです。文字列ziyaがハッシュアルゴリズムによって計算される場合、バケットインデックスは2に等しくなります。

ステップ2:バケットのデータ構造は配列です。最初のステップは、配列の添え字を生成することです。このとき、文字列ziyaはリンクリストノードノードにカプセル化されます。ここで、文字列ziyaはindex = 2の最初の要素であり、文字列ziyaによってカプセル化されたリンクリストノードノードはリンクリストヘッダーとして使用されます。ノードのメモリアドレスは、index=2の場所に格納されます。

このとき、問題について考える必要があります。バケットは配列です。C++コードを記述した人なら誰でも、配列の長さを指定しないと配列を作成できないことを知っています。指定の大きさはどれくらいですか?ホットスポットのソースコードは20011です。なぜ20011?私もこの質問がありますが、答えを見つけようとして見つかりませんでしたが、この数字には特別な意味があるはずです。それを知っている友人は私に知らせるためにメッセージを残すことができます。もちろん、私は真実の追求をあきらめません!

接下来我们用发展的眼光看问题:如果存储的字符串经过hash算法得到的buckets index不重复,最多可以存储20011个字符串。如果超过20011个字符串呢?这时候肯定会出现一种情况:不同的字符串经过hash算法运算得到的buckets index是相同的,这种情况叫hash碰撞。

比如图中的字符串[手写]与字符串[JVM],它们经hash算法运算得到的buckets index都是0,这时候怎么存储呢?先把字符串[手写]封装成链表节点Node,因为是index=0的第一个节点,将该Node的内存地址写入index=0的位置。然后把字符串[JVM]封装成链表节点Node,插入链表尾部。注意:链表的节点在内存中不是连续的!

后面有字符串写入,就是重复这个动作了:字符串到来,经过hash算法运算得到buckets index,然后将字符串封装成链表节点Node。如果到来的字符串是该index的第一个元素,将节点的内存地址写入该index位置,如果不是第一个元素,插入到链表尾部。

一种特殊情况

先看图,再说话

该图想表达的现象是buckets index=0对应的链表深度过深,达到了345,这样如果我们查找的字符串恰好是[内功],抛开其他运算,就光遍历链表都要345次。对于现在的高性能计算机来说,345次好像也不慢,但是性能随数据增长而出现性能下降,意味着总有一天会达到性能瓶颈,需要优化。

于是就出现了数组+红黑树结构,后面又发展出了数组+链表+红黑树结构。我解释下原因:大量数据的情况下,红黑树的查找性能比链表高。那为什么后面又会出现数组+链表+红黑树结构呢?注意看前提:大量数据的情况。当数据量比较少的时候,比如拿HashMap底层实现来说,这个阈值是8,即hash碰撞严重情况下,数据少于8,两者性能上差异不大,但是链表相对实现起来更容易、占用空间更少…

hash碰撞严重的情况,改变底层存储容器是一种思路,还有没有其他思路呢?有!改变hash算法,Hotspot源码就是这样干的。默认算法是java_lang_String::hash_code,触发rehash后使用的算法是AltHashing::murmur3_32。

Hotspot触发rehash的条件是:查找一个字符串超过100次。

串一串

JVM中与字符串相关的有两个表,一个是SymbolTable,一个是StringTable。我们通常说的字符串常量池是指StringTable。但是StringTable的运行与SymbolTable紧密相连。这篇文章讲到的内容,全部都是SymbolTable的底层原理。关于它俩之间的联系,本文篇幅已经够长了,放下篇文章讲。

SymbolTable底层是基于hashtable实现的,结构是数组+链表,当存储的数据足够多,遇到hash碰撞严重时,是通过切换hash算法实现的。默认算法是java_lang_String::hash_code,触发rehash后使用的算法是AltHashing::murmur3_32。

留个问题吧:切换hash算法不会马上生效,需要等到安全点,你觉得是为什么?欢迎留言讨论

我是子牙老师,喜欢钻研底层,深入研究Windows、Linux内核、JVM。如果你也喜欢研究底层,欢迎关注我的公众号 【道格子牙】

おすすめ

転載: juejin.im/post/7087772381443260452