본문 바로가기

Programming

자바 메모리 누수 체크/확인/고치는 방법, Memory leak check/fix in Java application, cleanCode/좋은코드/oop/객체지향

자바 메모리 누수 체크/확인/고치는 방법, Memory leak check/fix in Java application, cleanCode/좋은코드/oop/객체지향




 만들고 있는 Java application 에서 메모리 누수가 의심되어 이것저것 체크하며 검색하던 도중에 읽은 것 중에서 가장 좋았던 글
불필요한 부분은 좀 지워버림

 The best article I read during searching and checking about memory leak in Java application now I making.
I deleted a few useless part of it.



원문 ( 여기 )
Origin ( HERE )



 **20191004 - 이 포스팅은 전반적으로 자바 프로그래밍 시에 메모리 누수가 일어날 수 있는 원인에 대한 내용이 많은 것 같고,  개인적으로 대략적인 메모리 누수 확인을 위해서 프로그램 만들어둔 것은 코드 정리 후 공유 가능. (필요시 댓글 등 남겨주세요)




The Java automatic garbage collection process typically operates as a low-priority thread that constantly searches memory for unreachable objects, objects not referenced by any other object reachable by a live thread. Different JVMs use different algorithms to determine how to collect garbage most efficiently.


자바의 자동 가비지컬렉션 프로세스는 보통 우선순위가 낮은 쓰레드로, 살아있는 쓰레드에 닿아있는 다른 객체들이 참조를 하고 있지 않은 객체들(닿아있지 않은 객체들)을 메모리에서 지속적으로 찾아 정리한다. 다른 JVM들은 각기 다른 알고리즘으로 가비지 컬렉트를 효율적으로 한다. 



In the JVM, memory is allocated in two regions:  JVM 에는, 메모리가 두 부분에 할당이 된다.


Stack: 


Where local variables (declared in methods and constructors) are allocated. Local variables are allocated when a method is invoked and de-allocated when the method is exited.


Heap: 


Where all objects created with the new keyword are allocated. Since local variables are few in number, only primitive types and references, usually the stack will not overflow, except in cases of unusually deep or infinite recursion. The JVM throws a Java out-of-memory error if it is not able to get more memory in the heap to allocate more Java objects. The JVM cannot allocate more objects if the heap is full of live objects and unable to expand further.


 Stack : 


스택, 지역 변수들 (메소드나 생성자들에서 선언되는) 것들이 할당된다. 지역 변수들은 메소드가 시작될 때 할당되고, 메소드가 끝날 때 할당해제된다.


 Heap : 


힙, new 키워드를 통해서 만들어지는 모든 객체들이 할당된다. 지역변수는 primitive 자료형과 참조형 뿐 거의 없기 때문에, 어쩌다가 깊은 케이스나, 무한 재귀의 경우를 제외하면 스택은 잘 오버플로가 나지 않는다. JVM 은 힙에 살아있는 객체들이 가득 차고, 더 확장할 수 없다면 더 많은 객체를 할당 할 수 ㅇ




Causes of memory leaks in Java



The four typical causes of memory leaks in a Java program are:


 자바 프로그램 메모리 누수의 전형적인 4가지 원인들은: 



Unknown or unwanted object references: 잘 모르고 있거나 원치않는 객체참조


These objects are no longer needed, but the garbage collector can not reclaim the memory because another object still refers to it.


 이러한 객체들은 더이상 필요가 없지만 다른 객체가 여전히 참조 중이기 때문에, 가비지 컬렉터가 수거할 수 없다.


Long-living (static) objects: 오래 사는 static 객체들


These objects stay in the memory for the application's full lifetime. Objects tagged to the session may also have the same lifetime as the session, which is created per user and remains until the user logs out of the application.


 이러한 객체들은 프로그램의 전체 생명주기동안 메모리에 머문다. 세션에 붙어있는 객체들은 아마 세션의 생명주기와 같을 것이고, 각각 유저마다 만들어져서 그 유저가 프로그램에서 로그아웃 할 때까지 유지될 것이다.



Failure to clean up or free native system resources:  native system resource 들을 정리하는 것에 실패했을 때


