การใช้หน่วยความจำของวัตถุใน Java คืออะไร?


216

พื้นที่หน่วยความจำถูกใช้โดยวัตถุหนึ่งตัวที่มี 100 คุณลักษณะเหมือนกับของวัตถุ 100 ชิ้นโดยที่มีหนึ่งแอตทริบิวต์

มีการจัดสรรหน่วยความจำเท่าใดสำหรับวัตถุ?
มีการใช้พื้นที่เพิ่มเติมเท่าใดเมื่อเพิ่มแอตทริบิวต์

คำตอบ:


180

Mindprodชี้ให้เห็นว่านี่ไม่ใช่คำถามที่ตรงไปตรงมาที่จะตอบ:

JVM มีอิสระในการจัดเก็บข้อมูลไม่ว่าจะเป็นที่ชื่นชอบภายในองค์กรไม่ว่าใหญ่หรือเล็กก็ตามด้วยการแพ็ดดิ้งหรือโอเวอร์เฮดในปริมาณที่พอเหมาะ
ยกตัวอย่างเช่น JVM หรือคอมไพเลอร์พื้นเมืองอาจตัดสินใจที่จะเก็บboolean[]ใน 64 BitSetบิตชิ้นยาวเช่น มันไม่จำเป็นต้องบอกคุณตราบใดที่โปรแกรมให้คำตอบเดียวกัน

  • มันอาจจัดสรรวัตถุชั่วคราวบางอย่างในกองซ้อน
  • มันอาจปรับค่าตัวแปรหรือวิธีการบางอย่างให้เหมาะสมที่สุดโดยแทนที่ค่าคงที่
  • มันอาจจะเป็นวิธีการหรือลูปเช่นการรวบรวมวิธีการสองเวอร์ชันแต่ละวิธีได้รับการปรับให้เหมาะสมสำหรับสถานการณ์ที่แน่นอน

แน่นอนว่าฮาร์ดแวร์และระบบปฏิบัติการมีหลายแคชบนชิปแคชแคช SRAM แคช DRAM ชุดการทำงาน RAM ทั่วไปและที่เก็บข้อมูลสำรองบนดิสก์ ข้อมูลของคุณอาจซ้ำซ้อนในทุกระดับแคช ความซับซ้อนทั้งหมดนี้หมายความว่าคุณสามารถคาดการณ์ปริมาณการใช้ RAM ได้อย่างคร่าวๆ

วิธีการวัด

คุณสามารถใช้Instrumentation.getObjectSize()เพื่อรับการประเมินของหน่วยความจำที่ใช้โดยวัตถุ

เห็นภาพที่เกิดขึ้นจริงรูปแบบวัตถุรอยและการอ้างอิง, คุณสามารถใช้JOL (Java วัตถุเค้าโครง) เครื่องมือ

ส่วนหัวของวัตถุและการอ้างอิงวัตถุ

ใน JDK 64- บิตที่ทันสมัยวัตถุมีส่วนหัว 12 ไบต์เพิ่มเป็น 8 ไบต์ดังนั้นขนาดวัตถุขั้นต่ำคือ 16 ไบต์ สำหรับ JVM แบบ 32 บิตค่าใช้จ่ายจะอยู่ที่ 8 ไบต์ซึ่งจะเพิ่มเป็น 4 ไบต์ (จากคำตอบของ Dmitry Spikhalskiy , คำตอบ Jayen ของและJavaWorld .)

โดยปกติแล้วการอ้างอิง 4 ไบต์บนแพลตฟอร์ม 32bit หรือ 64bit แพลตฟอร์มถึง-Xmx32G; และ 8 ไบต์เหนือ 32Gb ( -Xmx32G) (ดูการอ้างอิงวัตถุที่บีบอัด )

เป็นผลให้ JVM แบบ 64 บิตโดยทั่วไปจะต้องการพื้นที่ฮีปเพิ่มขึ้น 30-50% ( ฉันควรใช้ JVM 32- หรือ 64- บิต , 2012, JDK 1.7)

ชนิดกล่องและสตริงที่บรรจุอยู่ในกล่อง

