【包装类】面试官:为什么尽量避免使用包装类?
前言
离职得第四天(准备八卦文的过程中),最近发现了一个比较有意思得东西,包装类大家都知道,实际工作共也有很多的使用,但是大家都说这个东西特别浪费性能,不知道各位同仁有没有实际感受过,一下我通过一个小例子来展示一下,各位看官做好准备。(接下下的内容可能会非常硬核)
提示:以下是本篇文章正文内容,下面案例可供参考
一、准备代码
最近写了一个这样的代码:
一个非常的普通的一个接口,我们在这个接口逻辑里面做了一个循环累加的方法。(接下来看下如图的消耗的时间和JVM内存损耗)
package com.momo.theta.controller;
import com.alibaba.fastjson.JSONObject;
import com.google.common.base.Stopwatch;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@Slf4j
@RestController
@RequestMapping("test")
public class TestController {
/**
* 这是一个业务处理的请求
*/
@GetMapping("gcTest")
public void queueNormalDelay() {
//创建一个监听器材
Stopwatch started = Stopwatch.createStarted();
Integer sum = 0 ;
for (int i = 0; i <Integer.MAX_VALUE; i++) {
sum += i;
}
started.stop();
log.info("waste time :{}", JSONObject.toJSONString(started.toString()));
}
}
通过请求的耗时,我们看到 9秒的时间。这是不可忍受的。
接下来我们再通过内存监控软件查看,在此次请求的过程中JVM的内存瞬间被拉高,而且查看通过GC activity 可以看到 JVM 进行GC。但是这里的逻只是简简单单的一个求和逻辑,
2.修改代码如下
package com.momo.theta.controller;
import com.alibaba.fastjson.JSONObject;
import com.google.common.base.Stopwatch;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@Slf4j
@RestController
@RequestMapping("test")
public class TestController {
/**
* 这是一个业务处理的请求
*/
@GetMapping("gcTest")
public void queueNormalDelay() {
//创建一个监听器材
Stopwatch started = Stopwatch.createStarted();
//这里进行了修改
int sum = 0 ;
for (int i = 0; i <Integer.MAX_VALUE; i++) {
sum += i;
}
started.stop();
log.info("waste time :{}", JSONObject.toJSONString(started.toString()));
}
}
经过查看时间瞬间被我们缩短,但是我们只是简单的做了一些修改,这些修改只是把包装修改了基础变量int吗,这是为什么呢?
再往下看这张图片
似乎也没有触发GC?真的像大家说的这东西真的这么消耗性能么?
所以,这种现象看来,各位的代码是不是又有了很多的优化空间。
问题来了 ?编译器到底帮我做了什么?
编译器似乎做了一件很合乎常理的事情,在每次循环的内部使用了包装类对应的valueOf();方法,如果对于JDK很了解的话,我们的知道这个方法每次都会创建一个对于当前基础类型的对象。似乎这真的就是内存消耗的原因。
这个是个什么东西?invokevirtual
??
查看Orcale 官网的JVM规范文档
原文中 大致意思是这样的:
indexbyte1和indexbyte2均为无符号字节,用来构造指向当前Class的运行时常量池的索引index,该索引通过如下方式构造:(indexbyte1<< 8) | indexbyte2.运行时常量池在索引index的位置必须为一个指向方法的符号引用,该符号引用包含了该方法的名称,描述符以及定义该方法的类的符号引用。并且,该方法会被解析,解析的结果不能为实例初始化方法,类或者接口的初始化方法。最终,如果解析出来的方法是Protected类型,并且:该方法是当前类的父类的成员函数以及该方法没有在当前类所属的运行时Package中声明,那么:objectref的类型必须是当前类或者当前类的子类。
这个也是机器翻译出来的,其实我们知道这种指令集是按照顺序执行的,而他就是“罪魁祸首”,细心的你也可以发现在每一次invokevirtual
的后面,都已会有另外一个指令astore_2
而这个指令就是在调用Integer == null
的地方,其实他是每次把调用Integer.valueOf();
方法后的值存入到一个我们一开始初始化的变量中,进行一个累加的。这样寻循环 Integer.MAX_VALUE [2147483647]次,JVM相当于频繁的创建了2147483647 次对象。你说这谁受得了,
但是再看看右边的途中,使用基础数据类型的变量似乎就没有繁琐的操作,直接从本地变量表中
获取到istore_2
这数据直接,直接进行了iadd
指令。
总结
经过上面的文章,你似乎可以看懂JVM指令了,也要注意实际开发过程尽量减少对包装类的循环使用。其实这些东西也不是很复杂,只要你愿意付出,那么肯定会有回报,当然这是一些简单的东西,也希望各位大佬指出此片文章的问题。此片文章的代码我放到了一个开源项目中:Theta 点击此处跳转。这个项目是中封装了很多Java程序中的公共代码,和一个基于MySQL的发号机,感兴趣的小伙伴可以去看看。支持一下点个start 小星星 ~谢谢给位。