Chúng ta có nên tránh việc tạo đối tượng trong Java không?


243

Tôi đã được một đồng nghiệp nói rằng trong việc tạo đối tượng Java là thao tác đắt nhất bạn có thể thực hiện. Vì vậy, tôi chỉ có thể kết luận để tạo ra càng ít đối tượng càng tốt.

Điều này dường như phần nào để đánh bại mục đích của lập trình hướng đối tượng. Nếu chúng ta không tạo đối tượng thì chúng ta chỉ đang viết một kiểu lớp C dài, để tối ưu hóa?


39
"Tạo đối tượng Java là hoạt động tốn kém nhất bạn có thể thực hiện" . Tâm chia sẻ nguồn gốc của yêu sách đó?
Songo

92
Và - hoạt động đắt nhất so với những gì? So với tính pi đến 900 chữ số hay so với tăng int?
jasonk

4
Có lẽ họ đã nói về một phần cụ thể của việc tạo đối tượng cụ thể đó? Tôi đang nghĩ làm thế nào Apple sử dụng một hàng đợi cho bảngViewCells. Có lẽ đồng nghiệp của bạn đã đề nghị tạo ra một vài đối tượng và tái sử dụng chúng do một số chi phí liên quan đến các đối tượng cụ thể đó? Chỉ cần cố gắng tìm ra lý do tại sao họ đưa ra yêu cầu như vậy.
Tyler DeWitt

3
Đây là một trong những điều buồn cười nhất tôi từng nghe về lập trình.)
Mert Akcakaya

22
Arithmatic cũng khá đắt tiền; chúng ta nên tránh các phép tính, ồ, và bắt đầu một JVM thực sự tốn kém vì vậy chúng ta không nên bắt đầu bất kỳ JVM nào :-).
James Anderson

Câu trả lời:


478

Đồng nghiệp của bạn không biết họ đang nói về cái gì.

Hoạt động đắt tiền nhất của bạn sẽ được lắng nghe họ . Họ đã lãng phí thời gian của bạn để hướng bạn đến thông tin đã lỗi thời hơn một thập kỷ (kể từ ngày ban đầu câu trả lời này được đăng) cũng như bạn phải dành thời gian đăng lên đây và nghiên cứu về sự thật.

Hy vọng rằng họ chỉ đang thờ ơ với những gì họ nghe hoặc đọc từ hơn một thập kỷ trước và không biết gì hơn. Tôi cũng sẽ lấy bất cứ điều gì khác mà họ nói là nghi ngờ, đây sẽ là một lời ngụy biện nổi tiếng bởi bất cứ ai luôn cập nhật theo bất kỳ cách nào.

Tất cả mọi thứ là một đối tượng (ngoại trừ primitives)

Mọi thứ khác ngoài nguyên thủy ( int, long, double, v.v.) đều là các Đối tượng trong Java. Không có cách nào để tránh việc tạo Object trong Java.

Việc tạo đối tượng trong Java do các chiến lược phân bổ bộ nhớ của nó nhanh hơn C ++ trong hầu hết các trường hợp và cho tất cả các mục đích thực tế so với mọi thứ khác trong JVM có thể được coi là "miễn phí" .

Ngay từ cuối những năm 1990, các triển khai JVM đã có một số chi phí hoạt động trong việc phân bổ các Đối tượng thực tế. Điều này đã không xảy ra kể từ ít nhất là năm 2005.

Nếu bạn điều chỉnh -Xmsđể hỗ trợ tất cả bộ nhớ bạn cần để ứng dụng của bạn chạy chính xác, thì GC có thể không bao giờ phải chạy và quét hầu hết rác trong các triển khai GC hiện đại, các chương trình có thời gian ngắn có thể không bao giờ là GC.

Nó không thử và tối đa hóa không gian trống, dù sao đó là cá trích đỏ, nó tối đa hóa hiệu suất của thời gian chạy. Nếu điều đó có nghĩa là Heap JVM được phân bổ gần như 100% mọi lúc, thì cũng vậy. Bộ nhớ heap JVM miễn phí không cung cấp cho bạn bất cứ điều gì chỉ cần ngồi ở đó.

Có một quan niệm sai lầm rằng GC sẽ giải phóng bộ nhớ trở lại phần còn lại của hệ thống một cách hữu ích, điều này là hoàn toàn sai!

Heap JVM không phát triển và co lại để phần còn lại của hệ thống bị ảnh hưởng tích cực bởi bộ nhớ trống trong HVM JVM . -Xmsphân bổ TẤT CẢ những gì được chỉ định khi khởi động và heuristic của nó là không bao giờ thực sự giải phóng bất kỳ bộ nhớ nào đó trở lại HĐH để được chia sẻ với bất kỳ quy trình HĐH nào khác cho đến khi phiên bản JVM đó thoát hoàn toàn. -Xms=1GB -Xmx=1GBphân bổ 1GB RAM bất kể có bao nhiêu đối tượng thực sự được tạo tại bất kỳ thời điểm nào. Có một số cài đặt cho phép phát hành phần trăm bộ nhớ heap, nhưng với tất cả các mục đích thực tế , JVM không bao giờ có thể giải phóng đủ bộ nhớ này để điều này xảy rado đó, không có quá trình nào khác có thể lấy lại bộ nhớ này, vì vậy phần còn lại của hệ thống không được hưởng lợi từ Heap JVM là miễn phí. Một RFE cho điều này đã được "chấp nhận" 29-NOV-2006, nhưng chưa có gì được thực hiện về nó. Đây là hành vi không được coi là mối quan tâm của bất cứ ai có thẩm quyền.

