String、StringBufer、StringBuilder
String 它是典型的Immutable(不可改变)类,被声明成为fnal class,所有属性也都是fnal的。也由于它的不可变性,类似拼接、裁剪字符串等动作,都会产生新的String对象。在循环场景下不建议使用这些功能。
StringBufer是为解决上面提到拼接产生太多中间对象的问题而提供的一个类,可以用append或者add方法,把字符串添加到已有序列的末尾或者指定位置。StringBufer本质是一个线程安全的可修改字符序列,它保证了线程安全(简单粗暴各种修改数据的方法都加上synchronized关键字实现的),也随之带来了额外的性能开销,所以除非有线程安全的需要,不然还是推荐使用它的后继者,也就是StringBuilder。
StringBuilder 在能力上和StringBufer没有本质区别,但是它去掉了线程安全的部分,有效减小了开销,是绝大部分情况下进行字符串拼接的首选。从 builder 命名来看这个类专门就是为创建而生。
- Immutable对象在拷贝时不需要额外复制数据。
字符串的内部数组
-
为了实现修改字符序列的目的,StringBufer和StringBuilder底层都是利用可修改的char数组(JDK 9以后是byte),都继承了AbstractStringBuilder。
-
这个内部数组应该创建成多大的呢?如果太小,拼接的时候可能要重新创建足够大的数组;如果太大,又会浪费空间。目前的实现是,构建时初始字符串长度加16(这意味着,如果没有构建对象时输入最初的字符串,那么初始值就是16)。我们如果确定拼接会发生非常多次,而且大概是可预计的,那么就可以指定合适的大小,避免很多次扩容的开销。扩容会产生多重开销,因为要抛弃原有数组,创建新的(可以简单认为是倍数)数组,还要进行arraycopy。
字符串拼接
- 如果在代码拼接说 String 拼接效率低下,但是在 jdk8即使你使用 String 拼接编译器还是会把你优化成 StringBuilder,jdk9优化成StringConcatFactory(暂时没用了解过)
字符串缓存、内存
-
String在jdk6以后提供了intern()方法,目的是提示JVM把相应字符串缓存起来,以备重复使用。但是不建议使用。
-
jdk6被缓存的字符串是存在所谓PermGen里的,也就是臭名昭著的“永久代”,这个空间是很有限的,也基本不会被FullGC之外的垃圾收集照顾到。所以,如果使用不当,OOM就会光顾。
-
jdk7存放在堆中。
-
jdk8出现了MetaSpace(元数据区),就存在元空间中。
-
intern是一种显式地排重机制,但是它也有一定的副作用,因为需要开发者写代码时明确调用,一是不方便,每一个都显式调用是非常麻烦的;另外就是我们很难保证效率,应用开发阶段很难清楚地预计字符串的重复情况,有人认为这是一种污染代码的实践。
幸好在Oracle JDK 8u20之后,推出了一个新的特性,也就是G1 GC下的字符串排重。它是通过将相同数据的字符串指向同一份数据来做到的,是JVM底层的改变,并不需要Java类库做什么修改。
开启 G1 GC 后可加入这个参数开启-XX:+UseStringDeduplication
动态代理
深入理解RPC之动态代理篇这篇文章针对集中动态代理有比较详细的分析
动态代理的应用场景比如:RPC、安全、日志、事务
静态代理
事先写好代理类,可以手工编写,也可以用工具生成。缺点是每个业务类都要对应一个代理类,非常不灵活。
动态代理
运行时自动生成代理对象。缺点是生成代理代理对象和调用代理方法都要额外花费时间。
JDK动态代理:基于Java反射机制实现,必须要实现了接口的业务类才能用这种办法生成代理对象。新版本也开始结合ASM机制。
cglib动态代理:基于ASM机制实现,通过生成业务类的子类作为代理类。
基本数据类型和引用数据类型
缓存机制
缓存机制并不是只有Integer才有,同样存在于其他的一些包装类,比如:
- Boolean,缓存了true/ false对应实例,确切说,只会返回两个常量实例Boolean.TRUE/ FALSE。
- Short,同样是缓存了-128到127之间的数值。
- Byte,数值有限,所以全部都被缓存。
- Character,缓存范围'\ u0000' 到 '\ u007F'。
继续深挖缓存,I nteger的缓存范围虽然默认是-128到127,但是在特别的应用场景,比如我们明确知道应用会频繁使用更大的数值,这时候应该怎么办呢?
-XX:AutoBoxCacheMax=N
缓存上限值实际是可以根据需要调整的,JVM提供了参数设置:
自动拆装箱的注意点
建议避免无意中的装箱、拆箱行为,尤其是在性能敏感的场合,创建10万个Java对象和10万个整数的开销可不是一个数量级的,不管是内存使用还是处理速度,光是对象头的空间占用就已经是数量级的差距了。
Integer 内存结构组成
- Mark Word: 标记位 4字节,类似轻量级锁标记位,偏向锁标记位等。
- Class对象指针: 4字节,指向对象对应class对象的内存地址。
- 对象实际数据:对象所有成员变量。
- 对齐:对齐填充字节,按照8个字节填充。
Integer占用内存大小,4+ 4+ 4+ 4= 16字节。
集合
狭义的集合框架Map
MapLinkedHashMap
LinkedHashMap通常提供的是遍历顺序符合插入顺序,它的实现是通过为条目(键值对)维护一个双向链表。注意,通过特定构造函数,我们可以创建反映访问顺序的实例,所谓的put、get、compute等,都算作“访问”。
网友评论