Javaインタビューの質問の全集(3)

Javaインタビューの質問の全集(3)

羅ハオITハハ

21.JVMがクラスファイルをロードする原理とメカニズムを説明してください。

回答:JVMでのクラスのロードは、クラスローダー(ClassLoader)とそのサブクラスによって実装されます。Javaのクラスローダーは、実行時にクラスを検索してロードする重要なJavaランタイムシステムコンポーネントです。ファイル内のクラス。
Javaはクロスプラットフォームであるため、コンパイルされたJavaソースプログラムは実行可能なプログラムではなく、1つ以上のクラスファイルです。Javaプログラムが特定のクラスを使用する必要がある場合、JVMは、このクラスがロードされ、接続(検証、準備、および解析)され、初期化されていることを確認します。クラスの読み込みとは、通常、.classファイルに読み込むバイト配列を作成し、読み込まれたクラスに対応するClassオブジェクトを生成することにより、クラスの.classファイルのデータをメモリに読み込むことを指します。ロード後、Classオブジェクトは完了していないため、現時点ではクラスはまだ利用できません。クラスがロードされると、接続フェーズに入ります。このフェーズには、検証、準備(静的変数にメモリを割り当て、デフォルトの初期値を設定)、および解決(シンボル参照を直接参照に置き換える)の3つのステップが含まれます。最後に、JVMは次のようにクラスを初期化します。1)クラスに直接の親クラスがあり、クラスが初期化されていない場合は、最初に親クラスを初期化します。2)クラスに初期化ステートメントがある場合、これらの初期化ステートメントは順番に実行されます。
クラスのロードは、ルートローダー(BootStrap)、拡張ローダー(Extension)、システムローダー(System)、およびユーザー定義のクラスローダー(java.lang.ClassLoader)を含むクラスローダーによって行われます。サブクラス)。Java 2(JDK 1.2)以降、クラスのロードプロセスに親委任メカニズム(PDM)が採用されました。PDMは、Javaプラットフォームのセキュリティをより適切に保証します。このメカニズムでは、JVMに付属するブートストラップがルートローダーであり、他のローダーには親ローダーが1つしかありません。クラスのロードは、最初に親クラスローダーにロードを要求し、親クラスローダーが何もできないときに子クラスローダーがそれをロードします。JVMは、Javaプログラムへのブートストラップへの参照を提供しません。以下は、いくつかのクラスローダーの説明です。

  • ブートストラップ:通常、ローカルコードで実装され、JVM基本コアクラスライブラリ(rt.jar)のロードを担当します。
  • 拡張機能:java.ext.dirsシステムプロパティで指定されたディレクトリからクラスライブラリをロードします。その親ローダーはBootstrapです。
  • システム:アプリケーションクラスローダーとも呼ばれ、その親クラスはExtensionです。これは最も広く使用されているクラスローダーです。これは、環境変数classpathまたはシステムプロパティjava.class.pathで指定されたディレクトリからクラスを記録し、ユーザー定義ローダーのデフォルトの親ローダーです。

22.中国語の文字をchar変数に格納できますか?なぜですか?

回答:Javaで使用されるエンコーディングはUnicodeであるため(特定のエンコーディングを選択せず​​、文字セット内の文字の番号を直接使用します。これが唯一の統一された方法です)、charタイプは中国語の文字を格納できます。charタイプは2文字を占有します。セクション(16ビット)なので、中国語を入れても大丈夫です。

補足:Unicodeの使用は、文字がJVMの内部と外部で異なる表現を持つことを意味します。JVMの内部では、それらはすべてUnicodeです。文字がJVMから外部(ファイルシステムに格納されているなど)に転送される場合、エンコード変換が必要です。したがって、Javaにはバイトストリームと文字ストリーム、および文字ストリームとバイトストリーム(InputStreamReaderやOutputStreamReaderなど)を変換する変換ストリームがあります。これら2つのクラスは、バイトストリームと文字ストリーム間のアダプタクラスです。コード変換のタスク; Cプログラマーにとって、そのようなコード変換を完了するために、私は達成するためにユニオン(ユニオン/ユニオン)共有メモリの特性に依存することを恐れています。

