Vùng hằng số Chuỗi của Java sống ở đâu, đống hay ngăn xếp?


103

Tôi biết khái niệm về nhóm hằng số và nhóm hằng số chuỗi được sử dụng bởi JVM để xử lý các chuỗi ký tự. Nhưng tôi không biết loại bộ nhớ nào được sử dụng bởi JVM để lưu trữ các chuỗi ký tự hằng số. Ngăn xếp hay đống? Vì một chữ của nó không được liên kết với bất kỳ trường hợp nào nên tôi sẽ giả sử rằng nó sẽ được lưu trữ trong ngăn xếp. Nhưng nếu nó không được tham chiếu bởi bất kỳ trường hợp nào, nghĩa đen phải được thu thập bởi GC run (sửa cho tôi nếu tôi sai), vậy điều đó được xử lý như thế nào nếu nó được lưu trữ trong ngăn xếp?


11
Làm thế nào một hồ bơi có thể được lưu trữ trên ngăn xếp? bạn có biết khái niệm ngăn xếp không?
Scrum Meister

1
Xin chào Scrum Meister, tôi cố ý nói là không được. Xin lỗi vì quy ước sai. Về GC Vừa rồi tôi mới biết. Cảm ơn cho điều đó
Rengasami Ramanujam

@TheScrumMeister - trên thực tế, trong một số trường hợp, chúng có thể được thu gom. "Bộ ngắt giao dịch" là đối tượng mã cho bất kỳ lớp nào đề cập đến một ký tự chuỗi sẽ có tham chiếu đến đối tượng Chuỗi đại diện cho ký tự.
Stephen C

Câu trả lời:


74

Câu trả lời là không về mặt kỹ thuật. Theo Đặc điểm kỹ thuật máy ảo Java, khu vực lưu trữ các ký tự chuỗi nằm trong nhóm hằng số thời gian chạy . Vùng bộ nhớ nhóm hằng số thời gian chạy được phân bổ trên cơ sở mỗi lớp hoặc mỗi giao diện, vì vậy nó không bị ràng buộc với bất kỳ trường hợp đối tượng nào. Nhóm hằng số thời gian chạy là một tập hợp con của vùng phương thức "lưu trữ các cấu trúc cho mỗi lớp như nhóm hằng số thời gian chạy, dữ liệu trường và phương thức cũng như mã cho các phương thức và hàm tạo, bao gồm các phương thức đặc biệt được sử dụng trong giao diện và khởi tạo lớp và phiên bản kiểu khởi tạo ”. Thông số máy ảo nói rằng mặc dù khu vực phương thức về mặt logic là một phần của heap, nó không ra lệnh rằng bộ nhớ được cấp phát trong vùng phương thức phải chịu sự thu gom rác hoặc các hành vi khác sẽ được liên kết với cấu trúc dữ liệu bình thường được cấp phát cho heap.


8
Trên thực tế, khi các lớp được tải trong VM, các hằng số chuỗi sẽ được sao chép vào heap, vào một nhóm chuỗi toàn VM (trong permgen, như Stephen C đã nói), vì các ký tự chuỗi bằng nhau trong các lớp khác nhau phải là cùng một đối tượng Chuỗi (bởi JLS).
Paŭlo Ebermann

1
Cảm ơn tất cả các câu trả lời của bạn. Tôi đã hiểu rất nhiều điều với cuộc thảo luận này. Rất vui được biết các bạn :)
Rengasami Ramanujam

4
Paŭlo, điều đó đúng với máy ảo của Sun, nhưng không nhất thiết đúng với tất cả các triển khai của JVM. Như thông số JVM đã đề cập, mặc dù vùng phương thức và vùng hằng số thời gian chạy là một phần hợp lý của heap, chúng không nhất thiết phải có cùng một hành vi. Chỉ là một sự khác biệt nhỏ về ngữ nghĩa, thực sự :)
Duane Moore


54

Như đã giải thích bằng câu trả lời này , vị trí chính xác của nhóm chuỗi không được chỉ định và có thể thay đổi từ một triển khai JVM này sang triển khai JVM khác.

Điều thú vị là cho đến Java 7, nhóm nằm trong không gian cố định của heap trên hotspot JVM nhưng nó đã được chuyển đến phần chính của heap kể từ Java 7 :