Native system resources are resources allocated by a function external to Java, typically native code written in C or C++. Java Native Interface (JNI) APIs are used to embed native libraries/code into Java code.


 Native system resources 는 자바 외부의 함수들에 의해 할당된 자원들인데, 일반적으로 C, C++ 로 쓰여져 있다. Java Native Interface (JNI) API 들이 Java code 로 native libraries/code 들을 넣기 위해서 사용된다.



Bugs in the JDK or third-party libraries:  JDK 나 3rd party libraries 의 버그들


Bugs in various versions of the JDK or in the Abstract Window Toolkit and Swing packages can cause memory leaks.


 JDK의 다양한 버전이나 가상 윈도우 툴킷, 스윙 패키지들의 버그도 메모리 누수를 일으킬 수 있다.



Detection of memory leaks 메모리 누수 탐지, 확인



Some approaches to detecting memory leaks follow in the list below:


메모리 누수를 찾는 몇가지 접근법은 아래와 같다.



1. Use the operating system process monitor, which tells how much memory a process is using. On Microsoft Windows, the task manager can show memory usage when a Java program is running. This mechanism gives a coarse-grained idea of a process's total memory utilization.


1. 얼마나 많은 메모리 프로세스가 사용되는지 볼수 있는 운영체제의 프로세스 모니터를 사용한다. 윈도우즈에서는 작업관리자가 실행되고 있는 자바 프로그램의 메모리 사용량을 보여줄 수 있다. 이 메카니즘은 프로세스의 총 메모리 사용량의 굵직한 아이디어를 줄 것이다.



2. Use the totalMemory() and freeMemory() methods in the Java runtime class, which shows how much total heap memory is being controlled by the JVM, along with how much is not in use at a particular time. This mechanism can provide the heap's memory utilization. However, details of the heap utilization indicating the objects in the heap are not shown.


2. JVM에 의해 컨트롤 되고 있는 총 힙 메모리를 보여주는 자바 런타임 클래스의 totalMemory() 와 freeMemory() 메소드를 사용한다. 이 메카니즘은 힙의 메모리 사용량을 제공해 줄 수 있다. 하지만, 힙 안의 객체들이 힙 사용량의 자세한 것은 보여주지 않는다.



3. Use memory-profiling tools, e.g., JProbe Memory Debugger. These tools can provide a runtime picture of the heap and its utilization. However, they can be used only during development, not deployment, as they slow application performance considerably.


 3. 메모리 프로파일링 툴을 사용한다. 예를들면 JProbe Memory Debugger 와 같은. 이러한 툴들은 힙의 런타임 이미지와 사용량을 제공할 수 있다. 하지만 그것들은 개발 중에만 사용가능하고, 느려지기 때문에 프로그램 퍼포먼스를 고려하면 배치에서는 사용할 수 없다.



Causes of memory leaks in enterprise Java applications 자바 프로그램 메모리 누수의 원인


In the subsequent sections, I analyze some causes of memory leaks in enterprise Java applications using a sample application and a memory profiling tool. I also suggest strategies for detecting and plugging such leaks in your own projects.


 이 후의 섹션들에서, 샘플 프로그램과 메모리 프로파일링 툴을 사용해서 자바 프로그램에서의 메모리 누수들의 원인들을 분석해보았다. 또한 네 프로젝트에서 누수를 탐지하고 막는 전략을 제안한다.



1. ResultSet and Statement Objects. JDBC 같은 곳에서 쓰이는 ResultSet/Statement 객체들



The Statement and ResultSet interfaces are used with Java Database Connectivity (JDBC) APIs. Statement/PreparedStatment objects are used for executing a SQL Statement; ResultSet objects are used for storing SQL queries' results.


Statement 와 ResultSet 인터페이스들은 자바 데이터베이스 커넥티비티에서 사용되는 api 이다. Statement/PreparedStatement 객체들은 SQL 문을 실행하기 위해 사용되고, ResultSet 객체들은 SQL 쿼리문의 결과물을 저장하는데 사용된다.


