Do I need to manually destroy material/model instances that are lazily copied?

When I say lazily copied, I mean the practice that Unity uses where it's copied-on-read. I.e. I call renderer.material or meshFilter.mesh, and now I have an instanced copy of that asset that's being used by that game object.

Do I need to destroy these objects manually, or do they get freed when the game object itself gets destroyed? If I do need to destroy these objects, what's the best way to do this since there's no OnDestroy() Monobehaviour function?

Yes, you have to destroy them manually. As to where to do that, you can use the OnDisable() hack:

function OnDisable() { 
   if (gameObject.active) { 
      print ("Disabled");
   } 
   else { 
      print ("Destroyed or deactivated");
   } 
}

The problem with that is there's no way to distinguish between an object being destroyed and an object being deactivated. Works for a lot of cases though. Otherwise you have to make a custom destroy function and call that specifically when destroying the object.

But how could you destroy a material instance in that case?

private void OnDestroy()
{
Renderer m_Renderer = this.GetComponentInChildren();
Destroy(m_Renderer.material);
}