JVM の詳細な分析: JVM はリフレクションをどのように実装しますか?

JVM の詳細な分析: JVM はリフレクションをどのように実装しますか?

[[422686]]

リフレクションは Java 言語の非常に重要な機能であり、実行中の Java プログラムがプログラムの動的な動作を観察したり、変更したりすることを可能にします。

たとえば、Class オブジェクトを通じてクラス内のすべてのメソッドを列挙できます。また、Method.setAccessible (java.lang.reflect パッケージにあり、このメソッドは AccessibleObject から継承されます) を介して Java 言語のアクセス権をバイパスし、プライベート メソッドが配置されているクラス外でメソッドを呼び出すこともできます。

リフレクションは Java で広く使用されています。開発者が日常的に使用する Java 統合開発環境 (IDE) では、この機能が使用されています。ピリオドを入力するたびに、IDE はピリオドの前の内容に基づいて、アクセス可能なフィールドまたはメソッドを動的に表示します。

もう 1 つの日常的なアプリケーションは、デバッグ中にオブジェクトのすべてのフィールドの値を列挙できる Java デバッガーです。

(図中のEclipseプロンプトは反射を使用しています)

Web 開発では、さまざまな構成可能な一般的なフレームワークに触れる機会が多くあります。フレームワークのスケーラビリティを確保するために、多くの場合、Java のリフレクション メカニズムを使用して、構成ファイルに応じてさまざまなクラスをロードします。たとえば、Spring フレームワークの依存性反転 (IoC) はリフレクション メカニズムに依存しています。

しかし、多くの開発者はリフレクション メカニズムの遅さを嫌っていると思います。 Oracleのリフレクションに関するチュートリアルページ[1]でも、リフレクションの高いパフォーマンスオーバーヘッドの欠点が強調されています。

リフレクションコールの実装

まず、メソッドのリフレクション呼び出し、つまり Method.invoke がどのように実装されているかを見てみましょう。

  1. パブリックファイナルクラスメソッドはExecutableを拡張します{
  2. ...
  3. パブリックオブジェクトinvoke(Object obj, Object... args) throws ... {
  4. ... // 権限チェック
  5. メソッドアクセサ ma = メソッドアクセサ;
  6. ma == null場合
  7. ma = メソッドアクセサを取得します。
  8. }
  9. ma.invoke(obj, args)を返します
  10. }
  11. }

Method.invoke のソース コードを見ると、実際には MethodAccessor に処理を委任していることがわかります。 MethodAccessor は、既存の具体的な実装が 2 つあるインターフェースです。1 つはローカル メソッドを通じてリフレクション呼び出しを実装するもので、もう 1 つは委任パターンを使用するものです。覚えやすくするために、これら 2 つを「ローカル実装」と「委任実装」と呼びます。