wrapper ชนิดบรรจุกล่องมีค่าใช้จ่ายเมื่อเทียบกับประเภทดั้งเดิม (จากJavaWorld ):

  • Integer: ผลลัพธ์ขนาด 16 ไบต์แย่กว่าที่ฉันคาดไว้เล็กน้อยเพราะintค่าสามารถเพิ่มเป็น 4 ไบต์พิเศษได้ ใช้Integerค่าใช้จ่ายฉันเป็นค่าใช้จ่ายหน่วยความจำร้อยละ 300 เมื่อเทียบกับเมื่อฉันสามารถเก็บค่าเป็นชนิดดั้งเดิม

  • Long: 16 ไบต์เช่นกัน: ชัดเจนขนาดวัตถุจริงบนฮีปนั้นขึ้นอยู่กับการจัดตำแหน่งหน่วยความจำระดับต่ำโดยการใช้ JVM เฉพาะสำหรับประเภท CPU เฉพาะ ดูเหมือนว่าLongจะเป็น 8 ไบต์ของค่าใช้จ่ายของวัตถุและเพิ่มอีก 8 ไบต์สำหรับค่าความยาวจริง ในทางตรงกันข้ามIntegerมีรูขนาด 4 ไบต์ที่ไม่ได้ใช้งานมากที่สุดเนื่องจาก JVM ฉันใช้การจัดเรียงวัตถุบนขอบเขตของคำ 8 ไบต์

ภาชนะอื่น ๆ มีราคาแพงเกินไป:

  • อาร์เรย์หลายมิติ : มันมีความประหลาดใจอีกอย่างหนึ่ง
    นักพัฒนามักใช้โครงสร้างเช่นint[dim1][dim2]ในการคำนวณเชิงตัวเลขและเชิงวิทยาศาสตร์

    ในint[dim1][dim2]อินสแตนซ์อาร์เรย์ทุกint[dim2]อาร์เรย์ที่ซ้อนกันจะอยู่Objectในสิทธิ์ของตนเอง แต่ละอันจะเพิ่มโอเวอร์เฮดของอาร์เรย์ขนาด 16 ไบต์ตามปกติ เมื่อฉันไม่ต้องการอาเรย์รูปสามเหลี่ยมหรือ ragged นั่นหมายถึงค่าใช้จ่ายที่บริสุทธิ์ ผลกระทบจะเพิ่มขึ้นเมื่อขนาดของอาร์เรย์แตกต่างกันอย่างมาก

    ตัวอย่างเช่นint[128][2]อินสแตนซ์ใช้เวลา 3,600 ไบต์ เมื่อเปรียบเทียบกับ 1,040 ไบต์ที่int[256]อินสแตนซ์ใช้ (ซึ่งมีความจุเท่ากัน) 3,600 ไบต์แทนค่าใช้จ่าย 246 เปอร์เซ็นต์ ในกรณีที่รุนแรงbyte[256][1]ปัจจัยค่าใช้จ่ายเกือบ 19! เปรียบเทียบกับสถานการณ์ C / C ++ ที่ไวยากรณ์เดียวกันไม่ได้เพิ่มค่าใช้จ่ายในการจัดเก็บใด ๆ

  • String: การStringเติบโตของหน่วยความจำติดตามการเติบโตของอาร์เรย์ภายใน อย่างไรก็ตามStringคลาสเพิ่มค่าใช้จ่ายอีก 24 ไบต์

    สำหรับStringขนาดที่ไม่มีอักขระ 10 ตัวหรือน้อยกว่านั้นค่าใช้จ่ายที่เพิ่มจะสัมพันธ์กับเพย์โหลดที่มีประโยชน์ (2 ไบต์สำหรับถ่านแต่ละตัวบวก 4 ไบต์สำหรับความยาว) ตั้งแต่ 100 ถึง 400 เปอร์เซ็นต์

การวางแนว

พิจารณาวัตถุตัวอย่างนี้:

class X {                      // 8 bytes for reference to the class definition
   int a;                      // 4 bytes
   byte b;                     // 1 byte
   Integer c = new Integer();  // 4 bytes for a reference
}

ผลรวม na wouldve จะแนะนำว่าอินสแตนซ์ของXจะใช้ 17 ไบต์ อย่างไรก็ตามเนื่องจากการจัดตำแหน่ง (หรือที่เรียกว่าการขยาย) JVM จะจัดสรรหน่วยความจำเป็นทวีคูณ 8 ไบต์ดังนั้นแทนที่จะเป็น 17 ไบต์จึงจะจัดสรร 24 ไบต์


