Java sử dụng nhiều bộ nhớ hơn kích thước heap (hoặc kích thước chính xác là giới hạn bộ nhớ Docker)


118

Đối với ứng dụng của tôi, bộ nhớ được sử dụng bởi quy trình Java nhiều hơn kích thước heap.

Hệ thống nơi các vùng chứa đang chạy bắt đầu có vấn đề về bộ nhớ vì vùng chứa đang chiếm nhiều bộ nhớ hơn kích thước heap.

Kích thước heap được đặt thành 128 MB ( -Xmx128m -Xms128m) trong khi vùng chứa chiếm tới 1GB bộ nhớ. Trong điều kiện bình thường, nó cần 500MB. Nếu bộ chứa docker có giới hạn dưới đây (ví dụ mem_limit=mem_limit=400MB), quá trình sẽ bị giết bởi kẻ giết hết bộ nhớ của HĐH.

Bạn có thể giải thích tại sao quy trình Java sử dụng nhiều bộ nhớ hơn heap không? Làm thế nào để kích thước chính xác giới hạn bộ nhớ Docker? Có cách nào để giảm dung lượng bộ nhớ off-heap của quy trình Java không?


Tôi thu thập một số chi tiết về sự cố bằng cách sử dụng lệnh từ theo dõi bộ nhớ gốc trong JVM .

Từ hệ thống máy chủ, tôi nhận được bộ nhớ được sử dụng bởi vùng chứa.

$ docker stats --no-stream 9afcb62a26c8
CONTAINER ID        NAME                                                                                        CPU %               MEM USAGE / LIMIT   MEM %               NET I/O             BLOCK I/O           PIDS
9afcb62a26c8        xx-xxxxxxxxxxxxx-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.0acbb46bb6fe3ae1b1c99aff3a6073bb7b7ecf85   0.93%               461MiB / 9.744GiB   4.62%               286MB / 7.92MB      157MB / 2.66GB      57

Từ bên trong vùng chứa, tôi nhận được bộ nhớ được sử dụng bởi quá trình.

$ ps -p 71 -o pcpu,rss,size,vsize
%CPU   RSS  SIZE    VSZ
11.2 486040 580860 3814600

$ jcmd 71 VM.native_memory
71:

Native Memory Tracking:

Total: reserved=1631932KB, committed=367400KB
-                 Java Heap (reserved=131072KB, committed=131072KB)
                            (mmap: reserved=131072KB, committed=131072KB) 

