1. Trang chủ
  2. » Công Nghệ Thông Tin

Cảm ơn bộ nhớ Hiểu cách JVM sử dụng bộ nhớ riêng trên Windows và Linux như thế nào pps

48 342 0
Tài liệu đã được kiểm tra trùng lặp

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

THÔNG TIN TÀI LIỆU

Thông tin cơ bản

Định dạng
Số trang 48
Dung lượng 7,27 MB

Các công cụ chuyển đổi và chỉnh sửa cho tài liệu này

Nội dung

Các ứng dụng Java chạy trong môi trường ảo hóa của thời gian chạy runtime Java, nhưng thời gian chạy bản thân nó là một chương trình riêng được viết bằng một ngôn ngữ ví dụ như C, có tiê

Trang 1

Cảm ơn bộ nhớ

Hiểu cách JVM sử dụng bộ nhớ riêng trên Windows và Linux như thế nào

Andrew Hall, Kỹ sư phần mềm, IBM

Tóm tắt: Việc sử dụng hết heap (ND: heap là vùng lưu trữ đặc biệt trong bộ nhớ

được dùng để lưu giữ các tài liệu quan trọng như tài nguyên hệ thống và các loại đối tượng khác nhau đang được sử dụng Các heap đều được giới hạn trong phạm

vi 64k Sau đây gọi là vùng heap) của Java™ không phải là nguyên nhân duy nhất

tạo ra một lỗi java.lang.OutOfMemoryError Nếu bộ nhớ riêng (native memory)

dùng hết, có thể xảy ra các lỗi OutOfMemoryError (lỗi thiếu bộ nhớ) mà các kỹ thuật gỡ lỗi thông thường của bạn sẽ không thể giải quyết được Bài viết này giải thích bộ nhớ riêng là gì, thời gian chạy của Java sử dụng nó như thế nào, việc dùng hết nó sẽ như thế nào và làm thế nào để gỡ lỗi cho một lỗi

OutOfMemoryError trên Windows® và Linux® Một bài viết khác của cùng tác giả trình bày về các chủ đề tương tự nhưng dành cho các hệ thống AIX®

Vùng heap của Java, nơi cấp phát không gian nhớ cho mọi đối tượng Java, là vùng

bộ nhớ gắn kết mật thiết nhất với bạn khi viết các ứng dụng Java Máy ảo Java (JVM) được thiết kế để cách ly chúng ta khỏi các điểm đặc thù của máy chủ, vì thế hoàn toàn tự nhiên, có thể coi vùng heap như là một bộ nhớ Chắc chắn là bạn đã từng gặp phải một lỗi OutOfMemoryError của vùng heap của Java — gây ra bởi một lỗ rò đối tượng hoặc do không tạo ra vùng heap đủ lớn để lưu trữ tất cả các dữ liệu của bạn — và có lẽ bạn đã học được một vài thủ thuật để gỡ lỗi các kịch bản này Nhưng khi các ứng dụng Java của bạn xử lý nhiều dữ liệu hơn và nạp công việc đồng thời nhiều hơn, bạn có thể bắt đầu nếm trải các lỗi OutOfMemoryError không thể sửa chữa được khi sử dụng cả túi các thủ thuật thông thường của bạn —

đó là các kịch bản trong đó các lỗi xuất hiện ngay cả khi vùng heap của Java chưa đầy Khi điều này xảy ra, bạn cần phải hiểu những gì đang xảy ra bên trong Môi trường thời gian chạy Java (Java Runtime Environment-JRE) của bạn

Các ứng dụng Java chạy trong môi trường ảo hóa của thời gian chạy (runtime) Java, nhưng thời gian chạy bản thân nó là một chương trình riêng được viết bằng

một ngôn ngữ (ví dụ như C), có tiêu dùng tài nguyên riêng, bao gồm cả bộ nhớ riêng Bộ nhớ riêng là bộ nhớ có sẵn dùng cho tiến trình thời gian chạy, để phân

biệt với bộ nhớ của vùng heap Java do một ứng dụng Java sử dụng Mỗi tài

nguyên ảo — bao gồm cả vùng heap Java và các luồng (threads) Java — phải được lưu trữ trong bộ nhớ riêng, cùng với các dữ liệu được các máy ảo sử dụng khi nó chạy Điều này có nghĩa rằng những hạn chế về bộ nhớ riêng, do phần cứng của máy chủ và hệ điều hành (OS) áp đặt sẽ ảnh hưởng đến những gì bạn có thể làm với ứng dụng Java của bạn

Trang 2

Bài viết này là một trong hai bài trình bày cùng một chủ đề trên các nền tảng hệ thống khác nhau Trong cả hai bài, bạn sẽ tìm hiểu bộ nhớ riêng là gì, thời gian chạy Java dùng nó như thế nào, việc sử dụng hết nó sẽ ra sao và làm thế nào để gỡ lỗi một OutOfMemoryError riêng Bài viết này trình bày Windows và Linux và không tập trung vào bất kỳ thời gian chạy cụ thể nào Bài viết của cùng một tác giả trình bày về AIX và tập trung vào IBM® Developer Kit for Java (Bộ dụng cụ của nhà phát triển của IBM cho Java) (Các thông tin trong bài viết đó về việc thực hiện của IBM cũng đúng cho các nền tảng khác, không phải AIX, vì thế nếu bạn

sử dụng IBM Developer Kit cho Java trên Linux hay IBM 32-bit Runtime

Environment (Môi trường thời gian chạy 32-bit của IBM cho Windows), bạn có thể nhận thấy bài viết đó cũng có ích)

Tóm tắt về bộ nhớ riêng

Tôi sẽ bắt đầu bằng cách giải thích những hạn chế về bộ nhớ riêng do hệ điều hành

và phần cứng nằm bên dưới áp đặt Nếu bạn quen với việc quản lý bộ nhớ động trong một ngôn ngữ như C, thì bạn có thể chuyển sang phần tiếp theo

Những hạn chế về phần cứng

Rất nhiều hạn chế mà một tiến trình riêng phải trải qua là do phần cứng chứ không phải do hệ điều hành áp đặt Mỗi máy tính đều có một bộ xử lý và một số bộ nhớ truy cập ngẫu nhiên (RAM), cũng được gọi là bộ nhớ vật lý Một bộ xử lý dịch dòng dữ liệu thành các lệnh để thực hiện; nó có một hoặc nhiều đơn vị xử lý để thực hiện các phép tính số học số nguyên và dấu phẩy động cũng như nhiều phép

tính nâng cao hơn Một bộ xử lý có một số thanh ghi — đó là các phần tử nhớ rất