int [128] [6]: 128 อาร์เรย์ 6 ints - 768 ints ทั้งหมด, 3072 ไบต์ของข้อมูล + 2064 ไบต์ไบต์ค่าใช้จ่ายวัตถุ = 5166 ไบต์รวม int [256]: 256 ints ทั้งหมด - ดังนั้นจึงไม่สามารถเทียบเคียงได้ int [768]: 3072 ไบต์ของข้อมูล + 16 ไบต์ค่าใช้จ่าย - ประมาณ 3 / 5th ของพื้นที่ของอาร์เรย์ 2D - ค่าใช้จ่ายไม่ค่อนข้าง 246%!
JeeBee

อาบทความต้นฉบับใช้ int [128] [2] ไม่ได้ int [128] [6] - สงสัยว่ามันเปลี่ยนไปอย่างไร ยังแสดงให้เห็นว่าตัวอย่างที่รุนแรงสามารถบอกเล่าเรื่องราวที่แตกต่างได้
JeeBee

2
โอเวอร์เฮดคือ 16 ไบต์ใน JVM 64 บิต
Tim Cooper

3
@AlexWien: ชุดรูปแบบการรวบรวมขยะบางชุดอาจกำหนดขนาดของวัตถุขั้นต่ำซึ่งแยกออกจากการขยาย ในระหว่างการรวบรวมขยะเมื่อวัตถุถูกคัดลอกจากที่ตั้งเก่าไปยังที่ตั้งใหม่ที่ตั้งเก่าอาจไม่จำเป็นต้องเก็บข้อมูลสำหรับวัตถุอีกต่อไป แต่จะต้องเก็บการอ้างอิงไปยังที่ตั้งใหม่ มันอาจจำเป็นต้องเก็บการอ้างอิงไปยังตำแหน่งเก่าของวัตถุที่มีการค้นพบการอ้างอิงแรกและออฟเซ็ตของการอ้างอิงนั้นภายในวัตถุเก่า [เนื่องจากวัตถุเก่าอาจยังมีการอ้างอิงที่ยังไม่ได้ประมวลผล]
supercat

2
@AlexWien: การใช้หน่วยความจำที่ตำแหน่งเก่าของวัตถุเพื่อเก็บข้อมูลการเก็บหนังสือของตัวเก็บขยะช่วยหลีกเลี่ยงความจำเป็นในการจัดสรรหน่วยความจำอื่นเพื่อจุดประสงค์นั้น แต่อาจกำหนดขนาดวัตถุขั้นต่ำที่ใหญ่กว่าที่จำเป็น ฉันคิดว่าอย่างน้อยหนึ่งรุ่น. NET garbage collector ใช้วิธีการนั้น แน่นอนว่ามันจะเป็นไปได้สำหรับนักสะสมขยะ Java บางคนที่จะทำเช่นนั้น
supercat

34

มันขึ้นอยู่กับสถาปัตยกรรม / jdk สำหรับสถาปัตยกรรม JDK และ 64 บิตที่ทันสมัยวัตถุมีส่วนหัว 12 ไบต์และแพ็ดดิง 8 ไบต์ดังนั้นขนาดวัตถุขั้นต่ำคือ 16 ไบต์ คุณสามารถใช้เครื่องมือที่เรียกว่าJava Object Layoutเพื่อกำหนดขนาดและรับรายละเอียดเกี่ยวกับเค้าโครงของวัตถุและโครงสร้างภายในของเอนทิตีใด ๆ หรือเดาข้อมูลนี้โดยอ้างอิงคลาส ตัวอย่างของเอาต์พุตสำหรับ Integer บนสภาพแวดล้อมของฉัน:

Running 64-bit HotSpot VM.
Using compressed oop with 3-bit shift.
Using compressed klass with 3-bit shift.
Objects are 8 bytes aligned.
Field sizes by type: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]
Array element sizes: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]

java.lang.Integer object internals:
 OFFSET  SIZE  TYPE DESCRIPTION                    VALUE
      0    12       (object header)                N/A
     12     4   int Integer.value                  N/A
Instance size: 16 bytes (estimated, the sample instance is not available)
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total

