Proxy is a software design pattern which is generally used to implement additional functionality to control the access to the original subject, for example, adding caching for a heavy resource object, check access rights for sensitive data.

Magento 2 uses the proxy pattern to implement their own set of proxy classes. Proxy classes are automatically generated and contain a lazy loaded version of a class. The class contains only one dependency - object manager. This allows the class to be referenced anywhere and not all of the dependencies have to be loaded right away.

In development mode proxies are generated on the fly when application can't find the class during execution. In production mode the classes are generated using code compiler:

bin/magento setup:di:compile

To understand better lets look at an example of a native class that is generated.

<?php
namespace Magento\Catalog\Model\Indexer\Product\Price\PriceTableResolver;

/**
 * Proxy class for @see \Magento\Catalog\Model\Indexer\Product\Price\PriceTableResolver
 */
class Proxy extends \Magento\Catalog\Model\Indexer\Product\Price\PriceTableResolver implements \Magento\Framework\ObjectManager\NoninterceptableInterface
{
    /**
     * Object Manager instance
     *
     * @var \Magento\Framework\ObjectManagerInterface
     */
    protected $_objectManager = null;

    /**
     * Proxied instance name
     *
     * @var string
     */
    protected $_instanceName = null;

    /**
     * Proxied instance
     *
     * @var \Magento\Catalog\Model\Indexer\Product\Price\PriceTableResolver
     */
    protected $_subject = null;

    /**
     * Instance shareability flag
     *
     * @var bool
     */
    protected $_isShared = null;

    /**
     * Proxy constructor
     *
     * @param \Magento\Framework\ObjectManagerInterface $objectManager
     * @param string $instanceName
     * @param bool $shared
     */
    public function __construct(\Magento\Framework\ObjectManagerInterface $objectManager, $instanceName = '\\Magento\\Catalog\\Model\\Indexer\\Product\\Price\\PriceTableResolver', $shared = true)
    {
        $this->_objectManager = $objectManager;
        $this->_instanceName = $instanceName;
        $this->_isShared = $shared;
    }

    /**
     * @return array
     */
    public function __sleep()
    {
        return ['_subject', '_isShared', '_instanceName'];
    }

    /**
     * Retrieve ObjectManager from global scope
     */
    public function __wakeup()
    {
        $this->_objectManager = \Magento\Framework\App\ObjectManager::getInstance();
    }

    /**
     * Clone proxied instance
     */
    public function __clone()
    {
        $this->_subject = clone $this->_getSubject();
    }

    /**
     * Get proxied instance
     *
     * @return \Magento\Catalog\Model\Indexer\Product\Price\PriceTableResolver
     */
    protected function _getSubject()
    {
        if (!$this->_subject) {
            $this->_subject = true === $this->_isShared
                ? $this->_objectManager->get($this->_instanceName)
                : $this->_objectManager->create($this->_instanceName);
        }
        return $this->_subject;
    }

    /**
     * {@inheritdoc}
     */
    public function resolve($index, array $dimensions)
    {
        return $this->_getSubject()->resolve($index, $dimensions);
    }
}
generated/code/Magento/Catalog/Model/Indexer/Product/Price/PriceTableResolver/Proxy.php

As you can see the Proxy class extends the original class and implements NoninterceptableInterface to make sure not interceptors are generated for this Proxy class.

There are for 4 protected variables - object manager, instance name, subject, and shareability flag. These are used to instantiate object manager and set properties for the object.

The only method that is relevant from the subject class is the resolve function which calls the following:

    public function resolve($index, array $dimensions)
    {
        return $this->_getSubject()->resolve($index, $dimensions);
    }
\Magento\Catalog\Model\Indexer\Product\Price\PriceTableResolver\Proxy::resolve

The logic that is happening here means that the function first gets the subject (original class instantiated through object manager) and then calls the original function. Take a look how the subject is fetched:

protected function _getSubject()
{
    if (!$this->_subject) {
        $this->_subject = true === $this->_isShared
            ? $this->_objectManager->get($this->_instanceName)
            : $this->_objectManager->create($this->_instanceName);
    }
    return $this->_subject;
}
\Magento\Catalog\Model\Indexer\Product\Price\PriceTableResolver\Proxy::resolve

First we check if the subject class is already loaded and if not it creates the object using object manager.

This is an extremely powerful pattern that allows you to save performance where possible.

You may be wondering why not always use proxies if they are so efficient. The answer here is that generating the proxy code takes a bit of resources, and instantiating each object through proxy takes more time than doing it directly. So proxies should be used only when the constructor contains long running operations and does not need to be used right away.