A Java Enterprise Edition (Java EE) application usually connects to the database by either making a direct connection to the database using JDBC thin drivers provided by the database vendor or creating a pool of database connections within the Java EE container using the JDBC drivers. If the application directly connects to the database, then on calling the close() method on the connection object, the database connection closes and the associated Statement and ResultSet objects close and are garbage collected. If a connection pool is used, a request to the database is made using one of the existing connections in the pool. In this case, on calling close() on the connection object, the database connection returns to the pool. So merely closing the connection does not automatically close the ResultSet and Statement objects. As a result, ResultSet and Statement will not become eligible for garbage collection, as they continue to remain tagged with the database connection in the connection pool.


 자바 EE 에서는 보통 데이터베이스 vendor 에 의해 제공되는 JDBC 드라이버를 이용하여 데이터베이스에 직접 연결을 만들거나, JDBC 드라이버를 사용해서 자바 EE 컨테이너 안에 데이터베이스 풀을 만든다. 만약 어플리케이션이 데이터베이스에 직접 연결 되어있다면, 연결 객체에 close() 메소드를 호출하면서 데이터베이스 연결을 닫고, 관련된 Statement 와 ResultSet 객체를 닫아서 가비지컬렉션 될 수 있다. 만약 커넥션 풀이 사용되고 있다면, 데이터베이스에 요청은 풀에 존재하는 연결중 하나에 사용된다. 이 케이스의 경우는, 연결 객체에 close() 를 호출하면서, 데이터베이스 연결을 풀로 반환한다. 그래서 닫히는 커넥션은 자동으로 ResultSet 과 Statement 객체들을 닫지 않는다. 결과적으로 ResultSet 과 Statement 는 가비지컬렉션 되지 않고, 커넥션풀안의 데이터베이스 연결에 붙어서 남아있을 것이다. 


To investigate the memory leak caused by not closing Statement and ResultSet objects while using a connection pool, I used a sample Java EE application that queries a database table and displays the results in a JSP (JavaServer Pages) page. It also allows you to save records to the database table. The application is deployed in iPlanet App Server 6.0. I used JProbe to analyze the memory utilization by the application.


 커넥션풀을 쓰는동안 닫히지 않은 Statement 와 ResultSet 객체에서 생기는 메모리 누수를 찾기 위하여, 데이터 베이스 테이블 쿼리문과 JSP 페이지로 그 결과를 보여주는 샘플 자바프로그램을 사용했다. 또한 네가 결과물을 데이터베이스 테이블에 저장할 수 있게 했다. (이 샘플 내용은 생략했음)

 

The sample application uses a database table with the following structure: 

..

( Skip )


 

Recommendation  추천 해결 방법


When using connection pools, and when calling close() on the connection object, the connection returns to the pool for reuse. It doesn't actually close the connection. Thus, the associated Statement and ResultSet objects remain in the memory. Hence, JDBC Statement and ResultSet objects must be explicitly closed in a finally block.


 커넥션 풀을 사용할 때, 그리고 연결 객체에 close() 를 호출할 때, 커넥션은 재사용을 위해 풀에 반환된다. 이것은 실제 연결이 정리된 것이 아니다. 따라서, 관련된 Statement 와 ResultSet 객체는 메모리에 남아있다. 따라서, JDBC Statement 와 ResultSet 객체는 반드시 별도로 finally 블록에서 닫아주어야 한다.



2. Collection objects  컬렉션 객체들



A collection is an object that organizes references to other objects. The collection itself has a long lifetime, but the elements in the collection do not. Hence, a memory leak will result if items are not removed from the collection when they are no longer needed.


 컬렉션은 다른 객체들의 참조를 조직하는 객체이다. 컬렉션은 스스로 긴 생명주기를 가지고 있지만, 컬렉션 내의 요소들은 그렇지 않다. 따라서, 내부 요소들이 필요가 없을 때 컬렉션에서 지워지지 않으면 메모리 누수의 결과가 될 수 있다.


Java provides the Collection interface and implementation classes of this interface such as ArrayList and Vector. Using the same Java EE application tested in the previous scenario, I added the database query results to an ArrayList. When 35,000 rows were present in the database table, the application server threw a java.lang.OutOfMemoryError, with a default JVM heap size of 64 MB.


 자바는 ArrayList 와 Vector 같은 인터페이스의 구현클래스와 컬렉션 인터페이스를 제공한다. 이전 시나리오에서 썼던 자바 프로그램과 같은 것을 사용하여, ArrayList에 데이터베이스 쿼리 결과를 추가했다. 35000 줄이 테이터베이스에 보여졌을 때, 기본 JVM 힙 사이즈 64mb 어플리케이션 서버는 OutOutMemoryError를 던졌다.



Recommendation 추천 해결 방법


