バグの書き方、一般的なOOM例外分析を教える

バグの書き方、一般的なOOM例外分析を教える

Java 仮想マシン仕様によれば、プログラム カウンタに加えて、仮想マシン メモリの他のいくつかのランタイム領域も OutOfMemoryError 例外の影響を受けます。

[[332758]]

この記事には主に次の OOM の紹介と例が含まれています。

  • java.lang.StackOverflowError
  • java.lang.OutOfMemoryError: Java ヒープスペース
  • java.lang.OutOfMemoryError: GC オーバーヘッド制限を超えました
  • java.lang.OutOfMemoryError-->メタスペース
  • java.lang.OutOfMemoryError: ダイレクトバッファメモリ
  • java.lang.OutOfMemoryError: 新しいネイティブ スレッドを作成できません
  • java.lang.OutOfMemoryError: メタスペース
  • java.lang.OutOfMemoryError: 要求された配列サイズが VM 制限を超えています
  • java.lang.OutOfMemoryError: スワップ領域不足
  • java.lang.OutOfMemoryError:プロセスを強制終了するか、子プロセスを犠牲にする

よく話題になるOOM例外は実際にはエラーです

1. スタックオーバーフローエラー

1.1 バグを書く

  1. パブリッククラスStackOverflowErrorDemo {
  2.  
  3. 公共 静的void main(String[] args) {
  4. javaKeeper();
  5. }
  6.  
  7. プライベート静的void javaKeeper() {
  8. javaKeeper();
  9. }
  10. }

JVM 仮想マシン スタックは深いです。メソッドを実行すると、スタック プッシュとスタック ポップが伴います。上記の方法からわかるように、メイン メソッドは実行後に再帰を継続し、最終的にスタックがバーストします。

  1. スレッド「main」例外が発生しましたjava.lang.StackOverflowError
  2. oom.StackOverflowErrorDemo.javaKeeper(StackOverflowErrorDemo.java:15)

1.2 原因分析

  • 無限再帰ループ呼び出し (最も一般的な理由)。コード内に終了できないループ呼び出しがあるかどうかに常に注意してください。
  • 多数のメソッドが実行され、スレッドスタックスペースが枯渇する
  • メソッド内に多数のローカル変数が宣言されている
  • ネイティブ コードにはスタック上に割り当てられるロジックがあり、必要なメモリは小さくありません。たとえば、java.net.SocketInputStream.read0 では、スタック上に 64KB のバッファを割り当てる必要があります (64 ビット Linux)

1.3 解決策

  • 無限再帰呼び出しを引き起こす例外コードを修正します。プログラムによってスローされた例外スタックから繰り返しコード行を見つけ、手がかりをたどって無限再帰バグを修正します。
  • クラス間に循環依存関係があるかどうかを確認します (2 つのオブジェクトが相互に参照している場合、toString メソッドを呼び出すときにもこの例外が発生します)
  • JVM 起動パラメータ -Xss を使用してスレッド スタックのメモリ領域を増やします。通常の使用シナリオでは、多数のメソッドを実行したり、多数のローカル変数を含めたりする必要があります。この場合、スレッド スタックのスペース制限を適切に増やすことができます。

2. Javaヒープスペース

Java ヒープはオブジェクト インスタンスを格納するために使用されます。オブジェクトを作成し続け、GC ルートからオブジェクトに到達可能なパスを確保して GC によるこれらのオブジェクトのクリアを回避する限り、オブジェクトの数が増加するにつれて、合計容量がヒープの最大容量制限に達し、メモリ オーバーフロー例外が発生します。

Java ヒープ メモリ OOM 例外は、実際のアプリケーションで最も一般的なメモリ オーバーフロー例外です。

2.1 バグを書く

  1. /**
  2. * JVMパラメータ: -Xmx12m
  3. */
  4. パブリッククラスJavaHeapSpaceDemo {
  5.  
  6. 静的最終整数 サイズ= 2 * 1024 * 1024;
  7.  
  8. 公共 静的void main(String[] a) {
  9. int [] i = 新しいint [サイズ];
  10. }
  11. }

コードは 2M の int 配列を割り当てようとします。起動パラメータ -Xmx12m が指定されている場合、割り当てられたメモリが不十分になります。これは、XXXL サイズのオブジェクトを S サイズの Java ヒープ スペースに詰め込むのと同様です。

  1. スレッド「main」例外が発生しましたjava.lang.OutOfMemoryError: Java ヒープスペース 
  2. oom.JavaHeapSpaceDemo.main(JavaHeapSpaceDemo.java:13)

