Статьи

Невоспетые герои Явы

Всякий раз, когда мы покупаем гаджет для электроники, самая страшная часть проходит через подробное руководство. Тем не менее, раздел «Начало работы» предоставляет некоторую передышку. То же самое верно, когда мы пытаемся выучить новый язык, где мы фокусируемся на кикстарте (звучит довольно естественно), но длительное пребывание на одном и том же механизме может помешать нам понять ограничения языка. Java ничем не отличается, и в этом процессе мы игнорируем многие пакеты / API, потому что они не находятся на пути кикстарта, несмотря на тот факт, что они могут иметь огромный потенциал и возможности. Я постараюсь показать некоторых незамеченных героев Java SDK. Однако это не может быть исчерпывающим списком. 

1. Пакет Java NIO ( java.nio )

Этот пакет изменил скорость серверов, которые переключились со старых API IO, но этот API получил ограниченную поддержку в пользовательских приложениях по нескольким причинам. Несмотря на то, что для использования пакетов NIO требуются некоторые действия в отношении проблемы OutOfMemory, но мы можем об этом не упоминать.

Одним из наиболее важных аспектов NIO является возможность работать в неблокирующем режиме , что запрещено традиционной библиотекой ввода-вывода Java.

При старом вводе-выводе обычно недоступные задачи, такие как блокировка файлов, неблокирующие и асинхронные операции ввода-вывода и возможность сопоставления файла с памятью. Неблокирующие операции ввода / вывода были достигнуты за счет обхода, такого как многопоточность или использование JNI. Это вызвало проблемы с производительностью .

Способность сервера эффективно обрабатывать несколько клиентских запросов зависит от того, как он использует потоки ввода-вывода. Когда сервер должен обрабатывать сотни клиентов одновременно, он должен иметь возможность использовать службы ввода-вывода одновременно. Один из способов удовлетворить этот сценарий в Java — использовать потоки, но почти одинаковое соотношение потоков (100 клиентов будет иметь 100 потоков) склонно к огромным накладным расходам потоков и может привести к проблемам производительности и масштабируемости из-за потребления стеки памяти (т. е. каждый поток имеет свой собственный стек) и переключение контекста процессора (т. е. переключение между потоками, а не выполнение реальных вычислений). Чтобы преодолеть эту проблему, новый набор неблокирующих классов ввода / вывода был представлен на платформе Java в пакете java.nio. Неблокирующий механизм ввода / вывода построен вокруг селекторов и каналов .Каналы, буферы и селекторы являются ядром NIO. Мультиплексированный ввод / вывод позволяет растущему числу пользователей обслуживаться фиксированным числом потоков. Мультиплексирование относится к отправке нескольких сигналов или потоков одновременно по одной несущей. Селектор обрабатывает несколько открытых сокетов (а не 1 поток на сокет). Это позволяет серверу управлять несколькими клиентами в одном потоке.

 

· До SDK 1.4 серверы имели ряд проблем с производительностью: ввод-вывод можно было легко заблокировать; мусор легко генерируется при чтении ввода / вывода; для масштабирования сервера требуется много потоков.

· Многие потоки, каждый из которых заблокирован на вводе / выводе, является неэффективной архитектурой по сравнению с одним потоком, заблокированным на многих вызовах ввода / вывода (мультиплексированный ввод / вывод).

 Буфер — это многоразовая часть памяти. DirectByteBuffer напрямую отображает часть файла, которую можно напрямую загрузить с диска в оперативную память. IndirectBuffers включают промежуточный шаг COPY к нижележащему байту [].

Прямой буфер байт может быть создан путем вызова allocateDirect фабричного метода этого класса. Буферы, возвращаемые этим методом, обычно имеют несколько более высокие затраты на выделение и освобождение, чем непрямые буферы. Содержимое прямых буферов может находиться за пределами обычной кучи сбора мусора, поэтому их влияние на объем памяти приложения может быть неочевидным. Поэтому рекомендуется, чтобы прямые буферы были выделены в первую очередь для больших долговечных буферов, которые подчиняются собственным операциям ввода-вывода базовой системы. В общем случае лучше всего размещать прямые буферы только тогда, когда они дают ощутимый выигрыш в производительности программы.

NIO умнее, чем потоковые операции ввода-вывода ранее. Но они влекут за собой более высокую стоимость, поскольку они выполняют собственные вызовы, а не вызовы JVM, в отличие от косвенных буферов.

 final int CAPACITY = 0x800000;

ByteBuffer.allocateDirect (МОЩНОСТЬ);

allocateDirect выделит его непосредственно в пространстве памяти ОС, а не в пространстве JVM. Главным преимуществом этого решения является то, что он не использует слишком много памяти JVM для больших файлов или других данных примитивного типа.

Прямой буфер байт может быть создан путем вызова allocateDirect фабричного метода этого класса. Буферы, возвращаемые этим методом, обычно имеют несколько более высокие затраты на выделение и освобождение, чем непрямые буферы . Поэтому рекомендуется, чтобы такие буферы использовались главным образом для больших, долгоживущих буферов, на которые распространяются собственные операции ввода-вывода базовой системы.

Прямой буфер создается с помощью метода ByteBuffer.allocateDirect () или FileChannel.map (), который отображает область файла непосредственно в память.

 


В DirectByteBuffer буфер в пользовательском пространстве не   требуется. Файл   получить    карту в пространстве ядра памяти. Нет никакого массива Java (зависит от impl), лежащего в основе прямого буфера, но просто «сырая» секция памяти. При обычном вводе-выводе промежуточное копирование в буфер включается в пространство пользователя. BufferedInputStream внутренне создает внутренний буферный массив (Userspace).