ดังนั้นสำหรับ Integer ขนาดอินสแตนซ์คือ 16 ไบต์เนื่องจาก int 4 ไบต์บีบอัดในสถานที่ทันทีหลังจากส่วนหัวและก่อนที่จะขยายขอบเขต

ตัวอย่างโค้ด:

import org.openjdk.jol.info.ClassLayout;
import org.openjdk.jol.util.VMSupport;

public static void main(String[] args) {
    System.out.println(VMSupport.vmDetails());
    System.out.println(ClassLayout.parseClass(Integer.class).toPrintable());
}

หากคุณใช้ Maven เพื่อรับ JOL:

<dependency>
    <groupId>org.openjdk.jol</groupId>
    <artifactId>jol-core</artifactId>
    <version>0.3.2</version>
</dependency>

28

แต่ละวัตถุมีค่าใช้จ่ายที่แน่นอนสำหรับการตรวจสอบที่เกี่ยวข้องและข้อมูลประเภทเช่นเดียวกับเขตข้อมูลตัวเอง นอกเหนือจากนั้นฟิลด์สามารถจัดวางได้ค่อนข้างมาก แต่ JVM เห็นว่าเหมาะสม (ฉันเชื่อ) - แต่ดังที่แสดงไว้ในคำตอบอื่นอย่างน้อยJVM บางตัวจะอัดแน่นพอสมควร พิจารณาชั้นเรียนเช่นนี้

public class SingleByte
{
    private byte b;
}

VS

public class OneHundredBytes
{
    private byte b00, b01, ..., b99;
}

บน JVM แบบ 32 บิตฉันคาดว่าจะมีอินสแตนซ์ 100 รายการที่SingleByteจะใช้ 1200 ไบต์ (8 ไบต์ของค่าใช้จ่าย + 4 ไบต์สำหรับเขตข้อมูลเนื่องจากการขยาย / การจัดตำแหน่ง) ฉันคาดว่าหนึ่งอินสแตนซ์ของOneHundredBytesการใช้ 108 ไบต์ - ค่าใช้จ่ายแล้ว 100 ไบต์บรรจุ แน่นอนว่ามันอาจแตกต่างกันไปตาม JVM - การใช้งานอย่างหนึ่งอาจตัดสินใจที่จะไม่แพ็คฟิลด์ในOneHundredBytesซึ่งนำไปสู่การใช้ 408 ไบต์ (= 8 ไบต์ค่าใช้จ่าย + 4 * 100 ไบต์จัดแนว / padded) ใน JVM 64 บิตค่าใช้จ่ายอาจสูงกว่าด้วย (ไม่แน่ใจ)

แก้ไข: ดูความคิดเห็นด้านล่าง; เห็นได้ชัดว่า HotSpot มีขอบเขตถึง 8 ไบต์แทนที่จะเป็น 32 ดังนั้นแต่ละอินสแตนซ์SingleByteจะใช้เวลา 16 ไบต์

ไม่ว่าจะด้วยวิธีใดก็ตาม "วัตถุขนาดใหญ่เดียว" อย่างน้อยก็มีประสิทธิภาพเท่ากับวัตถุขนาดเล็กหลายชิ้น - สำหรับกรณีง่าย ๆ เช่นนี้


9
ที่จริงแล้วอินสแตนซ์หนึ่งของ SingleByte จะใช้เวลา 16 ไบต์บน Sun JVM นั่นคือ 8 ไบต์ค่าใช้จ่าย, 4 ไบต์สำหรับเขตข้อมูลและจากนั้น 4 ไบต์สำหรับการเติมวัตถุเนื่องจากคอมไพเลอร์ HotSpot ปัดทุกอย่างเป็นทวีคูณ 8
Paul Wagland

6

จำนวนหน่วยความจำที่ใช้ / ฟรีทั้งหมดของโปรแกรมสามารถรับได้ในโปรแกรมผ่าน

java.lang.Runtime.getRuntime();

รันไทม์มีหลายวิธีที่เกี่ยวข้องกับหน่วยความจำ ตัวอย่างการเข้ารหัสต่อไปนี้แสดงให้เห็นถึงการใช้งาน