nhanh được sử dụng làm nơi lưu trữ làm việc khi đang thực hiện các phép tính; kích thước thanh ghi xác định số lượng lớn nhất mà một phép tính đơn lẻ có thể sử dụng

Bộ xử lý được kết nối với bộ nhớ vật lý bằng bus bộ nhớ Độ lớn của địa chỉ vật lý (địa chỉ được bộ xử lý sử dụng để lập chỉ số RAM vật lý) giới hạn dung lượng bộ nhớ có thể được đánh địa chỉ Ví dụ, một địa chỉ vật lý 16-bit có thể đánh địa chỉ

từ 0x0000 đến 0xFFFF, tạo ra 2^16 = 65536 vị trí nhớ duy nhất Nếu mỗi địa chỉ trỏ đến một byte của thiết bị lưu trữ, thì một địa chỉ vật lý 16-bit cho phép một bộ

xử lý đánh địa chỉ 64KB của bộ nhớ

Các bộ xử lý được mô tả như là một số bit nhất định Số này thường nói đến kích thước của các thanh ghi, mặc dù có các trường hợp ngoại lệ — như 390 31-bit —

ở đây nó nói đến kích thước địa chỉ vật lý Đối với các nền tảng hệ thống máy tính

để bàn và máy chủ, số này là 31, 32 hoặc 64; với thiết bị nhúng và các bộ vi xử lý,

nó có thể thấp tới mức bằng 4 Kích thước địa chỉ vật lý có thể giống như độ rộng

Trang 3

thanh ghi nhưng có thể lớn hơn hoặc nhỏ hơn Hầu hết các bộ xử lý 64-bit có thể chạy các chương trình 32-bit khi chạy một hệ điều hành phù hợp

Bảng 1 liệt kê một số các kiến trúc Linux và Windows phổ biến với kích thước thanh ghi và kích thước địa chỉ vật lý của chúng:

Bảng 1 Kích thước thanh ghi và địa chỉ vật lý của một số kiến trúc bộ xử lý phổ biến

x86 64 64 Hiện tại là 48-bit (có cơ hội để tăng lên sau)

sử dụng một hệ điều hành nào đó để chạy các chương trình của họ

Trong các hệ điều hành (OS) đa nhiệm như Windows và Linux, có nhiều hơn một chương trình sử dụng tài nguyên hệ thống, bao gồm cả bộ nhớ Mỗi chương trình

Trang 4

cần phải được cấp phát các vùng nhớ vật lý để làm việc trong đó Có thể thiết kế một hệ điều hành sao cho mọi chương trình làm việc trực tiếp với bộ nhớ vật lý và được tin tưởng sẽ chỉ sử dụng bộ nhớ mà nó đã được cấp Một số hệ điều hành nhúng làm việc giống như thế, nhưng sẽ là không thích hợp trong một môi trường

có nhiều chương trình không được thử nghiệm cùng với nhau vì bất kỳ chương trình nào có thể làm hỏng bộ nhớ của các chương trình khác hoặc của chính hệ điều hành

Bộ nhớ ảo cho phép nhiều tiến trình xử lý dùng chung bộ nhớ vật lý mà không thể

làm hỏng dữ liệu của nhau Trong một hệ điều hành có bộ nhớ ảo (như Windows, Linux và nhiều hệ khác), mỗi chương trình có vùng địa chỉ ảo riêng của nó — một vùng logic của các địa chỉ mà kích thước của nó do kích thước địa chỉ trên hệ thống đó quyết định (như vậy là 31, 32 hoặc 64 bit cho các nền tảng máy tính để bàn và máy chủ) Các vùng trong vùng địa chỉ ảo của một tiến trình có thể được ánh xạ tới bộ nhớ vật lý, đến một tệp hoặc tới bất kỳ thiết bị lưu trữ có đánh địa chỉ khác Hệ điều hành có thể di chuyển dữ liệu được giữ trong bộ nhớ vật lý đến

và ra khỏi một vùng trao đổi (tệp trang (page file) trên Windows hay phân vùng trao đổi (swap partition) trên Linux) khi nó không được sử dụng, để sử dụng bộ

nhớ vật lý một cách tốt nhất Khi một chương trình cố gắng truy cập vào bộ nhớ bằng cách sử dụng một địa chỉ ảo, hệ điều hành kết hợp với phần cứng trên chip ánh xạ địa chỉ ảo đến vị trí vật lý Vị trí đó có thể là RAM vật lý, một tệp hoặc tệp trang/phân vùng trao đổi Nếu một vùng bộ nhớ đã được di chuyển tới vùng trao đổi, thì sau đó nó được nạp lại vào bộ nhớ vật lý trước khi được sử dụng Hình 1 cho thấy bộ nhớ ảo làm việc như thế nào bằng cách ánh xạ các vùng của vùng địa chỉ tiến trình xử lý đến các tài nguyên dùng chung:

Trang 5

Hình 1 Bộ nhớ ảo ánh xạ vùng địa chỉ tiến trình tới các tài nguyên vật lý

Mỗi cá thể của một chương trình chạy như một tiến trình Một tiến trình trên

Linux và Windows là một tập hợp thông tin về tài nguyên do hệ điều hành kiểm soát (như tệp và thông tin về trình cắm thêm), thường là một vùng địa chỉ ảo (nhiều hơn một vùng trên một số kiến trúc) và ít nhất một luồng thi hành

Kích thước của vùng địa chỉ ảo có thể nhỏ hơn kích thước địa chỉ vật lý của bộ xử

lý Intel x86 32-bit ban đầu có một địa chỉ vật lý 32-bit, cho phép bộ xử lý đánh địa chỉ 4GB của thiết bị lưu trữ Sau đó, một đặc tính gọi là Physical Address Extension (PAE-Phần mở rộng địa chỉ vật lý) đã được thêm vào để mở rộng kích thước địa chỉ vật lý lên 36-bit — cho phép cài đặt và đánh địa chỉ RAM lên đến 64GB PAE đã cho phép các hệ điều hành ánh xạ các vùng địa chỉ ảo 4GB 32-bit

Trang 6

lên trên một dải địa chỉ vật lý lớn, nhưng nó không cho phép mỗi tiến trình có một vùng địa chỉ ảo 64GB Điều này có nghĩa là nếu bạn đặt nhiều hơn 4GB bộ nhớ trong một máy chủ Intel 32-bit, bạn không thể ánh xạ tất cả nó trực tiếp vào trong một tiến trình đơn

Tính năng Các phần mở rộng cửa sổ địa chỉ (Address Windowing Extensions) cho phép một tiến trình Windows ánh xạ một phần vùng địa chỉ 32-bit của nó như là một cửa sổ trượt vào trong một vùng bộ nhớ lớn hơn Linux sử dụng các công nghệ tương tự dựa vào việc ánh xạ các vùng vào trong vùng địa chỉ ảo Điều này