23.抽象クラスとインターフェースの類似点と相違点は何ですか?

回答:抽象クラスとインターフェースはインスタンス化できませんが、抽象クラスとインターフェースタイプへの参照は定義できます。クラスが抽象クラスを継承するか、インターフェイスを実装する場合は、その中のすべての抽象メソッドを実装する必要があります。そうでない場合でも、クラスを抽象クラスとして宣言する必要があります。抽象クラスはコンストラクター、抽象メソッド、および具象メソッドを定義できますが、インターフェースはコンストラクターを定義できず、すべてのメソッドが抽象メソッドであるため、インターフェースは抽象クラスよりも抽象的です。抽象クラスのメンバーはprivate、default、protected、およびpublicにすることができますが、インターフェイスのメンバーはすべてpublicです。メンバー変数は抽象クラスで定義でき、インターフェースで定義されたメンバー変数は実際には定数です。抽象メソッドを持つクラスは抽象クラスとして宣言する必要があり、抽象クラスには必ずしも抽象メソッドがあるとは限りません。

24.静的ネストクラスと内部クラスの違いは何ですか?

回答:静的ネストクラスは、静的として宣言されている内部クラスであり、外部クラスインスタンスに依存せずにインスタンス化できます。通常の内部クラスは、外部クラスがインスタンス化された後にインスタンス化する必要があり、以下に示すように、構文は非常に奇妙に見えます。


/**
 * 扑克类(一副扑克)
 * @author 骆昊
 *
 */
public class Poker {
    private static String[] suites = {"黑桃", "红桃", "草花", "方块"};
    private static int[] faces = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13};

    private Card[] cards;

    /**
     * 构造器
     * 
     */
    public Poker() {
        cards = new Card[52];
        for(int i = 0; i < suites.length; i++) {
            for(int j = 0; j < faces.length; j++) {
                cards[i * 13 + j] = new Card(suites[i], faces[j]);
            }
        }
    }

    /**
     * 洗牌 (随机乱序)
     * 
     */
    public void shuffle() {
        for(int i = 0, len = cards.length; i < len; i++) {
            int index = (int) (Math.random() * len);
            Card temp = cards[index];
            cards[index] = cards[i];
            cards[i] = temp;
        }
    }

    /**
     * 发牌
     * @param index 发牌的位置
     * 
     */
    public Card deal(int index) {
        return cards[index];
    }

    /**
     * 卡片类(一张扑克)
     * [内部类]
     * @author 骆昊
     *
     */
    public class Card {
        private String suite;   // 花色
        private int face;       // 点数

        public Card(String suite, int face) {
            this.suite = suite;
            this.face = face;
        }

        @Override
        public String toString() {
            String faceStr = "";
            switch(face) {
            case 1: faceStr = "A"; break;
            case 11: faceStr = "J"; break;
            case 12: faceStr = "Q"; break;
            case 13: faceStr = "K"; break;
            default: faceStr = String.valueOf(face);
            }
            return suite + faceStr;
        }
    }
}

テストコード:


class PokerTest {
    public static void main(String[] args) {
        Poker poker = new Poker();
        poker.shuffle();                // 洗牌
        Poker.Card c1 = poker.deal(0);  // 发第一张牌
        // 对于非静态内部类Card
        // 只有通过其外部类Poker对象才能创建Card对象
        Poker.Card c2 = poker.new Card("红心", 1);    // 自己创建一张牌
        System.out.println(c1);     // 洗牌后的第一张
        System.out.println(c2);     // 打印: 红心A
    }
}

インタビューの質問-次のコードはどこでコンパイルエラーを生成しますか?


class Outer {
    class Inner {}
    public static void foo() { new Inner(); }
    public void bar() { new Inner(); }
    public static void main(String[] args) {
        new Inner();
    }
}

注:Javaでの非静的内部クラスオブジェクトの作成は、外部クラスオブジェクトに依存します。上記のインタビューの質問では、fooメソッドとmainメソッドは静的メソッドです。静的メソッドにはこれがないため、いわゆる外部クラスオブジェクトはありません。内部クラスオブジェクトを作成します。静的メソッドで内部クラスオブジェクトを作成する場合は、次のようにします。


    new Outer().new Inner();