Có một quan niệm sai lầm rằng việc tạo ra nhiều đối tượng tồn tại nhỏ khiến JVM tạm dừng trong thời gian dài, điều này cũng sai.

Các thuật toán GC hiện tại thực sự được tối ưu hóa để tạo ra nhiều đối tượng nhỏ tồn tại trong thời gian ngắn, về cơ bản là heuristic 99% cho các đối tượng Java trong mọi chương trình. Các nỗ lực tại Object Pooling thực sự sẽ làm cho JVM hoạt động kém hơn trong hầu hết các trường hợp.

Các đối tượng duy nhất cần gộp ngày hôm nay là các Đối tượng tham chiếu đến các tài nguyên hữu hạn bên ngoài JVM; Ổ cắm, tệp, kết nối cơ sở dữ liệu, vv và có thể được sử dụng lại. Các đối tượng thông thường không thể được gộp chung theo nghĩa như trong các ngôn ngữ cho phép bạn truy cập trực tiếp vào các vị trí bộ nhớ. Bộ nhớ đệm đối tượng là một khái niệm khác nhau và có thể hoặc không phải là thứ mà một số người ngây thơ gọi là gộp chung , hai khái niệm này không giống nhau và không nên bị lẫn lộn.

Các thuật toán GC hiện đại không gặp phải vấn đề này bởi vì chúng không phân bổ theo lịch trình, chúng sẽ phân bổ khi cần bộ nhớ trống trong một thế hệ nhất định. Nếu heap đủ lớn, thì không có sự thỏa thuận nào xảy ra đủ lâu để gây ra bất kỳ tạm dừng nào.

Các ngôn ngữ động hướng đối tượng đang đánh bại C ngay cả ngày nay trong các bài kiểm tra nhạy cảm.


275
+1: "Hoạt động đắt nhất của bạn sẽ lắng nghe họ ...". Tốt nhất tôi đã nghe một thời gian.
rjnilsson

21
@DeadMG, ngay cả với chi phí tích lũy GC, Java thể nhanh hơn C ++ (ví dụ: do quá trình nén heap, giảm thiểu lỗi bộ nhớ cache cho các cấu trúc dữ liệu nhất định).
SK-logic

13
@ SK-logic: Vì GC không mang tính quyết định, nên thực sự rất khó để chứng minh điều đó. Đối với việc giảm thiểu lỗi bộ nhớ cache, thật khó để làm điều đó khi mọi đối tượng phải là một hướng dẫn khác, lãng phí không gian bộ nhớ cache và tăng thời gian thực hiện. Tuy nhiên, tôi cho rằng chỉ bằng cách sử dụng một bộ cấp phát thích hợp trong C ++ như nhóm đối tượng hoặc trường bộ nhớ, bạn có thể dễ dàng khớp hoặc đánh bại hiệu suất của trình thu gom rác. Ví dụ: bạn có thể viết một lớp đấu trường bộ nhớ sẽ thực hiện nhanh hơn _alloca, được khấu hao.
DeadMG

33
Tạo đối tượng bây giờ rẻ hơn so với trước đây. Nó không miễn phí mặc dù. Bất cứ ai nói với bạn rằng đang nói dối. Và liên kết về các ngôn ngữ OO đánh bại C là một phản ứng phóng đại với một người thực sự đang cố gắng học.
jasonk

17
Đó là vì những loại câu trả lời mà chúng tôi kết thúc với mã crappy. Câu trả lời đúng là tạo một đối tượng trong Java vừa tạo đối tượng Java vừa khởi tạo nó. Phần thứ nhất là rẻ, thứ hai thể rất đắt. Mọi người nên luôn luôn nhìn vào những gì xảy ra trong các nhà xây dựng trước khi sử dụng newtừ khóa ở một điểm nóng. Tôi đã nhìn thấy mọi người sử dụng new ImageIcon(Image)trong paint()phương pháp của các đối tượng Swing, mà là khá tốn kém và đã làm cho toàn bộ giao diện người dùng siêu chậm chạp. Vì vậy, đó không phải là một câu trả lời trắng đen, hãy suy nghĩ trước khi bạn sử dụng newở đâu đó.
qwertzguy

94

Điểm mấu chốt: Đừng thỏa hiệp thiết kế của bạn để có các phím tắt tạo đối tượng. Tránh tạo các đối tượng không cần thiết. Nếu hợp lý, thiết kế để tránh các hoạt động dư thừa (dưới bất kỳ hình thức nào).