có nghĩa rằng mặc dù bạn không thể trực tiếp tham chiếu nhiều hơn 4GB bộ nhớ, bạn có thể làm việc với các vùng bộ nhớ lớn hơn

Vùng nhân (kernel) và vùng người sử dụng

Mặc dù mỗi tiến trình có vùng địa chỉ riêng của mình, một chương trình thường

không thể sử dụng tất cả vùng ấy Các vùng địa chỉ được chia thành vùng người dùng (user space) và vùng nhân (kernel space) Nhân (kernel) là chương trình hệ

điều hành chính và chứa đựng logic để giao diện đến phần cứng máy tính, lập lịch trình các chương trình và cung cấp các dịch vụ như làm việc trên mạng và bộ nhớ

ảo

Là một phần của quá trình khởi động máy tính, nhân của hệ điều hành chạy và khởi động phần cứng Một khi nhân đã cấu hình phần cứng và trạng thái bên trong riêng của mình, tiến trình đầu tiên của vùng người dùng mới khởi động Nếu một chương trình của người dùng cần một dịch vụ từ hệ điều hành, nó có thể thực hiện

một hoạt động — có tên là cuộc gọi hệ thống — để nhảy vào trong chương trình

nhân (kernel), sau đó chương trình nhân thực hiện yêu cầu Các cuộc gọi hệ thống thường cần thiết cho các hoạt động như đọc và viết các tệp, làm việc trên mạng và bắt đầu các tiến trình mới

Nhân yêu cầu truy cập vào bộ nhớ riêng của nó và bộ nhớ của tiến trình gọi khi thi hành một cuộc gọi hệ thống Vì bộ xử lý, đang thi hành luồng hiện tại, được cấu hình để ánh xạ các địa chỉ ảo bằng cách sử dụng ánh xạ vùng địa chỉ cho tiến trình hiện tại, hầu hết các hệ điều hành ánh xạ một phần của mỗi vùng địa chỉ tiến trình tới một vùng bộ nhớ nhân chung Phần của vùng địa chỉ được ánh xạ để sử dụng bởi nhân được gọi là vùng nhân; phần còn lại, có thể được ứng dụng của người dùng sử dụng, được gọi là vùng người dùng

Sự cân bằng giữa vùng nhân và vùng người dùng khác nhau theo hệ điều hành và thậm chí khác nhau cả trong các cá thể của cùng một hệ điều hành chạy trên kiến trúc phần cứng khác nhau Sự cân bằng thường cấu hình được và có thể được điều chỉnh để cung cấp thêm vùng cho các ứng dụng của người dùng hay cho nhân Việc thu nhỏ vùng nhân có thể gây ra các vấn đề như hạn chế số lượng người sử

Trang 7

dụng có thể đăng nhập cùng lúc hoặc số các tiến trình có thể chạy; vùng người sử dụng nhỏ hơn có nghĩa là lập trình viên ứng dụng có phạm vi làm việc nhỏ hơn

Theo mặc định, Windows 32-bit có một vùng người dùng 2GB và một vùng nhân 2GB Sự cân bằng này có thể được thay đổi thành một vùng người sử dụng 3GB

và một vùng nhân 1GB trên một số phiên bản của Windows bằng cách thêm khóa chuyển đổi /3GB vào cấu hình khởi động và liên kết lại các ứng dụng bằng khóa chuyển đổi /LARGEADDRESSAWARE Trong Linux 32-bit, giá trị mặc định là vùng người sử dụng 3GB và vùng nhân 1GB Một số bản phân phối Linux cung

cấp một nhân hugemem hỗ trợ một vùng người sử dụng 4GB Để đạt được điều

này, nhân được cung cấp một vùng địa chỉ của riêng nó, được sử dụng khi một cuộc gọi hệ thống được bắt đầu Cái lợi về vùng người dùng phải trả giá bằng các cuộc gọi hệ thống chậm hơn vì hệ điều hành phải sao chép dữ liệu giữa các vùng địa chỉ và thiết lập lại các ánh xạ vùng-địa chỉ tiến trình mỗi khi một cuộc gọi hệ thống bắt đầu Hình 2 cho thấy bố trí vùng địa chỉ cho Windows 32-bit:

Hình 2 Bố trí vùng địa chỉ cho Windows 32-bit

Hình 3 cho thấy các bố trí vùng-địa chỉ cho Linux 32-bit:

Trang 8

Hình 3 Bố trí vùng-địa chỉ cho Linux 32-bit

Một vùng địa chỉ nhân riêng biệt cũng được sử dụng trên Linux 390 31-bit, trong

đó vùng địa chỉ nhỏ hơn 2GB làm cho việc phân chia một vùng địa chỉ duy nhất là không nên, tuy nhiên, kiến trúc 390 có thể làm việc với nhiều vùng địa chỉ đồng thời mà không làm hiệu năng giảm sút

Vùng địa chỉ tiến trình phải có mọi thứ mà một chương trình đòi hỏi — bao gồm chính chương trình đó và các thư viện dùng chung (các DLL trên Windows, các tệp so trên Linux) mà nó sử dụng Các thư viện dùng chung không chỉ có thể chiếm vùng mà một chương trình không thể sử dụng để lưu trữ dữ liệu vào nữa, chúng cũng còn có thể phân mảnh vùng địa chỉ và giảm số lượng bộ nhớ có thể được cấp phát như là một đoạn liên tục Điều này là dễ nhận thấy trong các

chương trình chạy trên Windows x86 với một vùng người sử dụng 3GB Các DLL được xây dựng với một địa chỉ nạp vào ưa thích: khi một DLL được nạp, nó được ánh xạ vào vùng địa chỉ tại một vị trí cụ thể trừ khi vị trí đó đã bị chiếm, trong trường hợp này nó được bố trí lại và được nạp vào nơi khác Với vùng người sử dụng 2GB có sẵn khi Windows NT được thiết kế ban đầu, việc xây dựng các thư viện hệ thống để nạp vào gần ranh giới 2GB là có ý nghĩa — làm như thế sẽ để lại hầu hết vùng người sử dụng tự do cho ứng dụng sử dụng Khi vùng người dùng được mở rộng đến 3GB, các thư viện dùng chung hệ thống vẫn nạp ở gần 2GB —

Trang 9

bây giờ nằm ở giữa vùng người dùng Mặc dù có một vùng người dùng tổng cộng 3GB, không thể cấp phát một khối 3GB của bộ nhớ vì các thư viện dùng chung đã