25. Javaでメモリリークが発生しますか?簡単に説明してください。

回答:理論的には、Javaのガベージコレクションメカニズム(GC)により、メモリリークの問題はありません(これは、Javaがサーバー側のプログラミングで広く使用されている重要な理由でもあります)。ただし、実際の開発では、役に立たないが到達可能である可能性があります。オブジェクト。これらのオブジェクトはGCで再利用できないため、メモリリークも発生する可能性があります。たとえば、休止状態のセッション(第1レベルのキャッシュ)内のオブジェクトは永続的な状態にあり、ガベージコレクターはこれらのオブジェクトを再利用しません。ただし、これらのオブジェクトには役に立たないガベージオブジェクトが含まれている可能性があります。時間内に閉じられないか、フラッシュされない場合、第1レベルのキャッシュは、メモリリークを引き起こす可能性があります。次の例のコードもメモリリークを引き起こします。


import java.util.Arrays;
import java.util.EmptyStackException;
public class MyStack<T> {
    private T[] elements;
    private int size = 0;
    private static final int INIT_CAPACITY = 16;
    public MyStack() {
        elements = (T[]) new Object[INIT_CAPACITY];
    }
    public void push(T elem) {
        ensureCapacity();
        elements[size++] = elem;
    }
    public T pop() {
        if(size == 0) 
            throw new EmptyStackException();
        return elements[--size];
    }
    private void ensureCapacity() {
        if(elements.length == size) {
            elements = Arrays.copyOf(elements, 2 * size + 1);
        }
    }
}

上記のコードは、スタック(first-in-last-out(FILO))構造を実装しています。一見、明らかな問題はないようです。作成したさまざまなユニットテストにも合格できます。ただし、popメソッドにはメモリリークの問題があります。popメソッドを使用してスタック内のオブジェクトをポップすると、スタックを使用するプログラムがこれらのオブジェクトを参照しなくなっても、スタックは内部で維持されるため、オブジェクトはガベージとして扱われません。これらのオブジェクトへの廃止された参照。ガベージコレクションをサポートする言語では、メモリリークは非常に隠されています。このようなメモリリークは、実際には無意識のオブジェクト保持です。オブジェクト参照が無意識に予約されている場合、ガベージコレクターはオブジェクトを処理せず、オブジェクトによって参照されている他のオブジェクトも処理しません。そのようなオブジェクトが少ない場合でも、多くのオブジェクトが除外される可能性があります。ガベージコレクションに加えて、パフォーマンスに大きな影響を与えます。極端な場合、ディスクページング(ハードディスク交換データの物理メモリと仮想メモリ)がトリガーされ、OutOfMemoryErrorも発生します。

26.抽象メソッドは同時に静的にすることができますか、同時にネイティブメソッドにすることができますか、同時に同期することによって変更できますか?

回答:なし。抽象メソッドはサブクラスでオーバーライドする必要がありますが、静的メソッドはオーバーライドできないため、この2つは矛盾しています。ネイティブメソッドはネイティブコード(Cコードなど)によって実装されるメソッドですが、抽象メソッドは実装されておらず、矛盾しています。同期はメソッドの実装の詳細に関連しています。抽象的なメソッドには実装の詳細が含まれないため、矛盾します。

27.静的変数とインスタンス変数の違いを説明します。

回答:静的変数は、静的修飾子によって変更された変数です。クラス変数とも呼ばれます。クラスに属し、クラスのどのオブジェクトにも属していません。クラスで作成されたオブジェクトの数に関係なく、メモリ内の静的変数のコピーは1つだけです。 ;インスタンス変数はインスタンスに依存する必要があります。オブジェクトを作成してから、オブジェクトを介してアクセスする必要があります。静的変数を実装して、複数のオブジェクトがメモリを共有できるようにすることができます。

補足:Java開発では、通常、コンテキストクラスとツールクラスに多数の静的メンバーがあります。

