Статьи

Кто вызывает этот метод?

Первоначально Автор Кристьян Рейнлоо.

ошибка исходного кода

Вы когда-нибудь стояли перед куском кода и задавались вопросом, почему он ведет себя так, как он. И хотел бы, чтобы у вас была информация о возможных местах, из которых этот метод был вызван. В прежние времена это могло быть легко — статический анализ кода мог бы дать вам ответ. Но в настоящее время, когда у нас есть инструменты отражения и модификации байт-кода, ответ часто не так прост.

Что ж, мы давно живем с одной и той же проблемой. В Plumbr мы облегчаем вашу жизнь, раскрывая источники проблем. В качестве примера — когда у вас случается утечка памяти в вашем приложении, мы сообщаем вам, где создаются утечки объектов. Вплоть до строки в исходном коде, где был создан объект. Но пока мы не смогли передать весь стек вызовов — просто слишком дорого собирать эту информацию во время выполнения приложения.

Но есть очень хороший инструмент, доступный только для задачи под названием BTrace . Это инструмент динамической трассировки, который можно подключить к процессу Java во время выполнения, чтобы получить стек вызовов, а затем отключить его, когда вы получите необходимую информацию. Btrace динамически обрабатывает байт-код класса в целевом приложении, чтобы внедрить код трассировки. Вы можете использовать тот же инструмент для других удивительных целей, но для ясности, давайте просто придерживаться этой функции.

Давайте рассмотрим пример с использованием того же отчета об утечке, который был получен из демонстрационного приложения Plumbr . Из приведенного ниже отчета об утечке мы видим, что что-то подозрительное происходит в строке 27 в методе класса preHandle () класса LeakingInterceptor .

Скриншот стека вызовов

Изучая исходный код, мы видим, что в строке 27 создается новый экземпляр ImageEntry :

lastUsedImages.add(new ImageEntry(image));

Чтобы получить полную трассировку стека в этой ситуации, мы должны дать подсказку BTrace о том, что отслеживать в нашем исходном коде.
Мы делаем это в форме небольшого Java-класса, который будет распечатывать всю трассировку стека каждый раз, когда создается новый экземпляр ImageEntry. Код для этого довольно прост:

import com.sun.btrace.annotations.*;
import static com.sun.btrace.BTraceUtils.*;
 
@BTrace
class LeakStack {
 
   @OnMethod(clazz="org.springframework.samples.petclinic.web.ImageEntry", method="<init>")
   public void printStack() {
      Threads.jstack();
      println("");
   }
}

Функция printStack () выполняется каждый раз, когда создается новый экземпляр ImageEntry. Отображение в аннотациях, которые определяют класс и метод для мониторинга. И, как вы помните из своих классов байт-кода, « <init> » обозначает конструктор.

Первоначально созданный

Now, надеюсь, вам удалось загрузить BTrace, и пришло время запустить демонстрационное приложение (запустите «start.sh», чтобы запустить приложение, и сценарий «create_usage.sh», чтобы сгенерировать использование). После этого мы можем узнать PID нашего демонстрационного приложения с помощью команды «jps». Чтобы запустить исполняемый файл btrace (находится в папке $ BTRACE_ROOT / bin /), мы должны указать PID нашего приложения и желаемый скрипт.

$ ./btrace LeakStack.java

После этого мы должны распечатать трассировки стека на нашей консоли, как показано ниже:

org.springframework.samples.petclinic.web.LeakingInterceptor.preHandle(LeakingInterceptor.java:27)
org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:891)
org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:827)
org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:882)
org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:778)
javax.servlet.http.HttpServlet.service(HttpServlet.java:707)
javax.servlet.http.HttpServlet.service(HttpServlet.java:820)
org.eclipse.jetty.servlet.ServletHolder.handle(ServletHolder.java:547)
org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1359)
org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:77)
org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:76)
org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1330)
org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:478)
org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:119)
org.eclipse.jetty.security.SecurityHandler.handle(SecurityHandler.java:483)
org.eclipse.jetty.server.session.SessionHandler.doHandle(SessionHandler.java:227)
org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:941)
org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:409)
org.eclipse.jetty.server.session.SessionHandler.doScope(SessionHandler.java:186)
org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:875)
org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:117)
org.eclipse.jetty.server.handler.ContextHandlerCollection.handle(ContextHandlerCollection.java:250)
org.eclipse.jetty.server.handler.HandlerCollection.handle(HandlerCollection.java:149)
org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:110)
org.eclipse.jetty.server.Server.handle(Server.java:345)
org.eclipse.jetty.server.HttpConnection.handleRequest(HttpConnection.java:441)
org.eclipse.jetty.server.HttpConnection$RequestHandler.headerComplete(HttpConnection.java:919)
org.eclipse.jetty.http.HttpParser.parseNext(HttpParser.java:582)
org.eclipse.jetty.http.HttpParser.parseAvailable(HttpParser.java:218)
org.eclipse.jetty.server.AsyncHttpConnection.handle(AsyncHttpConnection.java:51)
org.eclipse.jetty.io.nio.SelectChannelEndPoint.handle(SelectChannelEndPoint.java:586)
org.eclipse.jetty.io.nio.SelectChannelEndPoint$1.run(SelectChannelEndPoint.java:44)
org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:598)
org.eclipse.jetty.util.thread.QueuedThreadPool$3.run(QueuedThreadPool.java:533)
java.lang.Thread.run(Thread.java:722)

И мы успешно получили полный стек вызовов из запущенного приложения. Те из вас, кто сейчас смущен и обеспокоен тем, что запуск Plumbr сложен — не переживайте . Из тысяч обнаруженных нами утечек менее десяти были достаточно сложными, чтобы выследить их без полного стека вызовов. Плюс — наш RnD работает над улучшением нашего решения до такой степени, что вам не нужно проходить такие сложные упражнения. Им просто нужно немного больше времени. И, может быть, немного вдохновения.

Если вам удалось прочитать этот последний стек вызовов, вам определенно понравятся наши будущие сообщения. Так что подпишитесь на нашу ленту RSS или следите за нами в Twitter и следите за обновлениями.