package test;

 import java.util.ArrayList;
 import java.util.List;

 public class PerformanceTest {
     private static final long MEGABYTE = 1024L * 1024L;

     public static long bytesToMegabytes(long bytes) {
         return bytes / MEGABYTE;
     }

     public static void main(String[] args) {
         // I assume you will know how to create a object Person yourself...
         List < Person > list = new ArrayList < Person > ();
         for (int i = 0; i <= 100000; i++) {
             list.add(new Person("Jim", "Knopf"));
         }
         // Get the Java runtime
         Runtime runtime = Runtime.getRuntime();
         // Run the garbage collector
         runtime.gc();
         // Calculate the used memory
         long memory = runtime.totalMemory() - runtime.freeMemory();
         System.out.println("Used memory is bytes: " + memory);
         System.out.println("Used memory is megabytes: " + bytesToMegabytes(memory));
     }
 }

6

ปรากฏว่าวัตถุทุกชิ้นมีค่าใช้จ่าย 16 ไบต์บนระบบ 32 บิต (และ 24- ไบต์บนระบบ 64 บิต)

http://algs4.cs.princeton.edu/14analysis/เป็นแหล่งข้อมูลที่ดี ตัวอย่างหนึ่งของคนดีหลายคนดังต่อไปนี้

ป้อนคำอธิบายรูปภาพที่นี่

http://www.cs.virginia.edu/kim/publicity/pldi09tutorials/memory-efficient-java-tutorial.pdfนอกจากนี้ยังมีข้อมูลมากเช่น:

ป้อนคำอธิบายรูปภาพที่นี่


"ดูเหมือนว่าวัตถุทุกชิ้นมีค่าใช้จ่าย 16 ไบต์บนระบบ 32 บิต (และ 24- ไบต์บนระบบ 64 บิต)" มันไม่ถูกต้องอย่างน้อยสำหรับ JDK ปัจจุบัน ลองดูที่คำตอบของฉันสำหรับตัวอย่างจำนวนเต็ม โอเวอร์เฮดของวัตถุอย่างน้อย 12 ไบต์สำหรับส่วนหัวสำหรับระบบ 64- บิตและ JDK ที่ทันสมัย สามารถเพิ่มเติมได้เนื่องจากการแพ็ดดิ้งขึ้นอยู่กับรูปแบบของฟิลด์ที่แท้จริงในวัตถุ
Dmitry Spikhalskiy

ลิงค์ที่สองไปยังหน่วยความจำ Java tutorial ที่มีประสิทธิภาพดูเหมือนว่าจะตาย - ฉันได้รับ "Forbidden"
tsleyson

6

พื้นที่หน่วยความจำถูกใช้โดยวัตถุหนึ่งตัวที่มี 100 คุณลักษณะเหมือนกับของวัตถุ 100 ชิ้นโดยที่มีหนึ่งแอตทริบิวต์

เลขที่

มีการจัดสรรหน่วยความจำเท่าใดสำหรับวัตถุ?

  • โอเวอร์เฮดคือ 8 ไบต์ใน 32- บิต, 12 ไบต์บน 64- บิต; จากนั้นปัดเศษขึ้นเป็นทวีคูณของ 4 ไบต์ (32- บิต) หรือ 8 ไบต์ (64- บิต)

มีการใช้พื้นที่เพิ่มเติมเท่าใดเมื่อเพิ่มแอตทริบิวต์

  • แอตทริบิวต์ในช่วงตั้งแต่ 1 ไบต์ (ไบต์) ถึง 8 ไบต์ (ยาว / คู่) แต่อ้างอิงทั้ง 4 ไบต์หรือ 8 ไบต์ขึ้นอยู่ไม่ได้อยู่กับว่ามัน 32bit หรือ 64bit แต่ไม่ว่าจะ -Xmx คือ <32Gb หรือ> = 32 Gb: ทั่วไป 64 - bit JVM's มีการเพิ่มประสิทธิภาพที่เรียกว่า "-UseCompressedOops" ซึ่งบีบอัดการอ้างอิงถึง 4 ไบต์หากกองต่ำกว่า 32Gb

1
ถ่านคือ 16 บิตไม่ใช่ 8 บิต
comonad

ใช่คุณ ดูเหมือนว่าใครบางคนจะแก้ไขคำตอบเดิมของฉัน
Jayen

5

ไม่การลงทะเบียนวัตถุจะใช้หน่วยความจำเล็กน้อย วัตถุ 100 รายการที่มีแอตทริบิวต์ 1 รายการจะใช้หน่วยความจำเพิ่มขึ้น