28.静的メソッド内から非静的メソッドを呼び出すことは可能ですか?

回答:いいえ、静的メソッドは静的メンバーにのみアクセスできます。非静的メソッドの呼び出しでは最初にオブジェクトを作成する必要があり、静的メソッドを呼び出すときにオブジェクトが初期化されない場合があるためです。

29.オブジェクトのクローン作成を実装するにはどうすればよいですか?

回答:2つの方法があります:
  1)Cloneableインターフェースを実現し、Objectクラスのclone()メソッドを書き直します;
  2)オブジェクトのシリアル化と逆シリアル化を通じて、Serializableインターフェースを実現し、クローニングを実現します。真のディープクローニングを実現できます。 、コードは以下のように表示されます。


import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
public class MyUtil {
    private MyUtil() {
        throw new AssertionError();
    }
    @SuppressWarnings("unchecked")
    public static <T extends Serializable> T clone(T obj) throws Exception {
        ByteArrayOutputStream bout = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(bout);
        oos.writeObject(obj);
        ByteArrayInputStream bin = new ByteArrayInputStream(bout.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(bin);
        return (T) ois.readObject();
        // 说明:调用ByteArrayInputStream或ByteArrayOutputStream对象的close方法没有任何意义
        // 这两个基于内存的流只要垃圾回收器清理对象就能够释放资源,这一点不同于对外部资源(如文件流)的释放
    }
}
下面是测试代码:
import java.io.Serializable;
/**
 * 人类
 * @author 骆昊
 *
 */
class Person implements Serializable {
    private static final long serialVersionUID = -9102017020286042305L;
    private String name;    // 姓名
    private int age;        // 年龄
    private Car car;        // 座驾
    public Person(String name, int age, Car car) {
        this.name = name;
        this.age = age;
        this.car = car;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public Car getCar() {
        return car;
    }
    public void setCar(Car car) {
        this.car = car;
    }
    @Override
    public String toString() {
        return "Person [name=" + name + ", age=" + age + ", car=" + car + "]";
    }
}
/**
 * 小汽车类
 * @author 骆昊
 *
 */
class Car implements Serializable {
    private static final long serialVersionUID = -5713945027627603702L;
    private String brand;       // 品牌
    private int maxSpeed;       // 最高时速
    public Car(String brand, int maxSpeed) {
        this.brand = brand;
        this.maxSpeed = maxSpeed;
    }
    public String getBrand() {
        return brand;
    }
    public void setBrand(String brand) {
        this.brand = brand;
    }
    public int getMaxSpeed() {
        return maxSpeed;
    }
    public void setMaxSpeed(int maxSpeed) {
        this.maxSpeed = maxSpeed;
    }
    @Override
    public String toString() {
        return "Car [brand=" + brand + ", maxSpeed=" + maxSpeed + "]";
    }
}

class CloneTest {
    public static void main(String[] args) {
        try {
            Person p1 = new Person("Hao LUO", 33, new Car("Benz", 300));
            Person p2 = MyUtil.clone(p1);   // 深度克隆
            p2.getCar().setBrand("BYD");
            // 修改克隆的Person对象p2关联的汽车对象的品牌属性
            // 原来的Person对象p1关联的汽车不会受到任何影响
            // 因为在克隆Person对象时其关联的汽车对象也被克隆了
            System.out.println(p1);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

注:シリアル化と逆シリアル化に基づくクローン作成は、ディープクローン作成であるだけでなく、さらに重要なことに、一般的な制限により、クローン作成するオブジェクトがシリアル化をサポートしているかどうかを確認できます。この確認は、コンパイラではなくコンパイラによって行われます。実行時に例外がスローされます。このソリューションは、Objectクラスのcloneメソッドを使用してオブジェクトのクローンを作成するよりも明らかに優れています。実行時に問題を残すよりも、コンパイル時に問題を公開する方が常に優れています。

30. GCとは何ですか?なぜGCがあるのですか?

回答:GCはゴミ収集を意味します。メモリ処理はプログラマーが問題を起こしやすい場所です。メモリ収集を忘れたり間違ったりすると、プログラムやシステムが不安定になったり、クラッシュしたりする可能性があります。Javaが提供するGC機能は、オブジェクトがスコープを超えているかどうかを自動的に監視できます。メモリを自動的に再利用するという目的を達成するために、Java言語は、割り当てられたメモリを解放するための表示操作方法を提供していません。ガベージコレクターが自動的に管理するため、Javaプログラマーはメモリ管理について心配する必要はありません。ガベージコレクションを要求するには、System.gc()またはRuntime.getRuntime()。gc()のいずれかのメソッドを呼び出すことができますが、JVMは表示されたガベージコレクションの呼び出しをブロックできます。
ガベージコレクションは、メモリリークを効果的に防止し、使用可能なメモリを効果的に使用できます。ガベージコレクターは通常、別の優先度の低いスレッドとして実行されます。予測できない状況では、死亡したオブジェクトや長期間使用されていないオブジェクトはクリアされてリサイクルされます。プログラマーはガベージコレクターをリアルタイムで呼び出すことはできません。 1つまたはすべてのオブジェクトがゴミ収集されます。Javaの黎明期には、サーバー側のプログラミングでメモリリークを効果的に防止する必要があったため、ガベージコレクションはJavaの最大のハイライトの1つでしたが、時間が経ち、Javaのガベージコレクションメカニズムが批判されるようになりました。モバイルスマートターミナルのユーザーは通常、iOSシステムの方がAndroidシステムよりも優れたユーザーエクスペリエンスを提供していると感じています。根深い理由の1つは、Androidシステムでのゴミ収集の予測不可能性にあります。

補足:世代別複製ガベージコレクション、マーク付きガベージコレクション、インクリメンタルガベージコレクションなど、多くのガベージコレクションメカニズムがあります。標準のJavaプロセスには、スタックとヒープの両方があります。スタックはプリミティブローカル変数を保持し、ヒープは作成されるオブジェクトを保持します。ヒープメモリの回復と再利用のためのJavaプラットフォームの基本的なアルゴリズムは、マーキングとクリーニングと呼ばれますが、Javaは、「世代別ガベージコレクション」を使用してそれを改善しました。このメソッドは、Javaオブジェクトのライフサイクルに応じて、ヒープメモリをさまざまな領域に分割します。ガベージコレクションプロセス中に、オブジェクトをさまざまな領域に移動できます。

  • エデン:これはオブジェクトが最初に生まれたエリアであり、ほとんどのオブジェクトにとって、これはオブジェクトが存在した唯一のエリアです。
  • サバイバー:エデンの園から生き残ったオブジェクトはここに移動されます。
  • Tenured:これは十分に年をとった生存者の家です。マイナーGCプロセスはこの場所に影響を与えません。若い世代のコレクションがオブジェクトを生涯の夏の住居に置くことができない場合、完全なコレクション(Major-GC)がトリガーされ、大きなオブジェクトのための十分なスペースを作るためにここでも圧縮が含まれる場合があります。

ガベージコレクションに関連するJVMパラメータ:

  • -Xms / -Xmx —ヒープの初期サイズ/ヒープの最大サイズ
  • -Xmn —ヒープ内の若い世代のサイズ
  • -XX:-DisableExplicitGC — System.gc()は効果がありません
  • -XX:+ PrintGCDetails —GCの詳細を印刷します
  • -XX:+ PrintGCDateStamps —GC操作のタイムスタンプを出力します
  • -XX:NewSize / XX:MaxNewSize —若い世代のサイズ/若い世代の最大サイズを設定します
  • -XX:NewRatio —古い世代と新しい世代の比率を設定できます
  • -XX:PrintTenuringDistribution —サバイバーパラダイス内のオブジェクトの年齢分布を、各新世代GCの後に出力されるように設定します
  • -XX:InitialTenuringThreshold / -XX:MaxTenuringThreshold:旧世代のしきい値の初期値と最大値を設定します
  • -XX:TargetSurvivorRatio:サバイバルエリアの目標使用率を設定します

おすすめ

転載: blog.51cto.com/15061944/2593360
おすすめ