Trái với hầu hết các câu trả lời - có, phân bổ đối tượng DOES có chi phí liên quan. Đó là một chi phí thấp, nhưng bạn nên tránh tạo ra các đối tượng không cần thiết. Tương tự như bạn nên tránh bất cứ điều gì không cần thiết trong mã của bạn. Biểu đồ đối tượng lớn làm cho GC chậm hơn, ngụ ý thời gian thực hiện lâu hơn trong đó bạn có thể sẽ có nhiều cuộc gọi phương thức hơn, kích hoạt nhiều lỗi bộ nhớ cache CPU hơn và tăng khả năng quá trình của bạn bị hoán đổi vào đĩa trong trường hợp RAM thấp.

Trước khi bất kỳ ai muốn nói về trường hợp này - tôi đã lập hồ sơ các ứng dụng, trước khi tối ưu hóa, đã tạo ra hơn 20 MB đối tượng để xử lý ~ 50 hàng dữ liệu. Điều này là tốt trong thử nghiệm, cho đến khi bạn mở rộng tới một trăm yêu cầu một phút và đột nhiên bạn đang tạo ra 2GB dữ liệu mỗi phút. Nếu bạn muốn thực hiện 20 reqs / giây, bạn đang tạo 400MB đối tượng và sau đó ném nó đi. 20 reqs / giây là rất nhỏ đối với một máy chủ phong nha.


