Skip to content

Commit 1228f05

Browse files
committed
Java优质文章补充
1 parent 541d29b commit 1228f05

File tree

3 files changed

+517
-0
lines changed

3 files changed

+517
-0
lines changed
Lines changed: 267 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,267 @@
1+
今天来聊聊反射的性能问题。反射具体是怎么影响性能的?
2+
3+
# 01 反射真的存在性能问题吗?
4+
5+
为了放大问题,找到共性,采用逐渐扩大测试次数、每次测试多次取平均值的方式,针对同一个方法分别就直接调用该方法、反射调用该方法、直接调用该方法对应的实例、反射调用该方法对应的实例分别从 1-1000000,每隔一个数量级测试一次:
6+
7+
测试代码如下:
8+
9+
```java
10+
public class ReflectionPerformanceActivity extends Activity{
11+
private TextView mExecuteResultTxtView = null;
12+
private EditText mExecuteCountEditTxt = null;
13+
private Executor mPerformanceExecutor = Executors.newSingleThreadExecutor();
14+
private static final int AVERAGE_COUNT = 10;
15+
16+
@Override
17+
protected void onCreate(Bundle savedInstanceState){
18+
super.onCreate(savedInstanceState);
19+
setContentView(R.layout.activity_reflection_performance_layout);
20+
mExecuteResultTxtView = (TextView)findViewById(R.id.executeResultTxtId);
21+
mExecuteCountEditTxt = (EditText)findViewById(R.id.executeCountEditTxtId);
22+
}
23+
24+
public void onClick(View v){
25+
switch(v.getId()){
26+
case R.id.executeBtnId:{
27+
execute();
28+
}
29+
break;
30+
default:{
31+
32+
33+
}
34+
break;
35+
}
36+
}
37+
38+
private void execute(){
39+
mExecuteResultTxtView.setText("");
40+
mPerformanceExecutor.execute(new Runnable(){
41+
@Override
42+
public void run(){
43+
long costTime = 0;
44+
int executeCount = Integer.parseInt(mExecuteCountEditTxt.getText().toString());
45+
long reflectMethodCostTime=0,normalMethodCostTime=0,reflectFieldCostTime=0,normalFieldCostTime=0;
46+
updateResultTextView(executeCount + "毫秒耗时情况测试");
47+
for(int index = 0; index < AVERAGE_COUNT; index++){
48+
updateResultTextView("" + (index+1) + "");
49+
costTime = getNormalCallCostTime(executeCount);
50+
reflectMethodCostTime += costTime;
51+
updateResultTextView("执行直接调用方法耗时:" + costTime + " 毫秒");
52+
costTime = getReflectCallMethodCostTime(executeCount);
53+
normalMethodCostTime += costTime;
54+
updateResultTextView("执行反射调用方法耗时:" + costTime + " 毫秒");
55+
costTime = getNormalFieldCostTime(executeCount);
56+
reflectFieldCostTime += costTime;
57+
updateResultTextView("执行普通调用实例耗时:" + costTime + " 毫秒");
58+
costTime = getReflectCallFieldCostTime(executeCount);
59+
normalFieldCostTime += costTime;
60+
updateResultTextView("执行反射调用实例耗时:" + costTime + " 毫秒");
61+
}
62+
63+
updateResultTextView("执行直接调用方法平均耗时:" + reflectMethodCostTime/AVERAGE_COUNT + " 毫秒");
64+
updateResultTextView("执行反射调用方法平均耗时:" + normalMethodCostTime/AVERAGE_COUNT + " 毫秒");
65+
updateResultTextView("执行普通调用实例平均耗时:" + reflectFieldCostTime/AVERAGE_COUNT + " 毫秒");
66+
updateResultTextView("执行反射调用实例平均耗时:" + normalFieldCostTime/AVERAGE_COUNT + " 毫秒");
67+
}
68+
});
69+
}
70+
71+
private long getReflectCallMethodCostTime(int count){
72+
long startTime = System.currentTimeMillis();
73+
for(int index = 0 ; index < count; index++){
74+
ProgramMonkey programMonkey = new ProgramMonkey("小明", "", 12);
75+
try{
76+
Method setmLanguageMethod = programMonkey.getClass().getMethod("setmLanguage", String.class);
77+
setmLanguageMethod.setAccessible(true);
78+
setmLanguageMethod.invoke(programMonkey, "Java");
79+
}catch(IllegalAccessException e){
80+
e.printStackTrace();
81+
}catch(InvocationTargetException e){
82+
e.printStackTrace();
83+
}catch(NoSuchMethodException e){
84+
e.printStackTrace();
85+
}
86+
}
87+
88+
return System.currentTimeMillis()-startTime;
89+
}
90+
91+
private long getReflectCallFieldCostTime(int count){
92+
long startTime = System.currentTimeMillis();
93+
for(int index = 0 ; index < count; index++){
94+
ProgramMonkey programMonkey = new ProgramMonkey("小明", "", 12);
95+
try{
96+
Field ageField = programMonkey.getClass().getDeclaredField("mLanguage");
97+
ageField.set(programMonkey, "Java");
98+
}catch(NoSuchFieldException e){
99+
e.printStackTrace();
100+
}catch(IllegalAccessException e){
101+
e.printStackTrace();
102+
}
103+
}
104+
105+
return System.currentTimeMillis()-startTime;
106+
}
107+
108+
private long getNormalCallCostTime(int count){
109+
long startTime = System.currentTimeMillis();
110+
for(int index = 0 ; index < count; index++){
111+
ProgramMonkey programMonkey = new ProgramMonkey("小明", "", 12);
112+
programMonkey.setmLanguage("Java");
113+
}
114+
115+
return System.currentTimeMillis()-startTime;
116+
}
117+
118+
private long getNormalFieldCostTime(int count){
119+
long startTime = System.currentTimeMillis();
120+
for(int index = 0 ; index < count; index++){
121+
ProgramMonkey programMonkey = new ProgramMonkey("小明", "", 12);
122+
programMonkey.mLanguage = "Java";
123+
}
124+
125+
return System.currentTimeMillis()-startTime;
126+
}
127+
128+
private void updateResultTextView(final String content){
129+
ReflectionPerformanceActivity.this.runOnUiThread(new Runnable(){
130+
@Override
131+
public void run(){
132+
mExecuteResultTxtView.append(content);
133+
mExecuteResultTxtView.append("\n");
134+
}
135+
});
136+
}
137+
}
138+
```
139+
140+
测试结果如下:
141+
142+
![](http://img.topjavaer.cn/img/反射影响性能1.png)
143+
144+
测试结论:
145+
146+
**反射的确会导致性能问题。反射导致的性能问题是否严重跟使用的次数有关系,如果控制在100次以内,基本上没什么差别,如果调用次数超过了100次,性能差异会很明显。**
147+
148+
四种访问方式,直接访问实例的方式效率最高;其次是直接调用方法的方式,耗时约为直接调用实例的 1.4 倍;接着是通过反射访问实例的方式,耗时约为直接访问实例的 3.75 倍;最慢的是通过反射访问方法的方式,耗时约为直接访问实例的 6.2 倍。
149+
150+
# 02 反射到底慢在哪?
151+
152+
跟踪源码可以发现,四个方法中都存在实例化`ProgramMonkey`的代码,所以可以排除是这句话导致的不同调用方式产生的性能差异;通过反射调用方法中调用了`setAccessible`方法,但该方法纯粹只是设置属性值,不会产生明显的性能差异;所以最有可能产生性能差异的只有`getMethod和getDeclaredField、invoke``set`方法了,下面分别就这两组方法进行测试,找到具体慢在哪?
153+
154+
首先测试`invoke``set`方法,修改`getReflectCallMethodCostTime``getReflectCallFieldCostTime`方法的代码如下:
155+
156+
```java
157+
private long getReflectCallMethodCostTime(int count) {
158+
long startTime = System.currentTimeMillis();
159+
ProgramMonkey programMonkey = new ProgramMonkey("小明", "", 12);
160+
Method setmLanguageMethod = null;
161+
try {
162+
setmLanguageMethod = programMonkey.getClass().getMethod("setmLanguage", String.class);
163+
setmLanguageMethod.setAccessible(true);
164+
} catch (NoSuchMethodException e) {
165+
e.printStackTrace();
166+
}
167+
168+
for (int index = 0; index < count; index++) {
169+
try {
170+
setmLanguageMethod.invoke(programMonkey, "Java");
171+
} catch (IllegalAccessException e) {
172+
e.printStackTrace();
173+
} catch (InvocationTargetException e) {
174+
e.printStackTrace();
175+
}
176+
}
177+
178+
return System.currentTimeMillis() - startTime;
179+
}
180+
181+
private long getReflectCallFieldCostTime(int count) {
182+
long startTime = System.currentTimeMillis();
183+
ProgramMonkey programMonkey = new ProgramMonkey("小明", "", 12);
184+
Field ageField = null;
185+
try {
186+
ageField = programMonkey.getClass().getDeclaredField("mLanguage");
187+
188+
} catch (NoSuchFieldException e) {
189+
e.printStackTrace();
190+
}
191+
192+
for (int index = 0; index < count; index++) {
193+
try {
194+
ageField.set(programMonkey, "Java");
195+
} catch (IllegalAccessException e) {
196+
e.printStackTrace();
197+
}
198+
}
199+
200+
return System.currentTimeMillis() - startTime;
201+
}
202+
```
203+
204+
沿用上面的测试方法,测试结果如下:
205+
206+
![](C:\Users\Tyson\Desktop\img\0103\反射影响性能2.png)
207+
208+
修改`getReflectCallMethodCostTime``getReflectCallFieldCostTime`方法的代码如下,对`getMethod``getDeclaredField`进行测试:
209+
210+
```java
211+
private long getReflectCallMethodCostTime(int count){
212+
long startTime = System.currentTimeMillis();
213+
ProgramMonkey programMonkey = new ProgramMonkey("小明", "", 12);
214+
215+
for(int index = 0 ; index < count; index++){
216+
try{
217+
Method setmLanguageMethod = programMonkey.getClass().getMethod("setmLanguage", String.class);
218+
}catch(NoSuchMethodException e){
219+
e.printStackTrace();
220+
}
221+
}
222+
223+
return System.currentTimeMillis()-startTime;
224+
}
225+
226+
private long getReflectCallFieldCostTime(int count){
227+
long startTime = System.currentTimeMillis();
228+
ProgramMonkey programMonkey = new ProgramMonkey("小明", "", 12);
229+
for(int index = 0 ; index < count; index++){
230+
try{
231+
Field ageField = programMonkey.getClass().getDeclaredField("mLanguage");
232+
}catch(NoSuchFieldException e){
233+
e.printStackTrace();
234+
}
235+
}
236+
return System.currentTimeMillis()-startTime;
237+
}
238+
```
239+
240+
沿用上面的测试方法,测试结果如下:
241+
242+
![](http://img.topjavaer.cn/img/反射影响性能3.png)
243+
244+
测试结论:
245+
246+
- `getMethod``getDeclaredField`方法会比`invoke``set`方法耗时;
247+
- 随着测试数量级越大,性能差异的比例越趋于稳定;
248+
249+
由于测试的这四个方法最终调用的都是`native`方法,无法进一步跟踪。个人猜测应该是和在程序运行时操作`class`有关。
250+
251+
比如需要判断是否安全?是否允许这样操作?入参是否正确?是否能够在虚拟机中找到需要反射的类?主要是这一系列判断条件导致了反射耗时;也有可能是因为调用`natvie`方法,需要使用`JNI`接口,导致了性能问题(参照`Log.java、System.out.println`,都是调用`native`方法,重复调用多次耗时很明显)。
252+
253+
# 03 如果避免反射导致的性能问题?
254+
255+
通过上面的测试可以看出,过多地使用反射,的确会存在性能问题,但如果使用得当,所谓反射导致性能问题也就不是问题了,关于反射对性能的影响,参照下面的使用原则,并不会有什么明显的问题:
256+
257+
- 不要过于频繁地使用反射,大量地使用反射会带来性能问题;
258+
- 通过反射直接访问实例会比访问方法快很多,所以应该优先采用访问实例的方式。
259+
260+
# 04 总结
261+
262+
上面的测试并不全面,但在一定程度上能够反映出反射的确会导致性能问题,也能够大概知道是哪个地方导致的问题。如果后面有必要进一步测试,我会从下面几个方面作进一步测试:
263+
264+
- 测试频繁调用`native`方法是否会有明显的性能问题;
265+
- 测试同一个方法内,过多的条件判断是否会有明显的性能问题;
266+
- 测试类的复杂程度是否会对反射的性能有明显影响。
267+

0 commit comments

Comments
 (0)