Làm thế nào để đối phó với java java.lang.OutOfMemoryError: Lỗi không gian heap Java Java?


416

Tôi đang viết một ứng dụng Swing phía máy khách (trình thiết kế phông chữ đồ họa) trên Java 5 . Gần đây, tôi java.lang.OutOfMemoryError: Java heap spacegặp lỗi vì tôi không thận trọng trong việc sử dụng bộ nhớ. Người dùng có thể mở số lượng tệp không giới hạn và chương trình sẽ giữ các đối tượng đã mở trong bộ nhớ. Sau khi nghiên cứu nhanh, tôi đã tìm thấy Công thái học trong Máy ảo Java 5.0 và những người khác nói rằng trên máy Windows, JVM mặc định kích thước heap tối đa là 64MB.

Với tình huống này, tôi nên giải quyết hạn chế này như thế nào?

Tôi có thể tăng kích thước heap tối đa bằng cách sử dụng tùy chọn dòng lệnh cho java, nhưng điều đó sẽ yêu cầu tìm ra RAM có sẵn và viết một số chương trình hoặc tập lệnh khởi chạy. Bên cạnh đó, tăng đến một số max hữu hạn cuối cùng không thoát khỏi vấn đề.

Tôi có thể viết lại một số mã của mình để duy trì các đối tượng vào hệ thống tệp thường xuyên (sử dụng cơ sở dữ liệu là điều tương tự) để giải phóng bộ nhớ. Nó có thể hoạt động, nhưng có lẽ nó cũng rất nhiều công việc.

Nếu bạn có thể chỉ cho tôi chi tiết về các ý tưởng trên hoặc một số lựa chọn thay thế như bộ nhớ ảo tự động, mở rộng kích thước heap một cách linh hoạt , điều đó sẽ rất tuyệt.


Kích thước heap tối đa mặc định là 64 MB là từ trước J2SE 5.0. Để biết thông tin về J2SE 8.0, hãy xem "Công cụ thu gom rác" tại docs.oracle.com/javase/8/docs/technotes/guides/vm/ .
Andy Thomas

Nếu bạn hạ cánh ở đây vì mọi câu hỏi OOM bị lừa với câu hỏi này, hãy đảm bảo bạn cũng kiểm tra: stackoverflow.com/questions/299659/NH Nó cung cấp giải pháp để dọn sạch các tham chiếu bộ nhớ 'chỉ trong thời gian' trước OOM. SoftReferences có thể là công cụ giải quyết vấn đề thực tế của bạn.
Steve Steiner

Câu trả lời:


244

Cuối cùng, bạn luôn có một đống tối đa hữu hạn để sử dụng cho dù bạn đang chạy trên nền tảng nào. Trong Windows 32 bit, đây là khoảng 2GB(không phải là heap cụ thể mà là tổng dung lượng bộ nhớ cho mỗi tiến trình). Điều xảy ra là Java chọn làm cho mặc định nhỏ hơn (có lẽ để lập trình viên không thể tạo các chương trình có cấp phát bộ nhớ mà không gặp phải vấn đề này và phải kiểm tra chính xác những gì họ đang làm).

Vì vậy, điều này được đưa ra có một số cách tiếp cận bạn có thể thực hiện để xác định dung lượng bộ nhớ bạn cần hoặc để giảm dung lượng bộ nhớ bạn đang sử dụng. Một lỗi phổ biến với các ngôn ngữ được thu thập rác như Java hoặc C # là để các tham chiếu đến các đối tượng mà bạn không còn sử dụng hoặc phân bổ nhiều đối tượng khi bạn có thể sử dụng lại chúng. Miễn là các đối tượng có tham chiếu đến chúng, chúng sẽ tiếp tục sử dụng không gian heap vì trình thu gom rác sẽ không xóa chúng.

Trong trường hợp này, bạn có thể sử dụng trình lược tả bộ nhớ Java để xác định phương thức nào trong chương trình của bạn đang phân bổ số lượng lớn các đối tượng và sau đó xác định xem có cách nào để đảm bảo chúng không còn được tham chiếu hay không phân bổ chúng ở vị trí đầu tiên. Một tùy chọn mà tôi đã sử dụng trong quá khứ là "JMP" http://www.khelekore.org/jmp/ .