2.2 原因分析

  • 非常に大きなオブジェクト(通常は大きな配列)の作成を要求します
  • 訪問数/データ数が予想を上回りました。これは通常、さまざまなプロモーションやフラッシュセールでよく見られる、アップストリーム システム要求トラフィックの急増によって発生します。ビジネス トラフィック インジケーターに基づいて、トラフィックの急増があるかどうかを確認できます。
  • ファイナライザ(Finalizer)を過度に使用すると、オブジェクトはすぐにGCされない
  • メモリ リーク: 多数のオブジェクト参照が解放されず、JVM はそれらを自動的にリサイクルできません。これは、ファイルなどのリソースがリサイクルされない場合によく発生します。

2.3 解決策

ほとんどの場合、-Xmx パラメータを使用して JVM ヒープ メモリ領域を増やすだけで済みます。それでも問題が解決しない場合は、次の状況を参照してさらに処理してください。

  • 非常に大きなオブジェクトの場合、結果数を制限せずにデータベース内のすべての結果を一度に照会するかどうかなど、その合理性を確認できます。
  • ビジネスのピーク圧力の場合は、マシン リソースを追加するか、フローを制限してダウングレードすることを検討できます。
  • メモリ リークの場合は、保持されているオブジェクトを見つけて、解放されていない接続を閉じるなどのコード設計を変更する必要があります。

インタビュアー: メモリ リークとメモリ オーバーフローについてお話ししましょう。

知識ポイントをあげます。三つのことを続ければ、やがて偉大な神になれるだろう~~

メモリリークとメモリオーバーフロー

メモリ不足とは、プログラムがメモリを要求したときに、使用するメモリ領域が不足し、結果としてメモリ不足になることを意味します。たとえば、Integer が要求されたが、Long でのみ格納できる数値が格納されている場合、メモリ オーバーフローが発生します。

メモリ リークとは、プログラムが要求したメモリ領域を解放できない状況を指します。単一のメモリ リークによって引き起こされる損害は無視できますが、メモリ リークが蓄積された場合の結果は非常に深刻です。メモリがどれだけあっても、遅かれ早かれ占有されてしまいます。

メモリ リークは最終的にメモリ不足につながります。

3. GCオーバーヘッド制限を超えた

JVM にはガベージ コレクション メカニズム GC が組み込まれているため、Java 開発者はメモリを割り当てたり解放したりするために手動でコードを記述する必要がありません。ただし、Java プロセスが GC の実行に 98% 以上の時間を費やし、メモリの 2% 未満しか回復せず、このアクションが 5 回連続して繰り返されると、java.lang.OutOfMemoryError:GC オーバーヘッド制限超過エラー (一般にガベージ コレクション オーバーヘッドと呼ばれる) がスローされます。簡単に言えば、アプリケーションは基本的に利用可能なメモリをすべて使い果たしており、GC はそれを回収できません。

GC オーバーヘッド制限超過エラーがスローされない場合、GC によってクリーンアップされるわずかなメモリがすぐに再びいっぱいになり、GC が再度実行されることになり、CPU 使用率が 100% になり、GC の効果はほとんどなくなるという悪循環が続きます。

3.1 バグを書く

このエラーが発生する例では、実際に無限ループを記述し、List または Map にデータを追加しているため、耐えられなくなるまで Full GC が発生します。簡単には見つからない例を次に示します。マップに 1000 個の要素を追加します。

  1. /**
  2. * JVMパラメータ: -Xmx14m -XX:+PrintGCDetails
  3. */
  4. パブリッククラスKeylessEntry {
  5.  
  6. 静的クラスKey {
  7. 整数ID;
  8.  
  9. キー(整数ID ) {
  10. id は、
  11. }
  12.  
  13. @オーバーライド
  14. 公共  intハッシュコード() {
  15. id.hashCode()を返します
  16. }
  17. }
  18.  
  19. 公共 静的void main(String[] args) {
  20. マップ m = 新しい HashMap();
  21. )の間{
  22. ( int i = 0; i < 1000; i++) {
  23. if (!m.containsKey(新しいキー(i))){
  24. m.put(新しいキー(i)、 "数値:" + i);
  25. }
  26. }
  27. システム。出力.println( "m.size()=" + m.size ( ));
  28. }
  29. }
  30. }
  1. ...
  2. m.size ()=54000
  3. m.size ()=55000
  4. m.size ()=56000
  5. スレッド「main」例外が発生しましたjava.lang.OutOfMemoryError: GC オーバーヘッド制限を超えました