7
Tôi có thể thêm một ví dụ: Trong một số trường hợp, nó thực sự không có sự khác biệt về độ rõ ràng của mã nhưng có thể tạo ra sự khác biệt lớn hơn hoặc ít hơn về hiệu suất: Chẳng hạn, khi đọc từ các luồng, không có gì lạ khi sử dụng một cái gì đó giống như while(something) { byte[] buffer = new byte[10240]; ... readIntoBuffer(buffer); ...có thể lãng phí so với ví dụ byte[] buffer = new byte[10240]; while(something) { ... readIntoBuffer(buffer); ....
JimmyB

4
+1: Việc tạo đối tượng không cần thiết (hay đúng hơn là dọn dẹp sau khi xóa) đôi khi chắc chắn có thể tốn kém. Mã đồ họa 3D / OpenGL trong java là một nơi mà tôi đã thấy tối ưu hóa được thực hiện để giảm thiểu số lượng đối tượng được tạo vì GC có thể phá hỏng tốc độ khung hình của bạn.
Leo

1
Ồ 20+ MB để xử lý 50 hàng dữ liệu? Nghe có vẻ điên rồ. Trong mọi trường hợp, những đối tượng là sống lâu? Bởi vì đó là trường hợp quan trọng đối với GC. Mặt khác, nếu bạn chỉ đang thảo luận về các yêu cầu bộ nhớ , thì điều đó không liên quan đến hiệu quả thu gom rác hoặc hiệu quả tạo đối tượng ...
Andres F.

1
Các đối tượng có thời gian tồn tại ngắn (dưới 0,5 giây trong trường hợp chung). Khối lượng rác đó vẫn ảnh hưởng đến hiệu suất.
jasonk

2
Cảm ơn bạn đã thực sự đứng vững trên thực tế, bất cứ ai sống với những tác động của một kiến ​​trúc thiếu suy nghĩ có thể liên quan rất nhiều.
JM Becker

60

Trên thực tế, do các chiến lược quản lý bộ nhớ mà ngôn ngữ Java (hoặc bất kỳ ngôn ngữ được quản lý nào khác) tạo ra có thể, việc tạo đối tượng ít hơn nhiều so với việc tăng một con trỏ trong một khối bộ nhớ được gọi là thế hệ trẻ. Nó nhanh hơn nhiều so với C, trong đó việc tìm kiếm bộ nhớ trống phải được thực hiện.

Phần khác của chi phí là phá hủy đối tượng, nhưng thật khó để so sánh với C. Chi phí của một bộ sưu tập dựa trên số lượng đối tượng được lưu trong thời gian dài nhưng tần suất của các bộ sưu tập dựa trên số lượng đối tượng được tạo ... trong cuối cùng, nó vẫn nhanh hơn nhiều so với quản lý bộ nhớ kiểu C.


7
+1 Mặc dù trình thu gom rác "chỉ hoạt động", mọi lập trình viên Java nên tìm hiểu về trình thu gom rác thế hệ.
benzado

18
Các phiên bản Java mới hơn có thể thực hiện phân tích thoát, có nghĩa là nó có thể phân bổ bộ nhớ cho các đối tượng không thoát khỏi một phương thức trên ngăn xếp, do đó việc dọn dẹp chúng là miễn phí - trình thu gom rác không phải xử lý các đối tượng đó , chúng sẽ tự động bị loại bỏ khi khung ngăn xếp của phương thức không được mở (khi phương thức trở lại).
Jesper

4
Một người bước tiếp theo để phân tích lối thoát là để "bố trí" bộ nhớ cho đối tượng nhỏ hoàn toàn trong thanh ghi (ví dụ một Pointđối tượng có thể phù hợp trong 2 thanh ghi mục đích chung).
Joachim Sauer

6
@Joachim Sauer: Đó là những gì được thực hiện trong các triển khai mới hơn của HotSpot VM. Nó được gọi là thay thế vô hướng.
someguy

1
@someguy: Tôi đã đọc về nó một thời gian trước đây như là điều tiếp theo, nhưng không theo dõi để kiểm tra xem nó đã được thực hiện chưa. Đó là tin tuyệt vời khi biết rằng chúng tôi đã có điều này.
Joachim Sauer

38

Các áp phích khác đã chỉ ra một cách đúng đắn rằng việc tạo đối tượng cực kỳ nhanh trong Java và bạn thường không nên lo lắng về nó trong bất kỳ ứng dụng Java thông thường nào.

Có một vài tình huống rất đặc biệt một ý tưởng tốt để tránh tạo đối tượng.

  • Khi bạn đang viết một ứng dụng nhạy cảm với độ trễ và muốn tránh tạm dừng GC. Bạn càng tạo ra nhiều đối tượng, càng nhiều GC xảy ra và cơ hội tạm dừng càng lớn. Đây có thể là một sự cân nhắc hợp lệ cho các trò chơi, một số ứng dụng phương tiện, điều khiển robot, giao dịch tần số cao, v.v ... Giải pháp là phân bổ trước tất cả các đối tượng / mảng bạn cần lên trước và sử dụng lại chúng. Có những thư viện chuyên cung cấp loại khả năng này, ví dụ Javolution . Nhưng có thể nói, nếu bạn thực sự quan tâm đến độ trễ thấp, bạn nên sử dụng C / C ++ / trình biên dịch thay vì Java hoặc C # :-)
  • Tránh các nguyên thủy đóng hộp (Double, Integer, v.v.) có thể là một tối ưu hóa vi mô rất có lợi trong một số trường hợp nhất định. Do các nguyên hàm không được đóng hộp (double, int, v.v.) tránh được chi phí cho mỗi đối tượng, nên chúng hoạt động chuyên sâu với CPU nhanh hơn nhiều như xử lý số, v.v. Thông thường, các mảng nguyên thủy hoạt động rất tốt trong Java, vì vậy bạn muốn sử dụng chúng cho việc bẻ khóa số của mình thay vì bất kỳ loại đối tượng khác.
  • Trong các tình huống bộ nhớ bị ràng buộc, bạn muốn giảm thiểu số lượng đối tượng (hoạt động) được tạo do mỗi đối tượng mang một chi phí nhỏ (thường là 8-16 byte tùy thuộc vào việc thực hiện JVM). Trong những trường hợp như vậy, bạn nên ưu tiên một số lượng nhỏ các đối tượng hoặc mảng lớn để lưu trữ dữ liệu của bạn hơn là một số lượng lớn các đối tượng nhỏ.

3
Đáng lưu ý rằng phân tích thoát sẽ cho phép các đối tượng tồn tại trong thời gian ngắn (bị ràng buộc) được lưu trữ trên ngăn xếp, các đối tượng này có thể được giải phóng miễn phí ibm.com/developerworks/java/l Library / j
Richard Tingle

1
@Richard: điểm tuyệt vời - phân tích thoát cung cấp một số tối ưu hóa rất tốt. Mặc dù vậy, bạn phải cẩn thận khi dựa vào nó: điều đó không được đảm bảo xảy ra trong tất cả các triển khai Java và trong mọi trường hợp. Vì vậy, bạn thường cần phải chuẩn để chắc chắn.
mikera

2
Ngoài ra, phân tích thoát chính xác 100% tương đương với vấn đề tạm dừng. Đừng dựa vào phân tích thoát hiểm trong các tình huống phức tạp, vì nó có khả năng đưa ra câu trả lời sai ít nhất một số thời gian.
Jules

Điểm đầu tiên và điểm cuối không áp dụng cho các đối tượng nhỏ được giải phóng nhanh chóng, chúng chết trong eden (nghĩ rằng phân bổ ngăn xếp trong C, khá nhiều miễn phí) và không bao giờ ảnh hưởng đến thời gian GC hoặc cấp phát bộ nhớ. Hầu hết phân bổ đối tượng trong Java rơi vào thể loại này và do đó về cơ bản là miễn phí. Điểm thứ hai vẫn còn hiệu lực.
Bill K

17

Có một hạt nhân của sự thật trong những gì đồng nghiệp của bạn đang nói. Tôi trân trọng đề nghị vấn đề với việc tạo đối tượng thực sự là bộ sưu tập rác . Trong C ++, lập trình viên có thể điều khiển chính xác cách giải quyết bộ nhớ. Chương trình có thể tích lũy crud bao lâu hoặc ngắn tùy thích. Hơn nữa, chương trình C ++ có thể loại bỏ crud bằng cách sử dụng một luồng khác với luồng đã tạo ra nó. Do đó, chủ đề hiện đang làm việc không bao giờ phải dừng lại để làm sạch.

Ngược lại, Máy ảo Java (JVM) định kỳ dừng mã của bạn để lấy lại bộ nhớ không sử dụng. Hầu hết các nhà phát triển Java không bao giờ nhận thấy sự tạm dừng này, vì nó thường không thường xuyên và rất ngắn. Bạn tích lũy càng nhiều hoặc JVM của bạn càng bị ràng buộc, các tạm dừng này càng thường xuyên. Bạn có thể sử dụng các công cụ như VisualVM để trực quan hóa quá trình này.

Trong các phiên bản gần đây của Java, thuật toán thu gom rác (GC) có thể được điều chỉnh . Theo nguyên tắc chung, bạn muốn thực hiện các lần tạm dừng càng ngắn thì chi phí sử dụng trong máy ảo càng đắt (tức là CPU và bộ nhớ dành cho việc điều phối quá trình GC).

Khi nào thì vấn đề này? Bất cứ khi nào bạn quan tâm về tốc độ phản hồi dưới một phần nghìn giây nhất quán, bạn sẽ quan tâm đến GC. Các hệ thống giao dịch tự động được viết bằng Java điều chỉnh JVM rất nhiều để giảm thiểu tạm dừng. Các công ty thường viết Java chuyển sang C ++ trong các tình huống mà các hệ thống phải có khả năng đáp ứng cao mọi lúc.

Đối với hồ sơ, tôi không bỏ qua việc tránh đối tượng nói chung! Mặc định cho lập trình hướng đối tượng. Chỉ điều chỉnh cách tiếp cận này nếu GC cản trở bạn và sau đó chỉ sau khi cố gắng điều chỉnh JVM để tạm dừng ít thời gian hơn. Một cuốn sách hay về điều chỉnh hiệu năng Java là Hiệu suất Java của Charlie Hunt và Binu John.


10
Hầu hết các JVM hiện đại đều hỗ trợ các thuật toán thu gom rác tốt hơn là "ngăn chặn thế giới". Thời gian khấu hao dành cho việc thu gom rác thường ít hơn so với thời gian gọi malloc / miễn phí một cách rõ ràng. Ngoài ra quản lý bộ nhớ C ++ chạy trong một luồng riêng biệt?

10
Các phiên bản Java mới hơn của Oracle có thể thực hiện phân tích thoát, điều đó có nghĩa là các đối tượng không thoát khỏi một phương thức được phân bổ trên ngăn xếp, điều này giúp làm sạch chúng miễn phí - chúng sẽ tự động được xử lý khi phương thức quay trở lại.
Jesper

2
@ ThorbjørnRavnAndersen: Thật sao? Bạn thực sự có nghĩa là tạm dừng - ít GC hơn , hay bạn có nghĩa là "thường-song song-nhưng-đôi khi-một chút-nhỏ-tạm dừng"? Tôi không hiểu làm thế nào mà GC không bao giờ có thể tạm dừng một chương trình, đồng thời di chuyển các vật thể xung quanh ... có vẻ như, theo nghĩa đen, hoàn toàn không thể đối với tôi ...
Mehrdad

2
@ ThorbjørnRavnAndersen: Làm thế nào nó có thể "ngăn chặn thế giới" nhưng không bao giờ "tạm dừng JVM"? Điều đó thật vô nghĩa ...
Mehrdad

2
@ ThorbjørnRavnAndersen: Không tôi thực sự không; Tôi chỉ không hiểu những gì bạn nói. Đôi khi, GC đôi khi tạm dừng chương trình, trong trường hợp đó - theo định nghĩa - không phải là " tạm dừng ", hoặc nó không bao giờ tạm dừng chương trình (do đó, nó là "tạm dừng"), trong trường hợp đó tôi không hiểu làm thế nào tương ứng với câu lệnh "nó dừng thế giới khi không thể theo kịp", vì điều đó dường như ngụ ý rằng chương trình bị tạm dừng trong khi GC đang hoạt động. Bạn có phiền giải thích trường hợp nào không (có tạm dừng hay không?), Và làm thế nào là có thể?
Mehrdad


9

GC được điều chỉnh cho nhiều đối tượng sống ngắn

điều đó nói rằng nếu bạn có thể giảm phân bổ đối tượng một cách tầm thường thì bạn nên

một ví dụ sẽ là xây dựng một chuỗi trong một vòng lặp, cách ngây thơ sẽ là

String str = "";
while(someCondition){
    //...
    str+= appendingString;
}

tạo một Stringđối tượng mới trên mỗi +=thao tác (cộng với a StringBuildervà mảng char bên dưới mới)

bạn có thể dễ dàng viết lại thành:

StringBuilder strB = new StringBuilder();
while(someCondition){
    //...
    strB.append(appendingString);
}
String str = strB.toString();

mô hình này (kết quả bất biến và giá trị trung gian có thể thay đổi cục bộ) cũng có thể được áp dụng cho những thứ khác

nhưng khác hơn là bạn nên kéo một hồ sơ để tìm ra nút cổ chai thực sự thay vì đuổi theo ma


lợi thế lớn nhất trong StringBuildercách tiếp cận này là kích thước trước StringBuilderđể nó không bao giờ phải phân bổ lại mảng bên dưới với hàm StringBuilder(int)tạo. Điều này làm cho nó một phân bổ duy nhất , chứ không phải là một 1+Nphân bổ.

2
@JarrodRoberson StringBuilder sẽ tăng gấp đôi công suất hiện tại hoặc nói cách khác, công suất sẽ tăng theo cấp số nhân chỉ với phân bổ log (n)
ratchet freak

6

Joshua Bloch (một trong những người tạo nền tảng Java) đã viết trong cuốn sách Java hiệu quả vào năm 2001:

Tránh tạo đối tượng bằng cách duy trì nhóm đối tượng của riêng bạn là một ý tưởng tồi trừ khi các đối tượng trong nhóm cực kỳ nặng. Một ví dụ mẫu của một đối tượng thực hiện biện minh cho một nhóm đối tượng là một kết nối cơ sở dữ liệu. Chi phí thiết lập kết nối đủ cao để sử dụng lại các đối tượng này. Tuy nhiên, nói chung, việc duy trì nhóm đối tượng của riêng bạn sẽ làm tăng mã của bạn, tăng dung lượng bộ nhớ và gây hại cho hiệu suất. Các triển khai JVM hiện đại có các trình thu gom rác được tối ưu hóa cao, dễ dàng vượt trội hơn các nhóm đối tượng như vậy trên các đối tượng nhẹ.


1
Ngay cả với những thứ như kết nối mạng, điều quan trọng không phải là duy trì các đối tượng được phân bổ GC liên quan đến các kết nối, mà là duy trì tập hợp các kết nối tồn tại bên ngoài thế giới do GC quản lý. Có những lúc, việc tự tổng hợp các đối tượng có thể hữu ích; hầu hết trong số đó xuất phát từ các trường hợp một cặp tham chiếu đến cùng một đối tượng có thể tương đương về mặt ngữ nghĩa với một cặp tham chiếu đến các đối tượng giống hệt nhau nhưng khác biệt, nhưng dù sao cũng có một số lợi thế. Ví dụ: hai tham chiếu đến cùng chuỗi năm mươi nghìn ký tự có thể được kiểm tra tính bằng nhau ...
supercat

2
... nhanh hơn nhiều so với tham chiếu đến hai chuỗi khác biệt nhưng giống hệt nhau về độ dài đó.
supercat

5

Điều này thực sự phụ thuộc vào ứng dụng cụ thể, vì vậy nói chung thực sự rất khó để nói. Tuy nhiên, tôi sẽ khá ngạc nhiên nếu việc tạo đối tượng thực sự là một nút cổ chai hiệu năng trong một ứng dụng. Ngay cả khi chúng chậm, lợi ích kiểu mã có thể sẽ vượt trội hơn hiệu suất (trừ khi nó thực sự đáng chú ý đối với người dùng)

Trong mọi trường hợp, bạn thậm chí không nên bắt đầu lo lắng về những điều này cho đến khi bạn định được mã của mình để xác định các tắc nghẽn hiệu suất thực tế thay vì đoán. Cho đến lúc đó, bạn nên làm bất cứ điều gì tốt nhất cho khả năng đọc mã, không phải hiệu suất.


Trong các phiên bản đầu tiên của Java, có một chi phí đáng kể phát sinh khi trình thu gom rác dọn sạch các đối tượng bị loại bỏ. Các phiên bản gần đây của Java có các tùy chọn GC được tăng cường đáng kể, vì vậy việc tạo đối tượng hiếm khi là một vấn đề. Thông thường các hệ thống web bị giới hạn bởi các lệnh gọi IO đến các hệ thống bên ngoài trừ khi bạn tính toán một cái gì đó thực sự phức tạp trên máy chủ ứng dụng như một phần của tải trang.
greg

3

Tôi nghĩ rằng đồng nghiệp của bạn phải nói từ quan điểm của việc tạo đối tượng không cần thiết. Ý tôi là nếu bạn đang tạo cùng một đối tượng thường xuyên thì tốt hơn là chia sẻ đối tượng đó. Ngay cả trong trường hợp việc tạo đối tượng phức tạp và chiếm nhiều bộ nhớ hơn, bạn có thể muốn sao chép đối tượng đó và tránh tạo quá trình tạo đối tượng phức tạp đó (Nhưng điều đó phụ thuộc vào yêu cầu của bạn). Tôi nghĩ rằng tuyên bố "Tạo đối tượng là tốn kém" nên được đưa vào bối cảnh.

Theo như các yêu cầu về bộ nhớ JVM, hãy đợi Java 8, bạn thậm chí không cần chỉ định -Xmx, các cài đặt không gian meta sẽ giải quyết nhu cầu bộ nhớ JVM và nó sẽ tự động phát triển.


Sẽ rất có tính xây dựng nếu mọi người cũng bình luận trong khi bỏ phiếu bất kỳ câu trả lời nào. Sau tất cả chúng ta đều ở đây để chia sẻ kiến ​​thức, và chỉ cần thông qua phán xét mà không có lý do chính đáng sẽ không phục vụ bất kỳ mục đích nào.
AKS

1
Trao đổi ngăn xếp nên có một số cơ chế để thông báo cho người dùng bỏ phiếu bất kỳ câu trả lời nào, khi bình luận được đăng trên câu trả lời đó.
AKS

1

Có nhiều thứ để tạo một lớp hơn là phân bổ bộ nhớ. Ngoài ra còn có khởi tạo, tôi không chắc tại sao phần đó không được bao gồm trong tất cả các câu trả lời. Các lớp bình thường chứa một số biến và thực hiện một số hình thức khởi tạo và điều đó không miễn phí. Tùy thuộc vào lớp là gì, nó có thể đọc các tệp hoặc thực hiện bất kỳ số lượng hoạt động chậm nào khác.

Vì vậy, chỉ cần xem xét những gì các nhà xây dựng lớp làm, trước khi tìm hiểu xem nó có miễn phí hay không.


2
Một lớp được thiết kế tốt không nên thực hiện việc nâng hạng nặng trong hàm tạo của nó, vì lý do này. Ví dụ, lớp đọc tệp của bạn có thể giảm chi phí khởi tạo bằng cách chỉ xác minh rằng tệp mục tiêu của nó tồn tại khi khởi động và trì hoãn mọi hoạt động tệp thực tế cho đến khi phương thức đầu tiên gọi dữ liệu từ tệp.
GordonM

2
Nếu chi phí bổ sung được chuyển sang 'if (FirstTime)', thì việc tạo lại lớp trong một vòng lặp vẫn tốn kém hơn so với việc sử dụng lại lớp.
Alex

Tôi đã định đăng một câu trả lời tương tự, và tôi tin rằng đây là câu trả lời đúng. Vì vậy, nhiều người bị mù quáng bởi những câu nói rằng việc tạo các đối tượng Java là rẻ đến mức họ không nhận ra rằng thường các nhà xây dựng hoặc khởi tạo thêm có thể khác xa với giá rẻ. Ví dụ, lớp ImageIcon trong Swing, ngay cả khi bạn truyền cho nó một đối tượng Image được tải sẵn, thì hàm tạo khá đắt. Và tôi không đồng ý với @GordonM, nhiều lớp từ JDK thực hiện hầu hết các init trong hàm tạo và tôi nghĩ rằng nó làm cho mã gọn hơn, được thiết kế tốt hơn.
qwertzguy

1

GC của Java thực sự được tối ưu hóa rất nhiều về việc tạo ra nhiều đối tượng nhanh chóng theo kiểu "nổ". Từ những gì tôi có thể hiểu, họ sử dụng bộ cấp phát tuần tự (bộ cấp phát O (1) nhanh nhất và đơn giản nhất cho các yêu cầu có kích thước thay đổi) cho loại "chu kỳ nổ" đó vào không gian bộ nhớ mà họ gọi là "không gian Eden" và chỉ khi các đối tượng tồn tại sau một chu kỳ GC, chúng có được di chuyển đến một nơi mà GC có thể thu thập chúng từng cái một không.

Như đã nói, nếu nhu cầu hiệu suất của bạn trở nên đủ quan trọng (như được đo bằng các yêu cầu cuối của người dùng thực sự), thì các đối tượng sẽ mang một chi phí nhưng tôi sẽ không nghĩ về nó nhiều về mặt sáng tạo / phân bổ. Nó liên quan nhiều hơn đến địa phương tham chiếu và kích thước bổ sung của tất cả các đối tượng trong Java theo yêu cầu để hỗ trợ các khái niệm như phản xạ và công văn động ( Floatlớn hơn float, thường là lớn hơn 4 lần trên 64 bit với các yêu cầu căn chỉnh của nó và mảng Floatkhông nhất thiết phải được đảm bảo để được lưu trữ liền kề từ những gì tôi hiểu).

Một trong những điều bắt mắt nhất tôi từng thấy được phát triển ở Java khiến tôi coi đó là một ứng cử viên nặng ký trong lĩnh vực của mình (VFX) là một công cụ theo dõi đường dẫn tiêu chuẩn đa luồng (không sử dụng bộ nhớ đệm chiếu xạ hoặc BDPT hoặc MLS hoặc bất cứ thứ gì khác) trên CPU cung cấp các bản xem trước thời gian thực hội tụ khá nhanh thành hình ảnh không nhiễu. Tôi đã làm việc với các chuyên gia trong C ++ cống hiến sự nghiệp của họ cho những việc như vậy với các trình biên dịch ưa thích trong tay, người gặp khó khăn trong việc đó.

Nhưng tôi đã quan sát xung quanh mã nguồn và trong khi nó sử dụng nhiều đối tượng với chi phí không đáng kể, các phần quan trọng nhất của trình theo dõi đường đi (BVH và tam giác và vật liệu) rất rõ ràng và cố tình tránh các đối tượng có lợi cho các mảng lớn của các kiểu nguyên thủy ( chủ yếu float[]int[]), làm cho nó sử dụng ít bộ nhớ hơn và địa phương không gian được đảm bảo để đi từ floatmảng này sang mảng khác. Tôi không nghĩ rằng nó quá suy đoán để nghĩ rằng, nếu tác giả sử dụng các loại hình hộp thíchFloatở đó, nó sẽ có chi phí khá lớn so với hiệu suất của nó. Nhưng chúng ta đang nói về phần cực kỳ quan trọng nhất của động cơ đó, và tôi khá chắc chắn, vì nhà phát triển đã tối ưu hóa nó một cách khéo léo như thế nào, rằng anh ta đã đo lường điều đó và áp dụng tối ưu hóa đó một cách rất thận trọng, vì anh ta vui vẻ sử dụng các đối tượng ở mọi nơi khác chi phí tầm thường để theo dõi con đường thời gian thực ấn tượng của mình.

Tôi đã được một đồng nghiệp nói rằng trong việc tạo đối tượng Java là thao tác đắt nhất bạn có thể thực hiện. Vì vậy, tôi chỉ có thể kết luận để tạo ra càng ít đối tượng càng tốt.

Ngay cả trong một lĩnh vực quan trọng về hiệu suất như của tôi, bạn sẽ không viết các sản phẩm hiệu quả nếu bạn mắc kẹt ở những nơi không quan trọng. Tôi thậm chí sẽ đưa ra tuyên bố rằng các lĩnh vực quan trọng nhất về hiệu suất có thể đặt ra yêu cầu cao hơn về năng suất, vì chúng tôi cần tất cả thời gian để có thể điều chỉnh các điểm nóng thực sự quan trọng bằng cách không lãng phí thời gian vào những thứ không . Như với ví dụ về đường dẫn từ phía trên, tác giả đã khéo léo và thận trọng áp dụng những tối ưu hóa như vậy chỉ cho những nơi thực sự, thực sự quan trọng và có lẽ trong nhận thức sau khi đo, và vẫn vui vẻ sử dụng các đối tượng ở mọi nơi khác.


1
Đoạn đầu tiên của bạn là đi đúng hướng. Các không gian eden và trẻ đang sử dụng các bộ sưu tập sao chép có khoảng. 0 chi phí cho các đối tượng chết. Tôi rất khuyên bạn nên trình bày này . Bên cạnh đó, bạn nhận ra câu hỏi này là từ năm 2012, phải không?
JimmyJames

@JimmyJames Tôi cảm thấy buồn chán và thích chỉ xem qua các câu hỏi, kể cả những câu hỏi cũ. Tôi hy vọng mọi người đừng bận tâm đến sự cần thiết của tôi! :-D
Năng lượng rồng

@JimmyJames Không còn là trường hợp các loại hình hộp có yêu cầu bộ nhớ bổ sung và không được đảm bảo liền kề trong một mảng? Tôi có xu hướng nghĩ rằng các đối tượng là rẻ tiền trong Java, nhưng trường hợp chúng có thể tương đối đắt tiền giống như float[]so với Float[], trong đó việc xử lý một triệu trình tự trước đây có thể tương đối nhanh hơn lần thứ hai.
Năng lượng rồng

1
Khi tôi mới bắt đầu đăng bài ở đây, tôi cũng đã làm như vậy. Đừng ngạc nhiên khi câu trả lời cho các câu hỏi cũ có xu hướng nhận được rất ít sự chú ý.
JimmyJames

1
Tôi khá chắc chắn rằng các loại hình hộp nên được sử dụng để sử dụng nhiều không gian hơn so với nguyên thủy. Có khả năng tối ưu hóa xung quanh đó nhưng tôi nói chung, tôi nghĩ rằng nó là như bạn mô tả. Gil Tene (của bài trình bày ở trên) có một cuộc nói chuyện khác về đề xuất thêm một loại bộ sưu tập đặc biệt sẽ cung cấp một số lợi ích của cấu trúc nhưng vẫn sử dụng các đối tượng. Đó là một ý tưởng thú vị, tôi không chắc về trạng thái của JSR. Chi phí phần lớn là do thời gian là những gì bạn ám chỉ. Nếu bạn có thể tránh để cho các đối tượng của mình 'thoát', chúng thậm chí có thể được phân bổ ngăn xếp và không bao giờ chạm vào đống.
JimmyJames

0

Như mọi người đã nói việc tạo đối tượng không phải là một chi phí lớn trong Java (nhưng tôi đặt cược lớn hơn hầu hết các hoạt động đơn giản như thêm, v.v.) và bạn không nên tránh nó quá nhiều.

Điều đó nói rằng nó vẫn còn là một chi phí, và đôi khi bạn có thể thấy mình đang cố gắng loại bỏ càng nhiều đối tượng càng tốt. Nhưng chỉ sau khi hồ sơ đã cho thấy rằng đây là một vấn đề.

Đây là một bài thuyết trình tuyệt vời về chủ đề: https://www.cs.virginia.edu/kim/publicity/pldi09tutorials/memory-ffic-java-tutorial.pdf


0

Tôi đã làm một microbenchmark nhanh chóng về điều này và tôi đã cung cấp đầy đủ các nguồn trong github. Kết luận của tôi là việc tạo đối tượng có tốn kém hay không không phải là vấn đề, nhưng việc liên tục tạo đối tượng với quan niệm rằng GC sẽ chăm sóc mọi thứ cho bạn sẽ khiến ứng dụng của bạn kích hoạt quy trình GC sớm hơn. GC là một quá trình rất tốn kém và tốt nhất là tránh nó bất cứ khi nào có thể và không cố gắng đẩy nó để đá vào.


điều này dường như không cung cấp bất cứ điều gì đáng kể qua các điểm được thực hiện và giải thích trong 15 câu trả lời trước. Hầu như không có nội dung nào đáng để trả lời
gnat
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.