Historically compilation includes the creation of a Symbol Table which associates variable names with their attributes determined from the source code. This is a little simplistic for purposes of illustration, but the principles have not changed since FORTRAN was useful. User-defined types in languages such as C++ and Java form part of each compilation unit's meta-data that is collected during compilation and knitted together when a run-time executable is created or loaded into memory.
Note that all of the types must be defined before they may be used to define objects of a type. This is the purpose of 'import' in Java and '#include' in C/C++. The meta-data includes definitions of methods and object (or class) data elements and is used to determine the size of objects for creation in static memory, on the stack (block entry/exit) or the heap (dynamic allocation).
Type-checking at compile time or during execution is one of the most significant developments in the last forty years and is a major reason that we are able to use autonomous robots on Mars, on California highways or on the Internet. At it's core, the central problem of programming language compilation or translation is tracking everything known about objects and placing it in memory where it can be used properly at run-time.
Ancient languages like FORTRAN and COBOL had one type of variable (static) that would have only fundamental data type attributes. They had almost trivial symbol tables. The most complex issue was linking compilation units together for execution. We've come a long way Baby!