Language/Java

Java - 스택 트레이스(Stack Trace) 읽기

JaeHoney 2020. 10. 9. 16:59

스택트레이스란?

스택트레이스프로그램이 시작된 시점부터 현재 위치 까지의 메서드 호출 목록입니다. 이는 예외가 어디서 발생했는지 알려주기 위해 JVM이 자동으로 생성합니다.

 

스택트레이스를 읽는 능력은 선택이 아닌 필수입니다(초보자라면 더욱). 왜냐하면 오류 발생시, 무턱대고 오류내용을 복붙하고 해결을 위한 코드도 복붙해서 해결하면 직면한 문제는 해결할 수 있지만 발전 없이 머물러 있게 되기 때문입니다. 반면에, 한번만 주의 깊게 연구해 본다면, 다음부터는 쉽게 정확한 원인을 파악할 수 있고, 효율적으로 해결할 수 있게 됩니다. 또한, 그런 문제를 마주치는 빈도가 줄어들게 됩니다.

 

public class StackTraceTest 
{
	public static void main(String[] args) 
	{
		one();
	}
	
	public static void one()
	{
		two();
	}

	public static void two()
	{
		three();
	}
	
	public static void three()
	{
		Integer.parseInt("abcde");
	}
}

 

스택트레이스는 에러가 발생된 시점부터 프로그램이 시작된 시점까지 거슬러 올라가면서 출력하기 때문에 먼저 실행 된 메서드가 아래쪽에 위치합니다.

 

스택트레이스 읽는법

java.lang.reflect.InvocationTargetException
     at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
     at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
     at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
     at java.lang.reflect.Method.invoke(Unknown Source)
     at com.mylibrary.ap.xul.builder.handler.MethodCallback.invokeCallback(MethodCallback.java:32)
     at com.mylibrary.ap.xul.builder.handler.ViewHandler.invokeActionResult(ViewHandler.java:581)
     at com.mylibrary.ap.xul.context.impl.ViewContextImpl$2.onActionResult(ViewContextImpl.java:283)
     at com.mylibrary.ap.xul.context.impl.ActionContextImpl.setResult(ActionContextImpl.java:90)
     at com.mylibrary.ap.xul.action.stock.DialogAction.handleDialogClose(DialogAction.java:156)
     at com.mylibrary.ap.xul.action.stock.DialogAction$1.windowClosed(DialogAction.java:142)
     at com.mylibrary.api.Window.fireWindowClosedEvent(Unknown Source)
     at com.mylibrary.api.Window.onClose(Unknown Source)
     at com.mylibrary.api.platform.WindowBase.BaseClose(Native Method)
     at com.mylibrary.api.platform.WindowBase.BaseClose(Unknown Source)
     at com.mylibrary.api.Window.close(Unknown Source)
     at com.mylibrary.ap.xul.ui.MessageDialog.handleButtonClick(MessageDialog.java:145)
     at com.mylibrary.ap.xul.ui.MessageDialog$1.handleClick(MessageDialog.java:134)
     at com.mylibrary.api.ClickableSupport.fireClickEvent(Unknown Source)
     at com.mylibrary.api.ClickableSupport.handleAction(Unknown Source)
     at com.mylibrary.api.Button.actionHook(Unknown Source)
     at com.mylibrary.api.Component.action(Unknown Source)