各メソッド インスタンスの最初のリフレクション呼び出しによってデリゲート実装が生成され、デリゲート先の特定の実装はローカル実装になります。ネイティブ実装は非常に理解しやすいです。 Java 仮想マシンに入ると、Method インスタンスによって指されるメソッドの特定のアドレスが得られます。この時点では、リフレクション呼び出しは、受信パラメータを準備し、ターゲット メソッドを呼び出すことだけです。

  1. // v0 バージョン
  2. java.lang.reflect.Method をインポートします。
  3. パブリッククラステスト{
  4. 公共 静的voidターゲット( int i) {
  5. 新しい例外( "#" + i).printStackTrace();
  6. }
  7. 公共 静的void main(String[] args)は例外をスローします{
  8. クラス<?> klass = Class.forName( "Test" );
  9. メソッド method = klass.getMethod( "target" , int .class);
  10. メソッドを呼び出す( null , 0 );
  11. }
  12. }
  13. # 出力はバージョンによって若干異なりますが、ここでは Java 10 を使用しました。
  14. $ java テスト
  15. 例外: #0
  16. Test.target(Test.java:5)
  17. java.base /jdk.internal.reflect.NativeMethodAccessorImpl .invoke0(ネイティブメソッド
  18. java.base/java.lang.reflect.Method.invoke(Method.java:564)
  19. t Test.main(Test.java:131

理解を容易にするために、リフレクションがターゲット メソッドを呼び出すときにスタック トレースを出力できます。上記の v0 バージョンのコードでは、Test.target メソッドを指す Method オブジェクトを取得し、それをリフレクション呼び出しに使用します。 Test.target では、スタック トレースを出力します。

ご覧のとおり、リフレクション呼び出しは最初に Method.invoke を呼び出し、次に委任された実装 (DelegatingMethodAccessorImpl) に入り、次にローカル実装 (NativeMethodAccessorImpl) に入り、最後にターゲット メソッドに到達します。

ここで、リフレクション呼び出しではなぜ中間層として委任を使用する必要があるのか​​疑問に思うかもしれません。現地実装に直接引き継ぐことはできないのでしょうか?

実際、Java のリフレクション呼び出しメカニズムは、invoke 命令を直接使用してターゲット メソッドを呼び出す、動的に生成されたバイトコードの別の実装 (以下、動的実装と呼びます) も確立します。委任実装を使用する理由は、ローカル実装と動的実装を切り替えることができるためです。

  1. // 動的に実装された疑似コード。ここにはキー呼び出しロジックのみがリストされています。実際、呼び出し元検出とパラメータ検出のためのバイトコードも含まれています。
  2. パッケージ jdk.internal.reflect;
  3. パブリッククラス GeneratedMethodAccessor1 は ... を拡張します {
  4. @オーバーライド
  5. パブリックオブジェクトinvoke(Object obj, Object[] args) throws ... {
  6. テスト.target(( int ) args[0]);
  7. 戻る ヌル;
  8. }
  9. }

動的実装はローカル実装よりも 20 倍高速に実行されます。これは、動的実装では Java から C++、そして Java に切り替える必要がないためですが、バイトコードの生成には非常に時間がかかるため、一度だけ呼び出される場合は、ローカル実装の方が 3 ~ 4 倍高速になります。

多くのリフレクション呼び出しが 1 回だけ実行されることを考慮して、Java 仮想マシンはしきい値を 15 に設定します (これは -Dsun.reflect.inflationThreshold= で調整できます)。リフレクション呼び出しの呼び出し回数が 15 未満の場合、ローカル実装が使用されます。 15 に達すると、バイトコードが動的に生成され、デリゲート実装のデリゲート オブジェクトが動的実装に切り替わります。このプロセスをインフレと呼びます。

このプロセスを観察するために、前の例を次の v1 バージョンに変更しました。リフレクション呼び出しを 20 回ループします。

  1. // v1 バージョン
  2. java.lang.reflect.Method をインポートします。
  3. パブリッククラステスト{
  4. 公共 静的voidターゲット( int i) {
  5. 新しい例外( "#" + i).printStackTrace();
  6. }
  7. 公共 静的void main(String[] args)は例外をスローします{
  8. クラス<?> klass = Class.forName( "Test" );
  9. メソッド method = klass.getMethod( "target" , int .class);
  10. ( int i = 0; i < 20; i++) {
  11. メソッドを呼び出す( null 、 i );
  12. }
  13. }
  14. }
  15. #ロードされたクラスを出力するには -verbose:class を使用します
  16. $ java -verbose:クラステスト
  17. ...
  18. 例外: #14
  19. Test.target(Test.java:5)
  20. java.base /jdk.internal.reflect.NativeMethodAccessorImpl .invoke0(ネイティブ メソッド java.base/jdk.internal.reflect.NativeMethodAccessorImpl .invoke(NativeMethodA java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl .invoke(Delegatin java.base/java.lang.reflect.Method.invoke(Method.java:564)
  21. Test.main(Test.java:12)
  22. [0.158秒][情報][クラス、ロード] ...
  23. ...
  24. [0.160s][info][class, load ] jdk.internal.reflect.GeneratedMethodAccessor1 ソース: __JVM_Djava.lang.Exception: #15
  25. Test.target(Test.java:5)
  26. java.base /jdk.internal.reflect.NativeMethodAccessorImpl .invoke0(Native Method、java.base/jdk.internal.reflect.NativeMethodAccessorImpl .invoke(NativeMethodA、java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl .invoke(Delegating、java.base/java.lang.reflect.Method.invoke(Method.java:564)
  27. Test.main(Test.java:12)
  28. 例外: #16
  29. Test.target(Test.java:5)
  30. jdk.internal.reflect.GeneratedMethodAccessor1 .invoke(不明なソース)
  31. java.base /jdk.internal.reflect.DelegatingMethodAccessorImpl .invoke(Delegating java.base/java.lang.reflect.Method.invoke(Method.java:564) で
  32. Test.main(Test.java:12)
  33. ...

ご覧のとおり、15 回目 (0 から開始) のリフレクション呼び出しで、動的実装の生成がトリガーされました。このとき、Java 仮想マシンは多数の追加クラスをロードします。その中でも最も重要なのはGeneratedMethodAccessor1(30行目)です。そして、16 番目のリフレクション呼び出しから、新しく生成された動的実装 (行 40) に切り替えます。

リフレクション呼び出しのインフレーション メカニズムは、パラメーター (-Dsun.reflect.noInflation=true) によってオフにすることができます。この方法では、委任された実装やローカル実装を使用せずに、リフレクション呼び出しの開始時に動的実装が直接生成されます。

リフレクション呼び出しのオーバーヘッド

次に、リフレクション呼び出しのパフォーマンス オーバーヘッドを分析します。

先ほどの例では、Class.forName、Class.getMethod、Method.invoke 操作を順番に実行しました。このうち、Class.forName はローカル メソッドを呼び出し、Class.getMethod はクラスのパブリック メソッドを走査します。一致するものが見つからない場合は、親クラスのパブリック メソッドも走査します。ご想像のとおり、これらの操作は両方とも非常に時間がかかります。

getMethod で表されるメソッド検索操作は、検索結果のコピーを返すことに注意してください。したがって、不要なヒープ領域の消費を減らすために、ホット コードで Method 配列を返す getMethods メソッドまたは getDeclaredMethods メソッドの使用を避ける必要があります。

実際には、アプリケーションで Class.forName と Class.getMethod の結果をキャッシュすることがよくあります。したがって、以下では、リフレクション呼び出し自体のパフォーマンス オーバーヘッドにのみ焦点を当てます。

直接呼び出しとリフレクション呼び出しのパフォーマンスの差を比較するために、前の例を次の v2 バージョンに変更しました。リフレクション呼び出しを 20 億回ループします。さらに、1億回の実行ごとに時間を記録します。

ウォームアップ後のピークパフォーマンスとして、最後の 5 つの記録の平均を取得します。 (注:このパフォーマンス評価方法は厳密なものではありません。JMH を使ってパフォーマンスを測定する方法については、コラムの第 3 部で紹介します。)

私の古いラップトップでは、1 億回の直接呼び出しに約 120 ミリ秒かかります。これは呼び出されない時間と一致します。その理由は、このコードがホット ループに属しており、ジャストインタイム コンパイルもトリガーされるためです。さらに、ジャストインタイム コンパイルにより Test.target への呼び出しがインライン化されるため、呼び出しのオーバーヘッドが排除されます。

  1. // v2 バージョン
  2. java.lang.reflect.Method をインポートします。
  3. パブリッククラステスト{
  4. 公共 静的voidターゲット( int i) {
  5. //空のメソッド
  6. }
  7. 公共 静的void main(String[] args)は例外をスローします{
  8. クラス<?> klass = Class.forName( "Test" );
  9. メソッド method = klass.getMethod( "target" , int .class);
  10. 長い現在の= System.currentTimeMillis();
  11. ( int i = 1; i <= 2_000_000_000; i++)の場合{
  12. (i % 100_000_000 == 0)の場合{
  13. 長いtemp = System.currentTimeMillis();
  14. システム。出力.println( temp - current );
  15. 現在の値=温度;
  16. }
  17. メソッドを呼び出す( null , 128);
  18. }
  19. }
  20. }

以下では、リフレクション呼び出しのパフォーマンス オーバーヘッドを比較するためのベンチマークとして 120 ミリ秒を使用します。

ターゲットメソッド Test.target は int 型のパラメータを受け取るため、リフレクション呼び出しのパラメータとして 128 を渡しましたが、計測結果はベースラインの約 2.7 倍となりました。この数値が高いか低いかに関係なく、まずはリフレクション呼び出しの前にバイトコードが何を行うかを見てみましょう。

  1. aload_2 //メソッドオブジェクトのロード
  2. aconst_null //リフレクション呼び出しの最初のパラメータはnullです 
  3. アイコンt_1
  4. anewarray Object //長さ1のオブジェクト配列を生成する
  5. 重複
  6. アイコンt_0
  7. シプシュ128
  8. invokestatic Integer .valueOf // 128 を Integer73 に自動的にボックス化します: aastore // オブジェクト配列に格納します
  9. invokevirtual Method.invoke //リフレクション呼び出し

ここでは、ループ内のリフレクション呼び出しからコンパイルされたバイトコードをキャプチャしました。ご覧のとおり、このバイトコードはリフレクション呼び出しに加えて 2 つの追加操作を実行します。

  • Method.invoke は可変長パラメータ メソッドなので、バイトコード レベルでの最後のパラメータは Object 配列になります (興味のある人は javap を使用して非公開で表示できます)。 Java コンパイラは、メソッド呼び出しポイントで受信パラメータの数に等しい長さのオブジェクト配列を生成し、受信パラメータを 1 つずつ配列に格納します。
  • オブジェクト配列はプリミティブ型を格納できないため、Java コンパイラは渡されたプリミティブ型パラメータを自動的にボックス化します。

パフォーマンスのオーバーヘッドに加えて、これら 2 つの操作はヒープ メモリを占有し、GC の頻度を高める可能性もあります。 (興味があれば、仮想マシンパラメータ -XX:+PrintGC で試すことができます。) では、このオーバーヘッドをどのように排除するのでしょうか?

2 番目のオートボクシングに関しては、Java は [-128, 127] 内のすべての整数に対応する Integer オブジェクトをキャッシュします。自動的にボックス化される必要がある整数がこの範囲内にある場合、キャッシュされた Integer が返されます。それ以外の場合は、新しい Integer オブジェクトを作成する必要があります。

したがって、このキャッシュの範囲を 128 (パラメータ -Djava.lang.Integer.IntegerCache.high=128 に相当) まで拡張することができ、新しい Integer オブジェクトを作成する必要がなくなります。

あるいは、ループの外側に 128 個の自動ボックス化された Integer オブジェクトをキャッシュし、それらをリフレクション呼び出しに直接渡すこともできます。これら 2 つの方法で得られた結果は類似しており、ベンチマークの約 1.8 倍です。

ここで、可変長パラメータによって自動的に生成される最初の Object 配列に戻って見てみましょう。各リフレクション呼び出しに対応するパラメータの数は固定されているため、ループの外側で新しい Object 配列を作成し、パラメータを設定して、それをリフレクション呼び出しに直接渡すことができます。変更されたコードは、ドキュメント内の v3 バージョンを参照できます。

  1. // v3 バージョン
  2. java.lang.reflect.Method をインポートします。
  3. パブリッククラステスト{
  4. 公共 静的voidターゲット( int i) {
  5. //空のメソッド
  6. }
  7. 公共 静的void main(String[] args)は例外をスローします{
  8. クラス<?> klass = Class.forName( "Test" );
  9. メソッド method = klass.getMethod( "target" , int .class);
  10. オブジェクト[] arg = 新しいオブジェクト[1];
  11. //ループ外でパラメータ配列を構築
  12. 引数[0] = 128;
  13. 長い現在の= System.currentTimeMillis();
  14. ( int i = 1; i <= 2_000_000_000; i++) {
  15. (i % 100_000_000 == 0)の場合{
  16. 長いtemp = System.currentTimeMillis();
  17. システム。出力.println( temp - current );
  18. 現在の値=温度;
  19. }
  20. メソッドを呼び出します( null 、 arg );
  21. }
  22. }
  23. }

測定結果はさらに悪く、ベースラインの 2.9 倍でした。これはなぜでしょうか?

前の手順で自動ボクシングを解決した後、実行時に GC ステータスを確認すると、このプログラムは GC をトリガーしないことがわかります。その理由は、元のリフレクション呼び出しがインライン化されているため、ジャストインタイム コンパイラのエスケープ分析によって、新しく作成された Object 配列が非エスケープ オブジェクトとして判断されるためです。

オブジェクトがエスケープしない場合、ジャストインタイム コンパイラはスタック割り当てまたは仮想割り当てを選択できるため、ヒープ領域を占有しません。詳細については、このコラムの後半で説明します。

ループの外で新しい配列が作成されると、ジャストインタイムコンパイラは配列が途中で変更されるかどうか確信が持てないため、配列にアクセスする操作を最適化できず、その損失に見合う価値がありません。

これまでの最高記録は1.8倍です。それで、さらに改善できるのでしょうか?

先ほど、リフレクション呼び出しのインフレーション メカニズムをオフにして、委任された実装をキャンセルし、動的実装を直接使用できることを説明しました。さらに、各リフレクション呼び出しではターゲット メソッドの権限がチェックされますが、このチェックは Java コードでオフにすることもできます。これら 2 つのメカニズムをオフにすると、ベースラインの約 1.3 倍の測定値を持つ v4 バージョンが得られます。

  1. // v4 バージョン
  2. java.lang.reflect.Method をインポートします。
  3. // 実行コマンドに次の 2 つの仮想マシン パラメータを追加します。
  4. // -Djava.lang.Integer .IntegerCache.high =128
  5. // -Dsun.reflect.noInflation= true  
  6. パブリッククラステスト{
  7. 公共 静的voidターゲット( int i) {
  8. //空のメソッド
  9. }
  10. 公共 静的void main(String[] args)は例外をスローします{
  11. クラス<?> klass = Class.forName( "Test" );
  12. メソッド method = klass.getMethod( "target" , int .class);
  13. メソッド.setAccessible( true );
  14. //権限チェックを無効にする
  15. 長い現在の= System.currentTimeMillis();
  16. ( int i = 1; i <= 2_000_000_000; i++)の場合{
  17. (i % 100_000_000 == 0)の場合{
  18. 長いtemp = System.currentTimeMillis();
  19. システム。出力.println( temp - current );
  20. 現在の値=温度;
  21. }
  22. メソッドを呼び出す( null , 128);
  23. }
  24. }
  25. }

この時点で、リフレクションコールから得られる水分は基本的にすべて絞り出されています。次に、リフレクション呼び出しのパフォーマンス オーバーヘッドについて説明します。

まず、この例では、リフレクション呼び出しが非常に高速である理由は、主にジャストインタイム コンパイラでのメソッドのインライン化によるものです。インフレがオフの場合、インライン化のボトルネックとなるのは、Method.invoke メソッド内の MethodAccessor.invoke メソッドの呼び出しです。

メソッドのインライン化の具体的な実装については、後の記事で紹介します。結論は次のとおりです。実稼働環境では、複数の GeneratedMethodAccessor、つまり動的実装に対応する複数の異なるリフレクション呼び出しが頻繁に発生します。

上記の呼び出しポイントに対する Java 仮想マシンの型プロファイル (注:invokevirtual またはinvokeinterface の場合、Java 仮想マシンは呼び出し元の特定の型を記録します。これを型プロファイルと呼びます) は、同時に多くのクラスを記録できないため、テスト対象の反映された呼び出しがインライン化されない可能性があります。

  1. // v5 バージョン
  2. java.lang.reflect.Method をインポートします。
  3. パブリッククラステスト{
  4. 公共 静的voidターゲット( int i) {
  5. //空のメソッド
  6. }
  7. 公共 静的void main(String[] args)は例外をスローします{
  8. クラス<?> klass = Class.forName( "Test" );
  9. メソッド method = klass.getMethod( "target" , int .class);
  10. メソッド.setAccessible( true );
  11. //権限チェックを無効にする
  12. プロファイルを汚染します();
  13. 長い現在の= System.currentTimeMillis();
  14. ( int i = 1; i <= 2_000_000_000; i++)の場合{
  15. (i % 100_000_000 == 0)の場合{
  16. 長いtemp = System.currentTimeMillis();
  17. システム。出力.println( temp - current );
  18. 現在の値=温度;
  19. }
  20. メソッドを呼び出す( null , 128);
  21. }
  22. }
  23. 公共  static void polluteProfile() は例外をスローします {
  24. メソッド method1 = Test.class.getMethod( "target1" , int .class);
  25. メソッド method2 = Test.class.getMethod( "target2" , int .class);
  26. ( int i = 0; i < 2000; i++) {
  27. メソッド1.invoke( null , 0);
  28. メソッド2.invoke( null , 0);
  29. }
  30. }
  31. 公共 静的voidターゲット1( int i) {
  32. }
  33. 公共 静的voidターゲット2( int i) {
  34. }
  35. }

上記の v5 バージョンでは、テスト ループの前に polluteProfile メソッドを呼び出しました。このメソッドは、他の 2 つのメソッドをリフレクション呼び出しし、2000 回ループします。

テストサイクルは変更されません。測定結果はベンチマークの約6.7倍です。つまり、Method.invoke メソッドの型プロファイルが誤って乱れると、パフォーマンスのオーバーヘッドが 1.3 倍から 6.7 倍に増加します。

インライン化の欠如に加えて、エスケープ分析が効果的でなくなったことも、速度低下のもう 1 つの理由です。現時点では、バージョン v3 のソリューションを採用し、ループの外側でパラメータ配列を構築し、それをリフレクション呼び出しに直接渡すことができます。このようにして測定した結果はベンチマークの約5.2倍となります。

さらに、Java 仮想マシンが各呼び出しに対して記録できるタイプの数を増やすこともできます (仮想マシン パラメータ -XX:TypeProfileWidth に対応し、デフォルト値は 2 ですが、ここでは 3 に設定されています)。最終的な測定結果はベンチマークの約2.8倍です。当初の1.3倍からはまだ一定の距離はありますが、6.7倍よりはずっと良いです。

まとめと実践

デフォルトでは、メソッドへのリフレクション呼び出しは、メソッド呼び出しを実行するためにローカル実装に委任されます。 15 回を超える呼び出しの後、デリゲート実装はデリゲート オブジェクトを動的実装に切り替えます。この動的実装のバイトコードは自動的に生成され、invoke 命令を直接使用してターゲット メソッドを呼び出します。

メソッドのリフレクション呼び出しは、パフォーマンスのオーバーヘッドを大幅に増加させます。主な理由は 3 つあります。可変長パラメータ メソッドによって発生するオブジェクト配列、基本型の自動ボックス化とアンボックス化、そして最も重要なメソッドのインライン化です。

この記事の実践的な部分では、最後のコード スニペットの polluteProfile メソッドの 2 つの Method オブジェクトを、「target」という名前を取得するメソッドに変更できます。取得されたこれら 2 つの Method オブジェクトは同じですか (==)?それらは等しいですか (.equals(…))?ランニングの結果にどのような影響があるのでしょうか?

  1. java.lang.reflect.Method をインポートします。
  2. パブリッククラステスト{
  3. 公共 静的voidターゲット( int i) {
  4. //空のメソッド
  5. }
  6. 公共 静的void main(String[] args)は例外をスローします{
  7. クラス<?> klass = Class.forName( "Test" );
  8. メソッド method = klass.getMethod( "target" , int .class);
  9. メソッド.setAccessible( true );
  10. //権限チェックを無効にする
  11. プロファイルを汚染します();
  12. 長い現在の= System.currentTimeMillis();
  13. ( int i = 1; i <= 2_000_000_000; i++)の場合{
  14. (i % 100_000_000 == 0)の場合{
  15. 長いtemp = System.currentTimeMillis();
  16. システム。出力.println( temp - current );
  17. 現在の値=温度;
  18. }
  19. メソッドを呼び出す( null , 128);
  20. }
  21. }
  22. 公共  static void polluteProfile() は例外をスローします {
  23. メソッド method1 = Test.class.getMethod( "target" , int .class);
  24. メソッド method2 = Test.class.getMethod( "target" , int .class);
  25. ( int i = 0; i < 2000; i++) {
  26. メソッド1.invoke( null , 0);
  27. メソッド2.invoke( null , 0);
  28. }
  29. }
  30. 公共 静的voidターゲット1( int i) {
  31. }
  32. 公共 静的voidターゲット2( int i) {
  33. }
  34. }

<<:  サンライフファイナンシャル、デジタルサービスの変革に向けた長期戦略クラウドプロバイダーとしてアマゾンウェブサービスを選択

>>:  この記事では、JVMメモリモデルについて詳しく説明します。

推薦する

分散型グローバル一意 ID スキームはそんなにたくさんあるのでしょうか?

[[403814]]この記事はWeChatの公開アカウント「Java Geek Technology...

Qinzhe Excelサーバーソフトウェアはコスト統計レポートシステムを実現します

2018年最もホットなプロジェクト:テレマーケティングロボットがあなたの参加を待っています近年、経済...

ドメイン名の先占:侵害事件は年々増加、ビジネスチャンスと侵害は密接に関係

ジェレミー・リンは全米バスケットボール協会で急速に人気者となり、「リン・トルネード」もインターネット...

企業がSEO最適化を効果的に行うには

はじめに:SEO最適化は現在最も費用対効果の高いインターネットマーケティング手法であり、最もホットな...

人類文明の2つの法則の観点からクラウドコンピューティングを見る

01 人類文明の発展の要素原始社会史家モーガンは、その代表作『古代社会』の中でこう述べている。「野蛮...

安定した VPS の推奨: XEN ベースの sugarhosts (SSD 付き)

Sugarhosts は私たちにとって馴染みのある IDC です。皆さんも最初に仮想ホストに触れたの...

onetechcloud: 最大 31% オフ、米国 cn2 gia VPS (ネイティブ IP + DDoS 高防御)、香港 CN2 VPS、香港 cmi VPS (1G の大容量帯域幅)

onetechcloud では現在、ビジネス開始のための特別プロモーションを実施しており、月払いの場...

ウェブマスターネットワークレポート:タオバオの偽造注文は空中楼閣に過ぎず、迅雷クラウドブロードキャストも崩壊

1. SNS軍の地下産業チェーンが、タオバオの偽注文は単なる空想であることを暴露最近、SNS軍はさま...

メロンを急いで食べないで、まずはWeiboマーケティングの「セレブ効果」について話しましょう

ショートビデオ、セルフメディア、インフルエンサーのためのワンストップサービス毎日、Weiboには私た...

インターネットマーケティングの専門家、陳千万氏:精密マーケティングに必要な3つのステップ

2018年最もホットなプロジェクト:テレマーケティングロボットがあなたの参加を待っていますマーケティ...

大銀希生はGoogleとBaiduのホームページからウェブサイトのプロモーションを見る

春節までまだ1週間ありますが、春節のウェブサイトのメンテナンスとマーケティングはすでに一部のウェブサ...

#黒5# geecdn: 45% 割引、香港 VPS、米国 VPS、フランスの高防御 VPS

geecdn(2017年創業)が、いち早く「ブラックフライデー」プロモーションを実施。フランスのOV...

専用マインド - 7ドル年/96mメモリ/9gハードディスク192Mvswap/250gトラフィック/Gポート

Dedicated Mindsは、イギリスに登録された有限会社です(登録番号08536083)。ウェ...

セルフメディア後半は換金に失敗してアカウントがブロックされまくってた!

2018年5月、上場企業Hanye Co., Ltd.はQuantum Cloudを買収するために3...