JDK8中intern()的理解(笔记向)

好久都没有水博客了,前段时间再看算法+linux内核源码,被虐的死去活来…

今天在技术交流群看到一个人在做有关intern的实验,记得以前看过,就是返回常量池中的字符串,但也没做过实验,参照那个兄弟的例子,现在补上例子和我的理解:

		String string = new String("1");
		string.intern();
		String string2 = "1";
		System.out.println(string == string2);
		

其实这个string.intern()是个无用的操作,为什么说它无用呢,因为这里的结果是false;哈哈哈哈~当然这个说法并不科学,并不是结果导向的。

JDK8中intern源码上是这样说的:

When the intern method is invoked, if the pool already contains a string equal to this String object as determined by the equals(Object) method, then the string from the pool is returned. Otherwise, this String object is added to the pool and a reference to this String object is returned. 

该方法的返回结果及其操作为:“查找常量池中有没有调用equals()方法为true的String对象;如果有,就将这个常量池中的String对象作为结果返回;如果没有,则将当前对象加入到常量池中,将本对象返回”。

怎么样疑惑不疑惑?下面还有组合起来更让人疑惑的东西:

		String string3 = new String("1") + new String("1");
		string3.intern();
		String string4 = "11";
		System.out.println(string3 == string4);
这个结果是true。
为什么?不禁发出了疑惑的声音。
我来详细的解释一番发生了什么吧:
	看第一段:
	String string = new String("1");

这里感觉第一个赋值的应该是string是吧,其实不是的,第一个给“1”赋值的是构造方法里的那个参数:“1”;

推测在编译阶段已经能确定“1”这个常量的存在了,所以“1”在调用构造方法处已经被加入常量池了。所以string.intern()并没有将它本身的地址传到常量池中去。当前的“1”的地址值未知。

而string1 = “1”;这里,就直接将“1”所在的地址传递给了string。
所以我们如果执行:

		String string5 = string.intern();
		System.out.println(string5 == string2);
		或者
		System.out.println(string.intern() == string2);

这两个都可以输出true。
下面就还有一个疑惑了,为什么:

		String string3 = new String("1") + new String("1");
		string3.intern();
		String string4 = "11";
		System.out.println(string3 == string4);

可以输出true呢?

首先来看这个例子和原来的例子有什么不同:
这个例子中,string3的结果并不是有构造函数直接生成的。而是由两个new String()对象拼接而成。在这行的编译阶段并不能确定“11”这个字符串是否真正的存在,是一个未决的行为。因此手动调用string3.intern()就可以将string3的地址传到常量池中,和“11”这个字符串关联起来。
如果将string3.intern()写在了string4赋值的后面,那么结果就不一样了。
因为如果不在String string4 = "11"赋值之前显示的调用string3.intern(),那么在编译阶段可以确认的“11”字符串地址就是明确声明为“11”的string4了。那么这时候,不论在string4赋值以后,不论调不调用string3.intern()方法,都已经没用了,因为string4态度更明确,先来一步把常量池中属于“11”的位置占了。
所以此时

		System.out.println(string3.intern() == string4);//这个才为true。
		而
		string3.intern();
		System.out.println(string3 == string4);//这里就为false了

另外在我的了解中:intern()方法是采用一张类似于hashtable的方法来对数据进行存储的,这个table的的大小默认为1009,一般很少去改它。
这样就会带来一个问题:如果intern使用的太多了。会导致这个table里面的数据过多,hash冲突增大,部分对象退化成链。这样再次调用intern()方法的话,有很大可能去遍历链表来获取结果。这样虽然减少了内存使用,但却使得系统性能下降,得不偿失。

个人的愚见是:对象String类型的全局变量最好不要设置太多【当然如果一定要定义的String的话记得要用intern()毕竟第三方调用很难被约束…能省一点内存是一点…】,能用局部变量的话最好就用局部变量,那玩意生命周期比较短,一般用完就可以被回收了,当然在方法中也可以使用intern,如果方法中预期会有很多的重复的String对象的话,就可以调用intern方法来减少内存的使用
(JDK6的时候常量池在永久代【这也造成intern方法 在6 和 在7 有不同的表现,6的话对象在堆,常量池在永久代,这俩就不是同一种东西,所以==比较的话会返回false】,JDK7,JDK8【8虽然有metaspace,但常量池还是在堆中的,这点也好理解,毕竟堆外空间并不方便进行垃圾回收】的常量池在堆中。);
毕竟常量池中的东西虽然和old区对象一样,平时难得一动,但真想动还是可以动的。避免一次性创建太多对象,加大系统GC压力。毕竟内存还是比较宝贵的系统资源。
一句话总结:intern()方法虽然好用,但不是银弹。需要有条件有选择的去使用。

在系统发展时期最好做下trade off,方法被调用的很频繁,局部变量创建过快,MinorGC的频率可能就会增加,虽然MinorGC的消耗不大,但如果一次性创建太多临时对象,很可能会造成不符合条件的对象过早的晋升老年代(一般JDK8默认使用的是parallel GC,而且一般不会做一些特殊的配置,这时对象晋升的阈值并不是恒定的默认为7的,而是会随着每次minor GC动态变化,如果想阻止这种变化,则需要在启动参数上配置-XX:-UseAdaptiveSizePolicy来使晋升阈值以MaxTenturingThreshould为准,恒定不变【ref:gc对象晋升old区的一些规则总结】)。
如果持续进行如此大压力的操作,则容易把old区快速撑满,并频繁的进行full GC,这样又会造成系统的卡顿。

其实我们工程师最有创造力的工作就是区分变与不变;这点需要我在工作中不断地归纳总结,形成自己对业务的独到理解。这样才能设计出合理的架构,合理的对象;虽然工作之内以产出为导向,代码质量为次要,但提前完成任务之余或者有些时间,还是需要好好雕琢自己的代码。虽然身处泥泞,但并不是放弃自己代码质量的理由。所有的工种,说到底就是和人打交道的,不论是艺术家,老师,销售,工程师,程序员。首先得让别人听得懂你在讲什么,而不是乱糟糟的一团。还得好好修炼才是。

现在眼光还是太窄了,想去大一点的公司,看看更广阔的风景。

断断续续又写了很久,不过好在可以保存草稿…感觉写博客和搞开源一样都需要很用心的去呵护,去总结,尽自己所能写出误导性没那么大的文章。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值