为了活动小家电-关于 JVM 中的自动装箱和拆箱以及缓存(二)

49 阅读2分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的15天,点击查看活动详情

为了活动小家电,接着上篇搞!

JVM 中的缓存字符串和盒装原始对象

在 JVM 中,如果在一个虚拟机中有多个代码处理同一个字符串,则重用现有的字符串。

fun main() {
	val str1 = "hello world"
    val str2 = "hello world"
    val str3 = StringBuilder("hello world").toString()
    
    println(str1 == str2) // true, 等价比较
    println(str1 === str2) // true, 内存地址比较
    println(str2 === str3) // false, 创建新对象,分配新内存
    println(str2 === str3.intern()) // true, 从 JVM 字符串池中获取一个对象
}

默认情况下,Integer 和 Long 等盒装原语也会缓存 -128 到 127 的范围。

fun main() {
	val i1: Int? = 1
    val i2: Int? = 1
    val i3: Int? = 1000
    val i4: Int? = 1000
  
    println(i1==i2) // true, 比较值
    println(i1===i2) // true, 比较内存地址
    println(i3==i4) // true, 比较值
    println(i3===i4) // false,没有缓存所以比较内存地址
}

它不在协程范围内缓存。

让我们将上面的示例代码包装在一个简单的 runBlocking 范围内。

fun main() = runBlocking {
    val str1 = "hello world"
    val str2 = "hello world"
    val str3 = StringBuilder("hello world").toString()
    
    println(str1 == str2) // true
    println(str1 === str2) // true
    println(str2 === str3) // false
    println(str2 === str3.intern()) // true
}

String 对于字符串,即使在协程范围内,它也会显示与现有示例代码相同的结果。但是让我们看一下以下用于缓存盒装原语的示例代码。

fun main() = runBlocking {
    val i1: Int? = 1
    val i2: Int? = 1
    val i3: Int? = 1000
    val i4: Int? = 1000
  
    println(i1==i2) // true
    println(i1===i2) // false, 不缓存
    println(i3==i4) // true
    println(i3===i4) // false
}
}

可以看出(i1 === i2)的比较返回false。我可以看到它在正常函数中执行时被缓存了,但是为什么在协程范围内结果不同? 我反编译了那些代码。 image.png 红框标注的部分是需要注意的部分。左边指的是编译后缓存在 JVM 中的值,右边指的是 Boxing.boxInt(…) 。在这种情况下,内部创建了一个新对象并分配了新的内存,因此引用被划分。

我不知道为什么编译器的行为在协程范围内有所不同。但是,在某些情况下,盒装原始数据类型不会被缓存。