Nếu bạn xác định rằng bạn đang phân bổ các đối tượng này vì một lý do và bạn cần giữ xung quanh các tài liệu tham khảo (tùy thuộc vào những gì bạn đang làm có thể là trường hợp này), bạn sẽ chỉ cần tăng kích thước heap tối đa khi bạn khởi động chương trình. Tuy nhiên, một khi bạn thực hiện hồ sơ bộ nhớ và hiểu cách các đối tượng của bạn được phân bổ, bạn nên có ý tưởng tốt hơn về việc bạn cần bao nhiêu bộ nhớ.

Nói chung, nếu bạn không thể đảm bảo rằng chương trình của bạn sẽ chạy trong một số lượng bộ nhớ hữu hạn (có lẽ tùy thuộc vào kích thước đầu vào), bạn sẽ luôn gặp phải vấn đề này. Chỉ sau khi cạn kiệt tất cả những điều này, bạn mới cần xem xét các đối tượng lưu vào bộ nhớ cache ra đĩa, v.v. Tại thời điểm này, bạn nên có một lý do rất chính đáng để nói "Tôi cần Xgb bộ nhớ" cho một cái gì đó và bạn không thể làm việc xung quanh nó bằng cách cải thiện thuật toán hoặc mô hình phân bổ bộ nhớ của bạn. Nói chung, đây thường chỉ là trường hợp cho các thuật toán hoạt động trên các bộ dữ liệu lớn (như cơ sở dữ liệu hoặc một số chương trình phân tích khoa học) và sau đó các kỹ thuật như bộ nhớ đệm và bộ nhớ IO được ánh xạ trở nên hữu ích.


6
OpenJDK và OracleJDK đã đóng gói hồ sơ - jvisualvm. Nếu bạn muốn nhiều tiện ích hơn, tôi đề nghị Yourkit thương mại.
Petr Gladkikh

121

Chạy Java với tùy chọn dòng lệnh -Xmx, thiết lập kích thước tối đa của vùng heap.

Xem ở đây để biết chi tiết .


3
Làm thế nào để thiết lập tham số này mãi mãi? Vì tôi đang sử dụng lệnh 'gradlew lắp ráp'.
Dr.jacky

2
Chạy-> Chạy Cấu hình-> Nhấp vào đối số-> bên trong loại đối số VM -Xms1g -Xmx2g
Arayan Singh

2
Đó là câu trả lời thực sự.
nccc

85

Bạn có thể chỉ định cho mỗi dự án bao nhiêu không gian heap mà dự án của bạn muốn

Sau đây là cho Helios Eclipse / Juno / Kepler :

Nhấp chuột phải vào

 Run As - Run Configuration - Arguments - Vm Arguments, 

sau đó thêm cái này

-Xmx2048m

1
hi bighostkim và cuongHuyTo, "Đối số" ở đâu .. tôi có thể thấy tối đa Cấu hình Chạy. Hãy tel cho tôi. Tôi cần tải xuống và lưu trữ gần 2000 liên hệ từ gmail. Nó bị sập do ngoại lệ bộ nhớ
AndroidRaji

@AndroiRaji: bạn nhấp chuột phải vào lớp Java có lớp chính có thể chạy được (đó là "public static void main (String [] args)"), sau đó chọn Run As - Run Cấu hình. Sau đó "Đối số" là tab ngay sau Chính (bạn thấy các tab Chính, Đối số, JRE, Đường dẫn, Nguồn, Môi trường, Chung).
CườngHuyTo

47

Tăng kích thước heap không phải là "sửa chữa" mà là "thạch cao", tạm thời 100%. Nó sẽ sụp đổ một lần nữa ở một nơi khác. Để tránh những vấn đề này, hãy viết mã hiệu suất cao.

  1. Sử dụng các biến cục bộ bất cứ nơi nào có thể.
  2. Đảm bảo bạn chọn đúng đối tượng (EX: Lựa chọn giữa String, StringBuffer và StringBuilder)
  3. Sử dụng hệ thống mã tốt cho chương trình của bạn (EX: Sử dụng biến tĩnh VS biến không tĩnh)
  4. Những thứ khác có thể làm việc trên mã của bạn.
  5. Cố gắng di chuyển với nhiều THREADING

