It is indeed possible, and a few already exist
The canonical light-weight way to add some sort of automatic memory management to an environment that doesn't offer it is via reference counting (
shared_ptr<T> in C++,
Arc<T> in Rust), but based on a comment you made below your answer you want a tracing collector. It's worth noting you can add code to make a reference counting system correctly clean up cycles like in CPython's garbage collector. Another answer here mentions Bacon and Rajan's paper on how to do this, which is worth a look.
The two that comes to mind that have seen actual use by major projects are Boehm-Demers-Weiser conservative collector (C) and the Oilpan garbage collector (C++).
The Boehm-Demers-Weiser collector (normally called Boehm) is probably the closest to what I imagine you wanting here, as it does not require significant changes to what a normal C codebase might look like, aside from replacing
malloc() calls to
GC_malloc() (the library author suggest possibly using macros for replacing the usual C allocation functions, but I'm not sure if that wouldn't cause issues on some platforms).
It does this through conservative garbage collection, which involves treating word-sized values on the stack and in the Boehm-GC managed heap as roots, and then performing tracing on them. The disadvantage of that is that this will also treats non-pointer values as pointers, and if they happen to have the same bit-level representation as a pointer to a region of memory that does not have any other live references to it, it will keep that memory alive and not collect it (and in turn, potentially keep other objects that would be collected alive as well). A precise garbage collector (which most garbage collected systems use) would have a way of telling a pointer and non-pointer value apart (or whatever the equivalents would be), and not cause these sort of memory-leak-like situations.
However, to add more complex features like finalizers, you are going to be adding more complexity to your runtime regardless.
I'm not very familiar with it, but unlike Boehm it does require making changes to the objects you want to managed by the collector; this sample shows the general shape of it.
Is this worth it?
I think it is worth going over the negatives of pulling or creating a general purpose library for memory management instead of building a collector yourself or taking advantage another host runtime like the JVM or the CLR.
Since you probably have a much better understanding of what the particular contours of your language runtime, how it is used, expected performance characteristics and platforms, and what the object model looks like, you can create a collector that is more tuned and efficient for your purposes. This is the advantage of that level of tight integration with a language runtime that might make a particular GC implementation difficult to port to another language.
Oilpan happens to have some of these platform-specific optimizations -- Oilpan was built around being embedded in a browser with an event loop, and thus has a second optionally-triggerable collection cycle based on the assumption that no pointers to Oilpan objects are present on the C stack, and thus a fully precise collection cycle that only scans the heap can be performed.
On the other hand, if you are compiling or building a language for an existing runtime, you get the advantage of a garbage collector that has had thousands (if not millions) of engineer hours powered into it for multiple platforms, which is a pretty huge boon given the sort of complexity that goes into making efficient garbage collectors.
After posting this answer, I was informed of the existence of The Memory Management Toolkit (Rust, with bindings) that does provide hooks for inserting object write barriers and safe points, as well as the Ravenbrook MPS Library(C). I'm not very familiar with either of them, but I feel like I should add these after being informed of them as possible resources.