Caused by: java.lang.NullPointerException
     at com.mycompany.service.impl.PortalManagerImpl.deleteMenuItem(PortalManagerImpl.java:603)
     at com.mycompany.service.impl.PortalManagerImpl.deletePortal(PortalManagerImpl.java:358)
     at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
     at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
     at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
     at java.lang.reflect.Method.invoke(Unknown Source)
     at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:307)
     at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:182)
     at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:149)
     at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:106)
     at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:171)
     at org.springframework.security.intercept.method.aopalliance.MethodSecurityInterceptor.invoke(MethodSecurityInterceptor.java:66)
     at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:171)
     at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:204)
     at $Proxy54.deletePortal(Unknown Source)
     at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
     at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
     at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
     at java.lang.reflect.Method.invoke(Unknown Source)
     at com.mycompany.util.SpringSecurityContextInvocationHandler.invoke(SpringSecurityContextInvocationHandler.java:62)
     at $Proxy84.deletePortal(Unknown Source)
     at com.mycompany.ui.binding.PortalDataProvider.doDelete(PortalDataProvider.java:81)
     at com.mycompany.ui.binding.PortalDataProvider.doDelete(PortalDataProvider.java:12)
     at com.mycompany.ui.binding.AbstractEISDataProvider.delete(AbstractEISDataProvider.java:105)
     at com.mylibrary.ap.xul.binding.dataset.impl.DatasetImpl.doCommit(DatasetImpl.java:90)
     at com.mylibrary.ap.xul.binding.dataset.impl.AbstractDataset.commit(AbstractDataset.java:251)
     at com.mylibrary.ap.xul.binding.dataset.impl.AbstractDataset.deleteRow(AbstractDataset.java:201)
     at com.mylibrary.ap.xul.action.dataset.DeleteDataRowAction.execute(DeleteDataRowAction.java:22)
     at com.mylibrary.ap.xul.context.impl.ViewContextImpl.execute(ViewContextImpl.java:294)
     at com.mycompany.ui.portal.PortalInfoHandler.onPostConfirmDeleteAction(PortalInfoHandler.java:192)
     ... 21 more

많은 초보자분들은 이런 로그를 접하면 겁부터 먹고, 읽으려 들지 않습니다. 하지만 에러의 진정한 원인가장 아래쪽(초기)에 있는 Caused by:로 시작되는 줄부터 아래로 세 줄이면 충분합니다.

Caused by: java.lang.NullPointerException
     at com.mycompany.service.impl.PortalManagerImpl.deleteMenuItem(PortalManagerImpl.java:603)
     at com.mycompany.service.impl.PortalManagerImpl.deletePortal(PortalManagerImpl.java:358)

이 중 에러가 발생한 곳은 "at..."으로 시작하는 첫 번째 메시지입니다. 해당 메서드를 호출한 직후에 에러가 발생한 것입니다(역순이기 때문). 두 번째 줄은 'com.mycompany.service.impl.PortalManagerImpl' 클래스의 'deleteMenuItem' 메서드를 호출할 때 603번 째 줄에서 NullPointerException이 발생했습니다. 」라는 뜻입니다.

if (item == null) {
    throw new NullArgumentException("item");
}

//중간 생략
List<PortalMenu> children = getMenuItems(item.getPortal().getId(), item.getId()); // 603번째 줄

for (PortalMenu child : children) {
    deleteMenuItem(child);
 }

해당 부분입니다. 여기서 널값이 들어갈 수 있는 모든 경우의 수는 아래 5가지입니다.

  1. children
  2. item
  3. item.getPortal()
  4. item.getPortal().getId()
  5. item.getId()

이 중 적어도 두 가지, 즉 2번 혹은 3번으로 가능성을 좁히지 못했다면 NullPointerException을 정확히 이해하지 못한 것입니다. 왜냐하면 NullPointException은 단순히 변수에 널값이 들어가서 생기는 오류가 아니라, 널레퍼런스에 대해 메소드 호출이나 필드 참조, 배열 처리, throw, 동기화 등 작업을 했을 때 생기는 문제이기 때문입니다.

 

1번 children을 메서드를 호출하는 등의 작업을 하는게 아니라 널값을 할당하는 것만으로는 예외가 날 수 없고

 

4번과 5번도 널 레퍼런스에 대한 호출이 아니라 getMenuItems의 인자 값으로 넘기는 것 뿐이라 예외의 원인이 될 수 없습니다. (Null 체크를 안한 값을 사용하면 예외가 나겠지만, 절대 할당할 때 발생하지 않고 사용 할 때 발생함)

 

그리고 2번은 첫 번째 줄에서 null을 체크를 했기 때문에 3번이 원인이 될 수 밖에없습니다.

 

스택 트레이스를 읽을 수 있게 되면, 반복이 아닌 발전을 낳게 되고, 문제를 해결하는 시간을 점차 줄이게 될 것이고, 코드를 보는 안목이 생길 것입니다. 

 

 

관련글