Đúng đấy. Tôi đang cố gắng khắc phục một sự cố trong đó tôi nhận được OOM trên luồng AWT nhưng nếu tôi sử dụng luồng mới khác, tôi sẽ không gặp sự cố OOM. Tất cả những gì tôi có thể tìm thấy trực tuyến là tăng kích thước heap cho luồng AWT.
Ashish

@Ash: Có, khắc phục sự cố cốt lõi thay vì tìm kiếm thạch cao.
Nước chanh

Thu gom rác và cách tiếp cận quản lý bộ nhớ trong Java được cho là để giải quyết tất cả các biến chứng malloc-dealloc của những người tiền nhiệm của nó: -Cấu trúc được dọn sạch càng sớm càng tốt
Davos

31

Hãy cẩn thận ---- tại văn phòng của tôi, chúng tôi đã phát hiện ra rằng (trên một số máy windows) chúng tôi không thể phân bổ hơn 512m cho heap Java. Điều này hóa ra là do sản phẩm chống vi-rút của Kaspersky được cài đặt trên một số máy đó. Sau khi gỡ cài đặt sản phẩm AV đó, chúng tôi thấy rằng chúng tôi có thể phân bổ ít nhất 1.6gb, tức là -Xmx1600m(m là bắt buộc khác, nó sẽ dẫn đến một lỗi khác "Heap ban đầu quá nhỏ") hoạt động.

Không biết điều này có xảy ra với các sản phẩm AV khác không nhưng có lẽ điều này xảy ra vì chương trình AV đang dành một khối bộ nhớ nhỏ trong mỗi không gian địa chỉ, do đó ngăn chặn một phân bổ thực sự lớn.


22

Đối số VM làm việc cho tôi trong nhật thực. Nếu bạn đang sử dụng phiên bản nhật thực 3.4, hãy làm như sau

đi đến Run --> Run Configurations -->sau đó chọn dự án theo maven build -> sau đó chọn tab "JRE" -> sau đó nhập -Xmx1024m.

Hoặc bạn có thể làm Run --> Run Configurations --> select the "JRE" tab -->sau đó nhập -Xmx1024m

Điều này sẽ tăng đống bộ nhớ cho tất cả các bản dựng / dự án. Kích thước bộ nhớ trên là 1 GB. Bạn có thể tối ưu hóa theo cách bạn muốn.


18

Có, với -Xmxbạn có thể cấu hình thêm bộ nhớ cho JVM của bạn. Để chắc chắn rằng bạn không bị rò rỉ hoặc lãng phí bộ nhớ. Lấy một đống lớn và sử dụng Trình phân tích bộ nhớ Eclipse để phân tích mức tiêu thụ bộ nhớ của bạn.


JVMJ9VM007E Tùy chọn dòng lệnh không được nhận dạng: -Xmx Không thể tạo máy ảo Java. Downvote
Philip Rego

17

Tôi muốn thêm các đề xuất từ chụp ảnh rắc rối bài viết .

Ngoại lệ trong luồng thread_name: java.lang.OutOfMemoryError: Java heap space

Thông báo chi tiết không gian vùng heap Java biểu thị đối tượng không thể được phân bổ trong vùng heap Java. Lỗi này không nhất thiết có nghĩa là rò rỉ bộ nhớ

Nguyên nhân có thể:

  1. Vấn đề cấu hình đơn giản , trong đó kích thước heap được chỉ định là không đủ cho ứng dụng.

  2. Ứng dụng đang vô tình giữ các tham chiếu đến các đối tượng và điều này ngăn các đối tượng khỏi rác được thu thập.

  3. Sử dụng quá mức của quyết toán .

Một nguồn tiềm năng khác của lỗi này phát sinh với các ứng dụng sử dụng quá nhiều bộ hoàn thiện. Nếu một lớp có một phương thức hoàn thiện, thì các đối tượng thuộc loại đó không được lấy lại không gian của chúng tại thời điểm thu gom rác

Sau khi thu gom rác , các đối tượng được xếp hàng để hoàn thiện , xảy ra sau đó. quyết toán được thực hiện bởi một luồng daemon phục vụ hàng đợi quyết toán. Nếu luồng hoàn tất không thể theo kịp hàng đợi quyết toán, thì heap Java có thể lấp đầy và loại ngoại lệ OutOfMemoryError này sẽ bị ném.