Khu vực :
Tóm tắt nội dung HotSpot : Trong JDK 7, các chuỗi xen kẽ không còn được phân bổ trong thế hệ cố định của đống Java mà thay vào đó được phân bổ trong phần chính của đống Java (được gọi là thế hệ trẻ và cũ), cùng với các chuỗi khác các đối tượng do ứng dụng tạo ra. Thay đổi này sẽ dẫn đến nhiều dữ liệu nằm trong heap chính của Java và ít dữ liệu hơn trong thế hệ cố định và do đó có thể yêu cầu điều chỉnh kích thước heap. Hầu hết các ứng dụng sẽ chỉ thấy sự khác biệt tương đối nhỏ trong việc sử dụng heap do thay đổi này, nhưng các ứng dụng lớn hơn tải nhiều lớp hoặc sử dụng nhiều phương thức String.intern () sẽ thấy sự khác biệt đáng kể hơn. RFE: 6962931

Và trong Java 8 Hotspot, Permanent Generation đã bị loại bỏ hoàn toàn.


30

Chuỗi ký tự không được lưu trữ trên ngăn xếp. Không bao giờ. Trên thực tế, không có đối tượng nào được lưu trữ trên ngăn xếp.

Xâu (hay chính xác hơn, các đối tượng chuỗi đại diện cho họ) đang được lưu trữ lịch sử trong một Heap gọi là "PermGen" heap. (Permgen là viết tắt của thế hệ vĩnh viễn.)

Trong các trường hợp bình thường, các chuỗi ký tự và nhiều nội dung khác trong heap permgen có thể truy cập "vĩnh viễn" và không được thu thập rác. (Ví dụ: các ký tự chuỗi luôn có thể truy cập được từ các đối tượng mã sử dụng chúng.) Tuy nhiên, bạn có thể định cấu hình JVM để cố gắng tìm và thu thập các lớp được tải động không còn cần thiết nữa và điều này có thể khiến các ký tự chuỗi bị thu thập rác .

XÁC NHẬN # 1 - Tôi không nói rằng Permgen không đạt được GC'ed. Điều này xảy ra, thường là khi JVM quyết định chạy một GC đầy đủ. Quan điểm của tôi là các chuỗi ký tự sẽ có thể truy cập được miễn là có thể truy cập được mã sử dụng chúng và mã sẽ có thể truy cập được miễn là có thể truy cập được trình tải lớp của mã và đối với trình tải lớp mặc định, điều đó có nghĩa là "mãi mãi".

XÁC NHẬN # 2 - Trên thực tế, Java 7 trở lên sử dụng heap thông thường để giữ nhóm chuỗi. Do đó, các đối tượng String đại diện cho các chuỗi ký tự String và chuỗi intern'd thực sự nằm trong heap thông thường. (Xem Câu trả lời của @ assylias để biết thêm chi tiết.)


Nhưng tôi vẫn đang cố gắng tìm ra ranh giới giữa lưu trữ chuỗi ký tự và chuỗi được tạo bằng new.

Không có "đường mỏng". Cái này rất là đơn giản:

  • String các đối tượng đại diện / tương ứng với các ký tự chuỗi được giữ trong nhóm chuỗi.
  • Stringcác đối tượng được tạo bởi một String::interncuộc gọi được giữ trong nhóm chuỗi.
  • Tất cả các Stringđối tượng khác KHÔNG được giữ trong nhóm chuỗi.

Sau đó, có một câu hỏi riêng về nơi lưu trữ chuỗi ký tự. Trước Java 7, nó là heap permgen. Từ Java 7 trở đi, nó là đống chính.


23

Tổng hợp chuỗi

Tổng hợp chuỗi (đôi khi còn được gọi là chuỗi chuẩn hóa) là một quá trình thay thế một số đối tượng Chuỗi có giá trị bằng nhau nhưng khác danh tính bằng một đối tượng Chuỗi được chia sẻ duy nhất. Bạn có thể đạt được mục tiêu này bằng cách giữ Bản đồ của riêng mình (có thể có tham chiếu mềm hoặc yếu tùy thuộc vào yêu cầu của bạn) và sử dụng các giá trị bản đồ làm giá trị được chuẩn hóa. Hoặc bạn có thể sử dụng phương thức String.intern () được cung cấp cho bạn bởi JDK.

