You can define the data loading procedure in your Dataset:
class MyDataset(Dataset):
    def __init__(self, X, y):
        self.data = X
        self.target = y
        
    def __getitem__(self, index):
        x = self.data[index]
        y = self.target[index]
        
        return x, y
    
    def __len__(self):
        return len(self.data)

You could pass image paths for X and its targets for Y or already loaded Tensors, if you have enough RAM.
In the simplest usage, the DataLoader uses indices and calls __getitem__ on you Dataset.
Depending how the data is stored, it will be loaded or just extracted from RAM.
The shuffling basically is done by shuffling the indices. So your data won’t be loaded beforehand.
Note that the DataLoader has some more arguments like sampler, collate_fn etc.
My example is just a simple use case.