Một kịch bản có thể gây ra tình huống này là khi một ứng dụng tạo ra các luồng có mức độ ưu tiên cao làm cho hàng đợi hoàn thiện tăng với tốc độ nhanh hơn tốc độ mà luồng xử lý đang phục vụ hàng đợi đó.


9

Thực hiện theo các bước dưới đây:

  1. Mở catalina.shtừ tomcat / bin.

  2. Thay đổi JAVA_OPTS thành

    JAVA_OPTS="-Djava.awt.headless=true -Dfile.encoding=UTF-8 -server -Xms1536m 
    -Xmx1536m -XX:NewSize=256m -XX:MaxNewSize=256m -XX:PermSize=256m 
    -XX:MaxPermSize=256m -XX:+DisableExplicitGC"
  3. Khởi động lại tomcat của bạn


8

Tôi đã đọc ở một nơi khác mà bạn có thể thử - bắt java.lang.OutOfMemoryError và trên khối bắt, bạn có thể giải phóng tất cả các tài nguyên mà bạn biết có thể sử dụng nhiều bộ nhớ, đóng các kết nối, v.v. System.gc() đó thử lại bất cứ điều gì bạn sẽ làm

Một cách khác là mặc dù, tôi không biết liệu nó có hoạt động hay không, nhưng tôi hiện đang kiểm tra xem nó có hoạt động trên ứng dụng của mình không.

Ý tưởng là thực hiện bộ sưu tập Rác bằng cách gọi System.gc () được biết là tăng bộ nhớ trống. Bạn có thể tiếp tục kiểm tra điều này sau khi mã bộ nhớ được thực thi.

//Mimimum acceptable free memory you think your app needs
long minRunningMemory = (1024*1024);

Runtime runtime = Runtime.getRuntime();

if(runtime.freeMemory()<minRunningMemory)
 System.gc();

6
Nói chung, tôi nghĩ rằng JVM sẽ thích Thu thập rác (GC) hơn là ném OutOfMemoryError. Việc gọi một cách rõ ràng System.gc () sau OutOfMemoryError có thể giúp ích cho một số VM / cấu hình, nhưng tôi không mong đợi nó hoạt động tốt trong trường hợp chung. Tuy nhiên, bỏ các tham chiếu đối tượng không cần thiết chắc chắn sẽ giúp ích trong hầu hết các trường hợp.
Mike Clark

6
@mwangi Calling System.gc () trực tiếp từ mã nói chung là một ý tưởng tồi. Đó chỉ là một gợi ý cho JVM rằng nên thực hiện GC, nhưng hoàn toàn không có gì đảm bảo rằng nó sẽ được thực hiện.

7

Cách dễ dàng để giải quyết OutOfMemoryErrortrong java là tăng kích thước heap tối đa bằng cách sử dụng các tùy chọn JVM-Xmx512M , điều này sẽ ngay lập tức giải quyết OutOfMemoryError của bạn. Đây là giải pháp ưa thích của tôi khi tôi nhận được OutOfMemoryError trong Eclipse, Maven hoặc ANT trong khi xây dựng dự án vì dựa trên kích thước của dự án, bạn có thể dễ dàng hết Bộ nhớ.

Dưới đây là một ví dụ về việc tăng kích thước heap tối đa của JVM, Ngoài ra, tốt hơn là giữ tỷ lệ -Xmx thành -Xms theo tỷ lệ 1: 1 hoặc 1: 1.5 nếu bạn đang đặt kích thước heap trong ứng dụng java của mình.

export JVM_ARGS="-Xms1024m -Xmx1024m"

liên kết tham khảo


1
Bất cứ ý tưởng tại sao chúng ta cần giữ chúng theo tỷ lệ 1: 1 hoặc 1: 1.5?
ernesto

7

Theo mặc định để phát triển, JVM sử dụng kích thước nhỏ và cấu hình nhỏ cho các tính năng liên quan đến hiệu năng khác. Nhưng để sản xuất, bạn có thể điều chỉnh, ví dụ (Ngoài ra, có thể tồn tại cấu hình cụ thể của Máy chủ ứng dụng) -> (Nếu vẫn không đủ bộ nhớ để đáp ứng yêu cầu và heap đã đạt đến kích thước tối đa, OutOfMemoryError sẽ xảy ra)