When collections are used, the object references stored in the collections should be programmatically cleaned to ensure that the collection size does not grow indefinitely. If the collection is being used to store a large table's query results, data access should be completed in batches.


 컬렉션이 쓰일 때, 컬렉션에 저장된 객체들의 참조값들은 프로그래밍적으로 치워져서 컬렉션 사이즈가 무기한으로 커지지 않도록 해야한다. 만약에 컬렉션이 큰 테이블의 쿼리문 결과물을 저장하고 있다면, 데이타 접근은 batch 에서 끝나야 할 것이다. 

 


3. Static variables and classes  static(정적) 변수들과 클래스들


In Java, usually a class member (variable or method) is accessed in conjunction with an object of its class. In the case of static variables and methods, it is possible to use a class member without creating an instance of its class. A class with static members is known as a static class. In such cases, before a class instance is created, an object of its class will also be created by the JVM. The class object is allocated to the heap itself. The primordial class loader will load the class object. In the case of static classes, all the static members will also be instantiated along with the class object. Once the variable is initialized with data (typically an object), the variable remains in memory as long as the class that defines it stays in memory. If the primordial class loader loads class instances, they will stay in memory for the duration of the program and are not eligible for garbage collection. So static classes and associated static variables will never be garbage collected. Thus, using too many static variables leads to memory leaks.


 자바에서, 대게 클래스 멤버(변수나 메소드)는 그 클래스의 객체와 연관되어 있다. static 변수와 메소드들의 경우에는, 클래스의 객체를 만드는 것 없이 클래스 멤버를 사용하는 것이 가능하다. static 멤버가 있는 class 는 static 클래스로 알려져 있다. 그러한 경우에는 클래스 객체가 만들어지기 전에, 그 클래스의 객체 또한 JVM에 의해서 만들어진다. 그 클래스 객체는 힙에 할당되어 진다. 원시 클래스 로더는 그 클래스 객체를 로드할 겄이다. static 클래스들의 경우에, 모든 static 멤버들은 그 클래스 객체와 함깨 초기화 될 것이다. 일단 그 변수가 데이터와 초기화 되면 (일반적으로 객체와), 그 변수는 메모리에 그 글래스가 메모리에 있는 동안 유지된다. 만약에 원시 클래스 로더가 글래스 객체를 로드하면, 그것들은 가비지컬렉팅 되는 대상에 포함되지 않고, 프로그램의 기간동안 메모리에 남아있게 될 것이다. 그래서 static 클래스와 static 변수들은 가비지컬렉팅 되지 않을 것이다. 따라서, 너무 많은 static 변수들은 메모리 누수를 야기한다.

 


Recommendation 추천 해결 방법


Usage of static classes should be minimized as they stay in memory for the lifetime of the application.


프로그램의 생명주기에서 메모리 안에 쭉 머물기 때문에, static 클래스들의 사용은 최소화 되어야한다.


4. The Singleton pattern  싱글톤 패턴 사용


The Singleton pattern is an object-oriented design pattern used to ensure that a class has only one instance and provide a global point of access to that instance. The Singleton pattern can be implemented by doing the following:


 싱글턴 패턴은 객체지향 디자인에서 클래스가 딱 하나만 존재하는 것을 보장하고, 전역적으로 접근 가능한 객체를 제공하는데에 사용된다. 싱글튼 패턴은 아래와 같이 구현 가능하다.

 


1. Implementing a static method that returns an instance of the class

2. Making the constructor private so a class instance can be created only through the static method

3. Using a static variable to store the class instance


1. 해당 클래스의 객체를 반환하는 static 메소드를 구현한다.

2. 생성자의 가시성을 private 으로 만들어서 클래스는 static method 를 통해서만 만들어질 수 있도록 한다.

3. 해당 클래스의 객체를 저장하는 static 변수를 사용한다.



Example code for the Singleton pattern follows:

예제코드 



 public class Singleton {

   private static Singleton singleton=null;

   private singleton () {

   }

   public static Singleton getInstace() {

      if (singleton != null)

      singleton=new Singleton();

      return singleton;

  }

}



