1. 객체 및 메모리 제어에 대한 지식
1. 로컬 변수, 멤버 변수 (인스턴스 변수 및 클래스 변수)를 포함한 Java 변수의 초기화 프로세스.
2. 상속 관계에서, 컴파일 타임 유형 및 런타임 유형이 사용 된 객체 참조 변수의 컴파일 타임 유형과 다를 때, 객체에 액세스하는 속성과 방법에 차이가 있습니다.
3. 최종 수정 자 특성.
2. Java 변수의 부서 및 초기화 과정
Java 프로그램의 변수는 대략 회원 변수 및 로컬 변수로 나눌 수 있습니다. 멤버 변수는 인스턴스 변수 (비 정적 변수) 및 클래스 변수 (정적 변수)로 나눌 수 있습니다. 일반적으로 우리가 만나는 로컬 변수는 다음과 같은 상황에 나타납니다.
(1) 공식 매개 변수 : 메소드 서명에 정의 된 로컬 변수는 발신자가 할당하고 메소드가 끝나면 사라집니다.
(2) 메소드 내의 로컬 변수 : 방법에 정의 된 로컬 변수는 메소드에서 초기화 (초기 값을 지정)해야하며 변수 초기화가 시작되고 종료되면 사라집니다.
(3) 코드 블록의 로컬 변수 : 코드 블록에 정의 된 로컬 변수는 코드 블록에 표시 해야하는 초기 값을 초기화해야합니다 (초기 값 지정). 초기화가 완료되고 코드 블록이 끝나면 죽을 때 효과가 있습니다.
패키지 com.zlc.array; 공개 클래스 테스트 필드 {{문자열 b; // 초기화되지 않으면 컴파일러는 로컬 변수 B가 초기화되지 않았을 수 있다고보고합니다. } public static void main (String [] args) {int a; // 초기화되지 않으면 컴파일러는 로컬 변수를보고합니다. }} 정적으로 수정 된 멤버 변수는 클래스 자체에 속하는 클래스 변수입니다. 정적으로 수정되지 않은 멤버 변수는 인스턴스 변수입니다. 동일한 JVM 에서이 클래스에 속하는 인스턴스는 각 클래스가 하나의 클래스 객체에만 해당 할 수 있지만 각 클래스는 여러 개의 Java 객체를 생성 할 수 있습니다. (즉, 클래스 변수에는 한 가지 메모리 공간이 필요하며 클래스가 인스턴스를 생성 할 때마다 인스턴스 변수에 공간을 할당해야합니다).
인스턴스 변수의 초기화 프로세스 : 구문 관점에서 프로그램은 세 곳에서 인스턴스 변수의 초기화를 수행 할 수 있습니다.
(1) 인스턴스 변수를 정의 할 때 초기 값을 지정합니다.
(2) 비 정적 블록의 예를 들어 초기 값을 지정합니다.
(3) 생성자의 예를 들어 초기 값을 지정합니다.
그중 두 가지 방법 (1)과 (2)의 초기화 시간은 생성자에서 (3)의 초기화 시간보다 이르고, 두 개의 초기화 순서 (1) 및 (2)는 소스 코드에 배열 된 순서로 결정됩니다.
패키지 com.zlc.array; Public Class Testfield {public testfield (int age) {system.out.println ( "생성자에서 초기화 this.age ="+this.age); this.age = age; } {System.out.println ( "정적이 아닌 블록에서 초기화"); 나이 = 22; } // int age = 15를 초기화합니다. public static void main (String [] args) {testfield 필드 = New Testfield (24); System.out.println ( "최종 age ="+field.age); }} 실행 결과는 다음과 같습니다. 초기화 this.age = 15 비 정적 블록의 초기화 생성자
마지막 나이 = 24
Javap을 사용하는 방법을 알고 있다면 Javap -C XXXX (클래스 파일)를 사용하여 Java 클래스가 어떻게 컴파일되는지 확인할 수 있습니다.
인스턴스 변수를 정의 할 때 초기 값을 지정하십시오. 초기화 블록에서 인스턴스 변수의 초기 값을 지정하는 명령문의 상태는 동일합니다. 컴파일러가 컴파일되고 처리 된 후에는 모두 생성자에 언급됩니다. 위에서 언급 한 int 연령 = 15는 다음 두 단계로 나뉩니다.
1) int 연령; Java 객체를 만들 때 시스템은 문에 따라 메모리를 객체에 할당합니다.
2) 나이 = 15; 이 진술은 Java 클래스의 생성자로 추출되어 실행됩니다.
클래스 변수의 초기화 프로세스 : 구문 관점에서 프로그램은 두 곳에서 클래스 변수에 값을 초기화하고 할당 할 수 있습니다.
(1) 클래스 변수를 정의 할 때 초기 값을 지정합니다.
(2) 정적 블록에서 클래스 변수의 초기 값을 지정합니다.
두 실행 주문은 소스 코드의 배열과 동일합니다. 작은 비정상적인 예를 들어 봅시다 :
패키지 com.zlc.array; 클래스 teststatic {// 클래스 멤버 데모 테스트 스틱 인스턴스 최종 정적 테스트 스테이션 데모 = new teststatic (15); // 클래스 멤버 static int age = 20; // 인스턴스 변수 곡률 int Curage; public teststatic (int 년) {// todo 자동 생성 생성자 스터브 곡선 = 연령 - 년; }} public class test {public static void main (String [] args) {System.out.println (teststatic.demo.curage); teststatic staticdemo = new teststatic (15); System.out.println (staticdemo.curage); }} 출력 결과는 두 줄로 인쇄됩니다. 하나는 TestStatic Class 속성 데모의 인스턴스 변수를 인쇄하는 것이고, 두 번째는 Java 객체의 정적 데모를 통해 TestStatic의 인스턴스 속성을 출력하는 것입니다. 위에서 분석 한 인스턴스 변수 및 클래스 변수의 초기화 프로세스에 따르면 다음을 추론 할 수 있습니다.
1) 초기화의 첫 번째 단계에서 클래스를로드 할 때 클래스 변수 데모 및 나이에 대한 메모리 공간을 할당하십시오. 현재 데모와 나이의 기본값은 각각 NULL과 0입니다.
2) 초기화의 두 번째 단계에서 프로그램은 초기 값을 데모 및 시퀀스에 시전에 할당합니다. TestStatic (15)은 TestStatic의 생성자를 호출해야합니다. 현재 Age = 0이므로 인쇄 결과는 -15입니다. 정전기가 초기화되면 연령이 20에 할당되었으므로 출력 결과는 5입니다.
3. 상속 관계에서 멤버 변수 상속 및 멤버 메소드 상속의 차이
Java 객체를 만들 때 프로그램은 항상 부모 클래스의 비 정적 블록 및 상위 클래스 생성자를 먼저 호출하고 마지막 으로이 클래스의 비 정적 블록 및 생성자를 호출합니다. 서브 클래스의 생성자를 통해 부모 클래스의 생성자를 호출하는 것은 일반적으로 두 가지 상황으로 나뉩니다. 하나는 암시 적 호출이고, 다른 하나는 부모 클래스의 생성자를 호출하는 슈퍼 디스플레이입니다.
아동 클래스 방법은 부모 클래스의 인스턴스 변수를 호출 할 수 있습니다. 어린이 클래스가 상위 클래스를 상속하고 부모 클래스의 멤버 변수 및 방법을 얻기 때문입니다. 그러나 부모 클래스는 상위 클래스가 상속 될 클래스와 하위 클래스가 추가 할 멤버 변수를 알지 못하기 때문에 부모 클래스 메소드는 하위 클래스의 인스턴스 변수에 액세스 할 수 없습니다. 물론, 일부 극단적 인 예에서, 부모 수업은 여전히 아동 계급 변수를 호출 할 수 있습니다. 예를 들어, 아동 클래스는 부모 클래스 메소드를 다시 작성하고 일반적으로 기본값을 인쇄합니다.이 시점에서 아동 클래스의 인스턴스 변수가 초기화되지 않았기 때문입니다.
package com.zlc.array; 클래스 아버지 {int age = 50; 공개 아버지 () {// TODO 자동 생성 생성자 스텁 시스템 .out.println (this.getClass ()); //this.sonMethod (); 정보를 호출 할 수 없습니다 (); } public void info () {System.out.println (Age); }} 공공 계급 아들은 아버지를 확장합니다 {int age = 24; 공개 아들 (int Age) {// todo 자동 생성 생성자 스터브 this.age = age; } @override public void info () {// todo 자동 생성 메소드 스터브 시스템.err.println (age); } public static void main (String [] args) {New Son (28); } // 서브 클래스-특이 적 메소드 public void sonmethod () {system.out.println ( "son method"); }} 우리의 정상적인 추론에 따르면, 부모 클래스 생성자는 하위 클래스를 통해 암시 적으로 호출되며, info () 메소드는 부모 클래스 생성자에서 호출됩니다 (참고 : 부모 클래스가 호출된다고 말하지 않았습니다). 이론적으로는 상위 클래스의 연령 인스턴스 변수를 출력합니다. 인쇄 결과는 50이 될 것으로 예상되지만 실제 출력 결과는 0입니다. 이유 분석 :
1) Java 객체의 메모리 할당은 생성자에서 완료되지 않습니다. 생성자는 초기화 할당 프로세스 만 완료합니다. 즉, 부모 클래스의 생성자를 호출하기 전에 JVM은 아들 객체의 메모리 공간을 분류했습니다. 이 공간은 두 가지 연령 속성을 저장합니다. 하나는 서브 클래스의 나이이고 다른 하나는 부모 계급의 나이입니다.
2) 새 아들 (28)을 부를 때, 현재이 대상은 서브 클래스 아들 인 대상을 나타냅니다. Object.getClass ()를 인쇄하고 클래스 com.zlc.array.son의 결과를 얻을 수 있습니다. 그러나 현재 초기화 프로세스는 부모 클래스의 생성자에서 수행 되며이 컴파일 유형은 아버지이기 때문에 this.sonmethod ()를 통해 호출 할 수 없습니다.
3) 변수의 컴파일 타임 유형이 런타임 유형과 다른 경우, 변수를 통해 기준 객체의 인스턴스 변수에 액세스 할 때 인스턴스 변수의 값은 선언 된 변수의 유형에 의해 결정됩니다. 그러나 객체의 인스턴스 메소드가 변수를 통해 참조되는 경우, 메소드의 동작은 실제로 참조하는 객체에 의해 결정됩니다. 따라서 서브 클래스의 정보 방법이 여기에서 호출되므로 서브 클래스의 나이가 인쇄됩니다. 나이가 아직 긴급하게 초기화되지 않았으므로 기본값은 0입니다.
Layman의 용어에서, 선언 된 유형이 실제 새로운 유형과 일치하지 않는 경우, 사용 된 속성은 부모 클래스이고 메소드는 Child Class입니다.
JAVAP -C를 통해 속성 상속과 방법 사이에 큰 차이가있는 이유를 더 직접 이해할 수 있습니다. 위의 예제에서 서브 클래스 아들의 정보 재 작성 방법을 제거하면이 시점에서 부모 클래스의 정보 메소드가 호출됩니다. 컴파일시, 상위 클래스의 정보 메소드가 서브 클래스로 전송되고 평판 멤버 변수가 부모 클래스에 남아 전송되지 않기 때문입니다. 이런 식으로 서브 클래스와 상위 클래스에는 동일한 이름의 인스턴스 변수가 있습니다. 서브 클래스가 동일한 이름으로 상위 클래스의 메소드를 다시 작성하면 서브 클래스 메소드가 부모 클래스의 메소드를 완전히 덮어 씁니다 (Java가 이와 같이 설계된 이유는별로 명확하지 않습니다). 이름이 같은 변수는 존재할 수 있으며 동시에 덮어 쓰지 않습니다. 동일한 이름을 가진 메소드의 서브 클래스는 부모 클래스의 동일한 이름 메소드를 완전히 덮어 씁니다.
일반적으로, 참조 변수의 경우, 변수를 통해 참조하는 객체의 인스턴스 변수에 액세스 할 때 인스턴스 변수의 값은 변수가 선언 될 때 유형에 따라 다르며 변수를 통해 참조 된 객체의 메소드에 액세스 할 때 메소드 동작은 실제로 참조하는 객체의 유형에 따라 다릅니다.
마지막으로 작은 사례로 검토하겠습니다.
package com.zlc.array; 계급 동물 {int age; public Animal () {} public Animal (int Age) {// TODO 자동 생성 생성자 스터브 this.age = age; } void run () {System.out.println ( "동물 런"+age); }} 클래스 개는 동물 확장 {int age; 문자열 이름; 공개 개 (int age, string name) {// todo 자동 생성 생성자 스터브 this.age = age; this.name = 이름; } @override void run () {System.out.println ( "Dog Run"+age); }} public class rectextends {public static void main (String [] args) {Animal Animal = New Animal (5); System.out.println (Animal.age); Animal.run (); 개 개 = 새로운 개 (1, "Xiaobai"); System.out.println (dog.age); dog.run (); Animal Animal2 = 새로운 개 (11, "Wangcai"); System.out.println (Animal2.age); 동물성 2.run (); 동물 동물 3; 동물 3 = 개; System.out.println (Animal3.age); 동물 3.run (); }} 상위 클래스 메소드를 호출하려면 : Super를 통해 호출 할 수 있지만 Super 키워드는 객체를 참조하지 않으며 실제 참조 변수로 사용할 수 없습니다. 관심있는 친구들은 직접 공부할 수 있습니다.
위는 예제 변수 및 메소드입니다. 클래스 변수와 클래스 방법은 훨씬 간단하므로 클래스 이름을 직접 사용합니다. 이 방법은 훨씬 더 편리하며 그렇게 많은 문제가 발생하지 않습니다.
4. 최종 수정 자 사용 (특히 매크로 교체)
(1) INAL은 변수를 수정할 수 있습니다. Final에 의해 수정 된 변수에 초기 값이 할당 된 후에는 다시 할당 할 수 없습니다.
(2) INAL은 방법을 수정할 수 있으며 최종 수정 된 방법을 다시 작성할 수 없습니다.
(3) INAL은 클래스를 수정할 수 있으며 Final에 의해 수정 된 클래스는 서브 클래스를 도출 할 수 없습니다.
최종적으로 수정 된 변수를 표시 해야하는 지정된 초기 값을 표시해야합니다.
예를 들어 최종 수정 된 변수와 같은 초기 값은 다음 세 가지 위치에서만 할당 할 수 있습니다.
(1) 최종 인스턴스 변수를 정의 할 때 초기 값을 지정합니다.
(2) 비 정적 블록에서 최종 인스턴스 변수의 초기 값을 지정합니다.
(3) 생성자의 최종 인스턴스 변수의 초기 값을 지정합니다.
그것들은 결국 초기화를 위해 생성자에 언급 될 것입니다.
최종 : 최종 값으로 지정된 클래스 변수의 경우 초기 값은 지정된 두 장소에서만 할당 할 수 있습니다.
(1) 최종 클래스 변수를 정의 할 때 초기 값을 지정합니다.
(2) 정적 블록에서 최종 클래스 변수의 초기 값을 지정합니다.
인스턴스 변수와 달리 컴파일러에 의해 처리 된 클래스 변수는 모두 정적 블록에 초기 값을 할당하도록 언급되는 반면 인스턴스 변수는 생성자에게 언급됩니다.
Final에 의해 수정 된 클래스 변수의 또 다른 기능은 "매크로 교체"입니다. 변수를 정의 할 때 수정 된 클래스 변수가 초기 값을 충족 할 때, 편집 중에 초기 값을 결정할 수 있습니다 (예 : 18, "AAAA", 16.78 및 기타 직접 수량), Final에 의해 수정 된 클래스 변수는 변수가 아니며 시스템은이를 "매크로 변수"로 취급합니다. 편집 중에 초기 값을 결정할 수있는 경우, 초기화를 위해 정적 블록에 언급되지 않으며 초기 값은 클래스 정의의 최종 변수로 직접 대체됩니다. 연도의 연도의 예를 들어 봅시다.
패키지 com.zlc.array; 클래스 teststatic {// 클래스 멤버 데모 테스트 스틱 인스턴스 최종 정적 테스트 스테이션 데모 = new teststatic (15); // 클래스 멤버 연령 최종 정적 int 연령 = 20; // 인스턴스 변수 곡률 int Curage; public teststatic (int 년) {// todo 자동 생성 생성자 스터브 곡선 = 연령 - 년; }} public class test {public static void main (String [] args) {System.out.println (teststatic.demo.curage); teststatic static1 = new teststatic (15); System.out.println (static1.curage); }} 현재 연령은 최종적으로 수정되므로 컴파일 할 때 상위 클래스의 모든 연령이 변수가 아닌 20이되어 출력 결과가 기대에 부응 할 수 있습니다.
특히 문자열을 비교할 때 더 많이 표시 할 수 있습니다
패키지 com.zlc.array; public class teststring {static String static_name1 = "java"; 정적 문자열 static_name2 = "me"; 정적 문자열 static statci_name3 = static_name1+static_name2; 최종 정적 문자열 final_static_name1 = "java"; 최종 정적 문자열 final_static_name2 = "me"; // 최종 추가 여부를 추가하면 전면의 매크로로 교체 할 수 있습니다. 최종 문자열 Final_Statci_name3 = final_static_name1+final_static_name2; public static void main (String [] args) {String name1 = "java"; 문자열 이름 2 = "me"; 문자열 이름 3 = name1+name2; // (1) System.out.println (name3 == "javame"); // (2) System.out.println (teststring.statci_name3 == "javame"); // (3) System.out.println (teststring.final_statci_name3 == "javame"); }} 최종 수정 방법과 클래스 사용에 대한 말은 없습니다. 단지 하나는 서브 클래스 (비공개)로 다시 작성할 수 없으며 다른 하나는 서브 클래스를 도출 할 수 없다는 것입니다.
최종 변수를 사용하여 로컬 변수를 수정할 때 Java는 내부 클래스에서 액세스하는 로컬 변수를 최종으로 수정해야합니다. 이유가 있습니다. 일반적인 로컬 변수의 경우 범위가 메소드 내에 남아 있습니다. 메소드가 종료되면 로컬 변수가 사라지지만 내부 클래스는 암시 적 "클로저"를 생성 할 수있어 로컬 변수가 위치한 방법과 분리되어 있습니다.
때로는 스레드가 메소드에서 새 스레드가 새로운 다음 메소드의 로컬 변수가 호출됩니다. 현재 변경 변수는 최종 수정으로 선언해야합니다.
5. 객체 점유 메모리의 계산 방법
Java.lang.runtime 클래스에서 Freememory (), TotalMemory () 및 MaxMemory () 메소드를 사용하여 Java 객체의 크기를 측정하십시오. 이 방법은 일반적으로 많은 리소스를 정확하게 결정해야 할 때 사용됩니다. 이 방법은 생산 시스템 캐시를 구현하는 데 거의 쓸모가 없습니다. 이 방법의 장점은 데이터 유형이 크기와 무관하며 다른 운영 체제가 점유 된 메모리를 얻을 수 있다는 것입니다.
반사 API를 사용하여 객체의 멤버 변수의 계층 구조를 통과하고 모든 원래 변수의 크기를 계산합니다. 이 접근법은 많은 리소스가 필요하지 않으며 캐시 된 구현에 사용될 수 있습니다. 단점은 원래 유형 크기가 다르고 JVM 구현이 다른 계산 방법이 있다는 것입니다.
JDK5.0 후, 계측 API는 객체가 차지하는 메모리 크기를 계산하기위한 getObjectSize 메소드를 제공합니다.
기본적으로 참조 된 객체의 크기는 계산되지 않습니다. 참조 된 객체를 계산하려면 반사를 사용하여 얻을 수 있습니다. 다음 방법은 위의 기사에서 제공된 구현입니다. 참조 객체의 크기를 계산합니다.
공개 클래스 크기 -Agent {정적 인스트루먼트 inst; / ** 에이전트 초기화*/ public static void premain (String agentargs, Instrumentation Instp) {inst = instp; } /*** 멤버 서브 객체없이 객체 크기를 반환합니다. * @param o 객체 * @return 오브젝트 크기 */public static long sizeof (object o) {if (inst == null) { "새로운 불법 행위 환경에 액세스 할 수 없음 ./n" + "JAVA의 크기가 포함되어 있는지 여부를 확인하십시오. } return inst.getObjectSize (o); } /** * 계층 구조 그래프를 반복하는 객체의 전체 크기를 계산합니다. * @param 객체 * @return 객체 크기를 계산하는 */ public static long fullsize (object obj) {map <object, object> visited = new IdentityHashMap <object, object> (); 스택 <오브젝트> 스택 = 새 스택 <오브젝트> (); 긴 결과 = InternalSizeof (OBJ, 스택, 방문); while (! stack.isempty ()) {result += internalsizeof (stack.pop (), 스택, 방문); } visited.clear (); 반환 결과; } private static boolean skipobject (object obj, map <object, object> visited) {if (obj instanceof string) {// if (obj == (String) obj) .intern ()) {return true; }} return (obj == null) // 방문한 객체 건너 뛰기 || 방문. } private static long internalsizeof (Object obj, stack <bood> 스택,지도 <개체, 개체> 방문) {if (skipobject (obj, visited)) {return 0; } visited.put (obj, null); 긴 결과 = 0; // 객체의 크기를 얻습니다 + 원시 변수 + 멤버 포인트 결과 + = sizeofagent.sizeof (OBJ); // 모든 배열 요소를 처리 클래스 클래스 = obj.getClass (); if (clazz.isarray ()) {if (clazz.getName (). length ()! = 2) {// 원시 유형 array int length = array.getlength (obj); for (int i = 0; i <length; i ++) {stack.add (array.get (obj, i)); }} 반환 결과; } // 객체의 모든 필드를 처리합니다 (Clazz! = null) {field [] fields = clazz.getDeclaredFields (); for (int i = 0; i <fields.length; i ++) {if (! modifier.isstatic (fields [i] .getModifiers ())) {if (fields [i] .getType (). isprimital ())) {계속; // 원시 필드를 건너 뛰기} else {fields [i] .setAccessible (true); try {// 추정 될 객체는 ObjectToadd = fields [i] .get (obj); if (ObjectToadd! = null) {stack.add (ObjectToadd); }} catch (delegalaccessexception ex) {false; }}}}} clazz = clazz.getSuperClass (); } 반환 결과; }}