-Xms<size>        set initial Java heap size
-Xmx<size>        set maximum Java heap size
-Xss<size>        set java thread stack size

-XX:ParallelGCThreads=8
-XX:+CMSClassUnloadingEnabled
-XX:InitiatingHeapOccupancyPercent=70
-XX:+UnlockDiagnosticVMOptions
-XX:+UseConcMarkSweepGC
-Xms512m
-Xmx8192m
-XX:MaxPermSize=256m (in java 8 optional)

Ví dụ: Trên nền tảng linux cho các cài đặt ưu tiên chế độ sản xuất.

Sau khi tải xuống và định cấu hình máy chủ theo cách này http: //www.ehow ware.com/how-to-install-and-setup-apache-tomcat-8-on-centos-7-1-rhel-7/

1. tạo tệp setenv.sh trên thư mục / opt / tomcat / bin /

   touch /opt/tomcat/bin/setenv.sh

2.Mở và viết thông số này để cài đặt chế độ thích hợp hơn.

nano  /opt/tomcat/bin/setenv.sh 

export CATALINA_OPTS="$CATALINA_OPTS -XX:ParallelGCThreads=8"
export CATALINA_OPTS="$CATALINA_OPTS -XX:+CMSClassUnloadingEnabled"
export CATALINA_OPTS="$CATALINA_OPTS -XX:InitiatingHeapOccupancyPercent=70"
export CATALINA_OPTS="$CATALINA_OPTS -XX:+UnlockDiagnosticVMOptions"
export CATALINA_OPTS="$CATALINA_OPTS -XX:+UseConcMarkSweepGC"
export CATALINA_OPTS="$CATALINA_OPTS -Xms512m"
export CATALINA_OPTS="$CATALINA_OPTS -Xmx8192m"
export CATALINA_OPTS="$CATALINA_OPTS -XX:MaxMetaspaceSize=256M"

3.service tomcat restart

Lưu ý rằng JVM sử dụng nhiều bộ nhớ hơn chỉ là heap. Ví dụ, các phương thức Java, các ngăn xếp luồng và các thẻ điều khiển riêng được phân bổ trong bộ nhớ tách biệt với heap, cũng như các cấu trúc dữ liệu bên trong JVM.


7

Tôi đã phải đối mặt với vấn đề tương tự từ kích thước heap java.

Tôi có hai giải pháp nếu bạn đang sử dụng java 5 (1.5).

  1. chỉ cần cài đặt jdk1.6 và đi đến các tùy chọn của nhật thực và đặt đường dẫn jre của jav1 1.6 như bạn đã cài đặt.

  2. Kiểm tra đối số VM của bạn và để cho nó là bất cứ điều gì. chỉ cần thêm một dòng bên dưới của tất cả các đối số có trong các đối số VM là -Xms512m -Xmx512m -XX: MaxPermSize = ... m (192m).

Tôi nghĩ nó sẽ hoạt động ...


7

Nếu bạn cần theo dõi việc sử dụng bộ nhớ của mình trong thời gian chạy, java.lang.managementgói cung cấpMBeans có thể được sử dụng để giám sát các nhóm bộ nhớ trong VM của bạn (ví dụ: không gian eden, thế hệ được thuê, v.v.) và cả hành vi thu gom rác.

Không gian heap miễn phí được báo cáo bởi các MBeans này sẽ thay đổi rất nhiều tùy thuộc vào hành vi của GC, đặc biệt nếu ứng dụng của bạn tạo ra rất nhiều đối tượng mà sau này là GC-ed. Một cách tiếp cận khả thi là theo dõi không gian heap miễn phí sau mỗi GC đầy đủ, mà bạn có thể sử dụng để đưa ra quyết định giải phóng bộ nhớ bằng cách duy trì các đối tượng.

Cuối cùng, cách tốt nhất của bạn là hạn chế khả năng duy trì trí nhớ của bạn càng nhiều càng tốt trong khi hiệu suất vẫn ở mức chấp nhận được. Như một nhận xét trước đây đã lưu ý, bộ nhớ luôn bị giới hạn, nhưng ứng dụng của bạn nên có chiến lược xử lý tình trạng cạn kiệt bộ nhớ.


5