出力結果から、1000 データという制限が機能せず、マップの容量が 1000 をはるかに超え、最終的に必要なエラーが発生したことがわかります。これは、Key クラスが hashCode() メソッドのみを書き換え、equals() メソッドを書き換えなかったためです。実際には、containsKey() メソッドを使用したときに問題が発生したため、GC がキーをクリーンアップできなくなるまで、HashMap にキーを追加し続けました。

インタビュアーがまた登場します。HashMapの原理と、なぜequalsとhashcodeを同時に実装する必要があるのか​​について話しましょう。

このプログラムを実行する際の最終的なエラーも、JVM 構成に関連しています。設定されたヒープ メモリが小さすぎる場合は、Java ヒープ領域が直接報告されます。このエラーは、リソースが制限されているときにプログラムが停止する原因です。

3.2 解決策

  • JVM パラメータ -XX:-UseGCOverheadLimit を追加することはお勧めしません。それは実際に問題を解決するわけではなく、例外を延期するだけです。
  • プロジェクト内にデッドループやメモリを大量に使用するコードが多数あるかどうかを確認し、コードを最適化します。
  • メモリ分析をダンプして、メモリ リークがあるかどうかを確認します。そうでない場合は、メモリを増やしてください

4. ダイレクトバッファメモリ

NIO を使用する場合、データの読み取りや書き込みに ByteBuffer を使用する必要があることがよくあります。これは、チャネルとバッファに基づいた I/O 方式です。ネイティブ関数ライブラリを使用してオフヒープ メモリを直接割り当て、Java ヒープに格納されている DirectByteBuffer オブジェクトをこのメモリへの参照として使用して操作を行うことができます。これにより、一部のシナリオで Java ヒープとネイティブの間でデータのコピーが回避され、パフォーマンスが向上します。

Java では、アプリケーションが Direct ByteBuffer を通じてオフヒープ メモリに直接アクセスできるようになります。多くの高性能プログラムは、メモリマップファイルと組み合わせた Direct ByteBuffer を通じて高速 IO を実現します。

4.1 バグを書く

  • ByteBuffer.allocate(capability) は JVM ヒープメモリを割り当てます。これは GC の管轄下にあり、メモリコピーが必要なので、速度は比較的遅くなります。
  • ByteBuffer.allocateDirect(capability) は、GC の管轄外にある OS ローカル メモリを割り当てます。メモリコピーが不要なので、速度が比較的速いです。

ローカル メモリが継続的に割り当てられ、ヒープ メモリがほとんど使用されない場合、JVM は GC を実行する必要がなくなり、DirectByteBuffer オブジェクトはリサイクルされません。このとき、ヒープメモリは十分であるにもかかわらず、ローカルメモリが十分でない場合があり、OOM (ローカルダイレクトメモリオーバーフロー) が発生します。

  1. /**
  2. * VM オプション:-Xms10m、-Xmx10m、-XX:+PrintGCDetails -XX:MaxDirectMemorySize=5m
  3. */
  4. パブリッククラス DirectBufferMemoryDe​​mo {
  5.  
  6. 公共 静的void main(String[] args) {
  7. システム。 out .println( "maxDirectMemory は:" +sun.misc.VM.maxDirectMemory() / 1024 / 1024 + "MB" );
  8.  
  9. //ByteBuffer バッファ = ByteBuffer.allocate(6*1024*1024);
  10. ByteBuffer バッファ = ByteBuffer.allocateDirect(6*1024*1024);
  11.  
  12. }
  13. }

最大直接メモリはデフォルトでコンピュータのメモリの 1/4 に設定されているため、これを小さく設定します。使用される直接メモリがこの値を超えると、OOM が発生します。

  1. maxDirectMemory5MB
  2. スレッド「main」例外java.lang.OutOfMemoryError: ダイレクト バッファ メモリ