ở trong đó rồi

Sử dụng khóa chuyển đổi /3GB trên Windows làm giảm vùng nhân tới một nửa so với những gì nó đã được thiết kế ban đầu Trong một số kịch bản có thể dùng hết vùng nhân 1GB và nếm trải vào/ra (I/O) chậm hoặc các vấn đề khi tạo phiên người dùng mới Mặc dù khóa chuyển đổi /3GB có thể rất có giá trị cho một số ứng dụng, bất cứ môi trường nào khi sử dụng nó cần được kiểm tra tải kỹ lưỡng trước khi được triển khai Xem Tài nguyên với các đường liên kết đến nhiều thông tin hơn về khóa chuyển đổi /3GB và các lợi thế và các bất lợi của nó

Lỗi lỗ rò bộ nhớ riêng hoặc sử dụng bộ nhớ riêng quá mức sẽ gây ra những vấn đề khác nhau tùy thuộc vào việc bạn tận dụng hết vùng địa chỉ hay là chạy hết bộ nhớ vật lý Việc cạn kiệt vùng địa chỉ thường xảy ra chỉ với các tiến trình 32-bit — vì tối đa 4GB dễ dàng cấp phát Một tiến trình 64-bit có một vùng người sử dụng bằng hàng trăm hoặc hàng ngàn gigabyte, rất khó để lấp đầy ngay cả khi bạn cố gắng Nếu bạn dùng hết vùng địa chỉ của một tiến trình Java, thì sau đó thời gian chạy Java có thể bắt đầu hiển thị các triệu chứng kỳ lạ mà tôi sẽ mô tả sau trong bài viết này Khi chạy trên một hệ thống có nhiều vùng địa chỉ tiến trình hơn bộ nhớ vật lý, một lỗ rò bộ nhớ hoặc việc sử dụng quá mức bộ nhớ riêng buộc hệ điều hành trao đổi, đưa ra thiết bị lưu trữ hậu thuẫn một số vùng địa chỉ ảo của tiến trình riêng Truy cập vào một địa chỉ bộ nhớ đã được trao đổi đưa ra ngoài chậm hơn nhiều so với đọc địa chỉ đang thường trú (trong bộ nhớ vật lý) vì hệ điều hành phải lấy dữ liệu ra từ đĩa cứng Có thể cấp phát đủ bộ nhớ để tận dụng hết tất cả bộ nhớ vật lý và tất cả bộ nhớ trao đổi (vùng phân trang); trên Linux, điều này sẽ kích hoạt trình sát thủ hết bộ nhớ (OOM) nhân, trình sát thủ này buộc phải giết tiến trình thiếu bộ nhớ nhất Trên Windows, việc cấp phát bắt đầu thất bại theo cùng một cách như chúng đã xảy ra nếu vùng địa chỉ đã đầy

Nếu bạn đồng thời cố gắng sử dụng nhiều bộ nhớ ảo hơn bộ nhớ vật lý hiện có, dĩ nhiên vấn đề xuất hiện sớm hơn nhiều trước khi tiến trình này bị giết vì dùng quá

bộ nhớ Hệ thống sẽ luẩn quẩn — dành phần lớn thời gian của nó sao chép bộ nhớ quay đi quay lại từ vùng trao đổi Khi điều này xảy ra, hiệu năng của máy tính và các ứng dụng riêng lẻ sẽ trở nên tồi tệ đến mức người dùng không thể không nhận thấy đã có vấn đề Khi một vùng heap Java của JVM bị trao đổi ra, hiệu năng của các bộ thu gom dữ liệu rác trở nên cực kỳ kém, đến mức mà ứng dụng có thể bị treo Nếu nhiều thời gian chạy Java đồng thời đang chạy trên một máy tính tại cùng một thời điểm, thì bộ nhớ vật lý phải đủ để chứa hết tất cả các vùng heap của Java

Trang 10

Thời gian chạy của Java sử dụng bộ nhớ riêng như thế nào

Thời gian chạy của Java là một tiến trình hệ điều hành chịu các ràng buộc của phần cứng và hệ điều hành mà tôi nêu trong phần trước Các môi trường thời gian chạy cung cấp các khả năng theo đòi hỏi của mã của người sử dụng còn chưa biết; điều này làm cho không thể dự đoán môi trường thời gian chạy sẽ đòi hỏi tài nguyên nào trong mỗi tình huống Mỗi hành động mà một ứng dụng Java thực hiện bên trong môi trường Java được quản lý đều có khả năng có thể ảnh hưởng đến các yêu cầu tài nguyên của thời gian chạy cung cấp môi trường đó Phần này

mô tả các ứng dụng Java tiêu dùng bộ nhớ riêng như thế nào và tại sao

Vùng heap của Java và việc thu dọn dữ liệu rác

Vùng heap của Java là vùng bộ nhớ mà các đối tượng được cấp phát ở đó Hầu hết các triển khai thực hiện Java SE có một vùng heap lôgic, mặc dù một số thời gian chạy Java chuyên biệt, ví dụ như là triển khai thực hiện Đặc tả thời gian thực cho Java (Real Time Specification for Java -RTSJ) có nhiều vùng heap Một vùng heap vật lý có thể được chia một cách lô gic thành các phần tùy thuộc vào thuật toán thu dọn dữ liệu rác (GC) được sử dụng để quản lý bộ nhớ của vùng heap Những phần này thường được triển khai thực hiện như các ô nhớ liền khối của bộ nhớ riêng dưới sự kiểm soát của trình quản lý bộ nhớ Java (bao gồm các bộ thu dọn dữ liệu rác)

Kích thước của vùng heap được điều khiển từ dòng lệnh Java bằng cách sử dụng các tuỳ chọn -Xmx và -Xms (mx là kích thước tối đa của vùng heap, ms là kích thước ban đầu) Mặc dù vùng heap lô-gic (vùng bộ nhớ được sử dụng thực sự) có thể tăng lên và thu nhỏ theo số lượng các đối tượng trên vùng heap và thời gian dành cho GC, dung lượng bộ nhớ riêng được sử dụng vẫn không đổi và được quyết định bởi giá trị -Xmx: kích thước vùng heap tối đa Hầu hết các thuật toán

GC dựa trên vùng heap đang được cấp phát như một dãy ô nhớ liền khối của bộ nhớ, do đó không thể cấp phát thêm nhiều bộ nhớ riêng khi vùng heap cần mở rộng Tất cả bộ nhớ của vùng heap phải được dự trữ trước

