Does Java Reflection Really Slow Down Your App? A Deep Performance Test
This article investigates whether Java reflection impacts performance by running systematic benchmarks that compare direct method calls, reflective method calls, direct field access, and reflective field access across varying iteration counts, presenting detailed results, analysis of the slow paths, and practical guidelines to mitigate any overhead.
Does reflection really have performance issues?
Reflecting on how reflection affects performance sparked my curiosity. Many articles simply state the conclusion without explaining the cause, leading to superficial understanding.
Test Methodology
Using the demo from the previous article, I enlarged the problem by testing four access patterns: direct method call, reflective method call, direct field access, and reflective field access. For each pattern I measured execution time from 1 to 1,000,000 iterations, increasing the count by orders of magnitude and averaging over multiple runs.
public class ReflectionPerformanceActivity extends Activity {
private TextView mExecuteResultTxtView = null;
private EditText mExecuteCountEditTxt = null;
private Executor mPerformanceExecutor = Executors.newSingleThreadExecutor();
private static final int AVERAGE_COUNT = 10;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_reflection_performance_layout);
mExecuteResultTxtView = (TextView)findViewById(R.id.executeResultTxtId);
mExecuteCountEditTxt = (EditText)findViewById(R.id.executeCountEditTxtId);
}
public void onClick(View v) {
switch(v.getId()){
case R.id.executeBtnId:{
execute();
}
break;
default:{
}
break;
}
}
private void execute(){
mExecuteResultTxtView.setText("");
mPerformanceExecutor.execute(new Runnable(){
@Override
public void run(){
long costTime = 0;
int executeCount = Integer.parseInt(mExecuteCountEditTxt.getText().toString());
long reflectMethodCostTime=0,normalMethodCostTime=0,reflectFieldCostTime=0,normalFieldCostTime=0;
updateResultTextView(executeCount + "毫秒耗时情况测试");
for(int index = 0; index < AVERAGE_COUNT; index++){
updateResultTextView("第 " + (index+1) + " 次");
costTime = getNormalCallCostTime(executeCount);
reflectMethodCostTime += costTime;
updateResultTextView("执行直接调用方法耗时:" + costTime + " 毫秒");
costTime = getReflectCallMethodCostTime(executeCount);
normalMethodCostTime += costTime;
updateResultTextView("执行反射调用方法耗时:" + costTime + " 毫秒");
costTime = getNormalFieldCostTime(executeCount);
reflectFieldCostTime += costTime;
updateResultTextView("执行普通调用实例耗时:" + costTime + " 毫秒");
costTime = getReflectCallFieldCostTime(executeCount);
normalFieldCostTime += costTime;
updateResultTextView("执行反射调用实例耗时:" + costTime + " 毫秒");
}
updateResultTextView("执行直接调用方法平均耗时:" + reflectMethodCostTime/AVERAGE_COUNT + " 毫秒");
updateResultTextView("执行反射调用方法平均耗时:" + normalMethodCostTime/AVERAGE_COUNT + " 毫秒");
updateResultTextView("执行普通调用实例平均耗时:" + reflectFieldCostTime/AVERAGE_COUNT + " 毫秒");
updateResultTextView("执行反射调用实例平均耗时:" + normalFieldCostTime/AVERAGE_COUNT + " 毫秒");
}
});
}
private long getReflectCallMethodCostTime(int count){
long startTime = System.currentTimeMillis();
for(int index = 0; index < count; index++){
ProgramMonkey programMonkey = new ProgramMonkey("小明", "男", 12);
try{
Method setmLanguageMethod = programMonkey.getClass().getMethod("setmLanguage", String.class);
setmLanguageMethod.setAccessible(true);
setmLanguageMethod.invoke(programMonkey, "Java");
}catch(IllegalAccessException e){e.printStackTrace();}
catch(InvocationTargetException e){e.printStackTrace();}
catch(NoSuchMethodException e){e.printStackTrace();}
}
return System.currentTimeMillis() - startTime;
}
private long getReflectCallFieldCostTime(int count){
long startTime = System.currentTimeMillis();
for(int index = 0; index < count; index++){
ProgramMonkey programMonkey = new ProgramMonkey("小明", "男", 12);
try{
Field ageField = programMonkey.getClass().getDeclaredField("mLanguage");
ageField.set(programMonkey, "Java");
}catch(NoSuchFieldException e){e.printStackTrace();}
catch(IllegalAccessException e){e.printStackTrace();}
}
return System.currentTimeMillis() - startTime;
}
private long getNormalCallCostTime(int count){
long startTime = System.currentTimeMillis();
for(int index = 0; index < count; index++){
ProgramMonkey programMonkey = new ProgramMonkey("小明", "男", 12);
programMonkey.setmLanguage("Java");
}
return System.currentTimeMillis() - startTime;
}
private long getNormalFieldCostTime(int count){
long startTime = System.currentTimeMillis();
for(int index = 0; index < count; index++){
ProgramMonkey programMonkey = new ProgramMonkey("小明", "男", 12);
programMonkey.mLanguage = "Java";
}
return System.currentTimeMillis() - startTime;
}
private void updateResultTextView(final String content){
ReflectionPerformanceActivity.this.runOnUiThread(new Runnable(){
@Override
public void run(){
mExecuteResultTxtView.append(content);
mExecuteResultTxtView.append("
");
}
});
}
}Test results:
Reflection indeed causes performance degradation.
The severity depends on call frequency; under 100 calls the impact is negligible, but beyond that the difference becomes obvious.
Performance ranking (fastest to slowest): direct field access, direct method call (~1.4×), reflective field access (~3.75×), reflective method call (~6.2×).
Where exactly is reflection slow?
Source tracing shows that all four methods instantiate ProgramMonkey, so object creation is not the cause. The setAccessible call has minimal impact. The likely bottlenecks are getMethod / getDeclaredField and the subsequent invoke / set operations.
Testing invoke vs. set
private long getReflectCallMethodCostTime(int count){
long startTime = System.currentTimeMillis();
ProgramMonkey programMonkey = new ProgramMonkey("小明", "男", 12);
Method setmLanguageMethod = null;
try{
setmLanguageMethod = programMonkey.getClass().getMethod("setmLanguage", String.class);
setmLanguageMethod.setAccessible(true);
}catch(NoSuchMethodException e){e.printStackTrace();}
for(int index = 0; index < count; index++){
try{
setmLanguageMethod.invoke(programMonkey, "Java");
}catch(IllegalAccessException e){e.printStackTrace();}
catch(InvocationTargetException e){e.printStackTrace();}
}
return System.currentTimeMillis() - startTime;
}
private long getReflectCallFieldCostTime(int count){
long startTime = System.currentTimeMillis();
ProgramMonkey programMonkey = new ProgramMonkey("小明", "男", 12);
Field ageField = null;
try{
ageField = programMonkey.getClass().getDeclaredField("mLanguage");
}catch(NoSuchFieldException e){e.printStackTrace();}
for(int index = 0; index < count; index++){
try{
ageField.set(programMonkey, "Java");
}catch(IllegalAccessException e){e.printStackTrace();}
}
return System.currentTimeMillis() - startTime;
}Results:
Conclusion: invoke and set are faster than repeatedly calling getMethod or getDeclaredField.
Avoiding Reflection Performance Issues
Avoid excessive use of reflection; heavy usage leads to noticeable overhead.
Prefer direct field access over reflective method invocation when possible, as it is significantly faster.
Afterword
The current tests are not exhaustive but demonstrate that reflection can cause performance problems and hint at the underlying reasons. Future work may include:
Testing frequent native method calls for performance impact.
Evaluating the cost of numerous conditional checks within a method.
Assessing how class complexity influences reflection overhead.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Programmer DD
A tinkering programmer and author of "Spring Cloud Microservices in Action"
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
