The documentation for g_value_get_object() says that the returned data is owned by the GValue instance—i.e. the returned value is annotated as transfer none—which means that MyObj does not acquire a reference on the object instance passed to the other-obj property. If that were the case, you’d see g_value_dup_object() instead.
From an idiomatic code perspective, the other-obj implementation is problematic: now MyObj and the other object instance share an implicit lifetime. Idiomatic code inside MyObj would do either of these two:
acquire a reference to the other object inside the setter function, and release it inside its dispose() implementation
add a weak reference to the other object, and nullify the pointer in the MyObj instance structure when the weak reference callback is invoked
Otherwise, if the other object instance disappears before MyObj, the other_obj pointer will contain garbage and will—if you’re lucky—cause a crash on access.
When you call g_object_new() (or g_object_set()), GObject will box the instance pointer inside a GValue, and that GValue will acquire a reference to the instance. The reference held by GValue will be released when the GValue is cleared using g_value_unset(), which happens at the end of g_object_new() (or g_object_set()), before control returns to the caller. This means that the instance is guaranteed to be valid for the duration of the call, but not after.