Việc dự trữ bộ nhớ riêng không giống như việc cấp phát nó Khi bộ nhớ riêng được dự trữ, nó không được hậu thuẫn bởi bộ nhớ vật lý hoặc thiết bị lưu trữ khác Mặc dù việc dự trữ các đoạn của vùng địa chỉ sẽ không làm cạn kiệt tài nguyên vật

lý, nhưng nó ngăn cản không cho bộ nhớ đó được sử dụng cho các mục đích khác

Lỗ rò do việc dự trữ bộ nhớ gây ra vì không bao giờ được sử dụng cũng nghiêm trọng không kém lỗ rò bộ nhớ được cấp phát

Trang 11

Một số bộ thu gom dữ liệu rác giảm thiểu việc sử dụng bộ nhớ vật lý bằng cách không chuyển giao (giải phóng thiết bị lưu trữ phía sau cho) các phần của vùng heap khi mà vùng heap sử dụng bị thu nhỏ

Thêm bộ nhớ riêng là cần thiết để duy trì trạng thái của hệ thống quản lý bộ nhớ đang duy trì vùng heap của Java Các cấu trúc dữ liệu phải được phân phối để theo dõi thiết bị lưu trữ chưa sử dụng và ghi lại tiến trình khi thu dọn dữ liệu rác Kích thước chính xác và bản chất của các cấu trúc dữ liệu ấy thay đổi tùy từng triển khai thực hiện, nhưng phần nhiều là tỉ lệ thuận với kích thước của vùng heap Trình biên dịch tức thời (JIT)

Trình biên dịch tức thời JIT biên dịch bytecode của Java thành mã thực thi riêng được tối ưu hóa trong thời gian chạy Điều này cải thiện rất nhiều tốc độ thời gian-chạy của các thời gian chạy của Java và cho phép các ứng dụng Java chạy ở các tốc độ so sánh được với mã riêng

Việc biên dịch Bytecode sử dụng bộ nhớ riêng (giống như cách mà một trình biên dịch tĩnh như là gcc đòi hỏi bộ nhớ để chạy), nhưng cả đầu vào (bytecode) lẫn đầu

ra (mã thực thi) từ JIT cũng phải được lưu trữ trong bộ nhớ riêng Các ứng dụng Java có chứa nhiều phương thức được biên dịch tức thời (JIT) sử dụng nhiều bộ nhớ riêng hơn các ứng dụng nhỏ hơn

Các lớp và các trình nạp lớp (classloader)

Các ứng dụng Java gồm có các lớp định nghĩa cấu trúc đối tượng và logic phương thức Chúng cũng sử dụng các lớp từ các thư viện lớp thời gian chạy Java (như java.lang.String) và có thể sử dụng các thư viện của bên thứ ba Các lớp này cần phải được lưu trữ trong bộ nhớ khi mà chúng được sử dụng

Theo cách triển khai thực hiện các lớp được lưu trữ thay đổi như thế nào Sun JDK

sử dụng vùng heap được tạo ra cố định (PermGen) Việc thực hiện của IBM từ Java 5 trở đi cấp phát dãy ô nhớ của bộ nhớ riêng cho mỗi một trình nạp lớp

(classloader) và lưu trữ dữ liệu lớp trong đó Thời gian chạy Java hiện đại có các công nghệ như việc dùng chung lớp có thể yêu cầu ánh xạ các vùng bộ nhớ dùng chung vào trong vùng địa chỉ Để hiểu cách các cơ chế cấp phát này ảnh hưởng đến dấu vết riêng của thời gian chạy Java của bạn như thế nào, bạn cần phải đọc tài liệu kỹ thuật về việc triển khai thực hiện đó Tuy nhiên, một số sự kiện phổ biến ảnh hưởng đến tất cả các việc thực hiện

Ở mức độ cơ sở nhất, việc sử dụng càng nhiều lớp hơn thì càng sử dụng nhiều bộ nhớ hơn (Điều này có thể có nghĩa là việc sử dụng bộ nhớ riêng của bạn tăng lên hoặc bạn phải thay đổi kích thước một vùng một cách rõ ràng — chẳng hạn như

Trang 12

PermGen hoặc bộ nhớ sẵn (cache) của lớp-dùng chung — để cho phép chứa hết tất

cả các lớp) Hãy nhớ rằng không chỉ cần chứa hết ứng dụng của bạn; mà các

khung công tác, các máy chủ ứng dụng, các thư viện của bên thứ ba và thời gian chạy của Java đều chứa các lớp được nạp theo yêu cầu và chiếm vùng nhớ

Thời gian chạy của Java có thể giải phóng các lớp để lấy lại vùng nhớ, nhưng chỉ trong những điều kiện nghiêm ngặt Không thể chỉ giải phóng một lớp đơn lẻ; thay vào đó các trình nạp lớp được giải phóng và mang theo tất cả các lớp mà chúng đã nạp Một trình nạp lớp có thể được giải phóng chỉ khi:

 Vùng heap của Java không chứa tham chiếu tới đối tượng

java.lang.ClassLoader đại diện cho trình nạp lớp đó

 Vùng heap của Java không chứa tham chiếu tới bất cứ các đối tượng

java.lang.Class đại diện cho các lớp được nạp bởi trình nạp lớp đó

 Không có đối tượng nào của lớp bất kỳ được trình nạp lớp đó nạp vào đang còn hoạt động (được tham chiếu) trên vùng heap của Java

Cần lưu ý rằng trong ba trình nạp lớp mặc định do thời gian chạy Java tạo ra cho

tất cả các ứng dụng Java — bootstrap (tự mồi), extension (phần mở rộng) và application (ứng dụng)— có thể không bao giờ đáp ứng các tiêu chí này; do vậy,

các lớp hệ thống bất kỳ (như java.lang.String) hoặc các lớp ứng dụng bất kỳ được nạp qua trình nạp lớp của ứng dụng không thể được giải phóng trong thời gian chạy

Ngay cả khi một trình nạp lớp đủ điều kiện bị thu gom, thời gian chạy thu gom các trình nạp lớp chỉ như là một phần của một chu kỳ GC Một số triển khai thực hiện chỉ giải phóng các trình nạp lớp trong một số chu kỳ GC nào đó

Cũng có khả năng các lớp được tạo ra trong thời gian chạy, mà bạn không biết điều đó Nhiều ứng dụng JEE sử dụng công nghệ JavaServer Pages (JSP) để sản xuất các trang Web Việc sử dụng JSP sẽ tạo ra một lớp cho mỗi trang jsp được thi hành sẽ tồn tại suốt vòng đời của trình nạp lớp đã nạp chúng — vòng đời tiêu biểu của ứng dụng Web

Một cách phổ biến khác để tạo ra các lớp là sử dụng sự phản chiếu của Java Cách thức sự phản chiếu Java hoạt động thay đổi theo các việc triển khai thực hiện Java, nhưng cả hai việc triển khai thực hiện của Sun và IBM đều sử dụng phương thức

