Conjunto de cadenas constantes, leer esto es suficiente (1)

Hola, soy Ziya. Con más de diez años de carrera técnica, ha superado dificultades desde principiante técnico hasta director técnico, experto en JVM y emprendimiento. Pilas de tecnología como ensamblaje, lenguaje C, C ++, kernel de Windows, kernel de Linux. Me gusta especialmente estudiar la implementación subyacente de las máquinas virtuales y hacer una investigación profunda sobre JVM. Los artículos compartidos son duros, muy duros.


Entrega JVM, grupo de memoria, algoritmo de recolección de basura, sincronizado, grupo de subprocesos, NIO, algoritmo de marcado de tres colores...

¿De qué te vas a hablar hoy? Grupo de constantes de cadenas, es decir, cómo se almacenan las cadenas en el código Java en la JVM.

Cuando analizamos un problema, necesitamos destilar un problema altamente abstracto en un problema que busca la esencia. Cuando se trata de razonar, el secreto para derrotar al enemigo con un solo movimiento es responder a los cambios con lo mismo. Estos dos fragmentos de código son la base para jugar con cadenas de Java. El término Go se llama "ojo de ajedrez", que significa el círculo de cuerdas en el mundo de Java. Todos los cambios se derivan de estos dos fragmentos de código.

análisis del problema

Hay dos perspectivas cuando se estudian las cosas: la perspectiva del investigador y la perspectiva del diseñador. La perspectiva del investigador significa que comenzamos desde la perspectiva del aprendizaje, seguimos la trayectoria de las cosas, profundizamos a lo largo de la trayectoria y hablamos el lenguaje humano para leer el código fuente para comprender la idea del diseñador. El punto de vista del diseñador significa que imaginamos que vamos a hacer una cosa, cómo la haremos, qué opciones tenemos por delante, analizamos los pros y los contras de cada opción y finalmente hacemos un intercambio.

Creo que la mayoría de las veces todos estudiamos los problemas desde la perspectiva de los investigadores. Hoy, cambiemos nuestro pensamiento y estudiemos este problema desde la perspectiva de los diseñadores.

Resumido en una pregunta esencial: si tuviéramos que escribir una JVM, ¿cómo manejaríamos las cadenas? Este problema es muy sencillo, utiliza una tabla hash, es decir, hashtable. Cabe señalar aquí que hay dos estructuras de tipo tabla hash en el mundo de Java: HashTable y HashMap de Java, que se implementan en Java puro, y la tabla hash de Hotspot, que se implementa en C++ puro. Lo que estamos discutiendo hoy es la tabla hash en el código fuente de Hotspot.

Cuando muchos amigos ven C++, tienen miedo... o piensan: ¿Es esto algo que puedo ver sin gastar dinero? ¿Es esto algo que puedo ver en este nivel? Realmente lo entiendo...

Uh, no tengas miedo, ¡hoy no hay código fuente! Solo Ziya ha aprendido la pequeña animación líder dibujada por AE durante mucho tiempo, lo que puede ayudarlo a comprenderla de manera precisa, rápida y precisa. Bueno, no te desanimes si quieres ver el código fuente, el siguiente artículo es código fuente puro, para satisfacerte. Siempre que quieras verlo no me es imposible volcar el auto a propósito, deja un mensaje y responde: quiero ver el vuelco

tabla de picadillo

Para garantizar la integridad del artículo, hablaré en detalle sobre la tabla hash. Si cree que esta pieza le resulta muy familiar, también se recomienda leerla nuevamente, debe ser recompensada.

En primer lugar, veamos cómo funciona la tabla hash, los sustantivos y los fenómenos involucrados, y hablaremos de ello en detalle más adelante.

Hablando de la implementación subyacente de la tabla hash, existen dos métodos principales: matriz + lista enlazada individualmente, matriz + árbol rojo-negro. Puede insistir en que hay un tercer tipo: matriz + lista enlazada individualmente + árbol rojo-negro. De repente puede tener muchas preguntas: ¿por qué quiere hacer esto? ¿Cuál es la diferencia entre las diferentes implementaciones? No te preocupes, hablaré de ello.

Primero comprenda cómo funciona la matriz + la lista enlazada individualmente. Nuestra imagen es una matriz + una lista enlazada individualmente.

Paso 1: cuando llega la cadena ziya, el algoritmo hash la recibe. El algoritmo hash también se llama algoritmo hash. Hay muchas maneras de realizarlo. No entraré en eso aquí. Los socios interesados ​​​​pueden Baidu por sí mismos. El efecto logrado es generar el índice de cubos. Si la cadena ziya se calcula mediante el algoritmo hash, el índice de cubos es igual a 2.

Paso 2: La estructura de datos de los cubos es una matriz. El primer paso es generar el subíndice de la matriz. En este momento, la cadena ziya se encapsulará en un nodo de lista enlazada Node, donde la cadena ziya es el primer elemento de index=2, el nodo de lista enlazada Node encapsulado por la cadena ziya se utilizará como encabezado de lista enlazada, y la dirección de memoria del Nodo se almacenará en donde index=2.

En este momento, tenemos que pensar en un problema: los cubos son matrices Cualquiera que haya escrito código C++ sabe que no se puede crear una matriz sin especificar la longitud de la matriz. ¿Qué tan grande es la designación? El código fuente del punto de acceso es 20011. ¿Por qué 20011? También tengo esta pregunta, pero traté de encontrar la respuesta y no pude encontrarla, pero estoy seguro de que este número debe tener un significado especial. Los amigos que lo sepan pueden dejar un mensaje para hacérmelo saber. ¡Por supuesto, no renunciaré a la búsqueda de la verdad!

接下来我们用发展的眼光看问题:如果存储的字符串经过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。如果你也喜欢研究底层,欢迎关注我的公众号 【道格子牙】

Supongo que te gusta

Origin juejin.im/post/7087772381443260452
Recomendado
Clasificación