Этот пост — следующее обновление в серии обсуждений, касающихся малоизвестных возможностей Java. Подпишитесь по электронной почте, чтобы быть в курсе, когда начнется следующее обсуждение. И не забудьте высказать свое мнение в разделе комментариев.
Java — безопасный язык программирования, который не позволяет программистам совершать множество глупых ошибок, большинство из которых были связаны с управлением памятью. Но если вы полны решимости с ним повозиться, у вас есть класс Unsafe. Этот класс — API sun.*, который на самом деле не является частью J2SE, поэтому вы можете не найти никакой официальной документации. К сожалению, у него также нет хорошей документации по коду.
Создание экземпляра sun.misc.Unsafe
Если вы попытаетесь создать экземпляр класса Unsafe, вам это не будет разрешено по двум причинам.
1) Небезопасный класс имеет закрытый конструктор.
2) Он также имеет статический метод getUnsafe(), но если вы наивно попытаетесь вызвать Unsafe.getUnsafe(), вы, вероятно, получите SecurityException. Этот класс может быть создан только из доверенного кода.
Но всегда есть какие-то обходные пути. Похожий простой способ создания экземпляра — использовать рефлексию:
Field f = Unsafe.class.getDeclaredField("theUnsafe"); //Internal referencef.setAccessible(true);Unsafe unsafe =(Unsafe) f.get(null);
Примечание: Ваша IDE, например Eclipse, может показывать ошибку, связанную с ограничением доступа. Не волнуйтесь. Продолжайте и запустите программу. Она запустится.
Теперь перейдем к основной части. С помощью этого объекта вы можете выполнять следующие «интересные» задания.
Использование sun.misc.Unsafe
1) Создать экземпляр без каких-либо ограничений
Используя метод allocateInstance(), вы можете создать экземпляр класса без вызова его конструкторского кода, кода инициализации, различных проверок безопасности JVM и всех других низкоуровневых вещей. Даже если у класса есть закрытый конструктор, вы также можете использовать этот метод для создания нового экземпляра.
Настоящий кошмар для всех любителей Singleton. Ребята, вы просто не можете так легко справиться с этой угрозой. 🙂
Давайте рассмотрим пример:
public class UnsafeDemo{public static void main(String[] args) throws NoSuchFieldException, SecurityException,IllegalArgumentException, IllegalAccessException, InstantiationException{Field f = Unsafe.class.getDeclaredField("theUnsafe"); //Internal referencef.setAccessible(true);Unsafe unsafe =(Unsafe) f.get(null);//This creates an instance of player class without any initializationPlayer p =(Player) unsafe.allocateInstance(Player.class);System.out.println(p.getAge()); //Print 0p.setAge(45); //Let's now set age 45 to un-initialized objectSystem.out.println(p.getAge()); //Print 45System.out.println(new Player().getAge()); //This the normal way to get fully initialized object; Prints 50}}class Player{private int age = 12;public Player(){ //Even if you create this constructor private;//You can initialize using Unsafe.allocateInstance()this.age = 50;}public int getAge(){return this.age;}public void setAge(int age){this.age = age;}}Output:04550
2) Поверхностное клонирование с использованием прямого доступа к памяти
Как обычно делать поверхностное клонирование? Вызов super.clone() в методе clone(){..}, верно? Здесь проблема в том, что вам нужно реализовать интерфейс Cloneable, а затем переопределить метод clone() во всех классах, где вы хотите реализовать поверхностное клонирование. Слишком много усилий для ленивого разработчика.
Я не рекомендую это делать, но с помощью unsafe мы можем создавать поверхностные клоны в несколько строк, и самое лучшее то, что его можно использовать с любым классом, как какой-то служебный метод.
Хитрость заключается в том, чтобы скопировать байты объекта в другое место в памяти, а затем привести этот объект к типу клонированного объекта.
3) Защита пароля от хакеров
Это кажется интересным? И да, это так. Разработчики создают пароли или хранят пароли в строках, а затем используют их в коде приложения. После использования пароля более умные разработчики делают ссылку на строку NULL, так что на нее больше не ссылаются и ее можно легко удалить сборщиком мусора.
Но с того момента, как вы сделали ссылку нулевой, и до момента запуска сборщика мусора этот экземпляр строки находится в пуле строк. И сложная атака на вашу систему сможет прочитать вашу область памяти и, таким образом, получить доступ к паролям. Шансы невелики, но они есть.
Вот почему предлагается использовать char[] для хранения паролей, чтобы после использования можно было перебрать массив и сделать каждый символ грязным/пустым.
Другой способ — использовать наш магический класс Unsafe. Здесь вы создаете еще одну временную строку с такой же длиной, как пароль, и сохраняете «?» или «*»(или любую букву) для каждого символа во временном пароле. Когда вы закончите с логикой пароля, вы просто копируете байты временного пароля(например, ????????) в исходный пароль. Это означает перезапись исходного пароля временным паролем.
Пример кода будет выглядеть так.
String password = new String("l00k@myHor$e");String fake = new String(password.replaceAll(".", "?"));System.out.println(password); // l00k@myHor$eSystem.out.println(fake); // ????????????getUnsafe().copyMemory(fake, 0L, null, toAddress(password), sizeOf(password));System.out.println(password); // ????????????System.out.println(fake); // ????????????
Создавайте классы динамически во время выполнения
Мы можем создавать классы во время выполнения, например, из скомпилированного файла .class. Для этого прочитайте содержимое класса в массив байтов и передайте его методу defineClass.
//Sample code to craeet classesbyte[] classContents = getClassContent();Class c = getUnsafe().defineClass(null, classContents, 0, classContents.length);c.getMethod("a").invoke(c.newInstance(), null);//Method to read .class fileprivate static byte[] getClassContent() throws Exception {File f = new File("/home/mishadoff/tmp/A.class");FileInputStream input = new FileInputStream(f);byte[] content = new byte[(int)f.length()];input.read(content);input.close();return content;}
4) Очень большие массивы
Как вы знаете, константа Integer.MAX_VALUE — это максимальный размер массива Java. Если вы хотите создать действительно большие массивы(хотя в обычных приложениях это не нужно), вы можете использовать для этого прямое выделение памяти.
Возьмем пример этого класса, который создает последовательную память(массив) с размером, вдвое превышающим допустимый.
class SuperArray {private final static int BYTE = 1;private long size;private long address;public SuperArray(long size) {this.size = size;address = getUnsafe().allocateMemory(size * BYTE);}public void set(long i, byte value) {getUnsafe().putByte(address + i * BYTE, value);}public int get(long idx) {return getUnsafe().getByte(address + idx * BYTE);}public long size() {return size;}}
Пример использования:
long SUPER_SIZE =(long)Integer.MAX_VALUE * 2;
Массив SuperArray = новый SuperArray(SUPER_SIZE);
System.out.println(“Размер массива:” + array.size()); // 4294967294
для(целое i = 0; i < 100; i++) {
массив.set((long)Integer.MAX_VALUE + i,(byte)3);
сумма += массив. получить((long)Целое.МАКС_ЗНАЧЕНИЕ + i);
}
System.out.println("Сумма 100 элементов:" + sum); // 300
[/джава]
Будьте осторожны, это может привести к сбою JVM.
Заключение
sun.misc.Unsafe предоставляет практически неограниченные возможности для исследования и модификации структур данных времени выполнения VM. Несмотря на то, что эти возможности практически неприменимы в самой разработке Java, Unsafe является отличным инструментом для тех, кто хочет изучить HotSpot VM без отладки кода C++ или нуждается в создании специальных инструментов профилирования.
Ссылка:
https://mishadoff.com/blog/java-magic-part-4-sun-dot-misc-dot-unsafe/