mà tôi sẽ mô tả bây giờ

Khi sử dụng API java.lang.reflect, thời gian chạy Java phải kết nối các phương thức của một đối tượng phản chiếu java.lang.reflect.Field) đến đối tượng hoặc lớp

Trang 13

được phản chiếu tới Điều này có thể được thực hiện bằng cách sử dụng trình truy cập (accessor) của Giao diện riêng của Java (Java Native Interface-JNI), JNI đòi hỏi phải thiết lập rất ít, nhưng lại rất chậm khi chạy, hoặc bằng cách xây dựng một lớp động trong thời gian chạy cho từng kiểu đối tượng mà bạn muốn phản chiếu tới Phương thức sau thiết lập chậm hơn, nhưng lại chạy nhanh hơn, và là lý tưởng cho các ứng dụng thường xuyên phải phản chiếu đến một lớp cụ thể

Thời gian chạy Java sử dụng phương thức JNI vài lần đầu tiên khi một lớp được phản chiếu, nhưng sau khi được sử dụng một số lần, trình truy cập lớn lên thành một trình truy cập (accessor) bytecode, bao gồm việc xây dựng một lớp và nạp nó nhờ một trình nạp lớp mới Việc thực hiện nhiều sự phản chiếu có thể làm cho phải sinh ra nhiều lớp của trình truy cập và trình nạp lớp Việc duy trì các tham chiếu đến các đối tượng phản chiếu làm cho các lớp này vẫn hoạt động và tiếp tục chiếm vùng nhớ Vì việc tạo ra các trình truy cập bytecode khá chậm, nên thời gian chạy Java có thể ghi nhớ sẵn (cache) các trình truy cập này để sử dụng lại sau Một số ứng dụng và các khung công tác cũng ghi nhớ sẵn các đối tượng phản chiếu, do đó làm tăng dấu vết riêng của chúng

 Mã riêng cho một ứng dụng JNI được biên dịch thành một thư viện dùng chung hoặc mã có thể chạy được rồi nạp vào vùng địa chỉ tiến trình Các ứng dụng riêng lớn có thể chiếm một đoạn đáng kể của vùng địa chỉ tiến trình đơn giản ngay khi được nạp

 Mã riêng phải dùng chung vùng địa chỉ với thời gian chạy Java Bất kỳ các việc cấp phát bộ nhớ riêng hay các việc ánh xạ bộ nhớ nào được thực hiện bởi mã riêng đều lấy bộ nhớ từ thời gian chạy Java

 Một số hàm JNI nhất định có thể sử dụng bộ nhớ riêng như là một phần

hoạt động bình thường của chúng Các hàm GetTypeArrayElements và GetTypeArrayRegion có thể sao chép dữ liệu của vùng heap của Java vào

các bộ đệm của bộ nhớ riêng để cho mã riêng làm việc với chúng Việc có tạo ra một bản sao chép hay không phụ thuộc vào việc triển khai thực hiện thời gian chạy (Bộ dụng cụ của nhà phát triển của IBM cho Java 5 – IBM Developer for Java 5.0 - và cao hơn, có tạo ra một bản sao riêng) Việc truy

Trang 14

cập một số lượng lớn dữ liệu của vùng heap của Java theo cách này có thể

sử dụng một số lượng lớn của vùng heap riêng tương ứng

NIO

Các lớp I/O mới (NIO) được bổ sung thêm vào Java 1.4 đã đưa vào một cách làm mới để thực hiện I/O dựa trên các kênh và các bộ đệm Giống như các bộ đệm I/O được hậu thuẫn bởi bộ nhớ trên vùng heap Java, NIO bổ sung thêm sự hỗ trợ cho

