Java中容易踩到的”坑”系列之Auto Box/Unbox

Java中的Auto Box/Unbox对写代码带来了便利性,但也挺容易就踩进”坑“里。

例如这是最典型的Auto Box/Unbox的代码:
[code]
Integer i = 100;
int j = i;
[/code]
Auto Box/Unbox在有些场景下容易产生NPE,例如假设有一个这样的方法:
public void execute(int code)…
假设调用方是从一个Map或其他地方获取到的Integer,如果忘记判断是否为null,直接传给execute的方法,就有可能导致NPE。

下面这个关于Integer的Case也是比较常见的:
Integer i = 100;
Integer j = 100;
Integer m = 200;
Integer n = 200;
System.out.println(i == j);
System.out.println(m == n);
其执行结果为:true,false
原因是Integer会cache -128(-127)~127的Integer对象(感谢@yongchun的纠正),而不在这个范围的则会每次new Integer。

在JavaOne 2010大会上,还有一段关于Auto Box/Unbox带来的频繁YGC的案例代码,代码的关键信息如下:
[code]
public class A{
private int code;
public A(int code){
this.code = code;
}
public int get(){
return code;
}
}
public class Demo{
public statice void main(String[] args) throws Exception{
Map map = new HashMap();
// 往map里放1w个从1开始的A对象

while(true){
Collection values = map.values();
for(A a: values){
if(!map.containsKey(a.getId())){
// 不可能发生,所以可以随便打点什么
}
Thread.sleep(1);
}
}
}
}
[/code]
在上面的代码中,其实只需要把A中的private int code和public int get中的int改为Integer,就可以避免频繁的YGC,因此在写代码时还是要稍微注意下Auto Box/Unbox带来的影响。

————-互动问答—————–
问:能讲讲sun.misc.Unsafe的用法吗?
答:通常直接使用到Unsafe的都是一些特殊场景,例如对性能要求很高,或内存消耗很大的等等。
常见的是使用Unsafe来做反射的优化、内存的管理。
代码中通常有很多需要通过反射get/set来操作bean中的field,如果操作非常频繁的话,可以通过Unsafe来进行优化(在eclipse里使用Unsafe时需要修改下配置,否则会不能import,修改方法:Window->Preference->Java->Compiler->Errors/Warnings->Deprecated and restricted API -> Forbidden Reference 修改成Warning或Ignore),主要是通过Unsafe.objectFieldOffset和Unsafe.getXXX(例如getInt)来实现,代码如下:
[code]
public class User {

private String name;

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

}

import sun.misc.Unsafe;
import java.lang.reflect.Field;

public class UserTestForUnsafe {

private static final Unsafe unsafe;

static {
try {
Field field = Unsafe.class.getDeclaredField(“theUnsafe”);
field.setAccessible(true);
unsafe = (Unsafe) field.get(null);
}
catch (Exception e) {
throw new RuntimeException(“get unsafe fail”, e);
}
}

private static final Field nameField;
static {
try {
nameField = User.class.getDeclaredField(“name”);
}
catch (Exception e) {
throw new IllegalStateException(“get User.name error”, e);
}
}

public static final Field getNameField() {
return nameField;
}

public static void main(String[] args) throws Exception{
User user = new User();
user.setName(“bluedavy”);
long offset = unsafe.objectFieldOffset(nameField);
System.out.println(unsafe.getObject(user, offset));
}

}
[/code]
关于使用Unsafe直接进行内存管理,可参考这篇文章:
http://highlyscalable.wordpress.com/2012/02/02/direct-memory-access-in-java/

=============================
欢迎关注微信公众号:hellojavacases

关于此微信号:
用于分享Java的一些小知识点,Java问题排查的Cases,Java业界的动态,以及和大家一起互动讨论一些Java的场景和问题。

公众号上发布的消息都存放在http://hellojava.info上。

《Java中容易踩到的”坑”系列之Auto Box/Unbox》有7个想法

  1. “JavaOne 2010大会上,还有一段关于Auto Box/Unbox带来的频繁YGC的案例代码” —— 这段代码很有意思;有没有关于这个“young gc”问题的发现排查过程的信息?

    1. @pf_miles
      ? 排查过程? 这是一个刻意制造的case,只是为了让大家在写代码时尽可能注意…

  2. “原因是Integer会cache -127~127的Integer对象,而不在这个范围的则会每次new Integer。”这个范围应该是-128~127吧。ps:jdk这样设计就是给程序员挖坑吧,好的api是不应该让调用者知道底层实现细节的。

    1. 我认同你觉得API不好,不过我认为在程序员还是应该知道这些的,因为毕竟需要知道不同参数的作用和意义,这些也不是实现细节,并且在API中可以很容易就表达出来,我看了下API他只是简单说了下关于cache的performance,没有明确说明关于cache的范围和调整方式就是了。

  3. Integer的cache其实可以设置启动参数:-Djava.lang.Integer.IntegerCache.high 来根据实际情况调整下cache Integer的最大值,不过最小值通过正常手段是调不了的;
    另外就是Long没法调,代码写死的;Boolean是直接cache的;其余的都是直接new的,没做cache;

发表评论

电子邮件地址不会被公开。 必填项已用*标注


*