The Singleton class is typically used as a factory to create objects. I cached these objects into an ArrayList to enable their speedy retrieval. When a new object must be created, it will be retrieved from the cache if it is present there, otherwise, a new object will be created.


 싱글턴 클래스는 보통 객체들을 만드는 팩토리로 사용된다. 나는 이러한 객체들을 ArrayList 에 저장해두어 빠른 사용이 가능하게 한다. 새로운 객체가 반드시 만들어져야 할 때, list 에 만들어져있는 것이라면 저장된 것에서 가져올 것이고, 아니면 새로 만들어서 가져올 것이다.



Once the Singleton class is instantiated, it remains in memory for the application's lifetime. The other objects will also have a live reference to it and, as a result, will never be garbage collected.


 일단 싱글튼 클래스가 만들어지면, 어플리케이션 생명주기 내내 유지된다. 다른 객체들 또한 이것을 참조하고 있을 것이고, 결과적으로 가비지컬렉팅 되지 않는다.


Recommendation 추천 해결 방법


Avoid referencing objects from long-lasting objects. If such usage cannot be avoided, use a weak reference, a type of object reference that does not prevent the object from being garbage collected.


 오래 지속되는 객체들로부터의 참조를 피한다. 피할 수 없다면, 약한 참조를 만들어라 (약한 참조 - 가비지컬렉팅이 막히지 않는 객체 참조 형태)




5. HttpSession vs. HttpRequest   HttpSession 과 HttpRequest


HTTP is a request-response-based stateless protocol. If a client wants to send information to the server, it can be stored in an HttpRequest object. But that HttpRequest object will be available only for a single transaction. The HTTP server has no way to determine whether a series of requests came from the same client or from different clients. The HttpSession object is generally used to store data required from the time a user logs into the system until he logs out. It brings statefulness into a transaction. The session can be used for storing information such as a user's security permissions. But often, programmers mistakenly store complex long-living data, such as a shopping cart, into the session, instead of using the business tier.


 HTTP 는 request-response-based stateless protocol 이다. 만약 클라이언트가 서버로 정보를 보내고 싶다면, HttpRequest 객체에 저장될 수 있다. 하지만 HttpRequest 객체는 한번의 transaction 에 이용 가능하다. HTTP 서버는 같은 클라이언트나 다른 클라이언트들로부터 연속된 request를 받을지 결정할 방법이 없다. HttpSession 객체는 일반적으로 user 가 로그아웃 하기 전까지 시스템에 유저가 들어온 시간부터 요구되는 데이터를 저장하는데에 사용된다. 세션은 정보를 저장하는데에 사용되는데, 정보는 유저의 보안 허가 등이 있다. 하지만 가끔 프로그래머들은 실수로 business tier를 사용하는 대신에, 오래 살아있는 데이터를 저장하는데 이는 쇼핑카드와 같은게 있다.



I experimented with the sample application to find the difference in memory usage between the HttpSession and HttpRequest objects since data stored in HttpSession will stay in memory until the user logs out of the application. I added the database table's query results to an ArrayList, which I then placed into both the HttpSession and HttpRequest objects. Memory usage was observed for 50 query-and-save operations.


 HttpSession에 저장된 데이터는 메모리에서 application 유저가 나갈 때까지 유지되기 때문에, 나는 HttpSession 과 HttpRequest 사이에 메모리 사용의 차이를 찾기위해서 같은 application 으로 실험을 했다. database 쿼리문의 결과를 ArrayList에 저장하고, HttpSession 과 HttpRequest 객체 둘다에 놓았다. 메모리 사용량은 ~ ~ 아래 시나리오 생략



Scenario 1


The database table contains 100 rows. The output ArrayList is stored in the HttpRequest object to be passed back to the JSP page.


When the data is stored in HttpSession, instead of HttpRequest, 50 save-and-query operations increase memory usage by 22.424 KB. This happens on a per client basis. Hence for multiple clients, the multiplicative factor comes in as well. Over a period of time, this will definitely lead to a significant memory leak in the application.


The data stored in HttpSession stays in memory as long as the user is logged in. Putting too much data into HttpSession leads to the Overstuffed Session anti-pattern. Since HttpSession is implemented as a collection, this overstuffed session can be considered a variant of the Leak Collection anti-pattern.





Recommendation 추천 해결 방법


Use of HttpSessions should be minimized and used only for state that cannot realistically be kept on the request object

Remove objects from HttpSession if they are no longer used

Long-living data should be migrated to the business tier

Conclusion

