Hệ điều hành thực hiện các thao tác I / O trên vùng nhớ. Các vùng nhớ này, theo như hệ điều hành có liên quan, là các chuỗi byte liền kề nhau. Không có gì ngạc nhiên khi chỉ có bộ đệm byte mới đủ điều kiện tham gia vào các hoạt động I / O. Cũng nhắc lại rằng hệ điều hành sẽ truy cập trực tiếp vào không gian địa chỉ của quy trình, trong trường hợp này là quy trình JVM, để truyền dữ liệu. Điều này có nghĩa là các vùng nhớ là mục tiêu của các chuỗi I / O phải là các chuỗi byte liền kề nhau. Trong JVM, một mảng byte có thể không được lưu trữ liên tục trong bộ nhớ hoặc Trình thu gom rác có thể di chuyển nó bất cứ lúc nào. Mảng là các đối tượng trong Java và cách dữ liệu được lưu trữ bên trong đối tượng đó có thể thay đổi từ một triển khai JVM khác.
Vì lý do này, khái niệm về một bộ đệm trực tiếp đã được giới thiệu. Bộ đệm trực tiếp được dự định để tương tác với các kênh và các thói quen I / O gốc. Họ nỗ lực hết sức để lưu trữ các phần tử byte trong vùng nhớ mà kênh có thể sử dụng để truy cập trực tiếp hoặc thô, bằng cách sử dụng mã gốc để báo cho hệ điều hành thoát hoặc lấp đầy vùng nhớ trực tiếp.
Bộ đệm byte trực tiếp thường là lựa chọn tốt nhất cho các hoạt động I / O. Theo thiết kế, chúng hỗ trợ cơ chế I / O hiệu quả nhất có sẵn cho JVM. Bộ đệm byte không gián tiếp có thể được chuyển đến các kênh, nhưng làm như vậy có thể phải chịu một hình phạt hiệu suất. Thông thường, bộ đệm không gián tiếp thường là mục tiêu của hoạt động I / O gốc. Nếu bạn chuyển một đối tượng ByteBuffer không trực tiếp sang một kênh để ghi, kênh có thể ngầm thực hiện các thao tác sau trên mỗi cuộc gọi:
- Tạo một đối tượng ByteBuffer trực tiếp tạm thời.
- Sao chép nội dung của bộ đệm không gián tiếp vào bộ đệm tạm thời.
- Thực hiện thao tác I / O mức thấp bằng cách sử dụng bộ đệm tạm thời.
- Đối tượng bộ đệm tạm thời đi ra khỏi phạm vi và cuối cùng là rác được thu thập.
Điều này có khả năng dẫn đến việc sao chép bộ đệm và khởi động đối tượng trên mỗi I / O, đó chính xác là những điều chúng ta muốn tránh. Tuy nhiên, tùy thuộc vào việc thực hiện, mọi thứ có thể không tệ như vậy. Thời gian chạy sẽ có khả năng lưu trữ và sử dụng lại bộ đệm trực tiếp hoặc thực hiện các thủ thuật thông minh khác để tăng thông lượng. Nếu bạn chỉ đơn giản là tạo một bộ đệm để sử dụng một lần, sự khác biệt là không đáng kể. Mặt khác, nếu bạn sẽ sử dụng bộ đệm nhiều lần trong một kịch bản hiệu suất cao, tốt hơn hết bạn nên phân bổ bộ đệm trực tiếp và sử dụng lại chúng.
Bộ đệm trực tiếp là tối ưu cho I / O, nhưng chúng có thể tốn kém hơn để tạo hơn bộ đệm byte không gián tiếp. Bộ nhớ được sử dụng bởi các bộ đệm trực tiếp được phân bổ bằng cách gọi qua mã riêng, của hệ điều hành, bỏ qua heap JVM tiêu chuẩn. Thiết lập và phá bỏ bộ đệm trực tiếp có thể tốn kém hơn đáng kể so với bộ đệm thường trú, tùy thuộc vào hệ điều hành máy chủ và triển khai JVM. Các vùng lưu trữ bộ nhớ của bộ đệm trực tiếp không phải là bộ sưu tập rác vì chúng nằm ngoài heap JVM tiêu chuẩn.
Sự đánh đổi hiệu năng của việc sử dụng bộ đệm trực tiếp so với không trực tiếp có thể khác nhau tùy theo JVM, hệ điều hành và thiết kế mã. Bằng cách phân bổ bộ nhớ ngoài heap, bạn có thể khiến ứng dụng của mình chịu các lực bổ sung mà JVM không biết. Khi đưa các bộ phận chuyển động bổ sung vào chơi, hãy đảm bảo rằng bạn đang đạt được hiệu quả mong muốn. Tôi khuyên bạn nên châm ngôn phần mềm cũ: đầu tiên làm cho nó hoạt động, sau đó làm cho nó nhanh. Đừng lo lắng quá nhiều về việc tối ưu hóa trước; tập trung đầu tiên vào sự đúng đắn. Việc triển khai JVM có thể có thể thực hiện bộ đệm ẩn hoặc các tối ưu hóa khác sẽ cung cấp cho bạn hiệu năng bạn cần mà không cần nhiều nỗ lực không cần thiết từ phía bạn.