Какой смысл команды dup в JVM байт коде? И что за индексы локальных переменных?
Когда разрабатываешь сложные системы, стоит оперировать простыми абстракциями. А виртуальная машина - это очень сложно. Попытки кроилова на количестве операций в байткоде приведут к необходимости писать более сложный, менее стабильный и менее производительный код виртуальной машины.

Значения локальных переменных хранятся в нативном массиве, выделяемом в каждом кадре стека вызовов. Для примитивных типов в нём хранятся сами значения, для ссылочных - ссылки в кучу. На этапе генерации байткода потому нельзя впихивать напрямую адреса, что байткод должен быть переносимым, неизвестно как может быть устроена память машин, на которых его могут выполнять.
Какой смысл команды dup в JVM байт коде?
Жаль что ты не программировал на калькуляторе МК 60. Это целая эпоха.

Значит есть разные пути к вычислению арифметики. Например если тебе надо возвести в квадрат число 5 на регистровой машине - то ты должен загрузить регистр R1 числом. Потом регистр R2. И потом найти такую команду умножения которая свяжет регистры R1 и R2 и перемножит и сохранит еще где-то результат. В силу современного зоопарка процессоров практически нереально создать такой абстрактный байткод который бы эффективно отображался на опкоды разных железяк. Поэтому решили забить на регистровую машину.  И использовать стек как хранилище операндов для операций. Разумеется в JVM есть и аналоги регистров но с ними не выполняются операции. ЕМНИП. Если хошь что-то сложить или вызвать функцию - то положи на стек и там-же получи результат. И если вернуться к возведению числа 5 в квадрат это может быть так 

положить 5 на стек.
дублировать
вызвать функцию умножения

На каком-нибудь языке Forth это было-бы тремя командами
5 DUP * .
Результат - на вершине стека.

Это кратко записывается. Но это несет абсолютно ту-же смысловую нагрузку для арифметики.

Почему их только 256 может быть?
А чорт его знает. Так решили. Решили что 256 регистров это капец какой максимум для процедуры или функции. Послушай ради интереса видосы про процессор Эльбрус. Там тоже интересно с регистрами сделано.

о есть на этапе генерации байт кода, сюда можно было после команды, просто адрес сразу впихнуть. И скорость выполнения должна же быть быстрее.
Я думаю что твое предположение о том какой код соберет JIT не совсем верное.
Вместо спора я предлагаю взять какой-то тестовый сценарий и подсмотреть какой
будет собран код. Я знаю что скептики Java обычно после изучения вопроса глубже
меняют свою точку зрения. Ну по крайней мере перестают считать Java 
"медленным покемоном". Java действительно была слоупоком во времена Jdk 1.1.
Но щас это не так.

Несколько лет назад я мерял производительность floating-point вычислений на приложении
которое рендерит зеркальные шары. И разница между С++ и Java была не сильно большая.
10 секунд на сях и 12 секунд на втором языке соотв. Хотя этот тест узкий и он просто
показывает частный случай.