I have highlighted some of the important programming scenarios where the JVM's garbage collection mechanism becomes ineffective. These situations necessitate appropriate precautions during design of the application itself. While closing ResultSets and Statements can be done after application development with comparatively low costs, other aspects that I have explained get deeply embedded in the design and could prove costly to correct.


 HttpSession 의 사용은 최소화 되어야 하고, ~ ~ 생략



 

 The garbage collector is a low-priority thread. Hence in a heavily loaded Java EE application, garbage collection itself may happen infrequently. Even those objects that could have been potentially garbage collected may actually stay in memory for a long time. So explicitly cleaning the heap may be a mandatory programming requirement in some applications; doing so must be considered on a case-by-case basis.


 가비지컬렉터는 우선순위가 낮은 쓰레드이다. 따라서 무거운 자바 ee 프로그램에서, 가비지 걸렉션은 빈번하게 발생하지 않는다. 잠재적으로 가비지컬렉팅 될 수 있는 객체들이 실제로 메모리에 오랫동안 살아있더라도. 그래서 몇 프로그램들에서, 명백하게 깔끔한 힙은 필수적인 프로그래밍 필요조건이다. 케바케 상황을 고려해서 해봐라






Something else you might like...?




2018/12/26 - [Programming/Software Architecture] - Perfecting OO's Small Classes and Short Methods. 완벽한 객체지향의 작은 클래스와 짧은 메소드, Book:ThoughtWorks Anthology, Java,cpp,자바,oop,좋은코드,객체지향프로그래밍 (1)

2018/12/28 - [Programming/Software Architecture] - Perfecting OO's Small Classes and Short Methods. 완벽한 객체지향의 작은 클래스와 짧은 메소드, Book:ThoughtWorks Anthology, Java,cpp,자바,oop,좋은코드,객체지향프로그래밍 - (#9, Tell, Don't Ask)



2018/12/13 - [Computer/Linux] - How to check ongoing live process on Linux cmd, 리눅스 커맨드창에서 어떤 프로세스가 실행중인지 확인하기, java, ubuntu, cpp



2019/10/04 - [Algorithm/Leet Code] - LeetCode #999 AvailableCapturesForRook. Algorithm,알고리즘,LeetCode,Codefights,CodeSignal,코드파이트,코드시그널,예제,그래프,Graph,example,c++,java,재귀,recursive,datastructure,techinterview,coding,코딩인터뷰,기술면접

2019/10/04 - [Algorithm/Leet Code] - LeetCode #1038 SearchInABinarySearchTree. Algorithm,알고리즘,LeetCode,Codefights,CodeSignal,코드파이트,코드시그널,예제,그래프,Graph,example,c++,java,재귀,recursive,datastructure,techinterview,coding,코딩인터뷰,기술면접

2019/09/29 - [Algorithm/Leet Code] - LeetCode #700 SearchInABinarySearchTree. Algorithm,알고리즘,LeetCode,Codefights,CodeSignal,코드파이트,코드시그널,예제,그래프,Graph,example,c++,java,재귀,recursive,datastructure,techinterview,coding,코딩인터뷰,기술면접



2019/01/12 - [Algorithm/Code Fights (Code Signal)] - Aracade Intro #60 sudoku. Algorithm,알고리즘,Codefights,CodeSignal,코드파이트,코드시그널,예제,문제해결능력,example,c++,java,재귀,recursive

2019/01/12 - [Algorithm/Code Fights (Code Signal)] - Aracade Intro #59 spiralNumbers. Algorithm,알고리즘,Codefights,CodeSignal,코드파이트,코드시그널,예제,문제해결능력,example,c++,java,재귀,recursive

2019/01/08 - [분류 전체보기] - Aracade Intro #58 messageFromBinaryCode. Algorithm,알고리즘,Codefights,CodeSignal,코드파이트,코드시그널,예제,문제해결능력,example,c++,java,재귀,recursive



2019/01/02 - [Life/Health care] - Taurine 타우린 usage/side effects/dosage/fatigue/supplement,효능/부작용/성인,소아 용법/건강/피로회복/영양제

2019/01/04 - [Life/Health care] - Omega-3 오메가3 usage/side effects/dosage/fatigue/supplement,효능/부작용/성인,소아 용법/건강/피로회복/영양제

2018/12/16 - [Life/Health care] - L-Arginine 아르기닌 usage/side effects/dosage 효능/부작용/성인,소아 용법(2)