các ByteBuffer trực tiếp (được cấp phát bằng cách sử dụng phương thức

java.nio.ByteBuffer.allocateDirect() được hậu thuẫn bởi bộ nhớ riêng chứ không phải vùng heap Java Các ByteBuffer trực tiếp có thể được chuyển trực tiếp tới các hàm thư viện của hệ điều hành riêng để thực hiện I/O — làm cho chúng nhanh hơn đáng kể trong một số kịch bản vì chúng có thể tránh việc sao chép dữ liệu giữa vùng heap Java và vùng heap riêng

Dễ bị lúng túng về dữ liệu ByteBuffer trực tiếp đang được lưu giữ ở đâu Ứng dụng này vẫn còn sử dụng một đối tượng trên vùng heap Java để hòa phối các hoạt động I/O, nhưng bộ đệm chứa dữ liệu được tổ chức trong bộ nhớ riêng — đối tượng của vùng heap Java chỉ chứa một tham chiếu đến bộ đệm của vùng heap riêng Một ByteBuffer không trực tiếp sẽ chứa dữ liệu của nó trong một mảng byte[] trên vùng heap Java Hình 4 cho thấy sự khác biệt giữa các đối tượng

ByteBuffer trực tiếp và không trực tiếp:

Hình 4 Hình trạng (tô pô) của bộ nhớ với các java.nio.ByteBuffer trực tiếp

Trang 15

và không trực tiếp

Các đối tượng ByteBuffer trực tiếp tự động xóa bộ đệm riêng của chúng nhưng chỉ

có thể làm như vậy như là một phần của GC của vùng heap Java — vì vậy chúng không tự động đáp ứng với sức ép trên vùng heap riêng GC xảy ra chỉ khi vùng heap Java trở nên đầy đến nỗi nó không thể phục vụ một yêu cầu cấp phát- vùng heap hoặc nếu ứng dụng Java yêu cầu thực hiện một cách rõ ràng (việc này không được khuyến khích vì nó gây ra các vấn đề về hiệu năng)

Các trường hợp không hợp lý sẽ là vùng heap riêng trở nên đầy và một hoặc nhiều ByteBuffers (các bộ đệm byte) trực tiếp có đủ điều kiện để thu dọn dữ liệu rác (và

có thể được giải phóng để tạo ra chỗ trống dành cho vùng heap riêng), nhưng vùng heap Java chủ yếu trống rỗng nên việc thu dọn dữ liệu rác (GC) không xảy ra Các luồng

Mỗi luồng trong một ứng dụng đòi hỏi bộ nhớ để lưu trữ ngăn xếp của nó (vùng

bộ nhớ được sử dụng để chứa các biến tại chỗ và duy trì trạng thái khi gọi các hàm) Mỗi luồng Java yêu cầu vùng ngăn xếp để chạy Tùy thuộc vào việc triển

Trang 16

khai thực hiện, một luồng Java có thể có ngăn xếp riêng và ngăn xếp Java riêng biệt Ngoài vùng ngăn xếp, mỗi luồng yêu cầu một số bộ nhớ riêng để lưu trữ cục

bộ của luồng và các cấu trúc dữ liệu bên trong

Kích thước ngăn xếp thay đổi theo việc triển khai thực hiện Java và theo kiến trúc Một số việc triển khai thực hiện cho phép bạn quy định kích thước ngăn xếp cho các luồng Java Điển hình là các giá trị giữa 256KB và 756KB

Mặc dù số lượng bộ nhớ được sử dụng cho mỗi luồng là khá nhỏ, đối với một ứng dụng có hàng trăm luồng, tổng bộ nhớ sử dụng cho các ngăn xếp luồng có thể lớn Việc chạy một ứng dụng với nhiều luồng hơn số các bộ xử lý có sẵn để chạy

chúng thường không hiệu quả và có thể dẫn đến hiệu năng kém cũng như việc sử dụng bộ nhớ tăng lên

Tôi có thể nói như thế nào nếu tôi đang dùng hết bộ nhớ riêng?

Một thời gian chạy Java đối phó hoàn toàn khác nhau với việc dùng hết vùng heap Java so với việc dùng hết vùng heap riêng, mặc dù cả hai tình thế có thể làm xuất hiện các dấu hiệu tương tự nhau Một ứng dụng Java rất khó hoạt động khi vùng heap Java bị cạn kiệt — bởi vì rất khó cho một ứng dụng Java để thực hiện bất cứ việc gì mà không cấp phát các đối tượng Hiệu năng GC kém và các lỗi

OutOfMemoryError báo hiệu một vùng heap đầy được tạo ra ngay khi vùng heap tràn đầy

Trái lại, ngay khi một thời gian chạy Java đã khởi động và ứng dụng ở trong trạng thái ổn định, nó có thể tiếp tục hoạt động với vùng heap riêng đã cạn hết Không nhất thiết phải chỉ ra bất kỳ hành vi xấu nào, bởi vì các hành động đòi hỏi cấp phát

bộ nhớ riêng hiếm xảy ra hơn nhiều so với các hành động đòi hỏi cấp phát vùng heap Java Mặc dù các hành động yêu cầu bộ nhớ riêng thay đổi tùy theo việc triển khai thực hiện JVM, nhưng sau đây là một số ví dụ phổ biến: bắt đầu một luồng, nạp một lớp và thực hiện một số loại I/O mạng và tệp

Hành vi thiếu bộ nhớ riêng cũng ít nhất quán hơn so với các hành vi thiếu bộ nhớ của vùng heap Java, vì không có điểm kiểm soát duy nhất với các việc cấp phát vùng heap riêng Trong khi tất cả các việc cấp phát vùng heap Java nằm dưới sự kiểm soát của hệ thống quản lý bộ nhớ Java, thì bất kỳ mã riêng nào — cho dù nó

ở bên trong JVM, các thư viện lớp Java hoặc mã ứng dụng — có thể thực hiện một việc cấp phát bộ nhớ riêng và bị thất bại Sau đó, mã cố gắng thực hiện việc cấp phát ấy có thể xử lý nó như người thiết kế muốn: nó có thể đưa ra một lỗi

Trang 17

OutOfMemoryError thông qua giao diện JNI, in một thông báo trên màn hình, hay

âm thầm không chạy nữa và thử làm lại sau hoặc làm cái gì đó khác

Việc thiếu một hành vị dự tính trước có nghĩa là không có một cách đơn giản nào

để nhận biết sự cạn kiệt bộ nhớ riêng Thay vào đó, bạn cần phải sử dụng dữ liệu

từ hệ điều hành và từ thời gian chạy Java để xác định chẩn đoán đó

Lớp com.ibm.jtc.demos.NativeMemoryGlutton cung cấp phương thức

gobbleMemory(), gọi hàm malloc trong một vòng lặp cho đến khi hầu như tất cả

bộ nhớ riêng được sử dụng hết Khi nó đã hoàn thành nhiệm vụ của mình, nó in số byte được cấp phát thành lỗi tiêu chuẩn như sau:

Allocated 1953546736 bytes of native memory before running out

Kết quả đầu ra được thu giữ cho mỗi lần trình diễn (demo) với một thời gian chạy Java của Sun và một thời gian chạy Java của IBM đang chạy trên Windows 32-bit Các tệp mã nhị phân được cung cấp đã được thử nghiệm trên:

 Linux x86

 Linux PPC 32

 Linux 390 31

Trang 18

 Windows x86

Phiên bản sau đây của thời gian chạy Java của Sun đã được sử dụng để thu kết quả đầu ra:

java version "1.5.0_11" Java(TM) 2 Runtime Environment, Standard

Edition (build 1.5.0_11-b03) Java HotSpot(TM) Client VM (build

Phiên bản thời gian chạy Java của IBM được sử dụng là:

java version "1.5.0" Java(TM) 2 Runtime Environment, Standard

Edition (build pwi32devifx-20071025 (SR 6b)) IBM J9 VM (build 2.3, J2RE 1.5.0 IBM J9

Trang 19

-Cố gắng để khởi động một luồng khi hết bộ nhớ riêng

Lớp com.ibm.jtc.demos.StartingAThreadUnderNativeStarvation cố gắng để khởi

động một luồng khi vùng địa chỉ tiến trình đã dùng hết Đây là một cách phổ biến

để phát hiện ra rằng tiến trình Java của bạn thiếu bộ nhớ vì nhiều ứng dụng bắt đầu

các luồng trong vòng đời của chúng

Kết quả đầu ra từ trình diễn (demo) StartingAThreadUnderNativeStarvation khi

chạy trên thời gian chạy Java của IBM là:

Allocated 1019394912 bytes of native memory before running out

JVMDUMP006I Processing Dump Event "systhrow", detail

"java/lang/OutOfMemoryError" -

| -10 -20 -30 -40 -50 -60 -70 -80 -9|

| - XML error: The previous line is longer than the max of 90 characters -| Please Wait JVMDUMP007I JVM Requesting Snap Dump using

'C:\Snap0001.20080323.182114.5172.trc' JVMDUMP010I Snap Dump written to

C:\Snap0001.20080323.182114.5172.trc JVMDUMP007I JVM Requesting Heap Dump using

| -10 -20 -30 -40 -50 -60 -70 -80 -9|

| - XML error: The previous line is longer than the max of 90 characters -|

'C:\heapdump.20080323.182114.5172.phd' JVMDUMP010I Heap Dump written

Trang 20

'C:\javacore.20080323.182114.5172.txt' JVMDUMP010I Java Dump written to

C:\javacore.20080323.182114.5172.txt JVMDUMP013I Processed Dump Event

Trang 21

java.util.ResourceBundle.getBundle(ResourceBundle.java:716) at

com.ibm.oti.vm.MsgHelp.setLocale(MsgHelp.java:103) at

com.ibm.oti.util.Msg$1.run(Msg.java:44) at

java.security.AccessController.doPrivileged(AccessController.java:197) at com.ibm.oti.util.Msg.<clinit>(Msg.java:41) at

java.lang.J9VMInternals.initializeImpl(Native Method) at

java.lang.J9VMInternals.initialize(J9VMInternals.java:194) at

java.lang.ThreadGroup.uncaughtException(ThreadGroup.java:764) at java.lang.ThreadGroup.uncaughtException(ThreadGroup.java:758) at java.lang.Thread.uncaughtException(Thread.java:1315)

K0319java.lang.OutOfMemoryError: Failed to fork OS thread at

Việc gọi java.lang.Thread.start() cố gắng để cấp phát bộ nhớ cho một luồng hệ

điều hành mới Nỗ lực này không thành công và đưa ra lỗi OutOfMemoryError Các dòng JVMDUMP thông báo cho người dùng rằng thời gian chạy Java đã sinh

ra dữ liệu gỡ lỗi OutOfMemoryError tiêu chuẩn của nó

Việc cố gắng xử lý lỗi OutOfMemoryError đầu tiên gây ra một lỗi — thứ hai

:OutOfMemoryError, ENOMEM error in ZipFile.open Nhiều lỗi

OutOfMemoryError là dấu hiệu phổ biến khi bộ nhớ tiến trình riêng đã cạn kiệt

Trang 22

Thông báo Failed to fork OS thread (bị thất bại khi phân nhánh luồng hệ điều hành) có lẽ là dấu hiệu thường hay gặp nhất của việc thiếu bộ nhớ riêng

Các ví dụ được cung cấp với bài viết này bắt đầu một nhóm các lỗi

OutOfMemoryError, nghiêm trọng hơn nhiều so với bất cứ những gì mà bạn có thể thấy với các ứng dụng riêng của bạn Điều này một phần là vì hầu như tất cả các bộ nhớ riêng đã được sử dụng hết và không giống như trong một ứng dụng thực, bộ nhớ ấy không được giải phóng sau đó Trong một ứng dụng thực, khi lỗi OutOfMemoryError được đưa ra, các luồng sẽ bị tắt và sức ép về bộ nhớ riêng có nhiều khả năng giảm bớt một chút, mang lại cho thời gian chạy một cơ hội để xử

lý lỗi Bản chất tầm thường của các bài thử nghiệm còn có nghĩa là toàn bộ các phần của thư viện lớp (như các hệ thống an ninh) vẫn chưa được khởi động — và việc khởi động chúng được điều khiển bởi thời gian chạy khi đang cố xử lý tình trạng thiếu bộ nhớ Trong một ứng dụng thực, bạn có thể thấy một số lỗi được chỉ

ra ở đây, nhưng ít khả năng bạn sẽ thấy tất cả các lỗi đồng thời

Khi thực hiện cùng một bài thử nghiệm trên thời gian chạy Java của Sun, kết quả đầu ra trên cửa sổ màn hình như sau:

Allocated 1953546736 bytes of native memory before running out

Exception in thread "main" java.lang.OutOfMemoryError: unable to create new native

Mặc dù vết ngăn xếp và thông báo lỗi là hơi khác nhau, về bản chất các hành vi là giống nhau: việc cấp phát bộ nhớ riêng thất bại và một

Trang 23

java.lang.OutOfMemoryError được đưa ra Điều duy nhất để phân biệt các lỗi OutOfMemoryError được đưa ra trong kịch bản này với các lỗi được đưa ra do sử dụng hết vùng heap là thông báo

Việc cố gắng để cấp phát một ByteBuffer trực tiếp khi thiếu bộ nhớ riêng

Lớp com.ibm.jtc.demos.DirectByteBufferUnderNativeStarvation cố gắng cấp phát một đối tượng java.nio.ByteBuffer trực tiếp (nghĩa là được hậu thuẫn riêng) khi vùng địa chỉ bị cạn kiệt Khi chạy trong thời gian chạy Java của IBM, nó tạo kết quả đầu ra sau đây:

Allocated 1019481472 bytes of native memory before running out

JVMDUMP006I Processing Dump Event "uncaught", detail

"java/lang/OutOfMemoryError" -

| -10 -20 -30 -40 -50 -60 -70 -80 -9|

| - XML error: The previous line is longer than the max of 90 characters |

Please Wait JVMDUMP007I JVM Requesting Snap Dump using

'C:\Snap0001.20080324.100721.4232.trc' JVMDUMP010I Snap Dump written to

Trang 24

| - XML error: The previous line is longer than the max of 90 characters |

'C:\javacore.20080324.100721.4232.txt' JVMDUMP010I Java Dump written to

C:\javacore.20080324.100721.4232.txt JVMDUMP013I Processed Dump Event "uncaught",

| -10 -20 -30 -40 -50 -60 -70 -80 -9|

| - XML error: The previous line is longer than the max of 90 characters |

detail "java/lang/OutOfMemoryError" Exception in thread "main"

java.lang.OutOfMemoryError: Unable to allocate 1048576 bytes of direct memory after

Ngày đăng: 07/08/2014, 10:22

HÌNH ẢNH LIÊN QUAN

Hình 1. Bộ nhớ ảo ánh xạ vùng địa chỉ tiến trình tới các tài nguyên vật lý - Cảm ơn bộ nhớ Hiểu cách JVM sử dụng bộ nhớ riêng trên Windows và Linux như thế nào pps
Hình 1. Bộ nhớ ảo ánh xạ vùng địa chỉ tiến trình tới các tài nguyên vật lý (Trang 5)
Hình 2. Bố trí vùng địa chỉ cho Windows 32-bit - Cảm ơn bộ nhớ Hiểu cách JVM sử dụng bộ nhớ riêng trên Windows và Linux như thế nào pps
Hình 2. Bố trí vùng địa chỉ cho Windows 32-bit (Trang 7)
Hình 3. Bố trí vùng-địa chỉ cho Linux 32-bit - Cảm ơn bộ nhớ Hiểu cách JVM sử dụng bộ nhớ riêng trên Windows và Linux như thế nào pps
Hình 3. Bố trí vùng-địa chỉ cho Linux 32-bit (Trang 8)

TỪ KHÓA LIÊN QUAN

🧩 Sản phẩm bạn có thể quan tâm

w