Tại thời điểm Java 6 sử dụng String.intern () bị cấm theo nhiều tiêu chuẩn do khả năng cao nhận được OutOfMemoryException nếu việc gộp chung vượt quá tầm kiểm soát. Việc triển khai gộp chuỗi trong Oracle Java 7 đã được thay đổi đáng kể. Bạn có thể tìm chi tiết trong http://bugs.sun.com/view_bug.do?bug_id=6962931http://bugs.sun.com/view_bug.do?bug_id=6962930 .

String.intern () trong Java 6

Trong những ngày xưa đó, tất cả các chuỗi xen kẽ được lưu trữ trong PermGen - phần kích thước cố định của heap chủ yếu được sử dụng để lưu trữ các lớp được tải và nhóm chuỗi. Bên cạnh các chuỗi được xen kẽ rõ ràng, nhóm chuỗi PermGen cũng chứa tất cả các chuỗi theo nghĩa đen được sử dụng trước đó trong chương trình của bạn (từ quan trọng ở đây được sử dụng - nếu một lớp hoặc phương thức chưa bao giờ được tải / gọi, bất kỳ hằng số nào được xác định trong nó sẽ không được tải).

Vấn đề lớn nhất với nhóm chuỗi trong Java 6 là vị trí của nó - PermGen. PermGen có kích thước cố định và không thể mở rộng trong thời gian chạy. Bạn có thể đặt nó bằng tùy chọn -XX: MaxPermSize = 96m. Theo như tôi biết, kích thước PermGen mặc định thay đổi từ 32M đến 96M tùy thuộc vào nền tảng. Bạn có thể tăng kích thước của nó, nhưng kích thước của nó vẫn sẽ cố định. Hạn chế như vậy yêu cầu sử dụng String.intern rất cẩn thận - bạn không nên thực hiện bất kỳ thông tin nhập liệu không được kiểm soát nào của người dùng bằng phương pháp này. Đó là lý do tại sao việc gộp chuỗi vào thời điểm của Java 6 chủ yếu được thực hiện trong các bản đồ được quản lý thủ công.

String.intern () trong Java 7

Các kỹ sư của Oracle đã thực hiện một thay đổi cực kỳ quan trọng đối với logic gộp chuỗi trong Java 7 - nhóm chuỗi đã được chuyển đến heap. Nó có nghĩa là bạn không còn bị giới hạn bởi một vùng bộ nhớ có kích thước cố định riêng biệt. Tất cả các chuỗi hiện được đặt trong heap, như hầu hết các đối tượng thông thường khác, cho phép bạn chỉ quản lý kích thước heap trong khi điều chỉnh ứng dụng của mình. Về mặt kỹ thuật, chỉ riêng điều này đã có thể là một lý do đủ để xem xét lại việc sử dụng String.intern () trong các chương trình Java 7 của bạn. Nhưng có những lý do khác.

Giá trị nhóm chuỗi được thu thập rác

Có, tất cả các chuỗi trong nhóm chuỗi JVM đủ điều kiện để thu gom rác nếu không có tham chiếu đến chúng từ gốc chương trình của bạn. Nó áp dụng cho tất cả các phiên bản Java được thảo luận. Có nghĩa là nếu chuỗi được thực tập của bạn đã vượt ra ngoài phạm vi và không có tham chiếu nào khác đến nó - thì nó sẽ được thu thập từ nhóm chuỗi JVM.

Đủ điều kiện để thu gom rác và cư trú trong đống, một nhóm chuỗi JVM có vẻ là một nơi thích hợp cho tất cả các chuỗi của bạn, phải không? Về lý thuyết thì đúng là như vậy - các chuỗi không được sử dụng sẽ được gom lại từ nhóm, các chuỗi đã sử dụng sẽ cho phép bạn tiết kiệm bộ nhớ trong trường hợp sau đó bạn nhận được một chuỗi bằng nhau từ đầu vào. Có vẻ là một chiến lược tiết kiệm bộ nhớ hoàn hảo? Gần như vậy. Bạn phải biết nhóm chuỗi được triển khai như thế nào trước khi đưa ra bất kỳ quyết định nào.

nguồn.


11

Như các câu trả lời khác giải thích Bộ nhớ trong Java được chia thành hai phần