4.2 解決策

  1. Java は ByteBuffer.allocateDirect メソッドを通じてのみ Direct ByteBuffer を使用できるため、トラブルシューティングのために Arthas などのオンライン診断ツールを使用してこのメ​​ソッドをインターセプトできます。
  2. netty、jetty など、NIO が直接または間接的に使用されているかどうかを確認します。
  3. パラメータ-XX:MaxDirectMemorySizeを開始して、Direct ByteBufferの上限を調整します。
  4. JVM パラメータに -XX:+DisableExplicitGC オプションがあるかどうかを確認します。そうであれば、このパラメータにより System.gc() が無効になるため削除してください。
  5. オフヒープメモリ使用量コードをチェックして、メモリリークがあるかどうかを確認します。または、リフレクションを介してsun.misc.Cleanerのclean()メソッドを呼び出して、Direct ByteBufferによって保持されているメモリ領域を積極的に解放します。
  6. メモリ容量が不足しています。構成をアップグレードしてください

5. 新しいネイティブスレッドを作成できません

各 Java スレッドには一定量のメモリ領域が必要です。 JVM が基盤となるオペレーティング システムに新しいネイティブ スレッドの作成を要求すると、十分なリソースが割り当てられていない場合にこのタイプのエラーが報告されます。

5.1 バグを書く

  1. 公共 静的void main(String[] args) {
  2. while( true ){
  3. 新しいスレッド(() -> {
  4. 試す {
  5. スレッド.スリープ(整数.MAX_VALUE);
  6. } キャッチ(中断された例外 e) { }
  7. })。始める();
  8. }
  9. }
  10. VM初期化中にエラーが発生しました
  11. java.lang.OutOfMemoryError: できませ 新しいネイティブスレッドを作成する

5.2 原因分析

  • JVM が OS にネイティブ スレッドの作成を要求できない場合、新しいネイティブ スレッドを作成できませんというエラーがスローされます。一般的な理由は次のとおりです。
  • スレッド数がオペレーティング システムの最大スレッド数を超えています (プラットフォームによって異なります)
  • スレッド数がkernel.pid_maxを超えています(再起動のみ可能です)
    • ネイティブメモリが不足しています。この問題の一般的なプロセスには、主に次の手順が含まれます。
    • JVM 内のアプリケーションは新しい Java スレッドの作成を要求します。
    • JVM ネイティブ メソッドは要求をプロキシし、オペレーティング システムにネイティブ スレッドの作成を要求します。
    • オペレーティング システムは、新しいネイティブ スレッドを作成し、それにメモリを割り当てようとします。
    • オペレーティング システムの仮想メモリが使い果たされるか、32 ビット プロセスのアドレス空間によって制限される場合、オペレーティング システムはこのネイティブ メモリの割り当てを拒否します。
    • JVM は java.lang.OutOfMemoryError: 新しいネイティブ スレッドを作成できません エラーをスローします。

5.3 解決策

プログラムで作成されるスレッドの数を減らす方法を見つけ、アプリケーションが本当にそれほど多くのスレッドを作成する必要があるかどうかを分析します。

本当に多くのスレッドを作成する必要がある場合は、OS レベルで最大スレッド数を増やします。ulimia -a を実行して最大スレッド制限を表示し、ulimit -u xxx を使用して最大スレッド制限を調整します。

6. メタスペース

JDK 1.8 より前では Permgen スペース エラーが表示されます。このエラーは、通常、ロードされているクラスが多すぎるか、サイズが大きすぎるために、永続世代がいっぱいになっていることを意味します。 1.8 で永続的な生成がキャンセルされたため、この例外は発生しなくなります。

Metaspace は、HotSpot のメソッド領域の実装です。メタスペースと永続世代の最大の違いは、メタスペースが仮想マシンのメモリ内になく、ローカル メモリを使用することです。ただし、ローカルメモリがいっぱいになる可能性があるため、例外が発生します。

6.1 バグを書く

  1. /**
  2. * JVM オプション: -XX:MetaspaceSize=10m -XX:MaxMetaspaceSize=10m
  3. */
  4. パブリッククラスMetaspaceOOMDemo {
  5.  
  6. 公共 静的void main(String[] args) {
  7.  
  8. )の間{
  9. エンハンサー enhancer = new Enhancer();
  10. エンハンサー.setSuperclass(MetaspaceOOMDemo.class);
  11. エンハンサー.setUseCache( false );
  12. enhancer.setCallback((MethodInterceptor) (o, メソッド, オブジェクト, メソッドプロキシ) -> {
  13. //動的プロキシ作成オブジェクト
  14. メソッドProxy.invokeSuper(o, objects)を返します
  15. });
  16. エンハンサーを作成します();
  17. }
  18. }
  19. }