-                     Class (reserved=1120142KB, committed=79830KB)
                            (classes #15267)
                            (  instance classes #14230, array classes #1037)
                            (malloc=1934KB #32977) 
                            (mmap: reserved=1118208KB, committed=77896KB) 
                            (  Metadata:   )
                            (    reserved=69632KB, committed=68272KB)
                            (    used=66725KB)
                            (    free=1547KB)
                            (    waste=0KB =0.00%)
                            (  Class space:)
                            (    reserved=1048576KB, committed=9624KB)
                            (    used=8939KB)
                            (    free=685KB)
                            (    waste=0KB =0.00%)

-                    Thread (reserved=24786KB, committed=5294KB)
                            (thread #56)
                            (stack: reserved=24500KB, committed=5008KB)
                            (malloc=198KB #293) 
                            (arena=88KB #110)

-                      Code (reserved=250635KB, committed=45907KB)
                            (malloc=2947KB #13459) 
                            (mmap: reserved=247688KB, committed=42960KB) 

-                        GC (reserved=48091KB, committed=48091KB)
                            (malloc=10439KB #18634) 
                            (mmap: reserved=37652KB, committed=37652KB) 

-                  Compiler (reserved=358KB, committed=358KB)
                            (malloc=249KB #1450) 
                            (arena=109KB #5)

-                  Internal (reserved=1165KB, committed=1165KB)
                            (malloc=1125KB #3363) 
                            (mmap: reserved=40KB, committed=40KB) 

-                     Other (reserved=16696KB, committed=16696KB)
                            (malloc=16696KB #35) 

-                    Symbol (reserved=15277KB, committed=15277KB)
                            (malloc=13543KB #180850) 
                            (arena=1734KB #1)

-    Native Memory Tracking (reserved=4436KB, committed=4436KB)
                            (malloc=378KB #5359) 
                            (tracking overhead=4058KB)

-        Shared class space (reserved=17144KB, committed=17144KB)
                            (mmap: reserved=17144KB, committed=17144KB) 

-               Arena Chunk (reserved=1850KB, committed=1850KB)
                            (malloc=1850KB) 

-                   Logging (reserved=4KB, committed=4KB)
                            (malloc=4KB #179) 

-                 Arguments (reserved=19KB, committed=19KB)
                            (malloc=19KB #512) 

-                    Module (reserved=258KB, committed=258KB)
                            (malloc=258KB #2356) 

$ cat /proc/71/smaps | grep Rss | cut -d: -f2 | tr -d " " | cut -f1 -dk | sort -n | awk '{ sum += $1 } END { print sum }'
491080

Ứng dụng này là một máy chủ web sử dụng Jetty / Jersey / CDI được đóng gói bên trong dung lượng 36 MB.

Phiên bản sau của OS và Java được sử dụng (bên trong vùng chứa). Hình ảnh Docker dựa trên openjdk:11-jre-slim.

$ java -version
openjdk version "11" 2018-09-25
OpenJDK Runtime Environment (build 11+28-Debian-1)
OpenJDK 64-Bit Server VM (build 11+28-Debian-1, mixed mode, sharing)
$ uname -a
Linux service1 4.9.125-linuxkit #1 SMP Fri Sep 7 08:20:28 UTC 2018 x86_64 GNU/Linux

https://gist.github.com/prasanthj/48e7063cac88eb396bc9961fb3149b58


6
Heap là nơi các đối tượng được cấp phát, tuy nhiên JVM có nhiều vùng bộ nhớ khác bao gồm thư viện chia sẻ, bộ đệm bộ nhớ trực tiếp, ngăn xếp luồng, thành phần GUI, không gian siêu. Bạn cần phải xem xét JVM có thể lớn đến mức nào và đặt giới hạn đủ cao để bạn muốn quá trình chết hơn là sử dụng bất kỳ thứ gì khác.
Peter Lawrey

2
Có vẻ như GC đang sử dụng rất nhiều bộ nhớ. Thay vào đó, bạn có thể thử sử dụng bộ thu CMS. Có vẻ như ~ 125 MB được sử dụng cho metaspace + mã, tuy nhiên nếu không thu nhỏ cơ sở mã của bạn, bạn khó có thể làm cho nó nhỏ hơn. Không gian đã cam kết gần đến giới hạn của bạn nên không có gì ngạc nhiên khi nó bị giết.
Peter Lawrey

bạn đặt cấu hình -Xms và -Xmx ở đâu?
Mick


1
Chương trình của bạn có thực thi nhiều thao tác với tệp (ví dụ: tạo tệp ở kích thước gigabyte) không? Nếu vậy, bạn nên biết rằng cgroupsthêm disk-cache vào bộ nhớ đã sử dụng - ngay cả khi nó được xử lý bởi kernel và nó ẩn đối với chương trình người dùng. (Xin lưu ý bạn, các lệnh psdocker statskhông tính bộ nhớ đệm trên đĩa.)
Lorinczy Zsigmond

Câu trả lời:


204

Bộ nhớ ảo được sử dụng bởi một quy trình Java vượt xa chỉ Java Heap. Bạn biết đấy, JVM bao gồm nhiều hệ thống con: Bộ thu gom rác, Trình tải lớp, trình biên dịch JIT, v.v. và tất cả các hệ thống con này đều yêu cầu một lượng RAM nhất định để hoạt động.

JVM không phải là người tiêu dùng duy nhất của RAM. Thư viện gốc (bao gồm cả Thư viện lớp Java tiêu chuẩn) cũng có thể cấp phát bộ nhớ riêng. Và điều này thậm chí sẽ không hiển thị với Theo dõi bộ nhớ gốc. Bản thân ứng dụng Java cũng có thể sử dụng bộ nhớ off-heap bằng các ByteBuffers trực tiếp.

Vì vậy, những gì chiếm bộ nhớ trong một quy trình Java?

Các phần JVM (chủ yếu được hiển thị bằng Theo dõi bộ nhớ gốc)

  1. Java Heap

    Phần rõ ràng nhất. Đây là nơi các đối tượng Java sống. Heap chiếm dung -Xmxlượng bộ nhớ.

  2. Người thu gom rác

    Các cấu trúc và thuật toán GC yêu cầu thêm bộ nhớ để quản lý heap. Các cấu trúc này là Mark Bitmap, Mark Stack (để duyệt qua biểu đồ đối tượng), Bộ nhớ (để ghi các tham chiếu giữa các vùng) và các cấu trúc khác. Một số trong số chúng có thể điều chỉnh trực tiếp, ví dụ -XX:MarkStackSizeMax, một số khác phụ thuộc vào cách bố trí heap, ví dụ: vùng G1 ( -XX:G1HeapRegionSize) lớn hơn , các vùng nhỏ hơn được nhớ.

    Chi phí bộ nhớ GC khác nhau giữa các thuật toán GC. -XX:+UseSerialGC-XX:+UseShenandoahGCcó chi phí nhỏ nhất. G1 hoặc CMS có thể dễ dàng sử dụng khoảng 10% tổng kích thước heap.

  3. Bộ nhớ đệm mã

    Chứa mã được tạo động: các phương thức do JIT biên dịch, trình thông dịch và sơ khai thời gian chạy. Kích thước của nó bị giới hạn bởi -XX:ReservedCodeCacheSize(240M theo mặc định). Tắt -XX:-TieredCompilationđể giảm lượng mã đã biên dịch và do đó sử dụng Bộ đệm mã.

  4. Trình biên dịch

    Bản thân trình biên dịch JIT cũng yêu cầu bộ nhớ để thực hiện công việc của nó. Điều này có thể được giảm một lần nữa bằng cách tắt theo lớp biên soạn hoặc bằng cách giảm số lượng bài biên dịch: -XX:CICompilerCount.

  5. Tải lớp

    Siêu dữ liệu lớp (mã bytecodes, ký hiệu, vùng hằng số, chú thích, v.v.) được lưu trữ trong vùng off-heap được gọi là Metaspace. Càng nhiều lớp được tải - càng nhiều không gian được sử dụng. Tổng mức sử dụng có thể bị giới hạn bởi -XX:MaxMetaspaceSize(không giới hạn theo mặc định) và -XX:CompressedClassSpaceSize(1G theo mặc định).

  6. Bảng ký hiệu

    Hai bảng băm chính của JVM: bảng Ký hiệu chứa tên, chữ ký, số nhận dạng, v.v. và bảng Chuỗi chứa các tham chiếu đến các chuỗi xen kẽ. Nếu Theo dõi bộ nhớ gốc cho biết mức sử dụng bộ nhớ đáng kể của bảng Chuỗi, điều đó có thể có nghĩa là ứng dụng gọi quá mức String.intern.

  7. Chủ đề

    Ngăn xếp luồng cũng chịu trách nhiệm lấy RAM. Kích thước ngăn xếp được kiểm soát bởi -Xss. Mặc định là 1M mỗi luồng, nhưng may mắn thay, mọi thứ không quá tệ. Hệ điều hành phân bổ các trang bộ nhớ một cách lười biếng, tức là trong lần sử dụng đầu tiên, vì vậy mức sử dụng bộ nhớ thực tế sẽ thấp hơn nhiều (thường là 80-200 KB cho mỗi ngăn xếp luồng). Tôi đã viết một tập lệnh để ước tính lượng RSS thuộc về ngăn xếp luồng Java.

    Có những phần JVM khác phân bổ bộ nhớ gốc, nhưng chúng thường không đóng vai trò lớn trong tổng mức tiêu thụ bộ nhớ.

Bộ đệm trực tiếp

Một ứng dụng có thể yêu cầu rõ ràng bộ nhớ off-heap bằng cách gọi ByteBuffer.allocateDirect. Giới hạn off-heap mặc định là bằng -Xmx, nhưng nó có thể bị ghi đè bằng -XX:MaxDirectMemorySize. Bộ đệm Byte trực tiếp được bao gồm trong Otherphần của đầu ra NMT (hoặc Internaltrước JDK 11).

Lượng bộ nhớ trực tiếp đã sử dụng có thể nhìn thấy thông qua JMX, ví dụ như trong JConsole hoặc Java Mission Control:

BufferPool MBean

Bên cạnh MappedByteBufferscác bộ đệm Byte trực tiếp, có thể có - các tệp được ánh xạ tới bộ nhớ ảo của một tiến trình. NMT không theo dõi chúng, tuy nhiên, MappedByteBuffers cũng có thể lấy bộ nhớ vật lý. Và không có cách nào đơn giản để hạn chế lượng chúng có thể lấy. Bạn chỉ có thể xem mức sử dụng thực tế bằng cách xem bản đồ bộ nhớ quy trình:pmap -x <pid>

Address           Kbytes    RSS    Dirty Mode  Mapping
...
00007f2b3e557000   39592   32956       0 r--s- some-file-17405-Index.db
00007f2b40c01000   39600   33092       0 r--s- some-file-17404-Index.db
                           ^^^^^               ^^^^^^^^^^^^^^^^^^^^^^^^

Thư viện gốc

Mã JNI được tải bởi System.loadLibrarycó thể cấp phát nhiều bộ nhớ off-heap tùy thích mà không cần kiểm soát từ phía JVM. Điều này cũng liên quan đến Thư viện lớp Java tiêu chuẩn. Đặc biệt, các tài nguyên Java chưa được tiết lộ có thể trở thành nguồn rò rỉ bộ nhớ gốc. Ví dụ điển hình là ZipInputStreamhoặc DirectoryStream.

Đặc biệt, jdwptác nhân JVMTI, tác nhân gỡ lỗi - cũng có thể gây tiêu thụ bộ nhớ quá mức.

Câu trả lời này mô tả làm thế nào để hồ sơ cấp phát bộ nhớ quê hương với async-profiler .

Sự cố về phân bổ

Một quá trình thường yêu cầu bộ nhớ gốc trực tiếp từ OS (bằng mmaplệnh gọi hệ thống) hoặc bằng cách sử dụng malloc- bộ cấp phát libc tiêu chuẩn. Đổi lại, mallocyêu cầu các phần lớn bộ nhớ từ hệ điều hành sử dụng mmap, và sau đó quản lý các phần này theo thuật toán phân bổ của riêng nó. Vấn đề là - thuật toán này có thể dẫn đến phân mảnh và sử dụng bộ nhớ ảo quá mức .

jemalloc, một trình phân bổ thay thế, thường xuất hiện thông minh hơn libc thông thường malloc, do đó, việc chuyển sang jemalloccó thể dẫn đến một dấu chân nhỏ hơn miễn phí.

Phần kết luận

Không có cách nào đảm bảo để ước tính mức sử dụng bộ nhớ đầy đủ của một tiến trình Java, vì có quá nhiều yếu tố cần xem xét.

Total memory = Heap + Code Cache + Metaspace + Symbol tables +
               Other JVM structures + Thread stacks +
               Direct buffers + Mapped files +
               Native Libraries + Malloc overhead + ...

Có thể thu nhỏ hoặc giới hạn các vùng bộ nhớ nhất định (như Code Cache) bằng cờ JVM, nhưng nhiều vùng khác nằm ngoài tầm kiểm soát của JVM.

Một cách tiếp cận khả thi để đặt giới hạn Docker là xem việc sử dụng bộ nhớ thực tế ở trạng thái "bình thường" của quá trình. Có các công cụ và kỹ thuật để điều tra các vấn đề về tiêu thụ bộ nhớ Java: Theo dõi bộ nhớ riêng , pmap , jemalloc , async-profiler .

Cập nhật

Đây là bản ghi của bài thuyết trình Dấu chân Bộ nhớ của một Quy trình Java .

Trong video này, tôi thảo luận về những gì có thể tiêu tốn bộ nhớ trong một quy trình Java, cách giám sát và hạn chế kích thước của một số vùng bộ nhớ nhất định và cách lập hồ sơ rò rỉ bộ nhớ gốc trong một ứng dụng Java.


1
Các chuỗi không được thực tập trong đống kể từ jdk7? ( bug.java.com/bugdatabase/view_bug.do?bug_id=6962931 ) - có thể tôi nhầm.
j-keck

5
@ j-keck Các đối tượng chuỗi nằm trong heap, nhưng bảng băm (nhóm và các mục có tham chiếu và mã băm) nằm trong bộ nhớ off-heap. Tôi đã sửa lại câu để chính xác hơn. Cảm ơn vì đã chỉ ra.
apangin

để thêm vào điều này, ngay cả khi bạn sử dụng ByteBuffers không trực tiếp, JVM sẽ cấp phát bộ đệm trực tiếp tạm thời trong bộ nhớ gốc mà không áp đặt giới hạn bộ nhớ. Cf evanjones.ca/java-bytebuffer-leak.html
Cpt. Senkfuss

16

https://developers.redhat.com/blog/2017/04/04/openjdk-and-containers/ :

Tại sao khi tôi chỉ định -Xmx = 1g JVM của tôi sử dụng nhiều bộ nhớ hơn 1gb bộ nhớ?

Chỉ định -Xmx = 1g đang yêu cầu JVM phân bổ một heap 1gb. Nó không yêu cầu JVM giới hạn toàn bộ bộ nhớ sử dụng ở 1gb. Có các bảng thẻ, bộ nhớ đệm mã và tất cả các loại cấu trúc dữ liệu ngoài đống khác. Tham số bạn sử dụng để chỉ định tổng mức sử dụng bộ nhớ là -XX: MaxRAM. Lưu ý rằng với -XX: MaxRam = 500m heap của bạn sẽ xấp xỉ 250mb.

Java thấy kích thước bộ nhớ máy chủ lưu trữ và nó không biết về bất kỳ giới hạn bộ nhớ vùng chứa nào. Nó không tạo ra áp lực bộ nhớ, vì vậy GC cũng không cần giải phóng bộ nhớ đã sử dụng. Tôi hy vọng XX:MaxRAMsẽ giúp bạn giảm dấu chân bộ nhớ. Cuối cùng, bạn có thể tinh chỉnh cấu hình GC ( -XX:MinHeapFreeRatio, -XX:MaxHeapFreeRatio, ...)


Có nhiều loại thước đo bộ nhớ. Docker dường như đang báo cáo kích thước bộ nhớ RSS, có thể khác với bộ nhớ "cam kết" được báo cáo bởi jcmd(các phiên bản cũ hơn của Docker báo cáo bộ nhớ cache RSS + là mức sử dụng bộ nhớ). Thảo luận và liên kết tốt: Sự khác biệt giữa Kích thước bộ thường trú (RSS) và tổng bộ nhớ cam kết của Java (NMT) cho một JVM chạy trong vùng chứa Docker

Bộ nhớ (RSS) cũng có thể bị ăn bởi một số tiện ích khác trong container - shell, process manager, ... Chúng tôi không biết những gì khác đang chạy trong container và làm cách nào để bạn bắt đầu các tiến trình trong container.


Nó thực sự tốt hơn với -XX:MaxRam. Tôi nghĩ rằng nó vẫn đang sử dụng nhiều hơn mức tối đa được xác định nhưng nó tốt hơn, cảm ơn!
Nicolas Henneaux

Có thể bạn thực sự cần thêm bộ nhớ cho phiên bản Java này. Có 15267 lớp học, 56 chủ đề.
Jan Garaj

1
Dưới đây là các chi tiết khác, đối số Java -Xmx128m -Xms128m -Xss228k -XX:MaxRAM=256m -XX:+UseSerialGC, sản xuất Docker 428.5MiB / 600MiBjcmd 58 VM.native_memory -> Native Memory Tracking: Total: reserved=1571296KB, committed=314316KB. JVM đang chiếm khoảng 300MB trong khi vùng chứa cần 430MB. 130MB giữa báo cáo JVM và báo cáo hệ điều hành ở đâu?
Nicolas Henneaux

1
Đã thêm thông tin / liên kết về bộ nhớ RSS.
Jan Garaj

RSS được cung cấp là từ bên trong vùng chứa cho quy trình Java chỉ ps -p 71 -o pcpu,rss,size,vsizevới quy trình Java có pid 71. Thực ra -XX:MaxRamkhông giúp được gì nhưng liên kết bạn đã cung cấp sẽ giúp với GC nối tiếp.
Nicolas Henneaux

8

TL; DR

Việc sử dụng chi tiết bộ nhớ được cung cấp bởi chi tiết Theo dõi bộ nhớ gốc (NMT) (chủ yếu là siêu dữ liệu mã và bộ thu gom rác). Ngoài ra, trình biên dịch và trình tối ưu hóa Java C1 / C2 sử dụng bộ nhớ không được báo cáo trong phần tóm tắt.

Có thể giảm dung lượng bộ nhớ bằng cách sử dụng cờ JVM (nhưng có những tác động).

Định kích thước vùng chứa Docker phải được thực hiện thông qua thử nghiệm với tải dự kiến ​​của ứng dụng.


Chi tiết cho từng thành phần

Các không gian lớp học chia sẻ có thể được vô hiệu hóa bên trong một container từ các lớp học sẽ không được chia sẻ bởi một quá trình JVM. Cờ sau có thể được sử dụng. Nó sẽ xóa không gian lớp được chia sẻ (17MB).

-Xshare:off

Các nhà sưu tập rác nối tiếp có bộ nhớ tối thiểu phải trả giá bằng thời gian tạm dừng lâu hơn trong rác thải chế biến thu thập (xem Aleksey Shipilëv so sánh giữa GC trong một bức tranh ). Nó có thể được kích hoạt với cờ sau. Nó có thể tiết kiệm tối đa không gian GC được sử dụng (48MB).

-XX:+UseSerialGC

Các trình biên dịch C2 có thể bị vô hiệu với cờ sau để giảm dữ liệu hồ sơ sử dụng để quyết định xem có nên tối ưu hóa hoặc không phải là một phương pháp.

-XX:+TieredCompilation -XX:TieredStopAtLevel=1

Không gian mã giảm 20MB. Hơn nữa, bộ nhớ bên ngoài JVM giảm 80MB (sự khác biệt giữa không gian NMT và không gian RSS). Trình biên dịch tối ưu hóa C2 cần 100MB.

Các C1 và C2 các trình biên dịch có thể bị vô hiệu với cờ sau.

-Xint

Bộ nhớ bên ngoài JVM hiện thấp hơn tổng dung lượng đã cam kết. Không gian mã giảm 43MB. Hãy cẩn thận, điều này có ảnh hưởng lớn đến hiệu suất của ứng dụng. Việc tắt trình biên dịch C1 và C2 sẽ giảm bộ nhớ được sử dụng đi 170 MB.

Sử dụng trình biên dịch Graal VM (thay thế cho C2) dẫn đến dung lượng bộ nhớ nhỏ hơn một chút. Nó tăng 20MB không gian bộ nhớ mã và giảm 60MB từ bộ nhớ JVM bên ngoài.

Bài viết Quản lý bộ nhớ Java cho JVM cung cấp một số thông tin liên quan về các không gian bộ nhớ khác nhau. Oracle cung cấp một số chi tiết trong tài liệu Theo dõi bộ nhớ gốc . Thông tin chi tiết hơn về mức biên dịch trong chính sách biên dịch nâng cao và khi vô hiệu hóa C2 sẽ giảm kích thước bộ nhớ đệm mã xuống một hệ số 5 . Một số thông tin chi tiết về Tại sao JVM báo cáo bộ nhớ được cam kết nhiều hơn kích thước bộ thường trú của quy trình Linux? khi cả hai trình biên dịch đều bị tắt.


-1

Java cần rất nhiều bộ nhớ. Bản thân JVM cần rất nhiều bộ nhớ để chạy. Heap là bộ nhớ có sẵn bên trong máy ảo, có sẵn cho ứng dụng của bạn. Bởi vì JVM là một gói lớn được đóng gói với tất cả các tính năng tốt có thể nên cần rất nhiều bộ nhớ chỉ để tải.

Bắt đầu với java 9, bạn có một thứ gọi là Project Jigsaw , điều này có thể làm giảm bộ nhớ được sử dụng khi bạn khởi động ứng dụng java (cùng với thời gian bắt đầu). Ghép hình dự án và một hệ thống mô-đun mới không nhất thiết phải được tạo ra để giảm bộ nhớ cần thiết, nhưng nếu điều đó quan trọng, bạn có thể thử.

Bạn có thể xem ví dụ này: https://steveperkins.com/using-java-9-modulification-to-ship-zero-dependency-native-apps/ . Bằng cách sử dụng hệ thống mô-đun, nó dẫn đến ứng dụng CLI là 21MB (với JRE được nhúng). JRE chiếm hơn 200mb. Điều đó sẽ chuyển sang bộ nhớ được cấp phát ít hơn khi ứng dụng được khởi động (rất nhiều lớp JRE không sử dụng sẽ không còn được tải).

Đây là một hướng dẫn hay khác: https://www.baeldung.com/project-jigsaw-java-modularity

Nếu bạn không muốn mất thời gian cho việc này, bạn có thể chỉ cần phân bổ thêm bộ nhớ. Đôi khi nó là tốt nhất.


Việc sử dụng jlinkkhá hạn chế vì nó yêu cầu ứng dụng phải được sửa đổi. Mô-đun tự động không được hỗ trợ nên không có cách nào dễ dàng để đến đó.
Nicolas Henneaux

-1

Làm thế nào để kích thước chính xác giới hạn bộ nhớ Docker? Kiểm tra ứng dụng bằng cách theo dõi nó trong một thời gian. Để hạn chế bộ nhớ của vùng chứa, hãy thử sử dụng tùy chọn -m, --memory byte cho lệnh chạy docker - hoặc một cái gì đó tương đương nếu bạn đang chạy nó theo cách khác như

docker run -d --name my-container --memory 500m <iamge-name>

không thể trả lời các câu hỏi khác.

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.