Но обратите внимание, что обе операции по-разному переносят файл в память, поэтому более быстрая работа зависит от системы и данных. Но для больших файлов преимущество NIO слишком велико. Прямые буферы имеют более высокую стоимость создания, чем непрямые буферы, потому что они используют собственные системные операции, а не операции JVM.

2. Справочные API Java ( java.lang.ref )

 Приложения, которым требуется много кеша, обычно борются со смертельным java.lang.OutOfMemoryError, и им нужно контролировать размер кеша. По умолчанию все объекты в кэше являются жесткими ссылками, что означает, что JVM может очистить их, только если на них нет ссылок. Однако требования к кешу обязывают кешированные объекты быть вечными, но тогда это может вызвать OutOfMemoryError, если JVM испытывает недостаток памяти. В большинстве случаев OutOfMemoryError является фатальным, поскольку JVM не может выполнять какую-либо продуктивную работу, если у нее нет памяти, и она продолжает пытаться освободить память, которой не существует. Я думаю, что лучше замедлять работу приложения, а не зависать. 

Вот концепция мягких ссылок, которая, в отличие от их непреклонных двоюродных братьев по жестким ссылкам, порождает и становится мусором в случае, если JVM медленно работает с памятью. Предусмотрено три типа эталонных объектов, каждый слабее предыдущего: мягкий , слабый и фантомный . Каждый тип соответствует разному уровню достижимости, как определено ниже. Мягкие ссылки предназначены для реализации чувствительных к памяти кешей. Обратная сторона заключается в том, что приложению необходимо проверить наличие объекта в кэше, прежде чем вызывать для него метод. Если кешированный объект был собран gabage, тогда приложение должно восстановить объект и поместить его в кеш.

SoftRerence охватывает HardReference. Существует концепция ReferenceQueue, в которой объекты SoftReference, которые должны быть GCed, могут быть доступны для некоторых очисток уровня приложения. Мягкие и слабые ссылки автоматически очищаются сборщиком перед добавлением в очереди, в которых они зарегистрированы, если таковые имеются. Поэтому мягкие и слабые ссылки не должны быть зарегистрированы в очереди, чтобы быть полезными, в то время как фантомные ссылки делают. Объект, достижимый с помощью фантомных ссылок, будет оставаться таковым до тех пор, пока все такие ссылки не будут очищены или сами не станут недоступными. Кэш, реализованный из SoftReference, все еще может быть подклассом Map и предоставлять те же функциональные возможности, но вместо хранения HardReference он теперь хранит SoftReference.

Пример:  

public class SoftCacheMap implements Map{

private final Map cacheMap;
private Thread cleanupThread;
private final ReferenceQueue clearedReferences;

......

/**
* Wrapper class to enable efficient handling of the references
* @author vranjan
*
*/

private static class Entry extends SoftReference {
private final Object _key;

public Entry(Object key, Object value, ReferenceQueue queue) {
super(value, queue);
_key = key;
}

/**
* Gets the key
* @return the key associated with this value.
*/
final Object getKey() {
return _key;
}

/**
* Gets the value
* @return the value; null if it is no longer accessible
*/
final Object getValue() {
return this.get();
}
}

// Put object in the cache
public Object put(Object key, Object o) {
SoftReference refKey =new SoftCacheMap.Entry(key, o,clearedReferences);
Object obj = null;
synchronized (cacheMap) {
obj = cacheMap.put(key, refKey);
}
if(cacheMap.size() > peakSize)
peakSize = cacheMap.size();
if(!referenceQueueCleanupThread)
removeClearedReferences();
return obj;
}

//get the object from the cache

public Object get(Object key) {
if(!referenceQueueCleanupThread)
removeClearedReferences();
if (cacheMap.size() > 0) {
SoftReference sr = (SoftReference)cacheMap.get(key);
synchronized (cacheMap) {
cacheMap.remove(key);
}
if(sr!=null)
return sr.get();
}
return null;
}

 

3. ThreadLocal ( java.lang.ThreadLocal )

 Потоки обычно не переносят информацию по пути своего выполнения, и, следовательно, мы используем обходные пути для хранения данных, используя глобальные переменные, хотя мы допускаем избыточность, накладные расходы на синхронизацию и т. Д. ThreadLocal предоставляет механизм, с помощью которого исполняющий поток может переносить данные по своему исполнению. дорожка, сквозные любые модули. Любая часть приложения, через которую выполняется поток, может получить доступ к данным. Эти данные скрыты от других исполняющих потоков.

 Пример (класс WebdavServlet) для использования ThreadLocal:

// Declaring the ThreadLocal:

/**
* Mechanism to carry the data
*/
private static ThreadLocal<String> threadLocal = new ThreadLocal<String>();

public static void setThreadLocalValue(String reqUsername) {
threadLocal.set(reqUsername);
}

public static String getThreadLocalValue() {
return (String) threadLocal.get();
}

// Set the data in the ThreadLocal:

WebdavServlet.setThreadLocalValue(someString);

// Get the data from the ThreadLocal:

String str = WebdavServlet.getThreadLocalValue();

Вывод :

Это некоторые из незамеченных героев JDK, о которых я только мог подумать. В некоторых углах могут быть спрятаны другие, и я был бы признателен, если бы кто-то тоже мог их выделить. Всегда рекомендуется продолжать изучать API в javadoc, даже если вы не можете использовать их сразу. Это может помешать вам заново изобрести колесо в будущем.