SpringのGCLibでオブジェクトを動的に作成する

  1. スレッド「main」例外が発生しましたorg.springframework.cglib.core.CodeGenerationException: java.lang.OutOfMemoryError -->Metaspace  

6.2 解決策

メソッド領域のオーバーフローも、一般的なメモリ オーバーフロー例外です。実行時に大量の動的クラスが頻繁に生成されるアプリケーション シナリオでは、これらのクラスのリサイクルに特別な注意を払う必要があります。前述の GCLib バイトコード拡張と動的言語に加えて、このタイプのシナリオには、多数の JSP または動的に生成された JSP ファイル (従来のソフトウェア業界では古くから存在していた可能性があります) を含むアプリケーション、OSGi ベースのアプリケーション (同じクラス ファイルが異なるローダーによってロードされた場合でも、異なるクラスと見なされます) なども一般的に含まれます。

メソッド領域は、一般的に JDK8 では生成が容易ではありません。 HotSpot は、予防的な役割を果たすことができるメタスペースを設定するためのいくつかのパラメータを提供します。

  • -XX:MaxMetaspaceSize はメタスペースの最大値を設定します。デフォルト値は -1 で、制限がないことを意味します (ローカル メモリのサイズ制限は適用されます)
  • -XX:MetaspaceSize は、メタスペースの初期サイズをバイト単位で指定します。この値に達すると、GC がトリガーされ、型がアンロードされます。コレクターはこの値を調整します。
  • -XX:MinMetaspaceFreeRatio は、GC 後のメタスペースの残り容量の最小パーセンテージを制御し、メタスペース不足によるガベージ コレクションの頻度を減らすことができます。同様のMaxMetaspaceFreeRatioもあります

7. 要求された配列サイズが VM の制限を超えています

7.1バグを書く

  1. 公共 静的void main(String[] args) {
  2. int [] arr = 新しいint [整数.MAX_VALUE];
  3. }

これは比較的簡単です。非常に大きな配列を作成すると、OOM が発生します。これ以上は言いません。

  1. スレッド「main」例外が発生しましたjava.lang.OutOfMemoryError: 要求された配列のサイズがVM の制限を超えています

JVM は配列の最大長を制限します。このエラーは、プログラムが最大長の制限を超える配列の作成を要求していることを示します。

配列にメモリを割り当てる前に、JVM は割り当てられるデータ構造がシステム内でアドレス指定可能かどうか (通常は Integer.MAX_VALUE-2) を確認します。

この種の問題は比較的まれです。通常、コードをチェックして、ビジネスでこのような大規模な配列を作成する必要があるかどうか、また、複数のブロックに分割してバッチで実行できるかどうかを確認する必要があります。

8. スワップ領域不足

Java アプリケーションを起動すると、限られた量のメモリが割り当てられます。この制限は、-Xmx およびその他の同様の起動パラメータによって指定されます。

JVM によって要求されたメモリの合計が使用可能な物理メモリより大きい場合、オペレーティング システムはメモリからハード ドライブへのスワップを開始します。

このエラーは、使用可能な仮想メモリがすべて使い果たされたことを示します。仮想メモリは、物理メモリとスワップ領域の 2 つの部分で構成されます。

こんなエラーは見たことないよ〜〜〜

9. プロセスを殺すか子供を犠牲にする

オペレーティング システムはプロセスの概念に基づいて構築されます。これらのプロセスは複数のカーネル ジョブによって管理されます。そのうちの 1 つは「Out of memory Killer」と呼ばれ、使用可能なメモリが極端に少ない場合に特定のプロセスを「強制終了」します。 OOM Killer はすべてのプロセスにスコアを付け、スコアが低いプロセスを「強制終了」します。具体的なスコアリング ルールについては、「Surviving the Linux OOM Killer」を参照してください。

他の OOM エラーとは異なり、Killprocessorsacrifice 子エラーは JVM レベルではなく、オペレーティング システム レベルでトリガーされます。

9.1 原因分析

デフォルトでは、Linux カーネルは、プロセスによって要求されるメモリの合計量がシステムの使用可能なメモリよりも大きいことを許可します。この「ピークシフト」アプローチにより、システム リソースをより効率的に使用できます。