1. Ngăn xếp: Một ngăn xếp được tạo trên mỗi luồng và nó lưu trữ các khung ngăn xếp để lưu trữ lại các biến cục bộ và nếu một biến là kiểu tham chiếu thì biến đó sẽ tham chiếu đến một vị trí bộ nhớ trong heap cho đối tượng thực.

2. Heap: Tất cả các loại đối tượng sẽ chỉ được tạo trong heap.

Bộ nhớ đống lại được chia thành 3 phần

1. Young Generation: Lưu trữ những đồ vật có tuổi thọ ngắn, bản thân Young Generation có thể được chia thành hai loại Eden SpaceSurvivor Space .

2. Thế hệ cũ: Lưu trữ các đối tượng đã tồn tại qua nhiều chu kỳ thu gom rác và vẫn được tham chiếu.

3. Tạo vĩnh viễn: Lưu trữ siêu dữ liệu về chương trình, ví dụ nhóm hằng số thời gian chạy.

Nhóm hằng chuỗi thuộc về vùng tạo vĩnh viễn của bộ nhớ Heap.

Chúng ta có thể thấy nhóm hằng số thời gian chạy cho mã của chúng ta trong bytecode bằng cách sử dụng javap -verbose class_namenó sẽ hiển thị cho chúng ta các tham chiếu phương thức (#Methodref), đối tượng lớp (#Class), chuỗi ký tự (#String)

thời gian chạy-hằng số-nhóm

Bạn có thể đọc thêm về nó trên bài viết của tôi Cách xử lý quá tải và ghi đè phương pháp JVM trong nội bộ .


Vui lòng tiết lộ bất kỳ chi nhánh nào và không sử dụng trang web như một cách để quảng bá trang web của bạn thông qua đăng bài. Xem Làm thế nào để tôi viết một câu trả lời tốt? .

9

Đối với những câu trả lời tuyệt vời đã có ở đây, tôi muốn thêm một thứ gì đó còn thiếu trong góc nhìn của tôi - một hình minh họa.

Như bạn đã biết, JVM chia bộ nhớ được cấp phát cho một chương trình Java thành hai phần. một cái là ngăn xếp và một cái khác là đống . Stack được sử dụng cho mục đích thực thi và heap được sử dụng cho mục đích lưu trữ. Trong bộ nhớ heap đó, JVM phân bổ một số bộ nhớ dành riêng cho các ký tự chuỗi. Phần này của bộ nhớ heap được gọi là nhóm hằng số chuỗi .

Vì vậy, ví dụ: nếu bạn nhập các đối tượng sau:

String s1 = "abc"; 
String s2 = "123";
String obj1 = new String("abc");
String obj2 = new String("def");
String obj3 = new String("456);

Chuỗi ký tự s1s2sẽ chuyển đến nhóm hằng chuỗi, các đối tượng obj1, obj2, obj3 vào heap. Tất cả chúng, sẽ được tham chiếu từ Ngăn xếp.

Ngoài ra, xin lưu ý rằng "abc" sẽ xuất hiện trong heap và trong nhóm hằng chuỗi. Tại sao đang String s1 = "abc"String obj1 = new String("abc")sẽ được tạo ra theo cách này? Đó là bởi vì String obj1 = new String("abc")tạo ra một thể hiện mới và khác biệt rõ ràng về đối tượng String và String s1 = "abc"có thể sử dụng lại một thể hiện từ nhóm hằng chuỗi nếu có. Để được giải thích kỹ hơn: https://stackoverflow.com/a/3298542/2811258

nhập mô tả hình ảnh ở đây


Trong sơ đồ đã cho, các chữ "def" và "456" sẽ tồn tại ở đâu. Và chúng sẽ được tham chiếu như thế nào?
Satyendra

Cảm ơn bình luận của bạn @Satyendra, mình đã cập nhật hình ảnh minh họa và đáp án.
Johnny

@ Tại sao một đối tượng Chuỗi khác "abc" được tạo ra..nó nên sử dụng tham chiếu obj1 để trỏ đúng nghĩa đen?

Đó là bởi vì String obj1 = new String ("abc") tạo một cách rõ ràng một thể hiện mới và khác biệt về mặt tham chiếu của một đối tượng String và String s1 = "abc" có thể sử dụng lại một thể hiện từ nhóm hằng chuỗi nếu có. Đối với một lời giải thích phức tạp hơn: stackoverflow.com/a/3298542/2811258
Johnny
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.