4

คำถามจะเป็นคำถามที่กว้างมาก

มันขึ้นอยู่กับตัวแปรคลาสหรือคุณอาจเรียกว่าการใช้หน่วยความจำสถานะในจาวา

นอกจากนี้ยังมีข้อกำหนดหน่วยความจำเพิ่มเติมบางส่วนสำหรับส่วนหัวและการอ้างอิง

หน่วยความจำฮีปที่ใช้โดยวัตถุ Java ประกอบด้วย

  • หน่วยความจำสำหรับฟิลด์ดั้งเดิมตามขนาด (ดูด้านล่างสำหรับขนาดของประเภทดั้งเดิม);

  • หน่วยความจำสำหรับเขตข้อมูลอ้างอิง (4 ไบต์แต่ละรายการ);

  • ส่วนหัวของวัตถุซึ่งประกอบด้วยข้อมูล "แม่บ้าน" สองสามไบต์

วัตถุใน java ยังต้องการข้อมูล "การดูแล" บางอย่างเช่นการบันทึกคลาสของ ID และสถานะของวัตถุเช่นว่าวัตถุนั้นสามารถเข้าถึงได้ในปัจจุบันล็อคซิงโครไนซ์เป็นต้นหรือไม่

ขนาดส่วนหัวของวัตถุ Java แตกต่างกันไปใน 32 และ 64 บิต jvm

แม้ว่าสิ่งเหล่านี้จะเป็นหน่วยความจำหลักที่ผู้บริโภค jvm ต้องใช้นอกจากนี้บางฟิลด์ต้องการสำหรับการจัดวางโค้ด ฯลฯ

ขนาดของประเภทดั้งเดิม

บูลีนและไบต์ - 1

อักขระสั้น & - 2

int & float - 4

ยาว & สองเท่า - 8


ผู้อ่านอาจพบว่าบทความนี้มีความกระจ่างมาก: cs.virginia.edu/kim/publicity/pldi09tutorials/…
คิดเห็นที่

3

ฉันได้ผลลัพธ์ที่ดีมากจากวิธีjava.lang.instrument.Instrumentation ที่กล่าวถึงในคำตอบอื่น สำหรับตัวอย่างที่ดีของการใช้งานดูรายการInstrumentation Memory Counterจาก JavaSpecialists 'Newsletter และไลบรารีjava.sizeOfบน SourceForge


2

ในกรณีที่มันเป็นประโยชน์กับทุกคนที่คุณสามารถดาวน์โหลดได้จากเว็บไซต์ของฉันมีขนาดเล็กตัวแทน Java สำหรับการสอบถามการใช้งานหน่วยความจำของวัตถุ มันจะช่วยให้คุณค้นหาการใช้หน่วยความจำ "ลึก" เช่นกัน


สิ่งนี้ใช้งานได้ดีมากสำหรับการประเมินคร่าวๆว่าหน่วยความจำ(String, Integer)แคชของ Guava ใช้ต่อหน่วยองค์ประกอบเท่าใด ขอบคุณ!
Steve K

1

ไม่วัตถุขนาดเล็ก 100 ชิ้นต้องการข้อมูลเพิ่มเติม (หน่วยความจำ) มากกว่าวัตถุขนาดใหญ่หนึ่งชิ้น


0

กฎเกี่ยวกับปริมาณการใช้หน่วยความจำขึ้นอยู่กับการนำ JVM มาใช้และสถาปัตยกรรม CPU (ตัวอย่างเช่น 32 บิตต่อ 64 บิต)

สำหรับรายละเอียดกฎสำหรับ SUN JVM ตรวจสอบบล็อกเก่าของฉัน

ขอแสดงความนับถือ มาร์คัส


ฉันค่อนข้างมั่นใจว่า Sun Java 1.6 64 บิตต้องการ 12 ไบต์สำหรับวัตถุธรรมดา + 4 padding = 16; วัตถุ + ฟิลด์จำนวนเต็มหนึ่ง = 12 + 4 = 16
AlexWien

คุณปิดบล็อกหรือไม่
Johan Boulé

ไม่จริงไม่แน่ใจว่าบล็อกของ SAP ย้ายไปอย่างใด ส่วนใหญ่สามารถพบได้ที่นี่kohlerm.blogspot.com
kohlerm
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.