מודל זיכרון J2ObjC

במאמר הזה מוסבר איך מנהלים את הזיכרון בקוד המתורגם J2ObjC, ואיך תוכנות מתנהגים בזמן הגישה לזיכרון משותף.

ניהול זיכרון

אחת המטרות של J2ObjC היא ליצור קוד מתורגם שישולב בצורה חלקה בסביבה לספירת ההפניות ב-Objective-C. כך קל להשתמש בקוד Java מתורגם מ-Objective-C שכתוב במקור, כי אין העברת בעלות מוזרה על אובייקטים שמועברים בין סביבות Java ו-Objective-C.

מכיוון ש-Java משתמשת באיסוף אשפה לניהול זיכרון, קוד Java לא מכיל ניהול מפורש של הזיכרון של האובייקטים שלו. לכן J2ObjC צריך להכניס קריאות לספירת הפניות בהתאם כדי לוודא שהאובייקטים ממוקמים בזמן הנכון. קבענו כי קבענו את הכללים הבאים, שלדעתנו הם גם מעשיים וגם מבצעים:

  • כל האובייקטים יפעלו לפחות למשך הזמן של מאגר ההפצה האוטומטי הנוכחי.
    • הכלל הזה מאפשר לנו לדלג על רזולוציות רבות ועל שחרורים שהיו נחוצים במקרים אחרים.
  • משתנים מקומיים לא נשמרים.
    • אין הפניות לספירת קריאות לפעולות של קריאה או כתיבה של משתנים מקומיים.
  • השדות נשמרים.
    • הקצאה של קריאות שדה שומרת על הערך החדש ומשחררת באופן אוטומטי את הערך הישן.
  • אובייקטים חדשים מפורסמים מיד באופן אוטומטי. (אלא אם הוקצה מיד לשדה)

מחזורי הפניה

מחזור הפניות מתקיים כאשר אובייקט מפנה לעצמו באופן ישיר או עקיף דרך השדות שלו. אפשר לנקות מחזורי הפניות באמצעות איסוף האשפה ב-Java, אבל דליפת זיכרון בסביבה של ספירת ההפניות ב-Objective-C. אין דרך אוטומטית למנוע חזרה של מחזורי הפניות, אבל אנחנו מספקים כלי Cycle Finder שמאפשר לזהות מחזורים באופן אוטומטי. הנה כמה דרכים נפוצות לתיקון מחזור קובצי עזר:

  • כדי להחליש את אחת ההפניות, הוסיפו הערה מסוג @Weak או @WeakOuter.
  • מוסיפים את השיטה cleanup() לאחד האובייקטים שמגדירה חלק מהשדות כ-null. צריך להפעיל את cleanup() לפני שמוחקים את האובייקט.
  • לעצב מחדש את הקוד כדי להימנע לגמרי מיצירה של מחזור הפניות.

זיכרון משותף

בתוכנית עם כמה שרשורים, אפשר לשתף חלק מהנתונים באמצעות מספר שרשורים. ב-Java יש כמה כלים שמאפשרים גישה בטוחה לשרשורים לנתונים משותפים. בקטע הזה מתוארת התמיכה של J2ObjC בגישה לנתונים משותפים.

מסונכרנת

J2ObjC ממפה את מילת המפתח synchronized ישירות ליעד-ג @synchronized.

אטומיות

Java מבטיח אטימות של טעינות ואחסון מכל הסוגים, מלבד long ו-double. לפרטים נוספים, ראו JLS-17.7. למעט השדות volatile (כפי שמתואר בהמשך), לא מטפלים ב-J2ObjC באופן מיוחד כדי להבטיח טעינות ואחסון של אובייקטים אטומיים. כלומר:

  • מכיוון שכל פלטפורמות ה-iOS הן בגרסת 32 או 64 ביט, טעינות ומאגרים של סוגים פרימיטיביים מלבד long ו-double הם אטומיים במכשירים של 32 ביט, וכולן אטומיות במערכות של 64 ביט.
  • הטעינות והמאגרים של סוגי האובייקטים ב-J2ObjC לא אטומיים.
    • העדכון של מספרי הפניות באופן אטומי הוא יקר מדי.
    • אפשר להפוך שדה אטום לשדה אטומי על ידי הצהרה עליו volatile. (ראו בהמשך)

שדות נדיפים

בשדות volatile, Java מספקת גם סדר אטומי וגם סדר עקבי (JLS-8.3.1.4), שאפשר להשתמש בו לסנכרון. J2ObjC מספק את אותן התחייבויות כמו ב-Java לכל שדות ה-volatile. J2ObjC משתמש במנגנונים הבאים לשדות volatile:

  • סוגים פרימיטיביים ממופים לסוגים אטומיים של c11.
    • למשל volatile int -> _Atomic(jint)
  • שדות אובייקטים מוגנים באמצעות מנעולי pthread mutex. (לא ניתן להשתמש בנעילת סיבוב עקב היפוך עדיפות)
    • יש לבצע החרגה הדדית כדי למנוע תנאי גזע בזמן ספירת ההפניות.
    • היישום דומה מאוד למאפיינים האטומיים של Objective-C.

סוגים אטומיים

Java מספקת כמה סוגים אטומיים בחבילה java.util.concurrent.atomic. ב-J2ObjC יש תמיכה מלאה בכל אלה עם הטמעות בהתאמה אישית.

שדות סופיים

בעזרת Java אפשר לוודא ש-thread מקבל ערכים מאותחלים לשדות הסופיים של אובייקט, בלי שיהיה צורך בסנכרון במהלך שיתוף האובייקט. (JSL-17.5) עם זאת, מאחר ש-J2ObjC לא תומך בגישה אטומית של סוגי אובייקטים לא תנודתיים (ראו למעלה), אין דרך בטוחה לשתף אובייקט ללא סנכרון. לכן, לא מציבים מגבלות נוספות על משתמש ה-J2ObjC על ידי השמטת גדרות הזיכרון הנדרשות לשדות הסופיים.