しかし、このアプローチは必然的に「過剰販売」という一定のリスクをもたらします。たとえば、一部のプロセスがシステム メモリを占有し続けると、他のプロセスに使用可能なメモリがなくなることがあります。このとき、システムは自動的に OOM Killer を起動し、スコアの低いプロセスを探し、それらを「強制終了」してメモリ リソースを解放します。

9.2 解決策

  • 競合を避けるためにサーバー構成をアップグレードし、展開を分離する
  • OOMキラーチューニング。

最後に偉大な神「ヤハイ」の写真を添付し​​ます

「Java 仮想マシン 3 版の詳細な理解」

https://plumbr.io/outofmemoryerror

https://yq.aliyun.com/articles/711191

https://github.com/StabilityMan/StabilityGuide/blob/master/docs/diagnosis/jvm/Exception

この記事はWeChatの公開アカウント「JavaKeeper」から転載したもので、著者はHaixingです。この記事を転載する場合はJavaKeeper公式アカウントまでご連絡ください。

オリジナルリンク: https://mp.weixin.qq.com/s/gIJvtd8rrZz6ttaoGLddLg

<<:  7.10 カンファレンスまでのカウントダウン |エッジコンピューティングシナリオ向けにカスタマイズされた最初のモデル

>>:  エッジコンピューティングの成功または失敗の鍵は何でしょうか?

推薦する

V.PS: シンガポール VPS がリリース (高性能/大帯域幅/中国最適化)、複数の割引、先行販売イベント「このサイト限定でトラフィック 2 倍」

v.ps(XTOMデータセンター傘下のクラウドサーバー)は現在、新製品シリーズであるシンガポールVP...

百度の7月のアルゴリズム調整の簡単な分析:ROIの向上が百度の信頼度を決定する

最近、7月のBaiduのアルゴリズム変更の話題に注目する人がおり、アルゴリズム変更によって生じたラン...

メールマーケティングのコンバージョン率を向上させるためのヒントとボタンデザイン

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

SEOを学んだ後の私の考えについて簡単に話します

いつもの検索や「SEO」中に私が発見した簡単なルール: 1) Baidu の大規模な見直しにより、B...

エッジコンピューティングに関する3つの誤解

毎日何百万ものマシンやオブジェクトがインターネットに接続される中、企業はエッジ コンピューティングを...

過去2年間のBaiduの変化とインターネットの将来に対する認識について簡単に説明します。

5月がまたやってきました。 2年前の今頃、百度プラットフォームが大規模なインターネット浄化キャンペー...

操作を行うには、データを見るだけでは不十分です

EC業界では、運営者としてPVやUVなどのデータを毎日監視し、レポートを毎日記入することが必須です。...

AWS が新しい Amazon EC2 インスタンスを発表

[51CTO.com からのオリジナル記事] 本日の re:Invent カンファレンスで、AWS ...

Kuaiboは今日トラブルに巻き込まれた。多数の警察がその巣穴を襲撃した。

さらに読む: Qvodは調査に対して次のように回答した。「同社は通常通り営業しており、誰かが逮捕され...

両方の長所を兼ね備えたハイブリッドクラウド

パンデミックが人々に何かを教えてくれたとすれば、それは、これから何が起こるかは決して分からないという...

アプリを宣伝するために必要なことをカウントダウンしましょう!チャネルを理解し、計画を立て、つながりを持ち、競合製品を分析します...

1.競合製品の分析方法を学ぶ競合分析は、あらゆる職種において最も重要なスキルの 1 つです。競合製品...

分散型 Kv-2 ラフトリーダー選出の実装

[[441163]]この記事から、raft をベースに分散 KV を構築していきます。 Raft は...

パーソナルマーケティングの可視性を構築するにはどうすればよいでしょうか?

はじめに: 私の個人的な経験に基づいて、個人のマーケティングの可視性を構築する方法について書きます。...

Qiyiはサービス指向のコンセプトを提唱し、携帯電話業界のマーケティングを変えています

月収10万元の起業の夢を実現するミニプログラム起業支援プランマーケティング用携帯電話の誕生以来、マー...

口コミを活用してブランドで市場を開拓する方法を教えます

企業の発展と成長は、ユーザーの評判の浸透から切り離すことはできません。特に、成長段階にある新興企業は...