Inner Class 누수

Activity 내부에 아래와 같이 Inner Class를 정의 할 경우 잠재적으로 누수의 대상이 된다.

...

public class MainActivity extends Activity {

private static Innserclass inner;
private String mStr;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main2);
inner = new Innserclass();
new Thread(inner).start();
mStr = "hello";
}

public class Innserclass implements Runnable {
public Innserclass() {
mStr+="nono!!";
}

@Override
public void run() {
getApplicationContext();
}
}
}

첫번째 누수 사유는 static이다.

private static Innserclass inner;

일단 static에 memory를 assigne하게 되면 해당 instance는 모 class인 activity와는 별도로 메모리 상에 살아 남게 된다.

두번째 사유는 thread 처리이다.

inner class를 thread처리 함으로써 (이경우는 postdelay도 마찬가지다) UI Thread가 죽어도 살아 남을 가능 성이 있다. Destroy 시점에 반듯이 thread를 종료 처리 해줘야 한다. handler도 이 경우에 속하게 된다.

세번째 사유는 InnerClass 자체에 있다. 첫번째 사유든 두번째 사유든 해당 Instance는 잠재적으로 모 Class의 Context를 접근 할 수 있게 된다. 코드상으론

mStr+="nono!!";

이 부분이다.

해결 방법은 다음고 같다.

Innser Class를 절대로 static 처리 하지 말아라
-    private static Innserclass inner;
+ private Innserclass inner;

Destroy시점에 Activity와 같이 종료할 수있다면 상관이 없을 수는 있다. 그러나 라이프 사이클이 Activity와 같다면 Static을 쓸 이유가 없다.