Lưu ý rằng nếu bạn cần điều này trong tình huống triển khai, hãy xem xét sử dụng Java WebStart (với phiên bản "ondisk", chứ không phải mạng - có thể có trong Java 6u10 trở lên) vì nó cho phép bạn chỉ định các đối số khác nhau cho JVM theo cách chéo cách nền tảng.

Nếu không, bạn sẽ cần một trình khởi chạy cụ thể của hệ điều hành để thiết lập các đối số bạn cần.


Java WebStart đang được loại bỏ. Tôi chưa nhận thức được một sự thay thế phù hợp.
Thorbjørn Ravn Andersen

1

Nếu sự cố này xảy ra trong Wildfly 8 và JDK1.8, thì chúng ta cần chỉ định cài đặt MaxMetaSpace thay vì cài đặt PermGen.

Ví dụ, chúng ta cần thêm cấu hình bên dưới trong tệp setenv.sh của wildfly. JAVA_OPTS="$JAVA_OPTS -XX:MaxMetaspaceSize=256M"

Để biết thêm thông tin, vui lòng kiểm tra vấn đề Wildfly Heap


1

Liên quan đến netbeans, bạn có thể đặt kích thước heap tối đa để giải quyết vấn đề.

Đi đến 'Chạy', sau đó -> 'Đặt cấu hình dự án' -> 'Tùy chỉnh' -> 'chạy' cửa sổ bật lên của nó -> 'Tùy chọn VM' -> điền vào '-Xms2048m -Xmx2048m' .


1

Nếu bạn tiếp tục phân bổ và giữ các tham chiếu đến đối tượng, bạn sẽ lấp đầy bất kỳ dung lượng bộ nhớ nào bạn có.

Một tùy chọn là đóng và mở tệp trong suốt khi chúng chuyển tab (bạn chỉ giữ một con trỏ vào tệp và khi người dùng chuyển tab, bạn đóng và xóa tất cả các đối tượng ... nó sẽ làm cho tệp thay đổi chậm hơn ... nhưng ...) và có thể chỉ giữ 3 hoặc 4 tệp trong bộ nhớ.

Một điều khác bạn nên làm là, khi người dùng mở tệp, tải tệp và chặn mọi OutOfMemoryError, sau đó (vì không thể mở tệp), hãy đóng tệp đó, làm sạch các đối tượng và cảnh báo người dùng rằng anh ta nên đóng không sử dụng các tập tin.

Ý tưởng của bạn về việc mở rộng bộ nhớ ảo không giải quyết được vấn đề, vì máy bị giới hạn về tài nguyên, vì vậy bạn nên cẩn thận & xử lý các vấn đề về bộ nhớ (hoặc ít nhất, hãy cẩn thận với chúng).

Một vài gợi ý tôi đã thấy với rò rỉ bộ nhớ là:

-> Hãy nhớ rằng nếu bạn đặt một cái gì đó vào một bộ sưu tập và sau đó quên nó đi, bạn vẫn có một tài liệu tham khảo mạnh mẽ về nó, vì vậy hãy vô hiệu hóa bộ sưu tập, làm sạch nó hoặc làm một cái gì đó với nó ... nếu không bạn sẽ tìm thấy rò rỉ bộ nhớ khó tìm.

-> Có thể, sử dụng các bộ sưu tập có tham chiếu yếu (yếu sơ đồ ...) có thể giúp giải quyết vấn đề bộ nhớ, nhưng bạn phải cẩn thận với nó, vì bạn có thể thấy rằng đối tượng bạn tìm kiếm đã được thu thập.

-> Một ý tưởng khác mà tôi đã tìm thấy là phát triển một bộ sưu tập liên tục được lưu trữ trên các đối tượng cơ sở dữ liệu được sử dụng ít nhất và được tải trong suốt. Đây có lẽ sẽ là cách tiếp cận tốt nhất ...


0

Nếu mọi thứ khác không thành công, ngoài việc tăng kích thước heap tối đa, hãy thử tăng kích thước trao đổi. Đối với Linux, tính đến thời điểm hiện tại, có thể tìm thấy các hướng dẫn liên quan trong https://linuxize.com/post/create-a-linux-swap-file/ .

Điều này có thể giúp nếu bạn ví dụ biên dịch một cái gì đó lớn trong một nền tảng nhúng.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.