Inner Class는 static으로 정의 하던가 완전히 별도 파일로 제외를 시켜라
-    public class Innserclass implements Runnable {
+ public static class Innserclass implements Runnable {

static 처리를 하게 되면 해당 클래스는 모 class와 독립된 메모리 참조 구조를 갖게 되어 독립하게 된다.

weekRefernece를 사용해라
+    public static class Innserclass implements Runnable {
+ private final WeakReference<MainActivity> mActivity;

public Innserclass(MainActivity activity) {
+ mActivity = new WeakReference<>(activity);
}

@Override
public void run() {
- getApplicationContext();
+ mActivity.getApplicationContext();
}
}

직접적인 Reference를 주입해서는 안된다. 반듯이 weekRefernece를 이용해서 context를 공유 해야한다. thread와 같이 Main Thread의 범위 밖에서 작동이 된다면 MainActivity가 명시적으로 종료 되어도 해당 Context가 별도의 Thread에 남아있게 되어서 Memory Leak이 나게 된다.

Handler 누수

내용상으로는 Inner class와 동일하다. weekreference를 이용해서 instance를 생성하게 해야한다.

private static class MainHandler extends Handler {

private final WeakReference<MainActivity> mActivity;

public MainHandler(MainActivity activity) {
mActivity = new WeakReference<>(activity);
}

기본 적인 handler의 constructor는 인수가 없음으로 강제로 넣어야만 한다.

그리고 종료시에는 반듯이 다음 행위를 처리해줘야 한다.

@Override
protected void onDestroy() {

super.onDestroy();
if (MainHandler != null) {
MainHandler.removeCallbacksAndMessages(null);
MainHandler = null;
}

혹시 남아 있을지 모르는 메시지 큐 내용을 clear해줘야 한다.

Webview 누수

webview를 사용할때는 해당 view item이 destory가 완전히 되었고, 그에따른 메모리 반납이 이루어 졌는지 확인해야 한다. 그렇지 않다면 Native영역과 Others영역의 메모리가 지속 누적되는 현상이 발생 하게 된다. 이러한 현상은 layout상에 있는 webview를 call하게 되면 굉장히 높은 확률로 Leak이 발생하게 된다.

아래는 activity_main.xml과 MainActivity 수도 코드이다

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/webviewHell"
tools:context=".MainActivity">

<WebView
android:id="@+id/activity_main_webview"
android:layout_width="match_parent"
android:layout_height="match_parent" />

</RelativeLayout>
public class MainActivity extends Activity {

private WebView mWebView;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

getApplicationContext();
mWebView = findViewById(R.id.activity_main_webview);

Destory시 다음과 같은 code를 넣어 보아도 메모리 leak이 나는 것을 확인 할 수있다.

.....
@Override
protected void onDestroy() {
super.onDestroy();
mWebView.clearHistory();
mWebView.destroyDrawingCache();
mWebView.setWebViewClient(null);
mWebView.setWebChromeClient(null);
mWebView.removeAllViews();
mWebView.clearCache(true);
mWebView.freeMemory();
mWebView.removeAllViewsInLayout();
mWebView.setVisibility(View.GONE);
mWebView.destroy();
mWebView = null;
webview는 instance 생성을 통해서 처리해라
...
RelativeLayout mWebviewlayout;
WebView mWebView;

protected void onCreate(Bundle savedInstanceState) {
mWebviewlayout = (RelativeLayout) findViewById(R.id.webviewHell);
mWebView = new WebView(getApplicationContext());
mWebView.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT,LayoutParams.MATCH_PARENT));
mWebviewlayout.addView(mWebView);

...
완전히 연관 리로스를 clear해라
@Override
protected void onDestroy() {
super.onDestroy();
mWebView.removeJavascriptInterface("bridge");

mWebView.loadUrl("about:blank");

jsInterface = null;
mJSCallback = null;

mWebviewlayout.destroyDrawingCache();
mWebviewlayout.removeAllViews();
mWebviewlayout.removeAllViewsInLayout();
mWebviewlayout.setVisibility(View.GONE);
mWebviewlayout = null;

mWebView.clearHistory();
mWebView.destroyDrawingCache();
mWebView.setWebViewClient(null);
mWebView.setWebChromeClient(null);
mWebView.removeAllViews();
mWebView.clearCache(true);
mWebView.freeMemory();
mWebView.removeAllViewsInLayout();
mWebView.setVisibility(View.GONE);
mWebView.destroy();
mWebView = null;

상위 destroy 코드는 다소 과하다 싶을 정도로 remove하고 있다.

여기서 주의 깊게 봐야할 것은

mWebView.freeMemory();

이것이다. freeMemory api는 deprecated되었다. 공식 문서에서는 이 기능을 대체 하는 기능으로

mWebView.loadUrl("about:blank");

사용을 권장한다.

최후에는 app data를 전부 삭제해라
public static void deleteCache(Context context) {
try {
File dir = context.getCacheDir();
deleteDir(dir);
} catch (Exception e) {
e.printStackTrace();
}
}

public static boolean deleteDir(File dir) {
if (dir != null && dir.isDirectory()) {
String[] children = dir.list();
for (int i = 0; i < children.length; i++) {
boolean success = deleteDir(new File(dir, children[i]));
if (!success) { return false; }
}
return dir.delete();
} else if(dir!= null && dir.isFile()) {
return dir.delete();
} else {
return false;
}
}

안드로이드는 apk별로 데이타를 임시 저장할 수 있는 공간이 있다.

일반적으로 adb shell을 진입했을때

/data/<<app package>>

이하를 가르키게 된다. 해당 폴더 이내에 있는 디렉토리를 모두 다 지우는 행위임으로, 주의 깊게 사용해야 한다.

ImageView 사용

Bitmap에 대한 이미지 릭은 매우 유명하다. 허니콤(3.2)을 기준으로 이전에는 Bitmap 정보를 Native Heap영역에 담고 이를 포인팅 처리하도록 되어있었다.

이미지포인터(java heap) -> char[] (native heap)

하지만 이는 포인터를 잊는 순간 Native영역에 해소 되지 않는 메모리 릭을 발생 시켰고 이점을 해결하기 위해 메모리 영역을 Heap 영역으로 옮겼다 . Profiler에서도 보면 비트맵 힙 영역을 별도록 볼수있게 하였다.

그렇다고 Bitmap에 대한 메모리 릭이 사라지진 않았다. 무조건 Image View는 recycle 처리를 해줘야 한다.

...
ImageView image = (ImageView) findViewById(R.id.loadingImage);
Bitmap myBitmap = BitmapFactory.decodeFile(imgFile.getAbsolutePath());
image.setImageBitmap(myBitmap);

...
@Override
protected void onDestroy() {
super.onDestroy();
image.recycle();

그러나 이와 같은 코드 작성을 위해서 Bitmap을 모두 class 변수로 끌어 내는건 좋지 않음으로 다음 코드를 사용해서 recycle처리 할 수 있다.

private static void recycleBitmap(ImageView imageView) {
if (imageView != null) {
Drawable d = imageView.getDrawable();
if (d != null && d instanceof BitmapDrawable) {
Bitmap b = ((BitmapDrawable)d).getBitmap();
if (b != null) {
b.recycle();
}
}
imageView.setImageResource(android.R.color.transparent);
imageView.setImageBitmap(null);
}
}

단, 주의해야할 코드가 있다.

        ImageView image = (ImageView) findViewById(R.id.loadingImage);
Bitmap myBitmap = BitmapFactory.decodeFile(imgFile.getAbsolutePath());
- image.setImageBitmap(myBitmap);
+ image.setBackgroundResource(myBitmap);

setBackgroundResource의 경우는 recycle 처리가 어렵다. 해당 메서드는 ImageView가 아닌 View자체의 메서드를 사용함으로써 Drawable 객체를 반환하지 않는다.

Android Memory ART

ART의 목적

안드로이드 상 시스템이나 어플리케이션에 의해 관리 되는 Android Runtime을 대표 하는 말로써, 오직 안드로이드 프로젝트를 위해서만 만들어졌다. ART나 달빅의 경우는 Dex bytecode 러닝에 적합하게 만들어 졌다.

Ahead-of-tiem (AOT) compilation

인스톨 및 작동 시간을 빠르게 하기 위해 있는 것으로 최초 apk를 install하게 되면 dex2oat라는 device 내부에 있는 tool을 이용해서 작동가능한 상태로 만들어 준다.

Improved garbage collection
  • GC를 한번에 처리하게 만든다
  • GC pause동안 병행 프로세스 처리를 한다
  • 작은 객체(만들어진지 얼마 안된 객체, short live 객체)들을 수집한다
  • GC 처리를 적절하게 병행 처리 함으로써 GC_FOR_ALLOC이 최대한 발생 하지 않게 한다
  • 백그라운드 메모리 사용이나, 메모리 파편화를 줄여 준다 (Compacting GC)
Development and debugging improvements

아래와 같은 오류 정보들을 ART가 제공하게 된다.

java.lang.NullPointerException: Attempt to write to field 'int
android.accessibilityservice.AccessibilityServiceInfo.flags' on a null object
reference
ART 로그 설명

Art(Android Runtime)는 명시적으로 요청된 Garbage Collection에 대해서만 결과를 출력함

출력 형태는 다음과 같음

I/art: <GC_Reason> <GC_Name> <Objects_freed>(<Size_freed>) AllocSpace Objects, <Large_objects_freed>(<Large_object_size_freed>) <Heap_stats> LOS objects, <Pause_time(s)>

실제 사용은 다음과 같이 출력됨

I art : Explicit concurrent mark sweep GC freed 64(14KB) AllocSpace objects, 2(72KB) LOS objects, 24% free, 4MB/5MB, paused 448us total 20.082ms:

조금더 자세하게 나누어 보면 다음과 같음

Type 대상 설명
GC_Reason Explicit - Concurrent : 앱스레드 중단 않는 GC (백그라운드 실행)
- Alloc : 힙이 이미 가득 차서 더 이상 요청받은 메모리 할당을 할 수없어서, GC를 우선 실행 시킴
-Explicit : 앱이 명시적인 GC를 요청함. gc() 호출
- NativeAlloc : Bitmap또는 Renderscript 할당과 같은 Native 메모리 할당을 위해서 실행 되는 GC (허니비를 기점으로 Bitmap은 힙으로 위치를 변경하였다)
- CollectorTransition : RAM 사양이 낮은 기기에서 앱의 프로세스 상태 일시 중지 및 인식 할 수없는 상태로 변경 할때 변환이 발생함(메모리 공간을 범프 포인터 공간으로 복사하는 작업임)(Eden 영역에서 -> Survivor 영역으로의 전환 과정으로 보임)
- HomogeneousSpaceCompact : 사용가능한 공간(메모리로 보임)의 압축 기법으로 백그라운드 상태에서 RAM사용량 감소와 힙 조각 모음을 위해 처리 하는 것으로 보임
- HeapTrim : 힙트림을 마주칠 때까지 수집이 차단
- DisableMovingGc : 힙압축이 이루어지는 동안 GetPrimitiveArrayCritical이 사용 됨으로써 힙의 수집기 이동을 제한 시킴
GC_Name concurrent mark sweep - Concurrent mark sweep (CMS) : 이미지 공간 이외의 모든 공간을 회수하고 수집하는 완전한 힙 수집기
- Concurrent partial mark sweep : 이미지 및 zygote (app_process, 요청을 리스닝하고 요청 받은 클래스를 포크한다.)공간 이외의 공간을 전부 수집하는 수집기
- Concurrent sticky mark sweep : 마지막 GC이후로 할당된 객체만 회수 하는 세대별 수집기. 수집이 빠르고 일시 중지도 잘 안일어 남으로 자주 사용됨
-Marksweep + semispace : 공간 압축이나 힙전환시 사용되는 복사 GC
Objects_freed freed 64 회수한 객체의 수(작은규모)
Size_freed (14KB) AllocSpace objects, 2 GC를 통해서 회수한 바이트의 수
Large_objects_freed 2 회수한 객체의 수(큰 규모)
Large_object_size_freed (72KB) 회수한 객체의 크기
Heap_stats 24% free, 4MB/5MB, 회수한 비율로 (라이브 객체수)/(총 힙 크기), 예를 들자면 4MB가 살아있는 개체이고 5MB가 전체 힙의 크기이다. 약 24% 1MB 정도가 비어 있는 상황이라고 보면 된다. 이 값으 전체 적으로 증가만 하고 있다면 메모리 릭이라고 생각해 봐야한다
Pause Times paused 448us total 20.082ms 공식 문서상에는 일시 중지 횟수로 나와있으나 단위를 보면 일시 중지 시간이 맞는거 같다. 객체 수집시 나타나는 일시 중지 시간 평균 및 sum이 이 pause times으로 생각된다.

Android Memory Kill 순서

시스템 관련 서비스 memory status
Cached 현재 작동 되고 있지 않고 Background에 캐쉬 되어 있는 서비스들 lmk(Low memory killer) threshold
Previous 현재 작동 중인 서비스 직전에 사용된 서비스 critical
Home Home 기능이라고 하는데 내용상 바탕화면의 Wall paper 정도로 보임 critical
Service 클라우드 서비스, 싱크 서비스 등 백그라운드 서비스 critical
Perceptible 조회기능, 오디오, 키보드 등 듣고 입력하는 기능 들로 보임 critical
Foreground 현재 떠있는 서비스 critical
Persistent 전화 본연의 기능 및 통신 프로톨(와이파이, 블루투스 등) Crash ()
System system_server Crash (reboot)
Native init, kswapd,netd, logd, adbd 등 native bin(exec) 프로세스 Crash (reboot)

맨 위에서 부터 순차적으로 프로세스가 kill되기 시작함

lmk : low memory killer

kswapd : kernel swap daemon

Android Memory Debug Tool

  • Android Studio Profiler
  • showmap (사용방법?): https://android.googlesource.com/platform/system/extras/+/android-6.0.1_r28/showmap/showmap.c
  • ahat : https://android.googlesource.com/platform/art/+/master/tools/ahat/README.txt
  • debug malloc : adb logcat -d | grep "malloc debug" https://android.googlesource.com/platform/bionic/+/master/libc/malloc_debug/README.md

Profiler 만 사용하면 다른 툴을 쓸 필요가 없는 것으로 보인다. 단, malloc 같은 경우는 native 영역에 대한 내용을 볼 수 있는 유일한 방법으로 현재까지는 보임으로 native leak이 발생하면 쓸수 밖에 없지 않나 한다.

Android RAM의 종류

RAM 사용량 Command
adb shell dumpsys meminfo <package_name|pid> [-d]

해당 명령어는 adb connect 가 되어있다는 전제이다

  • -d 옵션 의미 : Dalvick 및 ART 메모리 사용량에 대한 정보가 상세히 출력된다
  • 기본적을 KB단위로 나열된다.
개인(클림 및 더티) RAM

RAM은 특정 APP에서만 사용중인 메모리로써, 해당 메모리의 양은 특정 APP의 서비스가 프로세스 킬되었을 때 온전히 Free RAM으로 되돌려 받을 수 있는 양이다. 이중에 개인 더티 RAM은 Android 스왑을 사용하지 않기 때문에 가장 중요한 대상이 된다. 메모리 Leak을 해결 해야 하는 이유도 이부분에 있다. 개발자가 지정하는 모든 Dalvik 및 Native 힙 메모리는 더티 RAM으로 처리된다. Zygote를 사용하는 공유 RAM도 있게 되는데 이부분은 공유 더티 Ram으로 불린다.

PSS(Propotional Set Size)

PSS는 하나의 프로세스만을 바라보는 사이징 방식이 아니라, 공유되는 모든 페이지에 대한 RAM까지 포함한 계산 방식이다. 예를 들자면 프로세스 2개가 2메가를 공유하는 프로세스를 접근 하고 있다면, 1MB씩 PSS 사이즈를 분배 받게 된다. 그외 개인 더티 RAM 사이즈는 직접적으로 PSS에 추가 된다.

정리하자면 다음과 같다.

  • PSS Size = 프로세스1 개인 더티 RAM + 프로세스 2 개인 더티 RAM + 프로세스 3 공유 RAM 절반

PSS외에 2가지 종류가 더 있다

RSS : Resident Set Size = Private 더티 RAM + Shared RAM 전체

USS : Unique Set Size = RSS - shared RAM 전체 = Private Dirty RAM만 대상으로 함

아래는 com.example.app의 메모리 사용량을 나타낸다.

Applications Memory Usage (kB):
Uptime: 2452592 Realtime: 2452592

** MEMINFO in pid 4739 [com.example.app] **
Pss Private Private Swapped Heap Heap Heap
Total Dirty Clean Dirty Size Alloc Free
------ ------ ------ ------ ------ ------ ------
Native Heap 6691 6648 0 0 16384 8945 7438
Dalvik Heap 1664 1512 0 0 7045 5418 1627
Dalvik Other 304 304 0 0
Stack 392 392 0 0
Ashmem 128 128 0 0
Other dev 4 0 4 0
.so mmap 8218 260 1640 0
.apk mmap 305 0 24 0
.ttf mmap 93 0 0 0
.dex mmap 2124 0 1776 0
.oat mmap 2144 0 64 0
.art mmap 2214 1988 4 0
Other mmap 296 4 0 0
Unknown 2072 2072 0 0
TOTAL 26649 13308 3512 0 23429 14363 9065

Dalvik Details
.Heap 964 964 0 0
.GC 188 188 0 0
.Zygote 424 272 0 0
.NonMoving 276 276 0 0
.IndirectRef 116 116 0 0

Objects
Views: 9 ViewRootImpl: 0
AppContexts: 3 Activities: 1
Assets: 3 AssetManagers: 3
Local Binders: 13 Proxy Binders: 15
Parcel memory: 5 Parcel count: 20
Death Recipients: 0 OpenSSL Sockets: 0

SQL
MEMORY_USED: 0
PAGECACHE_OVERFLOW: 0 MALLOC_SIZE: 0
Dalvik Heap

자바 머신인 달빅의 heap 모델은 External과 Dalvik heap으로 나뉘게 되는데 이중에 Dalvik Heap은

  • Java객체 인스턴스 메모리 할당
  • Zygote 할당 영역 포함

을 하게 된다.

그러나 java의 메모리 범위 밖을 보게 된다면 Native Heap이 나타나게 되는데, 이부분은 OS 영역의 메모리를 사용하는 범위로 주로 NDK로 개발된 경우 C++, C로 개발된 로우레벨 라이브러리를 사용하는 경우가 된다.

  • Bitmap의 경우 가장 대표적인 Native Heap 저장 대상이다.

Java는 Bitmap data를 Native Heap 부분으로 Pointing하는 역할만 한다.

Dalvik Other

문서상으로는 정확히 명시 되어 있지는 않는데 참고 문항으로 추리 해보면 다음과 같이 생각하면 될것 같다

  • Just in time 컴파일 기록
  • GC 기록

등 Dalvik Overhead

추측이긴 한데 ART에서 생성되는 각종 정보가 이쪽 메모리 사이즈로 쌓이는 것으로 생각된다.

Heap Alloc

해당 앱에 할당된 메모리 크기 인다. 이게 PSS Total보다 큰 이유는 공유 자원에 대한 값까지 같이 포함 하고 있기 때문이라고 한다.

Private Dirty

해당 앱 프로세스 내부에서만 사용되는 메모리로 타 앱 및 프로세스와 공유 되지 않는다. 프로세스가 종료 되면 이 메모리는 100% 반환 되어야 한다.

Private Cean

개발자 앱의 자체 코드임, 개발자가 만든 code자체에 대한 글이 이곳에 적제 되는 것으로 보임

so,dex mmap과 관련이 있음

.so에는 private 더티 RAM이 크게 할당 되어있는데 이는 네이티브 코드를 최종 주소 로드 할때 수정했기 때문이다.

실행 중인 코드에 대한 정보를 담는 부분으로 보임

.so .dex mmap

.so 네이티브 및 .dex 달빅 또는 ART를 위한 코드용으로 사용하는 RAM

.oat mmap

여러 앱이 공통적으로 사용하는 앱의 메모리 공간으로 특정 앱에 영향을 받지 않음

.Unknow

도구가 식별하기 힘든 RAM 페이지로, 주로 네이티브 할당이 주를 이룬다.

Dalvik Detail

.Heap : 앱의 힙의 메모리 크기 (zygote 공간 과 private 공간 포함)

.LOS : ART 대형 객체 공간 메모리

.GC : GC 계정 오버헤드 크기로 수정할 수없음

.JITCache : 실행되는 시점으로 o이 될것임

.Zygote : 공통 메모리 크기

.NonMoving : ART 비이동 공간 사용 RAM, 앱에서 사용하는 필드와 메서드 수를 줄이면 이부분이 줄어듬

.IndirectRef : ART 간접 참조 테이블, 로컬 및 전역 JNI 참조 수를 줄이면 줄일 수 있음

Objects

Views : 모든 View 객체의 수이다. 만약에 layout에 버튼 3개가 있고 views가 9인 상태라면, 하나의 버튼을 삭제 하면 8이 된다.

ViewRootImpl : 현재 작동중인 View의 갯수. 보이지 않는 창이라던가 연관되어있는 view와의 갯수를 확인해서 메모리 누수를 확인 할 수있다.

화면이 안떠있다면 0, 한개 View가 떠있다면 1이런식이다.

Activities

Activity의 갯수로써 현재 떠있는 Activity의 갯수가 된다. 한개의 큰 화면만 떠있다면 Activity는 1개이다

AppContexts

만약에 Activity A가 죽고 Activity B가 살아나는 시점에, A의 context가 살아있다면(Leak) 이때 AppContexts는 지속적으로 증가하는 것으로 보임

ProxyBinders/Parcel count/Local Binders

lyaout 상 객체를 Bind 하면서 카운트가 올라 가는 것으로 보임

Object.assign

Javascript의 Object를 Merge 하는 방법 중에 가장 많이 사용되는 것은 Object.assign을 사용하는 것이다.

기본 사용법은 다음과 같다.

Object.assign(target, ...sources)

  • target : Merge 당할 대상 객체
  • source : Merge의 대상 객체에게 주입을 할 오브젝트
기본 사용법
var obj1 = {name : 'hi', val: 1};
var obj2 = {name : 'hello', val: 2};

var newObj = Object.assign(obj1, obj2);

console.log(obj1);
console.log(newObj);

obj1.name = 'modified';

console.log(obj1);

if (obj1 === newObj){
console.log('same object!!')
}

실행 결과

{ name: 'hello', val: 2 }
{ name: 'hello', val: 2 }
{ name: 'modified', val: 2 }
same object!!

여기서 주목해야할 점은 target의 object는 assign 이후 자기 자신의 original 영역을 지속적으로 참조 하고 있다는 것이다.

  • 즉, obj1과 newobj는 완전히 동일한 object라는 것이다.

target이었던 obj1의 값은

var newObj = Object.assign(obj1, obj2);

이후 obj2의 값으로 덮어 쒸워 지는 것을 볼 수 있다.

  • target의 object는 source object와 동일한 properties가 있다면 overwrite 되게 된다.

코드를 아래와 같이 살짝 변경해 보겠다.

var obj1 = {pre:'King', name : 'hi', val: 1};
var obj2 = {name : 'hello', val: 2, nextVal :3};

var newObj = Object.assign(obj1, obj2);

console.log(obj1);
console.log(newObj);

obj1.name = 'modified';

console.log(obj1);

if (obj1 === newObj){
console.log('same object!!')
}

변경 점은 source에 pre property가 생겼고, obj2에는 nextVal을 생성 했다. 해당 코드를 실행해 보면 아래와 같은 결과 값을 확인 할 수 있다.

{ pre: 'King', name: 'hello', val: 2, nextVal: 3 }
{ pre: 'King', name: 'hello', val: 2, nextVal: 3 }
{ pre: 'King', name: 'modified', val: 2, nextVal: 3 }
same object!!

상위 결과에서 볼 수 있듯이

  • 해당 메서드는 서로에게 갖지 않는 값에 대해서는 target으로 값이 주입된다.
Cloning

이러한 방법을 이용해서 객체에 대한 내부 properties Cloning이 가능하다.

var obj1 = {pre:'King', name : 'hi', val: 1};

var clonedObj = Object.assign({}, obj1);

console.log(obj1);
console.log(clonedObj);

obj1.name = 'modified';

console.log(obj1);

if (obj1 === clonedObj){
console.log('same object!!');
} else {
console.log('different object!!');
}

결과 값은 다음과 같다.

{ pre: 'King', name: 'hi', val: 1 }
{ pre: 'King', name: 'hi', val: 1 }
{ pre: 'King', name: 'modified', val: 1 }
different object!!

객체에 대한 정보를 동일하게 copy 하였으나,

  • 기존 object와는 다른 object를 생성한 것을 확인 가능하다.

ES6 Spread Operator

ES6 문법에서 사용되는 spread operator를 이용해서 merge가 가능하다.

기본 사용법
var obj1 = [1,2,3];
var obj2 = [4,5,6];

var length = obj1.push(...obj2);

console.log(length);
console.log(obj1);
obj1.push(7);
console.log(obj2);

if (obj1 === obj2){
console.log('same object!!');
} else {
console.log('different object!!');
}

결과값은 다음과 같다.

6
[ 1, 2, 3, 4, 5, 6 ]
[ 4, 5, 6 ]
different object!!

본 사용법은 사실 object간의 결합을 대상으로 하지 않는다. spread operator 자체가 array와 같은 Iterator성 객체를 대상으로한 문법이기 때문이다.

  • 결과적으로 Array의 결합으로 볼수 있으나 object의 merge용도로 사용되지는 않는다.

이런식의 코드도 많이 사용할 수 있다.

> var obj1 = [1,2,3];
> var obj2 = [...obj1, 4,5,6];
>
> console.log(obj2);
> [ 1, 2, 3, 4, 5, 6 ]

ECMAScript 2018 기준으로는 다음 코드도 사용이 가능하다.

> var obj1 = {test:1};
> var obj2 = {test:2,after:3};
>
> var merged = {...obj1, ...obj2};
> console.log(obj1);

그러나 아직 널리 사용 되지 않음으로 더 다루지 않는다.

Undersocre Extend

언더스코어 라이브러리를 사용해서 객체 머지가 가능하다.

기본 사용법
const _ = require('underscore');

var obj1 = {a:1,b:2};
var obj2 = {c:3,d:4};
var obj3 = {c:3,d:5,e:6};

_.extend(obj1, obj2, obj3);

console.log(obj1);

if (obj1 === obj2) {
console.log('same Object');
} else {
console.log('different Object');
}

결과는 다음과 같다.

{ a: 1, b: 2, c: 3, d: 5, e: 6 }
different Object

Object.assign과 같이 target만을 수정하고, 이후 들어오는 source 객체들은 변화가 없다.

lodash merge

로데쉬 라이브러리의 merge기능을 사용하는것도 가능하다.

기본 사용법
const _ = require('lodash');

var obj1 = {a:1,b:2};
var obj2 = {c:3,d:4};
var obj3 = {c:3,d:5,e:6};

var newObj = _.merge(obj1,obj2,obj3);

obj1.a = 3;

console.log(obj1);
console.log(newObj);

if (obj1 === newObj) {
console.log('same Object');
} else {
console.log('different Object');
}

결과값은 다음과 같다.

{ a: 3, b: 2, c: 3, d: 5, e: 6 }
{ a: 3, b: 2, c: 3, d: 5, e: 6 }
same Object

Datasource command line 등록

  1. https://wildfly.org/downloads/ 에서 download

  2. unzip 아무 디렉토리나

  3. cd ~

  4. vi .bash_profile

  5. export JBOSS_HOME=<<아무 디렉토리나>>

    만약에 java 가 없다면 JDK를 install하고 JAVA_HOME 을 이와 같이 설정해야 한다.

  6. source .bash_profile

  7. https://jdbc.postgresql.org/download.html 에서 최신 드라이버 다운

  8. <<아무 디렉토리나>>/bin 으로 이동

  9. jdbc driver 등록

stevenucBookPro:bin steven$ ./jboss-cli.sh 
You are disconnected at the moment. Type 'connect' to connect to the server or 'help' for the list of supported commands.
[disconnected /] connect

[standalone@localhost:9990 /] module add --name=org.postgres --resources=/<<다운로드받은 디렉토리 Path>>/postgresql-42.2.6.jar --dependencies=javax.api,javax.transaction.api

[standalone@localhost:9990 /] /subsystem=datasources/jdbc-driver=postgres:add(driver-name="postgres",driver-module-name="org.postgres",driver-class-name=org.postgresql.Driver)
{"outcome" => "success"}

[standalone@localhost:9990 /] data-source add --jndi-name=java:jboss/jndiName --name=poolName --connection-url=jdbc:postgresql://url/databaseName --driver-name=postgres --user-name=userId --password=userPassword

[standalone@localhost:9990 /] exit

상위와 같이 작성 완료 후 다음 파일들이 추가 되었는지 확인 한다.

<>/modules/org/postgres/main

> stevenucBookPro:main steven$ ls -al
> total 1656
> drwxr-xr-x 4 steven admin 128 7 19 17:03 .
> drwxr-xr-x 3 steven admin 96 7 19 16:47 ..
> -rw-r--r-- 1 steven admin 317 7 19 16:47 module.xml
> -rw-r--r-- 1 steven admin 842825 7 19 16:47 postgresql-42.2.6.
>

>

<>/standalone/configuration

> ....        
> <subsystem xmlns="urn:jboss:domain:bean-validation:1.0"/>
> <subsystem xmlns="urn:jboss:domain:core-management:1.0"/>
> <subsystem xmlns="urn:jboss:domain:datasources:5.0">
> <datasources>
> <datasource jndi-name="java:jboss/datasources/ExampleDS" pool-name="ExampleDS" enabled="true" use-java-context="true" statistics-enabled="${wildfly.datasources.statistics-enabled:${wildfly.statistics-enabled:false}}">
> <connection-url>jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE</connection-url>
> <driver>h2</driver>
> <security>
> <user-name>sa</user-name>
> <password>sa</password>
> </security>
> </datasource>
> <datasource jndi-name="java:jboss/<<jndiName>>" pool-name="<<poolName>>">
> <connection-url>jdbc:postgresql://url/databaseName</connection-url>
> ....
>

>

Datasource wildfly console

https://jdbc.postgresql.org/download.html?source=post_page---------------------------

여기에서 드라이버 링크 복사

[root@localhost deployments]# pwd
/root/wildfly/wildfly/standalone/deployments

root@localhost deployments]# wget https://jdbc.postgresql.org/download/postgresql-42.2.6.jar
--2019-07-25 18:04:54-- https://jdbc.postgresql.org/download/postgresql-42.2.6.jar
Resolving jdbc.postgresql.org (jdbc.postgresql.org)... 72.32.157.228, 2001:4800:3e1:1::228
Connecting to jdbc.postgresql.org (jdbc.postgresql.org)|72.32.157.228|:443... connected.
HTTP request sent, awaiting responseㅔㅈㅇ... 200 OK
Length: 842825 (823K) [application/java-archive]
Saving to: ‘postgresql-42.2.6.jar’

100%[=================================================================================================>] 842,825 818KB/s in 1.0s

2019-07-25 18:04:56 (818 KB/s) - ‘postgresql-42.2.6.jar’ saved [842825/842825]

[root@localhost deployments]# ls
README.txt postgresql-42.2.6.jar
[root@localhost deployments]# systemctl restart wildfly.service

서비스 재 실행 이후

http://<>/console

접근 이후

Homepage > Configuration > Datasource&Driver > JDBC Driver

를 확인 해보면 해당 드라이버가 존재 하는 것을 확인 가능

이경우는 deployments에서 삭제할 경우 드라이버도 같이 삭제 됨으로 비추